Module memo
[hide private]
[frames] | no frames]

Source Code for Module memo

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2005 Joe Pham <djpham@netzero.net> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: memo.py 4714 2008-10-21 01:50:47Z djpham $ 
  9   
 10  """ 
 11  Code to handle memo/note items 
 12   
 13  The format for the memo is standardized.  It is a dict with the following 
 14  fields: 
 15   
 16  MemoEntry properties: 
 17  subject - 'string subject' 
 18  text - 'string text' 
 19  categories - [{ 'category': 'string' }] 
 20  secret - True/<False|None> 
 21  date - date time/stamp 'Mmm dd, YYYY HH:MM' (Read only) 
 22  id - unique id string that can be used as a dict key. 
 23   
 24  MemoEntry methods: 
 25  get() - return a copy of the memo dict. 
 26  set(dict) - set the internal dict to the new dict. 
 27  set_date_now() - set the date/time stamp to the current date/time 
 28  set_date_isostr(iso_string) - set the date/time stamp to the ISO date string 
 29                                (YYYYMMDDTHHMMSS) 
 30   
 31  To implement memo read/write for a phone module: 
 32   Add 2 entries into Profile._supportedsyncs: 
 33          ... 
 34          ('memo', 'read', None),     # all memo reading 
 35          ('memo', 'write', 'OVERWRITE')  # all memo writing 
 36   
 37  implement the following 2 methods in your Phone class: 
 38      def getmemo(self, result): 
 39          ... 
 40          return result 
 41   
 42      def savememo(self, result, merge): 
 43          ... 
 44          return result 
 45   
 46  The result dict key is 'memo'. 
 47  """ 
 48   
 49  # standard modules 
 50  from __future__ import with_statement 
 51  import copy 
 52  import datetime 
 53  import time 
 54   
 55  # wx modules 
 56  import wx 
 57   
 58  # BitPim modules 
 59  import bptime 
 60  import calendarentryeditor as cal_editor 
 61  import database 
 62  import field_color 
 63  import helpids 
 64  import phonebookentryeditor as pb_editor 
 65  import pubsub 
 66  import today 
 67  import guihelper 
 68  import guiwidgets 
 69  import widgets 
 70   
 71  widgets_list=[] 
 72  module_debug=False 
 73   
 74  #------------------------------------------------------------------------------- 
75 -class MemoDataObject(database.basedataobject):
76 _knownproperties=['subject', 'date'] 77 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 78 _knownlistproperties.update( {'categories': ['category'], 79 'flags': ['secret'], 80 'body': ['type', 'data', '*' ] }) 81
82 - def __init__(self, data=None):
83 if data is None or not isinstance(data, MemoEntry): 84 return; 85 self.update(data.get_db_dict())
86 memoobjectfactory=database.dataobjectfactory(MemoDataObject) 87 88 #-------------------------------------------------------------------------------
89 -class MemoEntry(object):
90 _body_subject_len=12 # the # of chars from body to fill in for subj + ... 91 _id_index=0 92 _max_id_index=999
93 - def __init__(self):
94 self._data={ 'body': [], 'serials': [] } 95 self.set_date_now() 96 self._create_id()
97
98 - def get(self):
99 return copy.deepcopy(self._data, {})
100 - def set(self, d):
101 self._data={} 102 self._data.update(d)
103
104 - def get_db_dict(self):
105 return self.get()
106 - def set_db_dict(self, d):
107 self.set(d)
108
109 - def _set_or_del(self, key, v, v_list=()):
110 if v is None or v in v_list: 111 if self._data.has_key(key): 112 del self._data[key] 113 else: 114 self._data[key]=v
115
116 - def _get_subject(self):
117 return self._data.get('subject', '')
118 - def _set_subject(self, v):
119 self._set_or_del('subject', v, ('',))
120 subject=property(fget=_get_subject, fset=_set_subject) 121
122 - def _get_text(self):
123 b=self._data.get('body', []) 124 for n in b: 125 if n.get('type', None)=='text': 126 return n.get('data', '') 127 return ''
128 - def _set_text(self, v):
129 if v is None: 130 v='' 131 if not self.subject: 132 self.subject=v[:self._body_subject_len]+'...' 133 b=self._data.get('body', []) 134 for n in b: 135 if n.get('type', None)=='text': 136 n['data']=v 137 return 138 self._data.setdefault('body', []).append(\ 139 {'type': 'text', 'data': v })
140 text=property(fget=_get_text, fset=_set_text) 141
142 - def _get_secret(self):
143 f=self._data.get('flags', []) 144 for n in f: 145 if n.has_key('secret'): 146 return n['secret'] 147 return False
148 - def _set_secret(self, v):
149 f=self._data.get('flags', []) 150 for i, n in enumerate(f): 151 if n.has_key('secret'): 152 if v is None or not v: 153 del f[i] 154 if not self._data['flags']: 155 del self._data['flags'] 156 else: 157 n['secret']=v 158 return 159 if v is not None and v: 160 self._data.setdefault('flags', []).append({'secret': v})
161 secret=property(fget=_get_secret, fset=_set_secret) 162
163 - def _get_categories(self):
164 return self._data.get('categories', [])
165 - def _set_categories(self, v):
166 self._set_or_del('categories', v, ([],))
167 categories=property(fget=_get_categories, fset=_set_categories) 168
169 - def set_date_now(self):
170 # set the date/time stamp to now 171 n=datetime.datetime.now() 172 self._data['date']=n.strftime('%b %d, %Y %H:%M')
173 - def set_date_isostr(self, iso_string):
174 n=bptime.BPTime(iso_string) 175 self._data['date']=n.date.strftime('%b %d, %Y')+n.time.strftime(' %H:%M')
176 - def _get_date(self):
177 return self._data.get('date', '')
178 date=property(fget=_get_date) 179
180 - def _create_id(self):
181 "Create a BitPim serial for this entry" 182 self._data.setdefault("serials", []).append(\ 183 {"sourcetype": "bitpim", 184 "id": '%.3f%03d'%(time.time(), MemoEntry._id_index) }) 185 if MemoEntry._id_index<MemoEntry._max_id_index: 186 MemoEntry._id_index+=1 187 else: 188 MemoEntry._id_index=0
189 - def _get_id(self):
190 s=self._data.get('serials', []) 191 for n in s: 192 if n.get('sourcetype', None)=='bitpim': 193 return n.get('id', None) 194 return None
195 id=property(fget=_get_id)
196 197 #-------------------------------------------------------------------------------
198 -class GeneralEditor(pb_editor.DirtyUIBase):
199 _dict_key_index=0 200 _label_index=1 201 _class_index=2 202 _get_index=3 203 _set_index=4 204 _w_index=5
205 - def __init__(self, parent, _=None):
206 global widgets_list 207 208 pb_editor.DirtyUIBase.__init__(self, parent) 209 self._fields=[ 210 ['subject', 'Subject:', cal_editor.DVTextControl, None, None, None], 211 ['date', 'Date:', wx.StaticText, self._get_date_str, self._set_date_str, None], 212 ['secret', 'Private:', wx.CheckBox, None, None, None] 213 ] 214 gs=wx.FlexGridSizer(-1, 2, 5, 5) 215 gs.AddGrowableCol(1) 216 for n in self._fields: 217 _txt=wx.StaticText(self, -1, n[self._label_index], 218 style=wx.ALIGN_LEFT) 219 widgets_list.append((_txt, n[0])) 220 gs.Add(_txt, 0, wx.EXPAND|wx.BOTTOM, 5) 221 w=n[self._class_index](self, -1) 222 gs.Add(w, 0, wx.EXPAND|wx.BOTTOM, 5) 223 n[self._w_index]=w 224 # event handlers 225 wx.EVT_CHECKBOX(self, self._fields[2][self._w_index].GetId(), 226 self.OnMakeDirty) 227 # all done 228 self.SetSizer(gs) 229 self.SetAutoLayout(True) 230 gs.Fit(self)
231
232 - def _set_date_str(self, w, data):
233 w.SetLabel(getattr(data, 'date'))
234 - def _get_date_str(self, w, _):
235 pass
236 - def OnMakeDirty(self, evt):
237 self.OnDirtyUI(evt)
238
239 - def Set(self, data):
240 self.ignore_dirty=True 241 if data is None: 242 for n in self._fields: 243 n[self._w_index].Enable(False) 244 else: 245 for n in self._fields: 246 w=n[self._w_index] 247 w.Enable(True) 248 if n[self._set_index] is None: 249 w.SetValue(getattr(data, n[self._dict_key_index])) 250 else: 251 n[self._set_index](w, data) 252 self.ignore_dirty=self.dirty=False
253
254 - def Get(self, data):
255 self.ignore_dirty=self.dirty=False 256 if data is None: 257 return 258 for n in self._fields: 259 w=n[self._w_index] 260 if n[self._get_index] is None: 261 v=w.GetValue() 262 else: 263 v=n[self._get_index](w, None) 264 if v is not None: 265 setattr(data, n[self._dict_key_index], v)
266 267 #-------------------------------------------------------------------------------
268 -class MemoWidget(wx.Panel, widgets.BitPimWidget):
269 color_field_name='memo' 270
271 - def __init__(self, mainwindow, parent):
272 wx.Panel.__init__(self, parent, -1) 273 self._main_window=mainwindow 274 self._data={} 275 self._data_map={} 276 # main box sizer 277 vbs=wx.BoxSizer(wx.VERTICAL) 278 # horizontal sizer for the listbox and tabs 279 hbs=wx.BoxSizer(wx.HORIZONTAL) 280 # the list box 281 self._item_list=wx.ListBox(self, wx.NewId(), 282 style=wx.LB_SINGLE|wx.LB_HSCROLL|wx.LB_NEEDED_SB) 283 hbs.Add(self._item_list, 1, wx.EXPAND|wx.BOTTOM, border=5) 284 # the detailed info pane 285 vbs1=wx.BoxSizer(wx.VERTICAL) 286 self._items=( 287 (GeneralEditor, 0, None), 288 (cal_editor.CategoryEditor, 1, 'category'), 289 (pb_editor.MemoEditor, 1, 'memo') 290 ) 291 self._w=[] 292 for n in self._items: 293 w=n[0](self, -1) 294 vbs1.Add(w, n[1], wx.EXPAND|wx.ALL, 5) 295 self._w.append(w) 296 if n[2]: 297 widgets_list.append((w.static_box, n[2])) 298 hbs.Add(vbs1, 3, wx.EXPAND|wx.ALL, border=5) 299 self._general_editor_w=self._w[0] 300 self._cat_editor_w=self._w[1] 301 self._memo_editor_w=self._w[2] 302 # the bottom buttons 303 hbs1=wx.BoxSizer(wx.HORIZONTAL) 304 self._save_btn=wx.Button(self, wx.ID_SAVE) 305 self._revert_btn=wx.Button(self, wx.ID_REVERT_TO_SAVED) 306 help_btn=wx.Button(self, wx.ID_HELP) 307 hbs1.Add(self._save_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 308 hbs1.Add(help_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 309 hbs1.Add(self._revert_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 310 # all done 311 vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5) 312 vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 313 vbs.Add(hbs1, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 314 self.SetSizer(vbs) 315 self.SetAutoLayout(True) 316 vbs.Fit(self) 317 # event handlers 318 wx.EVT_LISTBOX(self, self._item_list.GetId(), self._OnListBoxItem) 319 wx.EVT_BUTTON(self, self._save_btn.GetId(), self._OnSave) 320 wx.EVT_BUTTON(self, self._revert_btn.GetId(), self._OnRevert) 321 wx.EVT_BUTTON(self, wx.ID_HELP, 322 lambda _: wx.GetApp().displayhelpid(helpids.ID_TAB_MEMO)) 323 # DIRTY UI Event handlers 324 for w in self._w: 325 pb_editor.EVT_DIRTY_UI(self, w.GetId(), self.OnMakeDirty) 326 # populate data 327 self._populate() 328 # turn on dirty flag 329 self.ignoredirty=False 330 self.setdirty(False) 331 # register for Today selection 332 today.bind_notification_event(self.OnTodaySelection, 333 today.Today_Group_Memo) 334 # color code editable labels 335 field_color.reload_color_info(self, widgets_list) 336 pubsub.subscribe(self.OnPhoneChanged, pubsub.PHONE_MODEL_CHANGED)
337
338 - def OnPhoneChanged(self, _):
339 # just reload the color info based on the new phone 340 field_color.reload_color_info(self, widgets_list) 341 self.Refresh()
342
343 - def _send_today_data(self):
344 keys=self._data.keys() 345 keys.sort() 346 keys.reverse() 347 today_event=today.TodayMemoEvent() 348 for k in keys: 349 today_event.append(self._data[k].subject, 350 { 'key': k, 'index': self._data_map[k] }) 351 today_event.broadcast()
352
353 - def OnTodaySelection(self, evt):
354 self.ActivateSelf() 355 if evt.data: 356 self._item_list.SetSelection(evt.data.get('index', wx.NOT_FOUND)) 357 self._populate_each(evt.data.get('key', None))
358
359 - def _clear(self):
360 self._item_list.Clear() 361 self._clear_each()
362
363 - def _clear_each(self):
364 for w in self._w: 365 w.Set(None) 366 w.Enable(False) 367 self.Refresh()
368
369 - def _populate(self):
370 # populate new data 371 self._clear() 372 self._data_map={} 373 # populate the list with data 374 keys=self._data.keys() 375 keys.sort() 376 for k in keys: 377 n=self._data[k] 378 i=self._item_list.Append(n.subject) 379 self._item_list.SetClientData(i, k) 380 self._data_map[k]=i 381 self._send_today_data()
382
383 - def _populate_each(self, k):
384 # populate the detailed info of the item keyed k 385 if k is None: 386 # clear out all the subfields 387 self._clear_each() 388 return 389 # there're data, first enable the widgets 390 self.ignoredirty=True 391 for w in self._w: 392 w.Enable(True) 393 entry=self._data[k] 394 # set the general detail 395 self._general_editor_w.Set(entry) 396 self._cat_editor_w.Set(entry.categories) 397 self._memo_editor_w.Set({ 'memo': entry.text }) 398 self.ignoredirty=False 399 self.setdirty(False)
400 401 # called from various widget update callbacks
402 - def OnMakeDirty(self, _=None):
403 """A public function you can call that will set the dirty flag""" 404 if self.dirty or self.ignoredirty or not self.IsShown(): 405 # already dirty, no need to make it worse 406 return 407 self.setdirty(True)
408
409 - def setdirty(self, val):
410 """Set the dirty flag""" 411 if self.ignoredirty: 412 return 413 self.dirty=val 414 self._item_list.Enable(not self.dirty) 415 self._save_btn.Enable(self.dirty) 416 self._revert_btn.Enable(self.dirty)
417
418 - def CanAdd(self):
419 if self.dirty: 420 return False 421 return True
422
423 - def GetDeleteInfo(self):
424 return guihelper.ART_DEL_MEMO, "Delete Memo"
425
426 - def GetAddInfo(self):
427 return guihelper.ART_ADD_MEMO, "Add Memo"
428
429 - def OnAdd(self, _):
430 # add a new memo item 431 if self.dirty: 432 # busy editing, cannot add now, just return 433 return 434 m=MemoEntry() 435 m.subject='New Memo' 436 self._data[m.id]=m 437 self._populate() 438 self._save_to_db(self._data) 439 self._item_list.Select(self._data_map[m.id]) 440 self._populate_each(m.id)
441
442 - def CanDelete(self):
443 sel_idx=self._item_list.GetSelection() 444 if sel_idx is None or sel_idx==-1: 445 return False 446 return True
447
448 - def OnDelete(self, _):
449 # delete the current selected item 450 sel_idx=self._item_list.GetSelection() 451 if sel_idx is None or sel_idx==-1: 452 # none selected 453 return 454 self.ignoredirty=True 455 k=self._item_list.GetClientData(sel_idx) 456 self._item_list.Delete(sel_idx) 457 self._clear_each() 458 del self._data[k] 459 del self._data_map[k] 460 self._save_to_db(self._data) 461 self.ignoredirty=False 462 self.setdirty(False)
463
464 - def getdata(self,dict,want=None):
465 dict['memo']=copy.deepcopy(self._data) 466 return dict
467
468 - def get_selected_data(self):
469 # return a dict of selected items 470 res={} 471 for _idx in self._item_list.GetSelections(): 472 _key=self._item_list.GetClientData(_idx) 473 if _key: 474 res[_key]=self._data[_key] 475 return res
476
477 - def get_data(self):
478 return self._data
479
480 - def populate(self, dict):
481 self._data=dict.get('memo', {}) 482 self._populate()
483
484 - def _save_to_db(self, memo_dict):
485 db_rr={} 486 for k, e in memo_dict.items(): 487 db_rr[k]=MemoDataObject(e) 488 database.ensurerecordtype(db_rr, memoobjectfactory) 489 self._main_window.database.savemajordict('memo', db_rr)
490
491 - def populatefs(self, dict):
492 self._save_to_db(dict.get('memo', {})) 493 return dict
494
495 - def getfromfs(self, result):
496 # read data from the database 497 memo_dict=self._main_window.database.getmajordictvalues('memo', 498 memoobjectfactory) 499 r={} 500 for k,e in memo_dict.items(): 501 if __debug__ and module_debug: 502 print e 503 ce=MemoEntry() 504 ce.set_db_dict(e) 505 r[ce.id]=ce 506 result.update({ 'memo': r }) 507 return result
508
509 - def _OnListBoxItem(self, evt):
510 # an item was clicked on/selected 511 self._populate_each(self._item_list.GetClientData(evt.GetInt())) 512 self.Refresh()
513
514 - def _OnSave(self, evt):
515 # save the current changes 516 self.ignoredirty=True 517 sel_idx=self._item_list.GetSelection() 518 k=self._item_list.GetClientData(sel_idx) 519 entry=self._data[k] 520 self._general_editor_w.Get(entry) 521 entry.text=self._memo_editor_w.Get().get('memo', None) 522 entry.categories=self._cat_editor_w.Get() 523 entry.set_date_now() 524 self._general_editor_w.Set(entry) 525 self._item_list.SetString(sel_idx, entry.subject) 526 self._save_to_db(self._data) 527 self._send_today_data() 528 self.ignoredirty=False 529 self.setdirty(False)
530
531 - def _OnRevert(self, evt):
532 self.ignoredirty=True 533 # Enable the list to get the selection 534 self._item_list.Enable() 535 sel_idx=self._item_list.GetSelection() 536 if sel_idx!=wx.NOT_FOUND: 537 k=self._item_list.GetClientData(sel_idx) 538 self._populate_each(k) 539 self.ignoredirty=False 540 self.setdirty(False)
541
542 - def OnPrintDialog(self, mainwindow, config):
543 with guihelper.WXDialogWrapper(guiwidgets.MemoPrintDialog(self, mainwindow, config), 544 True): 545 pass
546 - def CanPrint(self):
547 return True
548
549 - def get_keys(self):
550 """Return the list of keys as being displayed""" 551 return [ self._item_list.GetClientData(x) \ 552 for x in range(self._item_list.GetCount()) ]
553
554 - def get_selected_keys(self):
555 """Return the list of keys of selected items being displayed""" 556 return [ self._item_list.GetClientData(x) \ 557 for x in self._item_list.GetSelections() ]
558