0001 #!/usr/bin/env python 0002 0003 ### BITPIM 0004 ### 0005 ### Copyright (C) 2006 Joe Pham <djpham@bitpim.org> 0006 ### 0007 ### This program is free software; you can redistribute it and/or modify 0008 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0009 ### 0010 ### $Id: imp_cal_preset.py 4380 2007-08-29 00:17:07Z djpham $ 0011 0012 """ Handle Import Calendar Preset feature 0013 """ 0014 0015 # System 0016 from __future__ import with_statement 0017 import calendar 0018 import copy 0019 import datetime 0020 import random 0021 import sha 0022 0023 # wx 0024 import wx 0025 import wx.wizard as wiz 0026 0027 # BitPim 0028 import common_calendar 0029 import database 0030 import guihelper 0031 import imp_cal_wizard 0032 import importexport 0033 import setphone_wizard 0034 0035 # modules constants 0036 IMP_OPTION_REPLACEALL=0 0037 IMP_OPTION_ADD=1 0038 IMP_OPTION_PREVIEW=2 0039 IMP_OPTION_MERGE=3 0040 0041 #------------------------------------------------------------------------------- 0042 class ImportCalendarDataObject(common_calendar.FilterDataObject): 0043 _knownproperties=common_calendar.FilterDataObject._knownproperties+\ 0044 ['name', 'type', 'source_id', 'option' ] 0045 _knownlistproperties=common_calendar.FilterDataObject._knownlistproperties 0046 allproperties=_knownproperties+\ 0047 [x for x in _knownlistproperties]+\ 0048 [x for x in common_calendar.FilterDataObject._knowndictproperties] 0049 importcalendarobjectfactory=database.dataobjectfactory(ImportCalendarDataObject) 0050 0051 #------------------------------------------------------------------------------- 0052 class ImportCalendarEntry(dict): 0053 # a dict class that automatically generates an ID for use with 0054 # BitPim database. 0055 0056 def __init__(self, data=None): 0057 super(ImportCalendarEntry, self).__init__() 0058 if data: 0059 self.update(data) 0060 0061 _persistrandom=random.Random() 0062 def _create_id(self): 0063 "Create a BitPim serial for this entry" 0064 rand2=random.Random() # this random is seeded when this function is called 0065 num=sha.new() 0066 num.update(`self._persistrandom.random()`) 0067 num.update(`rand2.random()`) 0068 return num.hexdigest() 0069 def _get_id(self): 0070 s=self.get('serials', []) 0071 _id=None 0072 for n in s: 0073 if n.get('sourcetype', None)=='bitpim': 0074 _id=n.get('id', None) 0075 break 0076 if not _id: 0077 _id=self._create_id() 0078 self._set_id(_id) 0079 return _id 0080 def _set_id(self, id): 0081 s=self.get('serials', []) 0082 for n in s: 0083 if n.get('sourcetype', None)=='bitpim': 0084 n['id']=id 0085 return 0086 self.setdefault('serials', []).append({'sourcetype': 'bitpim', 'id': id } ) 0087 id=property(fget=_get_id, fset=_set_id) 0088 0089 def validate_properties(self): 0090 # validate and remove non-persistent properties as defined in 0091 # ImportCalendarDataObject class 0092 _del_keys=[x for x in self \ 0093 if not x in ImportCalendarDataObject.allproperties] 0094 for _key in _del_keys: 0095 del self[_key] 0096 0097 #------------------------------------------------------------------------------- 0098 class FilterDialog(common_calendar.FilterDialog): 0099 def __init__(self, parent, id, caption, data): 0100 super(FilterDialog, self).__init__(parent, id, caption, []) 0101 self.set(data) 0102 0103 def _get_from_fs(self): 0104 pass 0105 def _save_to_fs(self, data): 0106 pass 0107 0108 #------------------------------------------------------------------------------- 0109 class PresetNamePage(setphone_wizard.MyPage): 0110 def __init__(self, parent): 0111 super(PresetNamePage, self).__init__(parent, 0112 'Calendar Import Preset Name') 0113 0114 def GetMyControls(self): 0115 vbs=wx.BoxSizer(wx.VERTICAL) 0116 vbs.Add(wx.StaticText(self, -1, 'Preset Name:'), 0, 0117 wx.ALL|wx.EXPAND, 5) 0118 self._name=wx.TextCtrl(self, -1, '') 0119 vbs.Add(self._name, 0, wx.ALL|wx.EXPAND, 5) 0120 return vbs 0121 0122 def ok(self): 0123 return bool(self._name.GetValue()) 0124 def get(self, data): 0125 data['name']=self._name.GetValue() 0126 def set(self, data): 0127 self._name.SetValue(data.get('name', '')) 0128 0129 #------------------------------------------------------------------------------- 0130 class PresetFilterPage(setphone_wizard.MyPage): 0131 def __init__(self, parent): 0132 self._data={} 0133 super(PresetFilterPage, self).__init__(parent, 0134 'Calendar Preset Filter') 0135 _col_names=({ 'label': 'Start Date:', 'attr': '_start' }, 0136 { 'label': 'End Date:', 'attr': '_end' }, 0137 { 'label': 'Preset Duration:', 'attr': '_preset' }, 0138 { 'label': 'Repeat Events:', 'attr': '_repeat' }, 0139 { 'label': 'Alarm Setting:', 'attr': '_alarm' }, 0140 { 'label': 'Alarm Vibrate:', 'attr': '_vibrate' }, 0141 { 'label': 'Alarm Ringtone:', 'attr': '_ringtone' }, 0142 { 'label': 'Alarm Value:', 'attr': '_alarm_value' }, 0143 ) 0144 def GetMyControls(self): 0145 gs=wx.GridBagSizer(5, 10) 0146 gs.AddGrowableCol(1) 0147 for _row, _col in enumerate(PresetFilterPage._col_names): 0148 gs.Add(wx.StaticText(self, -1, _col['label']), 0149 pos=(_row, 0), flag=wx.ALIGN_CENTER_VERTICAL) 0150 _w=wx.StaticText(self, -1, _col['attr']) 0151 setattr(self, _col['attr'], _w) 0152 gs.Add(_w, pos=(_row, 1), flag=wx.ALIGN_CENTER_VERTICAL) 0153 _btn=wx.Button(self, -1, 'Modify') 0154 wx.EVT_BUTTON(self, _btn.GetId(), self._OnFilter) 0155 gs.Add(_btn, pos=(_row+1, 0)) 0156 return gs 0157 0158 def _OnFilter(self, _): 0159 with guihelper.WXDialogWrapper(FilterDialog(self, -1, 'Filtering Parameters', self._data), 0160 True) as (dlg, retcode): 0161 if retcode==wx.ID_OK: 0162 self._data.update(dlg.get()) 0163 self._populate() 0164 0165 def _display(self, key, attr, fmt): 0166 # display the value field of this key 0167 _v=self._data.get(key, None) 0168 getattr(self, attr).SetLabel(_v is not None and eval(fmt) or '') 0169 def _populate(self): 0170 # populate the display with the filter parameters 0171 if self._data.get('preset_date', None) is None: 0172 _fmt="'%04d/%02d/%02d'%(_v[0], _v[1], _v[2])" 0173 else: 0174 _fmt="'<Preset>'" 0175 self._display('start', '_start', _fmt) 0176 self._display('end', '_end', _fmt) 0177 self._display('preset_date', '_preset', 0178 "['This Week', 'This Month', 'This Year', 'Next 7 Days'][_v]") 0179 self._display('rpt_events', '_repeat', 0180 "{ True: 'Import as mutil-single events', False: ''}[_v]") 0181 if self._data.get('no_alarm', None): 0182 _s='Disable All Alarms' 0183 elif self._data.get('alarm_override', None): 0184 _s='Set Alarm on All Events' 0185 else: 0186 _s='Use Alarm Settings from Import Source' 0187 self._alarm.SetLabel(_s) 0188 self._display('vibrate', '_vibrate', 0189 "{ True: 'Enable Vibrate for Alarms', False: ''}[_v]") 0190 self._display('ringtone', '_ringtone', "'%s'%((_v != 'Select:') and _v or '',)") 0191 self._display('alarm_value', '_alarm_value', "'%d'%_v") 0192 def get(self, data): 0193 data.update(self._data) 0194 def set(self, data): 0195 self._data=data 0196 self._populate() 0197 0198 #------------------------------------------------------------------------------- 0199 class ImportOptionPage(imp_cal_wizard.ImportOptionPage): 0200 _choices=('Replace All', 'Add', 'Preview', 'Merge') 0201 0202 #------------------------------------------------------------------------------- 0203 class ImportCalendarPresetWizard(wiz.Wizard): 0204 ID_ADD=wx.NewId() 0205 def __init__(self, parent, entry, 0206 id=-1, title='Calendar Import Preset Wizard'): 0207 super(ImportCalendarPresetWizard, self).__init__(parent, id, title) 0208 self._data=entry 0209 _import_name_page=PresetNamePage(self) 0210 _import_type_page=imp_cal_wizard.ImportTypePage(self) 0211 _import_source_page=imp_cal_wizard.ImportSourcePage(self) 0212 _import_filter_page=PresetFilterPage(self) 0213 _import_option=ImportOptionPage(self) 0214 0215 wiz.WizardPageSimple_Chain(_import_name_page, _import_type_page) 0216 wiz.WizardPageSimple_Chain(_import_type_page, _import_source_page) 0217 wiz.WizardPageSimple_Chain(_import_source_page, _import_filter_page) 0218 wiz.WizardPageSimple_Chain(_import_filter_page, _import_option) 0219 self.first_page=_import_name_page 0220 self.GetPageAreaSizer().Add(self.first_page, 1, wx.EXPAND|wx.ALL, 5) 0221 wiz.EVT_WIZARD_PAGE_CHANGING(self, self.GetId(), self.OnPageChanging) 0222 wiz.EVT_WIZARD_PAGE_CHANGED(self, self.GetId(), self.OnPageChanged) 0223 0224 def RunWizard(self, firstPage=None): 0225 return super(ImportCalendarPresetWizard, self).RunWizard(firstPage or self.first_page) 0226 0227 def OnPageChanging(self, evt): 0228 pg=evt.GetPage() 0229 if not evt.GetDirection() or pg.ok(): 0230 pg.get(self._data) 0231 else: 0232 evt.Veto() 0233 0234 def OnPageChanged(self, evt): 0235 evt.GetPage().set(self._data) 0236 0237 def get(self): 0238 return self._data 0239 0240 def GetActiveDatabase(self): 0241 return self.GetParent().GetActiveDatabase() 0242 0243 def get_categories(self): 0244 if self._data.get('data_obj', None): 0245 return self._data['data_obj'].get_category_list() 0246 return [] 0247 0248 #------------------------------------------------------------------------------- 0249 class CalendarPreviewDialog(wx.Dialog): 0250 ID_ADD=wx.NewId() 0251 ID_REPLACE=wx.NewId() 0252 ID_MERGE=wx.NewId() 0253 def __init__(self, parent, data): 0254 super(CalendarPreviewDialog, self).__init__(parent, -1, 0255 'Calendar Import Preview') 0256 _vbs=wx.BoxSizer(wx.VERTICAL) 0257 _lb=wx.ListBox(self, -1, style=wx.LB_SINGLE|wx.LB_HSCROLL|wx.LB_NEEDED_SB, 0258 choices=['%04d/%02d/%02d %02d:%02d - '%x.start+x.description\ 0259 for _,x in data.items()]) 0260 _vbs.Add(_lb, 0, wx.EXPAND|wx.ALL, 5) 0261 _vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 0262 _hbs=wx.BoxSizer(wx.HORIZONTAL) 0263 _btn=wx.Button(self, self.ID_REPLACE, 'Replace All') 0264 wx.EVT_BUTTON(self, self.ID_REPLACE, self._OnButton) 0265 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0266 _btn=wx.Button(self, self.ID_ADD, 'Add') 0267 wx.EVT_BUTTON(self, self.ID_ADD, self._OnButton) 0268 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0269 _btn=wx.Button(self, self.ID_MERGE, 'Merge') 0270 wx.EVT_BUTTON(self, self.ID_MERGE, self._OnButton) 0271 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0272 _hbs.Add(wx.Button(self, wx.ID_CANCEL, 'Cancel'), 0, 0273 wx.ALIGN_CENTRE|wx.ALL, 5) 0274 _vbs.Add(_hbs, 0, wx.EXPAND|wx.ALL, 5) 0275 self.SetSizer(_vbs) 0276 self.SetAutoLayout(True) 0277 _vbs.Fit(self) 0278 0279 def _OnButton(self, evt): 0280 self.EndModal(evt.GetId()) 0281 0282 #------------------------------------------------------------------------------- 0283 class ImportCalendarPresetDialog(wx.Dialog): 0284 ID_ADD=wx.NewId() 0285 ID_MERGE=wx.NewId() 0286 0287 def __init__(self, parent, id, title): 0288 self._parent=parent 0289 self._data={} 0290 self._import_data=None 0291 self._buttons=[] 0292 super(ImportCalendarPresetDialog, self).__init__(parent, id, 0293 title, 0294 size=(500, 500)) 0295 _vbs=wx.BoxSizer(wx.VERTICAL) 0296 _static_bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, 0297 'Available Presets:'), 0298 wx.VERTICAL) 0299 self._name_lb=wx.ListBox(self, -1, style=wx.LB_SINGLE|wx.LB_NEEDED_SB) 0300 wx.EVT_LISTBOX(self, self._name_lb.GetId(), self._set_button_states) 0301 _static_bs.Add(self._name_lb, 0, wx.EXPAND|wx.ALL, 5) 0302 _vbs.Add(_static_bs, 0, wx.EXPAND|wx.ALL, 5) 0303 _vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 0304 _hbs=wx.BoxSizer(wx.HORIZONTAL) 0305 _btn=wx.Button(self, -1, 'Import') 0306 wx.EVT_BUTTON(self, _btn.GetId(), self._OnRun) 0307 self._buttons.append(_btn) 0308 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0309 _btn=wx.Button(self, wx.ID_NEW) 0310 wx.EVT_BUTTON(self, _btn.GetId(), self._OnNew) 0311 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0312 _btn=wx.Button(self, -1, 'Edit') 0313 wx.EVT_BUTTON(self, _btn.GetId(), self._OnEdit) 0314 self._buttons.append(_btn) 0315 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0316 _btn=wx.Button(self, wx.ID_DELETE) 0317 self._buttons.append(_btn) 0318 wx.EVT_BUTTON(self, _btn.GetId(), self._OnDel) 0319 _hbs.Add(_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0320 _hbs.Add(wx.Button(self, wx.ID_CANCEL), 0321 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0322 _vbs.Add(_hbs, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0323 self.SetSizerAndFit(_vbs) 0324 if guihelper.IsGtk(): 0325 # On Linux, without this, the dialog clips the buttons 0326 self.SetSizeHints(-1, 175) 0327 self._get_from_fs() 0328 self._populate() 0329 0330 def _set_button_states(self, _=None): 0331 # set the appropriate states of the buttons depending on whether a 0332 # listbox item is selected or not 0333 _flg=self._name_lb.GetSelection()!=wx.NOT_FOUND 0334 [x.Enable(_flg) for x in self._buttons] 0335 0336 def _preview_data(self): 0337 # Display a preview of data just imported 0338 with guihelper.WXDialogWrapper(CalendarPreviewDialog(self, self._import_data.get()), 0339 True) as (_dlg, _ret_code): 0340 if _ret_code==CalendarPreviewDialog.ID_REPLACE: 0341 return wx.ID_OK 0342 elif _ret_code==CalendarPreviewDialog.ID_ADD: 0343 return self.ID_ADD 0344 elif _ret_code==CalendarPreviewDialog.ID_MERGE: 0345 return self.ID_MERGE 0346 return wx.ID_CANCEL 0347 0348 def _get_preset_thisweek(self): 0349 # return the dates of (today, Sat) 0350 _today=datetime.date.today() 0351 _dow=_today.isoweekday()%7 #Sun=0, Sat=6 0352 _end=_today+datetime.timedelta(6-_dow) 0353 return ((_today.year, _today.month, _today.day), 0354 (_end.year, _end.month, _end.day)) 0355 0356 def _get_preset_thismonth(self): 0357 # return the dates of (today, end-of-month) 0358 _today=datetime.date.today() 0359 _end=_today.replace(day=calendar.monthrange(_today.year,_today.month)[1]) 0360 return ((_today.year, _today.month, _today.day), 0361 (_end.year, _end.month, _end.day)) 0362 0363 def _get_preset_thisyear(self): 0364 # return the dates of (today, end-of-year) 0365 _today=datetime.date.today() 0366 _end=_today.replace(month=12, day=31) 0367 return ((_today.year, _today.month, _today.day), 0368 (_end.year, _end.month, _end.day)) 0369 0370 def _get_preset_next7(self): 0371 # return the dates of (today, today+6) 0372 _today=datetime.date.today() 0373 _end=_today+datetime.timedelta(days=6) 0374 return ((_today.year, _today.month, _today.day), 0375 (_end.year, _end.month, _end.day)) 0376 0377 def _adjust_filter_dates(self, entry): 0378 # Adjust the start/end dates of the filter 0379 _preset_date=entry.get('preset_date', None) 0380 if _preset_date is None: 0381 # No Preset date, bail 0382 return 0383 entry['start'], entry['end']=getattr(self, 0384 ['_get_preset_thisweek', 0385 '_get_preset_thismonth', 0386 '_get_preset_thisyear', 0387 '_get_preset_next7'][_preset_date])() 0388 @guihelper.BusyWrapper 0389 def _OnRun(self, _): 0390 _idx=self._name_lb.GetSelection() 0391 if _idx==wx.NOT_FOUND: 0392 return 0393 _entry=self._data[self._name_lb.GetClientData(_idx)] 0394 _my_type=_entry['type'] 0395 _info=[x for x in importexport.GetCalendarImports() \ 0396 if x['type']==_my_type] 0397 if not _info: 0398 return 0399 _info=_info[0] 0400 self._import_data=_info['data']() 0401 _source=_info['source']() 0402 _source.id=_entry['source_id'] 0403 with guihelper.WXDialogWrapper(wx.ProgressDialog('Calendar Data Import', 0404 'Importing data, please wait ...', 0405 parent=self)) as _dlg: 0406 self._import_data.read(_source.get(), _dlg) 0407 self._adjust_filter_dates(_entry) 0408 self._import_data.set_filter(_entry) 0409 global IMP_OPTION_PREVIEW, IMP_OPTION_REPLACEALL, IMP_OPTION_ADD, IMP_OPTION_MERGE 0410 _option=_entry.get('option', IMP_OPTION_PREVIEW) 0411 if _option==IMP_OPTION_PREVIEW: 0412 _ret_code=self._preview_data() 0413 elif _option==IMP_OPTION_ADD: 0414 _ret_code=self.ID_ADD 0415 elif _option==IMP_OPTION_MERGE: 0416 _ret_code=self.ID_MERGE 0417 else: 0418 _ret_code=wx.ID_OK 0419 self.EndModal(_ret_code) 0420 0421 def _OnNew(self, _): 0422 _entry=ImportCalendarEntry() 0423 with guihelper.WXDialogWrapper(ImportCalendarPresetWizard(self, _entry)) \ 0424 as _wiz: 0425 if _wiz.RunWizard(): 0426 _entry=_wiz.get() 0427 self._data[_entry.id]=_entry 0428 self._save_to_fs() 0429 self._populate() 0430 def _OnEdit(self, _): 0431 _idx=self._name_lb.GetSelection() 0432 if _idx==wx.NOT_FOUND: 0433 return 0434 _key=self._name_lb.GetClientData(_idx) 0435 _entry=self._data[_key].copy() 0436 with guihelper.WXDialogWrapper(ImportCalendarPresetWizard(self, _entry)) \ 0437 as _wiz: 0438 if _wiz.RunWizard(): 0439 _entry=ImportCalendarEntry(_wiz.get()) 0440 del self._data[_key] 0441 self._data[_entry.id]=_entry 0442 self._save_to_fs() 0443 self._populate() 0444 def _OnDel(self, _): 0445 _idx=self._name_lb.GetSelection() 0446 if _idx==wx.NOT_FOUND: 0447 return 0448 _key=self._name_lb.GetClientData(_idx) 0449 del self._data[_key] 0450 self._save_to_fs() 0451 self._populate() 0452 0453 def _populate(self): 0454 # populate the listbox with the name of the presets 0455 self._name_lb.Clear() 0456 for _key, _entry in self._data.items(): 0457 self._name_lb.Append(_entry['name'], _key) 0458 self._set_button_states() 0459 0460 def _expand_item(self, entry): 0461 item={} 0462 item.update(entry) 0463 if item.has_key('categories'): 0464 del item['categories'] 0465 if item.has_key('start'): 0466 del item['start'] 0467 if entry['start']: 0468 item['start']=[{'year': entry['start'][0], 0469 'month': entry['start'][1], 0470 'day': entry['start'][2] }] 0471 if item.has_key('end'): 0472 del item['end'] 0473 if entry['end']: 0474 item['end']=[{'year': entry['end'][0], 0475 'month': entry['end'][1], 0476 'day': entry['end'][2] }] 0477 return item 0478 def _collapse_item(self, entry): 0479 item={} 0480 item.update(entry) 0481 item['categories']=None 0482 if item.has_key('start') and item['start']: 0483 _d0=item['start'][0] 0484 item['start']=(_d0['year'], _d0['month'], _d0['day']) 0485 else: 0486 item['start']=None 0487 if item.has_key('end') and item['end']: 0488 _d0=entry['end'][0] 0489 item['end']=(_d0['year'], _d0['month'], _d0['day']) 0490 else: 0491 item['end']=None 0492 return item 0493 0494 def _get_from_fs(self): 0495 # read the presets data from DB 0496 _data=self._parent.GetActiveDatabase().getmajordictvalues('imp_cal_preset', 0497 importcalendarobjectfactory) 0498 self._data={} 0499 for _, _val in _data.items(): 0500 _entry=ImportCalendarEntry(self._collapse_item(_val)) 0501 self._data[_entry.id]=_entry 0502 self._populate() 0503 0504 def _save_to_fs(self): 0505 _data={} 0506 for _key, _entry in self._data.items(): 0507 _entry.validate_properties() 0508 _data[_key]=self._expand_item(_entry) 0509 database.ensurerecordtype(_data, importcalendarobjectfactory) 0510 self._parent.GetActiveDatabase().savemajordict('imp_cal_preset', 0511 _data) 0512 0513 def get(self): 0514 if self._import_data: 0515 return self._import_data.get() 0516 return {} 0517 0518 def get_categories(self): 0519 if self._import_data: 0520 return self._import_data.get_category_list() 0521 return [] 0522 0523 def GetActiveDatabase(self): 0524 return self._parent.GetActiveDatabase() 0525 0526 #------------------------------------------------------------------------------- 0527 # Testing 0528 if __name__=="__main__": 0529 app=wx.PySimpleApp() 0530 f=wx.Frame(None, title='imp_cal_preset') 0531 _data=ImportCalendarEntry() 0532 _data.id 0533 with guihelper.WXDialogWrapper(ImportCalendarPresetWizard(f, _data)) \ 0534 as w: 0535 print 'RunWizard:',w.RunWizard() 0536 print 'Data:',w.get() 0537
Generated by PyXR 0.9.4