Module csv_calendar
[hide private]
[frames] | no frames]

Source Code for Module csv_calendar

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2004 Joe Pham <djpham@bitpim.org> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: csv_calendar.py 4380 2007-08-29 00:17:07Z djpham $ 
  9   
 10  "Deals with CSV calendar import/export stuff" 
 11   
 12  # System modules 
 13  from __future__ import with_statement 
 14  import csv 
 15  import datetime 
 16   
 17  # wxPython modules 
 18  import wx 
 19   
 20  # Others 
 21   
 22  # My modules 
 23  import bpcalendar 
 24  import common_calendar 
 25  import helpids 
 26  import guihelper 
 27   
 28  module_debug=False 
29 30 #------------------------------------------------------------------------------- 31 -class ImportDataSource(common_calendar.ImportDataSource):
32 # how to define, and retrieve calendar import data source 33 message_str="Pick a CSV Calendar File" 34 wildcard='*.csv'
35
36 #------------------------------------------------------------------------------ 37 ExportCSVDialogParent=common_calendar.ExportCalendarDialog 38 -class ExportCSVDialog(ExportCSVDialogParent):
39 _default_file_name="calendar.csv" 40 _wildcards="CSV files (*.csv)|*.csv" 41
42 - def __init__(self, parent, title):
43 super(ExportCSVDialog, self).__init__(parent, title)
44
45 - def __get_str(self, entry, field):
46 s=getattr(entry, field, '') 47 if s is None: 48 s='' 49 if isinstance(s, unicode): 50 return s.encode('ascii', 'ignore') 51 else: 52 return str(s)
53
54 - def _export(self):
55 # do export 56 filename=self.filenamectrl.GetValue() 57 csv_event_template=( 58 ('Start', 'start_str', None), 59 ('End', 'end_str', None), 60 ('Description', 'description', None), 61 ('Location', 'location', None), 62 ('Priority', 'priority', None), 63 ('Alarm', 'alarm', None), 64 ('All-Day', 'allday', None), 65 ('Notes', 'notes', None), 66 ('Categories', 'categories_str', None), 67 ('Ringtone', 'ringtone', None), 68 ('Wallpaper', 'wallpaper', None)) 69 csv_repeat_template=( 70 ('Repeat Type', 'repeat_type', None), 71 ('Repeat Interval', 'interval', None), 72 ('Repeat Interval2', 'interval2', None), 73 ('Day-of-Week', 'dow_str', None), 74 ('Excluded Dates', 'suppressed_str', None)) 75 try: 76 f=file(filename, 'wt') 77 except: 78 f=None 79 if f is None: 80 guihelper.MessageDialog(self, 'Failed to open file ['+filename+']', 81 'Export Error') 82 return 83 s=['"'+x[0]+'"' for x in csv_event_template]+\ 84 ['"'+x[0]+'"' for x in csv_repeat_template] 85 f.write(','.join(s)+'\n') 86 all_items=self._selection.GetSelection()==0 87 dt=self._start_date.GetValue() 88 range_start=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay()) 89 dt=self._end_date.GetValue() 90 range_end=(dt.GetYear(), dt.GetMonth()+1, dt.GetDay()) 91 #--- 92 def __write_rec(f, cal_dict): 93 for k,e in cal_dict.items(): 94 if not all_items and \ 95 (e.end < range_start or e.start>range_end): 96 continue 97 l=[] 98 for field in csv_event_template: 99 if field[2] is None: 100 s=self.__get_str(e, field[1]) 101 else: 102 s=field[2](e, field[1]) 103 l+=['"'+s.replace('"', '')+'"'] 104 rpt=e.repeat 105 if rpt is None: 106 l+=['']*len(csv_repeat_template) 107 else: 108 for field in csv_repeat_template: 109 if field[2] is None: 110 s=self.__get_str(rpt, field[1]) 111 else: 112 s=field[2](rpt, field[1]) 113 l+=['"'+s.replace('"', '')+'"'] 114 f.write(','.join(l)+'\n')
115 #--- 116 cal_dict=self.GetParent().GetCalendarData() 117 __write_rec(f, cal_dict) 118 f.close()
119
120 #------------------------------------------------------------------------------ 121 -class CSVCalendarImportData(object):
122 __default_filter={ 123 'start': None, 124 'end': None, 125 'categories': None, 126 'rpt_events': False, 127 'no_alarm': False, 128 'ringtone': None, 129 'alarm_override':False, 130 'vibrate':False, 131 'alarm_value':0 132 }
133 - def __init__(self, file_name=None):
134 self.__calendar_keys=( 135 ('Start', 'start', self.__set_datetime), 136 ('End', 'end', self.__set_datetime), 137 ('Description', 'description', self.__set_str), 138 ('Location', 'location', self.__set_str), 139 ('Priority', 'priority', self.__set_priority), 140 ('Alarm', 'alarm_value',self.__set_alarm), 141 ('All-Day', 'allday', self.__set_bool), 142 ('Notes', 'notes', self.__set_str), 143 ('Categories', 'categories', self.__set_categories), 144 ('Ringtone', 'ringtone', self.__set_str), 145 ('Wallpaper', 'wallpaper', self.__set_str), 146 ('Repeat Type', 'repeat_type', self.__set_repeat_type), 147 ('Repeat Interval', 'repeat_interval', self.__set_int), 148 ('Repeat Interval2', 'repeat_interval2', self.__set_int), 149 ('Day-of-Week', 'repeat_dow', self.__set_dow), 150 ('Excluded Dates', 'exceptions', self.__set_exceptions) 151 ) 152 self.__file_name=file_name 153 self.__data=[] 154 self.__filter=self.__default_filter 155 self.read()
156
157 - def __accept(self, entry):
158 # start & end time within specified filter 159 if self.__filter['start'] is not None and \ 160 entry['start'][:3]<self.__filter['start'][:3]: 161 return False 162 if self.__filter['end'] is not None and \ 163 entry['end'][:3]>self.__filter['end'][:3] and \ 164 entry['end'][:3]!=common_calendar.no_end_date[:3]: 165 return False 166 # check the catefory 167 c=self.__filter['categories'] 168 if c is None or not len(c): 169 # no categories specified => all catefories allowed. 170 return True 171 if len([x for x in entry['categories'] if x in c]): 172 return True 173 return False
174
175 - def get(self):
176 res={} 177 single_rpt=self.__filter.get('rpt_events', False) 178 for k in self.__data: 179 try: 180 if self.__accept(k): 181 if k.get('repeat', False) and single_rpt: 182 d=self.__generate_repeat_events(k) 183 else: 184 d=[k] 185 for n in d: 186 ce=bpcalendar.CalendarEntry() 187 self.__populate_entry(n, ce) 188 res[ce.id]=ce 189 except: 190 if module_debug: 191 raise 192 return res
193
194 - def get_category_list(self):
195 l=[] 196 for e in self.__data: 197 l+=[x for x in e.get('categories', []) if x not in l] 198 return l
199
200 - def set_filter(self, filter):
201 self.__filter=filter
202
203 - def get_filter(self):
204 return self.__filter
205
206 - def get_display_data(self):
207 cnt=0 208 res={} 209 single_rpt=self.__filter.get('rpt_events', False) 210 for k in self.__data: 211 if self.__accept(k): 212 if k.get('repeat', False) and single_rpt: 213 d=self.__generate_repeat_events(k) 214 else: 215 d=[k.copy()] 216 for n in d: 217 if self.__filter.get('no_alarm', False): 218 n['alarm']=False 219 res[cnt]=n 220 cnt+=1 221 return res
222
223 - def get_file_name(self):
224 if self.__file_name is not None: 225 return self.__file_name 226 return ''
227
228 - def read(self, file_name=None, dlg=None):
229 if file_name is not None: 230 self.__file_name=file_name 231 if self.__file_name is None: 232 # no file name specified 233 return 234 try: 235 csv_file=file(self.__file_name, 'rb') 236 except: 237 return 238 reader=csv.reader(csv_file) 239 # retrieve the header and build the header keys 240 h=reader.next() 241 header_keys=[] 242 for e in h: 243 k=None 244 for x in self.__calendar_keys: 245 if e==x[0]: 246 k=x 247 break 248 header_keys.append(k) 249 # loop through the file, read each line, and parse it 250 self.__data=[] 251 for row in reader: 252 d={} 253 for i,e in enumerate(row): 254 if header_keys[i] is None: 255 continue 256 elif header_keys[i][2] is None: 257 self.__set_str(e, d, header_keys[i][1]) 258 else: 259 header_keys[i][2](e, d, header_keys[i][1]) 260 self.__data.append(d) 261 csv_file.close()
262
263 - def __populate_repeat_entry(self, e, ce):
264 # populate repeat entry data 265 if not e.get('repeat', False) or e.get('repeat_type', None) is None: 266 # not a repeat event 267 return 268 rp=bpcalendar.RepeatEntry() 269 rp_type=e['repeat_type'] 270 rp_interval=e.get('repeat_interval', 1) 271 rp_interval2=e.get('repeat_interval2', 1) 272 rp_dow=e.get('repeat_dow', 0) 273 274 if rp_type==rp.daily: 275 # daily event 276 rp.repeat_type=rp.daily 277 rp.interval=rp_interval 278 elif rp_type==rp.weekly or rp_type==rp.monthly: 279 rp.repeat_type=rp_type 280 rp.interval=rp_interval 281 rp.interval2=rp_interval2 282 rp.dow=rp_dow 283 elif rp_type==rp.yearly: 284 rp.repeat_type=rp.yearly 285 else: 286 # not yet supported 287 return 288 # add the list of exceptions 289 for k in e.get('exceptions', []): 290 rp.add_suppressed(*k[:3]) 291 # all done 292 ce.repeat=rp
293
294 - def __populate_entry(self, e, ce):
295 # populate an calendar entry with data 296 ce.description=e.get('description', None) 297 ce.location=e.get('location', None) 298 v=e.get('priority', None) 299 if v is not None: 300 ce.priority=v 301 if not self.__filter.get('no_alarm', False) and \ 302 not self.__filter.get('alarm_override', False) and \ 303 e.get('alarm', False): 304 ce.alarm=e.get('alarm_value', 0) 305 ce.ringtone=self.__filter.get('ringtone', "") 306 ce.vibrate=self.__filter.get('vibrate', False) 307 elif not self.__filter.get('no_alarm', False) and \ 308 self.__filter.get('alarm_override', False): 309 ce.alarm=self.__filter.get('alarm_value', 0) 310 ce.ringtone=self.__filter.get('ringtone', "") 311 ce.vibrate=self.__filter.get('vibrate', False) 312 ce.allday=e.get('allday', False) 313 ce_start=e.get('start', None) 314 ce_end=e.get('end', None) 315 if ce_start is None and ce_end is None: 316 raise ValueError, "No start or end datetime" 317 if ce_start is not None: 318 ce.start=ce_start 319 if ce_end is not None: 320 ce.end=ce_end 321 if ce_start is None: 322 ce.start=ce.end 323 elif ce_end is None: 324 ce.end=ce.start 325 ce.notes=e.get('notes', None) 326 v=[] 327 for k in e.get('categories', []): 328 v.append({ 'category': k }) 329 ce.categories=v 330 # look at repeat 331 self.__populate_repeat_entry(e, ce)
332
333 - def __generate_repeat_events(self, e):
334 # generate multiple single events from this repeat event 335 ce=bpcalendar.CalendarEntry() 336 self.__populate_entry(e, ce) 337 l=[] 338 new_e=e.copy() 339 new_e['repeat']=False 340 for k in ('repeat_type', 'repeat_interval', 'repeat_dow'): 341 if new_e.has_key(k): 342 del new_e[k] 343 s_date=datetime.datetime(*self.__filter['start']) 344 e_date=datetime.datetime(*self.__filter['end']) 345 one_day=datetime.timedelta(1) 346 this_date=s_date 347 while this_date<=e_date: 348 date_l=(this_date.year, this_date.month, this_date.day) 349 if ce.is_active(*date_l): 350 new_e['start']=date_l+new_e['start'][3:] 351 new_e['end']=date_l+new_e['end'][3:] 352 l.append(new_e.copy()) 353 this_date+=one_day 354 return l
355
356 - def __set_str(self, v, d, key):
357 d[key]=str(v)
358 - def __set_datetime(self, v, d, key):
359 # the date time should be in this format: YYYY-MM-DD hh:mm 360 # quick check for the format 361 if v[4]!='-' or v[7]!='-' or v[10]!= ' ' or v[13]!=':': 362 return 363 d[key]=(int(v[:4]), int(v[5:7]), int(v[8:10]), int(v[11:13]), 364 int(v[14:16]))
365 - def __set_priority(self, v, d, key):
366 if len(v): 367 d[key]=int(v) 368 else: 369 d[key]=None
370 - def __set_alarm(self, v, d, key):
371 if len(v): 372 d[key]=int(v) 373 d['alarm']=d[key]!=-1 374 else: 375 d[key]=None 376 d['alarm']=False
377 - def __set_int(self, v, d, key):
378 if not len(v): 379 d[key]=None 380 else: 381 d[key]=int(v)
382 - def __set_bool(self, v, d, key):
383 d[key]=v.upper()=='TRUE'
384 - def __set_categories(self, v, d, key):
385 if v is None or not len(v): 386 d[key]=[] 387 else: 388 d[key]=v.split(';')
389 - def __set_repeat_type(self, v, d, key):
390 if len(v): 391 d[key]=str(v) 392 d['repeat']=True 393 else: 394 d['repeat']=False
395 - def __set_dow(self, v, d, key):
396 dow=0 397 for e in v.split(';'): 398 dow|=bpcalendar.RepeatEntry.dow_names.get(e, 0) 399 d[key]=dow
400 - def __set_exceptions(self, v, d, key):
401 l=[] 402 for e in v.split(';'): 403 if len(e): 404 if e[4]=='-' and e[7]=='-': 405 l.append( (int(e[:4]), int(e[5:7]), int(e[8:10])) ) 406 d[key]=l
407 #------------------------------------------------------------------------------
408 -class CSVImportDialog(common_calendar.PreviewDialog):
409 410 __column_labels=[ 411 ('description', 'Description', 400, None), 412 ('start', 'Start', 150, common_calendar.bp_date_str), 413 ('end', 'End', 150, common_calendar.bp_date_str), 414 ('repeat_type', 'Repeat', 80, common_calendar.bp_repeat_str), 415 ('alarm', 'Alarm', 80, common_calendar.bp_alarm_str), 416 ('categories', 'Category', 150, common_calendar.category_str) 417 ] 418
419 - def __init__(self, parent, id, title):
420 self.__oc=CSVCalendarImportData() 421 common_calendar.PreviewDialog.__init__(self, parent, id, title, 422 self.__column_labels, 423 self.__oc.get_display_data(), 424 config_name='import/calendar/csvdialog')
425
426 - def getcontrols(self, main_bs):
427 hbs=wx.BoxSizer(wx.HORIZONTAL) 428 # label 429 hbs.Add(wx.StaticText(self, -1, "CSV File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2) 430 # where the folder name goes 431 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY) 432 self.folderctrl.SetValue(self.__oc.get_file_name()) 433 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2) 434 # browse button 435 id_browse=wx.NewId() 436 hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2) 437 main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5) 438 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 439 wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder)
440 - def getpostcontrols(self, main_bs):
441 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 442 hbs=wx.BoxSizer(wx.HORIZONTAL) 443 id_import=wx.NewId() 444 hbs.Add(wx.Button(self, id_import, 'Import'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 445 hbs.Add(wx.Button(self, wx.ID_OK, 'Replace All'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 446 hbs.Add(wx.Button(self, self.ID_ADD, 'Add'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 447 hbs.Add(wx.Button(self, self.ID_MERGE, 'Merge'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 448 hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 449 id_filter=wx.NewId() 450 hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 451 hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 452 main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 453 wx.EVT_BUTTON(self, id_import, self.OnImport) 454 wx.EVT_BUTTON(self, id_filter, self.OnFilter) 455 wx.EVT_BUTTON(self, self.ID_ADD, self.OnEndModal) 456 wx.EVT_BUTTON(self, self.ID_MERGE, self.OnEndModal) 457 wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT))
458 459 @guihelper.BusyWrapper
460 - def OnImport(self, evt):
461 with guihelper.WXDialogWrapper(wx.ProgressDialog('CSV Calendar Import', 462 'Importing CSV Calendar Data, please wait ...', 463 parent=self)) as dlg: 464 self.__oc.read(self.folderctrl.GetValue()) 465 self.populate(self.__oc.get_display_data())
466
467 - def OnBrowseFolder(self, evt):
468 with guihelper.WXDialogWrapper(wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv'), 469 True) as (dlg, id): 470 if id==wx.ID_OK: 471 self.folderctrl.SetValue(dlg.GetPath())
472
473 - def OnFilter(self, evt):
474 cat_list=self.__oc.get_category_list() 475 with guihelper.WXDialogWrapper(common_calendar.FilterDialog(self, -1, 'Filtering Parameters', cat_list), 476 True) as (dlg, retcode): 477 if retcode==wx.ID_OK: 478 self.__oc.set_filter(dlg.get()) 479 self.populate(self.__oc.get_display_data())
480
481 - def OnEndModal(self, evt):
482 self.EndModal(evt.GetId())
483
484 - def get(self):
485 return self.__oc.get()
486
487 - def get_categories(self):
488 return self.__oc.get_category_list()
489
490 #------------------------------------------------------------------------------- 491 -def ImportCal(folder, filters):
492 _oc=CSVCalendarImportData(folder) 493 _oc.set_filter(filters) 494 _oc.read() 495 res={ 'calendar':_oc.get() } 496 return res
497
498 #------------------------------------------------------------------------------- 499 -class CSVAutoConfCalDialog(wx.Dialog):
500 - def __init__(self, parent, id, title, folder, filters, 501 style=wx.CAPTION|wx.MAXIMIZE_BOX| \ 502 wx.SYSTEM_MENU|wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER):
503 self._oc=CSVCalendarImportData() 504 self._oc.set_filter(filters) 505 self.__read=False 506 wx.Dialog.__init__(self, parent, id=id, title=title, style=style) 507 main_bs=wx.BoxSizer(wx.VERTICAL) 508 hbs=wx.BoxSizer(wx.HORIZONTAL) 509 # label 510 hbs.Add(wx.StaticText(self, -1, "CSV Calendar File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2) 511 # where the folder name goes 512 self.folderctrl=wx.TextCtrl(self, -1, "", style=wx.TE_READONLY) 513 self.folderctrl.SetValue(folder) 514 hbs.Add(self.folderctrl, 1, wx.EXPAND|wx.ALL, 2) 515 # browse button 516 id_browse=wx.NewId() 517 hbs.Add(wx.Button(self, id_browse, 'Browse ...'), 0, wx.EXPAND|wx.ALL, 2) 518 main_bs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5) 519 main_bs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 520 wx.EVT_BUTTON(self, id_browse, self.OnBrowseFolder) 521 hbs=wx.BoxSizer(wx.HORIZONTAL) 522 hbs.Add(wx.Button(self, wx.ID_OK, 'OK'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 523 hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 524 id_filter=wx.NewId() 525 hbs.Add(wx.Button(self, id_filter, 'Filter'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 526 hbs.Add(wx.Button(self, wx.ID_HELP, 'Help'), 0, wx.ALIGN_CENTRE|wx.ALL, 5) 527 main_bs.Add(hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 528 wx.EVT_BUTTON(self, id_filter, self.OnFilter) 529 wx.EVT_BUTTON(self, wx.ID_HELP, lambda *_: wx.GetApp().displayhelpid(helpids.ID_DLG_CALENDAR_IMPORT)) 530 self.SetSizer(main_bs) 531 self.SetAutoLayout(True) 532 main_bs.Fit(self)
533
534 - def OnBrowseFolder(self, evt):
535 with guihelper.WXDialogWrapper(wx.FileDialog(self, "Pick a CSV Calendar File", wildcard='*.csv'), 536 True) as (dlg, retcode): 537 if retcode==wx.ID_OK: 538 self.folderctrl.SetValue(dlg.GetPath()) 539 self.__read=False
540
541 - def OnFilter(self, evt):
542 # read the calender to get the category list 543 if not self.__read: 544 self._oc.read(self.folderctrl.GetValue()) 545 self.__read=True 546 cat_list=self._oc.get_category_list() 547 with guihelper.WXDialogWrapper(common_calendar.AutoSyncFilterDialog(self, -1, 'Filtering Parameters', cat_list)) as dlg: 548 dlg.set(self._oc.get_filter()) 549 if dlg.ShowModal()==wx.ID_OK: 550 self._oc.set_filter(dlg.get())
551
552 - def GetFolder(self):
553 return self.folderctrl.GetValue()
554
555 - def GetFilter(self):
556 return self._oc.get_filter()
557