0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2005 Joe Pham <djpham@netzero.net> 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: memo.py 4378 2007-08-27 17:47:50Z djpham $ 0009 0010 """ 0011 Code to handle memo/note items 0012 0013 The format for the memo is standardized. It is a dict with the following 0014 fields: 0015 0016 MemoEntry properties: 0017 subject - 'string subject' 0018 text - 'string text' 0019 categories - [{ 'category': 'string' }] 0020 secret - True/<False|None> 0021 date - date time/stamp 'Mmm dd, YYYY HH:MM' (Read only) 0022 id - unique id string that can be used as a dict key. 0023 0024 MemoEntry methods: 0025 get() - return a copy of the memo dict. 0026 set(dict) - set the internal dict to the new dict. 0027 set_date_now() - set the date/time stamp to the current date/time 0028 set_date_isostr(iso_string) - set the date/time stamp to the ISO date string 0029 (YYYYMMDDTHHMMSS) 0030 0031 To implement memo read/write for a phone module: 0032 Add 2 entries into Profile._supportedsyncs: 0033 ... 0034 ('memo', 'read', None), # all memo reading 0035 ('memo', 'write', 'OVERWRITE') # all memo writing 0036 0037 implement the following 2 methods in your Phone class: 0038 def getmemo(self, result): 0039 ... 0040 return result 0041 0042 def savememo(self, result, merge): 0043 ... 0044 return result 0045 0046 The result dict key is 'memo'. 0047 """ 0048 0049 # standard modules 0050 from __future__ import with_statement 0051 import copy 0052 import datetime 0053 import time 0054 0055 # wx modules 0056 import wx 0057 0058 # BitPim modules 0059 import bptime 0060 import calendarentryeditor as cal_editor 0061 import database 0062 import field_color 0063 import helpids 0064 import phonebookentryeditor as pb_editor 0065 import pubsub 0066 import today 0067 import guihelper 0068 import guiwidgets 0069 import widgets 0070 0071 widgets_list=[] 0072 module_debug=False 0073 0074 #------------------------------------------------------------------------------- 0075 class MemoDataObject(database.basedataobject): 0076 _knownproperties=['subject', 'date'] 0077 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 0078 _knownlistproperties.update( {'categories': ['category'], 0079 'flags': ['secret'], 0080 'body': ['type', 'data', '*' ] }) 0081 0082 def __init__(self, data=None): 0083 if data is None or not isinstance(data, MemoEntry): 0084 return; 0085 self.update(data.get_db_dict()) 0086 memoobjectfactory=database.dataobjectfactory(MemoDataObject) 0087 0088 #------------------------------------------------------------------------------- 0089 class MemoEntry(object): 0090 _body_subject_len=12 # the # of chars from body to fill in for subj + ... 0091 _id_index=0 0092 _max_id_index=999 0093 def __init__(self): 0094 self._data={ 'body': [], 'serials': [] } 0095 self.set_date_now() 0096 self._create_id() 0097 0098 def get(self): 0099 return copy.deepcopy(self._data, {}) 0100 def set(self, d): 0101 self._data={} 0102 self._data.update(d) 0103 0104 def get_db_dict(self): 0105 return self.get() 0106 def set_db_dict(self, d): 0107 self.set(d) 0108 0109 def _set_or_del(self, key, v, v_list=()): 0110 if v is None or v in v_list: 0111 if self._data.has_key(key): 0112 del self._data[key] 0113 else: 0114 self._data[key]=v 0115 0116 def _get_subject(self): 0117 return self._data.get('subject', '') 0118 def _set_subject(self, v): 0119 self._set_or_del('subject', v, ('',)) 0120 subject=property(fget=_get_subject, fset=_set_subject) 0121 0122 def _get_text(self): 0123 b=self._data.get('body', []) 0124 for n in b: 0125 if n.get('type', None)=='text': 0126 return n.get('data', '') 0127 return '' 0128 def _set_text(self, v): 0129 if v is None: 0130 v='' 0131 if not self.subject: 0132 self.subject=v[:self._body_subject_len]+'...' 0133 b=self._data.get('body', []) 0134 for n in b: 0135 if n.get('type', None)=='text': 0136 n['data']=v 0137 return 0138 self._data.setdefault('body', []).append(\ 0139 {'type': 'text', 'data': v }) 0140 text=property(fget=_get_text, fset=_set_text) 0141 0142 def _get_secret(self): 0143 f=self._data.get('flags', []) 0144 for n in f: 0145 if n.has_key('secret'): 0146 return n['secret'] 0147 return False 0148 def _set_secret(self, v): 0149 f=self._data.get('flags', []) 0150 for i, n in enumerate(f): 0151 if n.has_key('secret'): 0152 if v is None or not v: 0153 del f[i] 0154 if not self._data['flags']: 0155 del self._data['flags'] 0156 else: 0157 n['secret']=v 0158 return 0159 if v is not None and v: 0160 self._data.setdefault('flags', []).append({'secret': v}) 0161 secret=property(fget=_get_secret, fset=_set_secret) 0162 0163 def _get_categories(self): 0164 return self._data.get('categories', []) 0165 def _set_categories(self, v): 0166 self._set_or_del('categories', v, ([],)) 0167 categories=property(fget=_get_categories, fset=_set_categories) 0168 0169 def set_date_now(self): 0170 # set the date/time stamp to now 0171 n=datetime.datetime.now() 0172 self._data['date']=n.strftime('%b %d, %Y %H:%M') 0173 def set_date_isostr(self, iso_string): 0174 n=bptime.BPTime(iso_string) 0175 self._data['date']=n.date.strftime('%b %d, %Y')+n.time.strftime(' %H:%M') 0176 def _get_date(self): 0177 return self._data.get('date', '') 0178 date=property(fget=_get_date) 0179 0180 def _create_id(self): 0181 "Create a BitPim serial for this entry" 0182 self._data.setdefault("serials", []).append(\ 0183 {"sourcetype": "bitpim", 0184 "id": '%.3f%03d'%(time.time(), MemoEntry._id_index) }) 0185 if MemoEntry._id_index<MemoEntry._max_id_index: 0186 MemoEntry._id_index+=1 0187 else: 0188 MemoEntry._id_index=0 0189 def _get_id(self): 0190 s=self._data.get('serials', []) 0191 for n in s: 0192 if n.get('sourcetype', None)=='bitpim': 0193 return n.get('id', None) 0194 return None 0195 id=property(fget=_get_id) 0196 0197 #------------------------------------------------------------------------------- 0198 class GeneralEditor(pb_editor.DirtyUIBase): 0199 _dict_key_index=0 0200 _label_index=1 0201 _class_index=2 0202 _get_index=3 0203 _set_index=4 0204 _w_index=5 0205 def __init__(self, parent, _=None): 0206 global widgets_list 0207 0208 pb_editor.DirtyUIBase.__init__(self, parent) 0209 self._fields=[ 0210 ['subject', 'Subject:', cal_editor.DVTextControl, None, None, None], 0211 ['date', 'Date:', wx.StaticText, self._get_date_str, self._set_date_str, None], 0212 ['secret', 'Private:', wx.CheckBox, None, None, None] 0213 ] 0214 gs=wx.FlexGridSizer(-1, 2, 5, 5) 0215 gs.AddGrowableCol(1) 0216 for n in self._fields: 0217 _txt=wx.StaticText(self, -1, n[self._label_index], 0218 style=wx.ALIGN_LEFT) 0219 widgets_list.append((_txt, n[0])) 0220 gs.Add(_txt, 0, wx.EXPAND|wx.BOTTOM, 5) 0221 w=n[self._class_index](self, -1) 0222 gs.Add(w, 0, wx.EXPAND|wx.BOTTOM, 5) 0223 n[self._w_index]=w 0224 # event handlers 0225 wx.EVT_CHECKBOX(self, self._fields[2][self._w_index].GetId(), 0226 self.OnMakeDirty) 0227 # all done 0228 self.SetSizer(gs) 0229 self.SetAutoLayout(True) 0230 gs.Fit(self) 0231 0232 def _set_date_str(self, w, data): 0233 w.SetLabel(getattr(data, 'date')) 0234 def _get_date_str(self, w, _): 0235 pass 0236 def OnMakeDirty(self, evt): 0237 self.OnDirtyUI(evt) 0238 0239 def Set(self, data): 0240 self.ignore_dirty=True 0241 if data is None: 0242 for n in self._fields: 0243 n[self._w_index].Enable(False) 0244 else: 0245 for n in self._fields: 0246 w=n[self._w_index] 0247 w.Enable(True) 0248 if n[self._set_index] is None: 0249 w.SetValue(getattr(data, n[self._dict_key_index])) 0250 else: 0251 n[self._set_index](w, data) 0252 self.ignore_dirty=self.dirty=False 0253 0254 def Get(self, data): 0255 self.ignore_dirty=self.dirty=False 0256 if data is None: 0257 return 0258 for n in self._fields: 0259 w=n[self._w_index] 0260 if n[self._get_index] is None: 0261 v=w.GetValue() 0262 else: 0263 v=n[self._get_index](w, None) 0264 if v is not None: 0265 setattr(data, n[self._dict_key_index], v) 0266 0267 #------------------------------------------------------------------------------- 0268 class MemoWidget(wx.Panel, widgets.BitPimWidget): 0269 color_field_name='memo' 0270 0271 def __init__(self, mainwindow, parent): 0272 wx.Panel.__init__(self, parent, -1) 0273 self._main_window=mainwindow 0274 self._data={} 0275 self._data_map={} 0276 # main box sizer 0277 vbs=wx.BoxSizer(wx.VERTICAL) 0278 # horizontal sizer for the listbox and tabs 0279 hbs=wx.BoxSizer(wx.HORIZONTAL) 0280 # the list box 0281 self._item_list=wx.ListBox(self, wx.NewId(), 0282 style=wx.LB_SINGLE|wx.LB_HSCROLL|wx.LB_NEEDED_SB) 0283 hbs.Add(self._item_list, 1, wx.EXPAND|wx.BOTTOM, border=5) 0284 # the detailed info pane 0285 vbs1=wx.BoxSizer(wx.VERTICAL) 0286 self._items=( 0287 (GeneralEditor, 0, None), 0288 (cal_editor.CategoryEditor, 1, 'category'), 0289 (pb_editor.MemoEditor, 1, 'memo') 0290 ) 0291 self._w=[] 0292 for n in self._items: 0293 w=n[0](self, -1) 0294 vbs1.Add(w, n[1], wx.EXPAND|wx.ALL, 5) 0295 self._w.append(w) 0296 if n[2]: 0297 widgets_list.append((w.static_box, n[2])) 0298 hbs.Add(vbs1, 3, wx.EXPAND|wx.ALL, border=5) 0299 self._general_editor_w=self._w[0] 0300 self._cat_editor_w=self._w[1] 0301 self._memo_editor_w=self._w[2] 0302 # the bottom buttons 0303 hbs1=wx.BoxSizer(wx.HORIZONTAL) 0304 self._save_btn=wx.Button(self, wx.ID_SAVE) 0305 self._revert_btn=wx.Button(self, wx.ID_REVERT_TO_SAVED) 0306 help_btn=wx.Button(self, wx.ID_HELP) 0307 hbs1.Add(self._save_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0308 hbs1.Add(help_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0309 hbs1.Add(self._revert_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0310 # all done 0311 vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5) 0312 vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 0313 vbs.Add(hbs1, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 0314 self.SetSizer(vbs) 0315 self.SetAutoLayout(True) 0316 vbs.Fit(self) 0317 # event handlers 0318 wx.EVT_LISTBOX(self, self._item_list.GetId(), self._OnListBoxItem) 0319 wx.EVT_BUTTON(self, self._save_btn.GetId(), self._OnSave) 0320 wx.EVT_BUTTON(self, self._revert_btn.GetId(), self._OnRevert) 0321 wx.EVT_BUTTON(self, wx.ID_HELP, 0322 lambda _: wx.GetApp().displayhelpid(helpids.ID_TAB_MEMO)) 0323 # DIRTY UI Event handlers 0324 for w in self._w: 0325 pb_editor.EVT_DIRTY_UI(self, w.GetId(), self.OnMakeDirty) 0326 # populate data 0327 self._populate() 0328 # turn on dirty flag 0329 self.ignoredirty=False 0330 self.setdirty(False) 0331 # register for Today selection 0332 today.bind_notification_event(self.OnTodaySelection, 0333 today.Today_Group_Memo) 0334 # color code editable labels 0335 field_color.reload_color_info(self, widgets_list) 0336 pubsub.subscribe(self.OnPhoneChanged, pubsub.PHONE_MODEL_CHANGED) 0337 0338 def OnPhoneChanged(self, _): 0339 # just reload the color info based on the new phone 0340 field_color.reload_color_info(self, widgets_list) 0341 self.Refresh() 0342 0343 def _send_today_data(self): 0344 keys=self._data.keys() 0345 keys.sort() 0346 keys.reverse() 0347 today_event=today.TodayMemoEvent() 0348 for k in keys: 0349 today_event.append(self._data[k].subject, 0350 { 'key': k, 'index': self._data_map[k] }) 0351 today_event.broadcast() 0352 0353 def OnTodaySelection(self, evt): 0354 self.ActivateSelf() 0355 if evt.data: 0356 self._item_list.SetSelection(evt.data.get('index', wx.NOT_FOUND)) 0357 self._populate_each(evt.data.get('key', None)) 0358 0359 def _clear(self): 0360 self._item_list.Clear() 0361 self._clear_each() 0362 0363 def _clear_each(self): 0364 for w in self._w: 0365 w.Set(None) 0366 w.Enable(False) 0367 self.Refresh() 0368 0369 def _populate(self): 0370 # populate new data 0371 self._clear() 0372 self._data_map={} 0373 # populate the list with data 0374 keys=self._data.keys() 0375 keys.sort() 0376 for k in keys: 0377 n=self._data[k] 0378 i=self._item_list.Append(n.subject) 0379 self._item_list.SetClientData(i, k) 0380 self._data_map[k]=i 0381 self._send_today_data() 0382 0383 def _populate_each(self, k): 0384 # populate the detailed info of the item keyed k 0385 if k is None: 0386 # clear out all the subfields 0387 self._clear_each() 0388 return 0389 # there're data, first enable the widgets 0390 self.ignoredirty=True 0391 for w in self._w: 0392 w.Enable(True) 0393 entry=self._data[k] 0394 # set the general detail 0395 self._general_editor_w.Set(entry) 0396 self._cat_editor_w.Set(entry.categories) 0397 self._memo_editor_w.Set({ 'memo': entry.text }) 0398 self.ignoredirty=False 0399 self.setdirty(False) 0400 0401 # called from various widget update callbacks 0402 def OnMakeDirty(self, _=None): 0403 """A public function you can call that will set the dirty flag""" 0404 if self.dirty or self.ignoredirty or not self.IsShown(): 0405 # already dirty, no need to make it worse 0406 return 0407 self.setdirty(True) 0408 0409 def setdirty(self, val): 0410 """Set the dirty flag""" 0411 if self.ignoredirty: 0412 return 0413 self.dirty=val 0414 self._item_list.Enable(not self.dirty) 0415 self._save_btn.Enable(self.dirty) 0416 self._revert_btn.Enable(self.dirty) 0417 0418 def CanAdd(self): 0419 if self.dirty: 0420 return False 0421 return True 0422 0423 def GetDeleteInfo(self): 0424 return guihelper.ART_DEL_MEMO, "Delete Memo" 0425 0426 def GetAddInfo(self): 0427 return guihelper.ART_ADD_MEMO, "Add Memo" 0428 0429 def OnAdd(self, _): 0430 # add a new memo item 0431 if self.dirty: 0432 # busy editing, cannot add now, just return 0433 return 0434 m=MemoEntry() 0435 m.subject='New Memo' 0436 self._data[m.id]=m 0437 self._populate() 0438 self._save_to_db(self._data) 0439 self._item_list.Select(self._data_map[m.id]) 0440 self._populate_each(m.id) 0441 0442 def CanDelete(self): 0443 sel_idx=self._item_list.GetSelection() 0444 if sel_idx is None or sel_idx==-1: 0445 return False 0446 return True 0447 0448 def OnDelete(self, _): 0449 # delete the current selected item 0450 sel_idx=self._item_list.GetSelection() 0451 if sel_idx is None or sel_idx==-1: 0452 # none selected 0453 return 0454 self.ignoredirty=True 0455 k=self._item_list.GetClientData(sel_idx) 0456 self._item_list.Delete(sel_idx) 0457 self._clear_each() 0458 del self._data[k] 0459 del self._data_map[k] 0460 self._save_to_db(self._data) 0461 self.ignoredirty=False 0462 self.setdirty(False) 0463 0464 def getdata(self,dict,want=None): 0465 dict['memo']=copy.deepcopy(self._data) 0466 return dict 0467 0468 def get_selected_data(self): 0469 # return a dict of selected items 0470 res={} 0471 for _idx in self._item_list.GetSelections(): 0472 _key=self._item_list.GetClientData(_idx) 0473 if _key: 0474 res[_key]=self._data[_key] 0475 return res 0476 0477 def get_data(self): 0478 return self._data 0479 0480 def populate(self, dict): 0481 self._data=dict.get('memo', {}) 0482 self._populate() 0483 0484 def _save_to_db(self, memo_dict): 0485 db_rr={} 0486 for k, e in memo_dict.items(): 0487 db_rr[k]=MemoDataObject(e) 0488 database.ensurerecordtype(db_rr, memoobjectfactory) 0489 self._main_window.database.savemajordict('memo', db_rr) 0490 0491 def populatefs(self, dict): 0492 self._save_to_db(dict.get('memo', {})) 0493 return dict 0494 0495 def getfromfs(self, result): 0496 # read data from the database 0497 memo_dict=self._main_window.database.getmajordictvalues('memo', 0498 memoobjectfactory) 0499 r={} 0500 for k,e in memo_dict.items(): 0501 if __debug__ and module_debug: 0502 print e 0503 ce=MemoEntry() 0504 ce.set_db_dict(e) 0505 r[ce.id]=ce 0506 result.update({ 'memo': r }) 0507 return result 0508 0509 def _OnListBoxItem(self, evt): 0510 # an item was clicked on/selected 0511 self._populate_each(self._item_list.GetClientData(evt.GetInt())) 0512 self.Refresh() 0513 0514 def _OnSave(self, evt): 0515 # save the current changes 0516 self.ignoredirty=True 0517 sel_idx=self._item_list.GetSelection() 0518 k=self._item_list.GetClientData(sel_idx) 0519 entry=self._data[k] 0520 self._general_editor_w.Get(entry) 0521 entry.text=self._memo_editor_w.Get().get('memo', None) 0522 entry.categories=self._cat_editor_w.Get() 0523 entry.set_date_now() 0524 self._general_editor_w.Set(entry) 0525 self._item_list.SetString(sel_idx, entry.subject) 0526 self._save_to_db(self._data) 0527 self._send_today_data() 0528 self.ignoredirty=False 0529 self.setdirty(False) 0530 0531 def _OnRevert(self, evt): 0532 self.ignoredirty=True 0533 # Enable the list to get the selection 0534 self._item_list.Enable() 0535 sel_idx=self._item_list.GetSelection() 0536 if sel_idx!=wx.NOT_FOUND: 0537 k=self._item_list.GetClientData(sel_idx) 0538 self._populate_each(k) 0539 self.ignoredirty=False 0540 self.setdirty(False) 0541 0542 def OnPrintDialog(self, mainwindow, config): 0543 with guihelper.WXDialogWrapper(guiwidgets.MemoPrintDialog(self, mainwindow, config), 0544 True): 0545 pass 0546 def CanPrint(self): 0547 return True 0548
Generated by PyXR 0.9.4