PyXR

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



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