PyXR

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



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