1
2
3
4
5
6
7
8
9
10 "Deals with CSV calendar import/export stuff"
11
12
13 from __future__ import with_statement
14 import csv
15 import datetime
16
17
18 import wx
19
20
21
22
23 import bpcalendar
24 import common_calendar
25 import helpids
26 import guihelper
27
28 module_debug=False
35
39 _default_file_name="calendar.csv"
40 _wildcards="CSV files (*.csv)|*.csv"
41
44
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
55
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
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 }
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
158
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
167 c=self.__filter['categories']
168 if c is None or not len(c):
169
170 return True
171 if len([x for x in entry['categories'] if x in c]):
172 return True
173 return False
174
193
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
202
205
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
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
233 return
234 try:
235 csv_file=file(self.__file_name, 'rb')
236 except:
237 return
238 reader=csv.reader(csv_file)
239
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
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
264
265 if not e.get('repeat', False) or e.get('repeat_type', None) is None:
266
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
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
287 return
288
289 for k in e.get('exceptions', []):
290 rp.add_suppressed(*k[:3])
291
292 ce.repeat=rp
293
294 - def __populate_entry(self, e, ce):
295
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
331 self.__populate_repeat_entry(e, ce)
332
334
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
359
360
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]))
366 if len(v):
367 d[key]=int(v)
368 else:
369 d[key]=None
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
378 if not len(v):
379 d[key]=None
380 else:
381 d[key]=int(v)
383 d[key]=v.upper()=='TRUE'
385 if v is None or not len(v):
386 d[key]=[]
387 else:
388 d[key]=v.split(';')
390 if len(v):
391 d[key]=str(v)
392 d['repeat']=True
393 else:
394 d['repeat']=False
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
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
425
427 hbs=wx.BoxSizer(wx.HORIZONTAL)
428
429 hbs.Add(wx.StaticText(self, -1, "CSV File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
430
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
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
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
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
480
482 self.EndModal(evt.GetId())
483
485 return self.__oc.get()
486
489
497
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
510 hbs.Add(wx.StaticText(self, -1, "CSV Calendar File:"), 0, wx.ALL|wx.ALIGN_CENTRE, 2)
511
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
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
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
551
554
557