Trees | Indices | Help |
|
---|
|
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: todo.py 4459 2007-11-24 07:55:33Z djpham $ 9 10 """ 11 Code to handle Todo Items 12 13 The format for the Todo items is standardized. It is a dict with the following 14 fields: 15 16 TodoEntry properties: 17 summary - 'string subject' 18 note - 'string note' 19 due_date - 'YYYYMMDD' 20 status - (None, NotStarted, InProgress, NeedActions, Completed, Cancelled) 21 percent_complete - None, range(101) 22 completion_date - 'YYYYMMDD' 23 categories - [{ 'category': string }] 24 private - True/<False|None> 25 priority - range(1, 11) 1=Highest, 5=Normal, 10=Lowest 26 27 TodoEntry Methods: 28 get() - return a copy of the internal dict 29 set(dict) - set the internal dict 30 check_completion() - check the task for completion and if so set appropriate 31 values 32 completion() - set the task as completed and set appropriate values 33 34 To implement Todo read/write for a phone module: 35 Add 2 entries into Profile._supportedsyncs: 36 ... 37 ('todo', 'read', None), # all todo reading 38 ('todo', 'write', 'OVERWRITE') # all todo writing 39 40 implement the following 2 methods in your Phone class: 41 def gettodo(self, result): 42 ... 43 return result 44 45 def savetodo(self, result, merge): 46 ... 47 return result 48 49 The result dict key is 'todo'. 50 51 """ 52 53 # standard modules 54 from __future__ import with_statement 55 import copy 56 import datetime 57 import time 58 59 # wx modules 60 import wx 61 import wx.lib.calendar 62 import wx.calendar as cal 63 import wx.lib.scrolledpanel as scrolled 64 65 # BitPim modules 66 import calendarentryeditor as cal_editor 67 import database 68 import guihelper 69 import helpids 70 import field_color 71 import phonebookentryeditor as pb_editor 72 import pubsub 73 import today 74 import guihelper 75 import widgets 76 77 widgets_list=[] 78 79 #-------------------------------------------------------------------------------81 _knownproperties=['summary', 'note', 'due_date', 'status', 82 'percent_complete', 'completion_date', 'priority' ] 83 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 84 _knownlistproperties.update( {'categories': ['category'], 85 'flags': ['secret'] }) 8691 todoobjectfactory=database.dataobjectfactory(TodoDataObject) 92 93 #-------------------------------------------------------------------------------95 ST_NotStarted=1 96 ST_InProgress=2 97 ST_NeedActions=3 98 ST_Completed=4 99 ST_Cancelled=5 100 ST_Last=6 101 ST_Range=xrange(ST_NotStarted, ST_Last) 102 ST_Names=( 103 '<None>', 'Not Started', 'In Progess', 'Need Actions', 104 'Completed', 'Cancelled', 'LAST') 105 PC_Range=xrange(101) # % Complete: 0-100% 106 PR_Range=xrange(1, 11) 107 _id_index=0 108 _max_id_index=999 109 113250 251 #-------------------------------------------------------------------------------115 return copy.deepcopy(self._data, {})119121 return self.get()123 self.set(d)124126 # complete this task: set relevant values to indicate so 127 if self.status != self.ST_Completed: 128 self.status=self.ST_Completed 129 if self.percent_complete != 100: 130 self.percent_complete=100 131 if not len(self.completion_date): 132 self.completion_date=datetime.date.today().strftime('%Y%m%d')133135 if self.status==self.ST_Completed or self.percent_complete==100 or \ 136 len(self.completion_date): 137 self.complete()138140 if v is None or v in v_list: 141 if self._data.has_key(key): 142 del self._data[key] 143 else: 144 self._data[key]=v145147 "Create a BitPim serial for this entry" 148 self._data.setdefault("serials", []).append(\ 149 {"sourcetype": "bitpim", 150 "id": '%.3f%03d'%(time.time(), TodoEntry._id_index) }) 151 if TodoEntry._id_index<TodoEntry._max_id_index: 152 TodoEntry._id_index+=1 153 else: 154 TodoEntry._id_index=0156 s=self._data.get('serials', []) 157 for n in s: 158 if n.get('sourcetype', None)=='bitpim': 159 return n.get('id', None) 160 return None161 id=property(fget=_get_id) 162164 return self._data.get('summary', '')166 self._set_or_del('summary', v, [''])167 summary=property(fget=_get_summary, fset=_set_summary) 168170 return self._data.get('note', '')172 self._set_or_del('note', v, [''])173 note=property(fget=_get_note, fset=_set_note) 174176 return self._data.get('due_date', '')178 self._set_or_del('due_date', v, [''])179 due_date=property(fget=_get_due_date, fset=_set_due_date) 180182 return self._data.get('status', None)184 if v is not None and v not in self.ST_Range: 185 raise ValueError, 'Illegal Status Value' 186 self._set_or_del('status', v, []) 187 if v==self.ST_Completed: 188 self.complete()189 status=property(fget=_get_status, fset=_set_status) 193195 return self._data.get('percent_complete', None)197 if v is not None and v not in self.PC_Range: 198 raise ValueError, 'Illegal Percent Complete Value' 199 self._set_or_del('percent_complete', v, []) 200 if v==100: 201 self.complete()202 percent_complete=property(fget=_get_percent_complete, 203 fset=_set_percent_complete) 204206 return self._data.get('completion_date', '')208 self._set_or_del('completion_date', v, ['']) 209 if v is not None and len(v): 210 self.complete()211 completion_date=property(fget=_get_completion_date, 212 fset=_set_completion_date) 213215 return self._data.get('priority', None)217 if v is not None and v not in self.PR_Range: 218 raise ValueError, 'Illegal priority value' 219 self._set_or_del('priority', v, [])220 priority=property(fget=_get_priority, fset=_set_priority) 221223 return self._data.get('categories', [])225 self._set_or_del('categories', s,[]) 226 if not s and self._data.has_key('categories'): 227 del self._data['categories']228 categories=property(fget=_get_categories, fset=_set_categories) 229231 f=self._data.get('flags', []) 232 for n in f: 233 if n.has_key('secret'): 234 return n['secret'] 235 return False237 f=self._data.get('flags', []) 238 for i, n in enumerate(f): 239 if n.has_key('secret'): 240 if v is None or not v: 241 del f[i] 242 if not self._data['flags']: 243 del self._data['flags'] 244 else: 245 n['secret']=v 246 return 247 if v is not None and v: 248 self._data.setdefault('flags', []).append({'secret': v})249 private=property(fget=_get_secret, fset=_set_secret)273 274 #-------------------------------------------------------------------------------254 self._choices=[TodoEntry.ST_Names[x] for x in range(TodoEntry.ST_Last)] 255 super(StatusComboBox, self).__init__(parent, -1, 256 self._choices[0], 257 (-1, -1), (-1, -1), 258 self._choices, wx.CB_READONLY) 259 wx.EVT_COMBOBOX(self, self.GetId(), parent.OnMakeDirty)261 s=super(StatusComboBox, self).GetValue() 262 for v,n in enumerate(self._choices): 263 if n==s: 264 break; 265 if v: 266 return v 267 else: 268 return None298 299 #-------------------------------------------------------------------------------277 self. _choices=['<None>', '0%', '10%', '20%', '30%', '40%', 278 '50%', '60%', '70%', '80%', '90%', '100%'] 279 super(PercentCompleteBox, self).__init__(parent, -1, self._choices[0], 280 (-1,-1), (-1,-1), 281 self._choices, wx.CB_READONLY) 282 wx.EVT_COMBOBOX(self, self.GetId(), parent.OnMakeDirty)284 s=super(PercentCompleteBox, self).GetValue() 285 for v,n in enumerate(self._choices): 286 if n==s: 287 break 288 if v: 289 return (v-1)*10 290 else: 291 return None293 if v is None: 294 v=0 295 else: 296 v=(v/10)+1 297 super(PercentCompleteBox, self).SetValue(self._choices[v])321 322 #-------------------------------------------------------------------------------302 self._choices=['<None>', '1 - Highest', '2', '3', '4', '5 - Normal', 303 '6', '7', '8', '9', '10 - Lowest'] 304 super(PriorityBox, self).__init__(parent, -1, self._choices[0], 305 (-1, -1), (-1, -1), 306 self._choices, wx.CB_READONLY) 307 wx.EVT_COMBOBOX(self, self.GetId(), parent.OnMakeDirty)309 s=super(PriorityBox, self).GetValue() 310 for v,n in enumerate(self._choices): 311 if n==s: 312 break 313 if v: 314 return v 315 else: 316 return None374 375 #-------------------------------------------------------------------------------325 super(DateControl, self).__init__(parent, -1) 326 self._dt=None 327 # main box sizer, a label, and a button 328 self._hs=wx.BoxSizer(wx.HORIZONTAL) 329 self._date_str=wx.StaticText(self, -1, '<None>') 330 self._date_btn=wx.Button(self, -1, 'Set Date') 331 self._hs.Add(self._date_str, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5) 332 self._hs.Add(self._date_btn, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5) 333 # events 334 wx.EVT_BUTTON(self, self._date_btn.GetId(), self._OnSetDate) 335 # all done 336 self.SetSizer(self._hs) 337 self.SetAutoLayout(True)339 if self._dt is None: 340 s='<None>' 341 else : 342 s=self._dt.strftime('%Y-%m-%d') 343 self._date_str.SetLabel(s) 344 self._hs.Layout() 345 self.GetParent().OnMakeDirty(None)347 # bring up a calendar dlg 348 if self._dt is None: 349 dt=datetime.date.today() 350 else: 351 dt=self._dt 352 with guihelper.WXDialogWrapper(wx.lib.calendar.CalenDlg(self, 353 month=dt.month, 354 day=dt.day, 355 year=dt.year)) as dlg: 356 dlg.Centre() 357 if dlg.ShowModal() == wx.ID_OK: 358 self._dt=datetime.date(dlg.calend.GetYear(), 359 dlg.calend.GetMonth(), 360 dlg.calend.GetDay()) 361 self._refresh()363 # set a date string from the dict 364 if v is None or not len(v): 365 self._dt=None 366 else: 367 self._dt=datetime.date(int(v[:4]), int(v[4:6]), int(v[6:])) 368 self._refresh()380 381 #-------------------------------------------------------------------------------378 super(DirtyCheckBox, self).__init__(parent, -1) 379 wx.EVT_CHECKBOX(self, self.GetId(), parent.OnMakeDirty)383 _dict_key_index=0 384 _label_index=1 385 _class_index=2 386 _get_index=3 387 _set_index=4 388 _w_index=5 389 _flg_index=6 390444 445 #-------------------------------------------------------------------------------392 global widgets_list 393 394 super(GeneralEditor, self).__init__(parent) 395 self._fields=[ 396 ['summary', 'Summary:', cal_editor.DVTextControl, None, None, None, wx.EXPAND], 397 ['status', 'Status:', StatusComboBox, None, None, None, 0], 398 ['due_date', 'Due Date:', DateControl, None, None, None, wx.EXPAND], 399 ['percent_complete', '% Complete:', PercentCompleteBox, None, None, None, 0], 400 ['completion_date', 'Completion Date:', DateControl, None, None, None, wx.EXPAND], 401 ['private', 'Private:', DirtyCheckBox, None, None, None, 0], 402 ['priority', 'Priority:', PriorityBox, None, None, None, 0] 403 ] 404 gs=wx.FlexGridSizer(-1, 2, 5, 5) 405 gs.AddGrowableCol(1) 406 for n in self._fields: 407 _txt=wx.StaticText(self, -1, n[self._label_index], 408 style=wx.ALIGN_LEFT) 409 widgets_list.append((_txt, n[self._dict_key_index])) 410 gs.Add(_txt, 0, wx.EXPAND|wx.BOTTOM, 0) 411 w=n[self._class_index](self, -1) 412 gs.Add(w, 0, n[self._flg_index]|wx.BOTTOM, 5) 413 n[self._w_index]=w 414 # event handlers 415 # all done 416 self.SetSizer(gs) 417 self.SetAutoLayout(True) 418 gs.Fit(self)419421 self.OnDirtyUI(evt)422424 self.ignore_dirty=True 425 if data is None: 426 for n in self._fields: 427 n[self._w_index].Enable(False) 428 else: 429 for n in self._fields: 430 w=n[self._w_index] 431 w.Enable(True) 432 w.SetValue(getattr(data, n[self._dict_key_index])) 433 self.ignore_dirty=self.dirty=False434447 color_field_name='todo' 448748 749 #------------------------------------------------------------------------------- 750450 global widgets_list 451 452 super(TodoWidget, self).__init__(parent, -1) 453 self._main_window=mainwindow 454 self._data=self._data_map={} 455 # main box sizer 456 vbs=wx.BoxSizer(wx.VERTICAL) 457 # horizontal sizer for the listbox and tabs 458 hbs=wx.BoxSizer(wx.HORIZONTAL) 459 # the list box 460 self._item_list=wx.ListBox(self, wx.NewId(), 461 style=wx.LB_SINGLE|wx.LB_HSCROLL|wx.LB_NEEDED_SB) 462 hbs.Add(self._item_list, 1, wx.EXPAND|wx.BOTTOM, border=5) 463 # the detailed info pane as a scrolled panel 464 scrolled_panel=scrolled.ScrolledPanel(self, -1) 465 vbs1=wx.BoxSizer(wx.VERTICAL) 466 self._items=( 467 (GeneralEditor, 0, None), 468 (cal_editor.CategoryEditor, 1, 'category'), 469 (pb_editor.MemoEditor, 1, 'memo') 470 ) 471 self._w=[] 472 for n in self._items: 473 w=n[0](scrolled_panel, -1) 474 vbs1.Add(w, n[1], wx.EXPAND|wx.ALL, 5) 475 self._w.append(w) 476 if n[2]: 477 widgets_list.append((w.static_box, n[2])) 478 scrolled_panel.SetSizer(vbs1) 479 scrolled_panel.SetAutoLayout(True) 480 vbs1.Fit(scrolled_panel) 481 scrolled_panel.SetupScrolling() 482 hbs.Add(scrolled_panel, 3, wx.EXPAND|wx.ALL, border=5) 483 # save references to the widgets 484 self._general_editor_w=self._w[0] 485 self._cat_editor_w=self._w[1] 486 self._memo_editor_w=self._w[2] 487 # the bottom buttons 488 hbs1=wx.BoxSizer(wx.HORIZONTAL) 489 self._save_btn=wx.Button(self, wx.ID_SAVE) 490 self._revert_btn=wx.Button(self, wx.ID_REVERT_TO_SAVED) 491 help_btn=wx.Button(self, wx.ID_HELP) 492 hbs1.Add(self._save_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 493 hbs1.Add(help_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 494 hbs1.Add(self._revert_btn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 495 # all done 496 vbs.Add(hbs, 1, wx.EXPAND|wx.ALL, 5) 497 vbs.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5) 498 vbs.Add(hbs1, 0, wx.ALIGN_CENTRE|wx.ALL, 5) 499 self.SetSizer(vbs) 500 self.SetAutoLayout(True) 501 vbs.Fit(self) 502 # event handlers 503 wx.EVT_LISTBOX(self, self._item_list.GetId(), self._OnListBoxItem) 504 wx.EVT_BUTTON(self, self._save_btn.GetId(), self._OnSave) 505 wx.EVT_BUTTON(self, self._revert_btn.GetId(), self._OnRevert) 506 wx.EVT_BUTTON(self, wx.ID_HELP, 507 lambda _: wx.GetApp().displayhelpid(helpids.ID_TAB_TODO)) 508 # DIRTY UI Event handlers 509 for w in self._w: 510 pb_editor.EVT_DIRTY_UI(self, w.GetId(), self.OnMakeDirty) 511 # populate data 512 self._populate() 513 # turn on dirty flag 514 self.ignoredirty=False 515 self.setdirty(False) 516 # register for Today selection 517 today.bind_notification_event(self.OnTodaySelection, 518 today.Today_Group_Todo) 519 today.bind_request_event(self.OnTodayRequest) 520 # color coded labels 521 field_color.reload_color_info(self, widgets_list) 522 pubsub.subscribe(self.OnPhoneChanged, pubsub.PHONE_MODEL_CHANGED)523525 # just reload the color info based on the new phone 526 field_color.reload_color_info(self, widgets_list) 527 self.Refresh()528 532 538540 now=datetime.datetime.now() 541 _today='%04d%02d%02d'%(now.year, now.month, now.day) 542 keys=self._data.keys() 543 keys.sort() 544 today_event=today.TodayTodoEvent() 545 for k in keys: 546 if self._data[k].is_active() and \ 547 (not self._data[k].due_date or \ 548 self._data[k].due_date<=_today): 549 today_event.append(self._data[k].summary, 550 { 'key': k, 551 'index': self._data_map[k] }) 552 today_event.broadcast()553555 now=datetime.datetime.now() 556 _today='%04d%02d%02d'%(now.year, now.month, now.day) 557 s=now+datetime.timedelta(7-now.isoweekday()%7) 558 _sun='%04d%02d%02d'%(s.year, s.month, s.day) 559 keys=self._data.keys() 560 keys.sort() 561 today_event=today.ThisWeekTodoEvent() 562 dow_flg=[False]*7 563 for k in keys: 564 due_date=self._data[k].due_date 565 if due_date>_today and due_date<_sun: 566 dt=datetime.datetime(int(due_date[:4]), int(due_date[4:6]), 567 int(due_date[6:8])) 568 _dow=dt.isoweekday()%7 569 if dow_flg[_dow]: 570 _name=today.dow_initials[-1]+' '+self._data[k].summary 571 else: 572 dow_flg[_dow]=True 573 _name=today.dow_initials[_dow]+' - '+self._data[k].summary 574 today_event.append(_name, { 'key': k, 575 'index': self._data_map[k] }) 576 today_event.broadcast()577 581583 self.ActivateSelf() 584 if evt.data: 585 self._item_list.SetSelection(evt.data.get('index', wx.NOT_FOUND)) 586 self._populate_each(evt.data.get('key', None))587589 # populate new data 590 self._clear() 591 self._data_map={} 592 # populate the list with data 593 keys=self._data.keys() 594 keys.sort() 595 for k in keys: 596 n=self._data[k] 597 i=self._item_list.Append(n.summary) 598 self._item_list.SetClientData(i, k) 599 self._data_map[k]=i 600 self._publish_today_events() 601 self._publish_thisweek_events()602604 # populate the detailed info of the item keyed k 605 if k is None: 606 # clear out all the subfields 607 self._clear_each() 608 return 609 # there're data, first enable the widgets 610 self.ignoredirty=True 611 for w in self._w: 612 w.Enable(True) 613 entry=self._data[k] 614 # set the general detail 615 self._general_editor_w.Set(entry) 616 self._cat_editor_w.Set(entry.categories) 617 self._memo_editor_w.Set({ 'memo': entry.note }) 618 self.ignoredirty=False 619 self.setdirty(False)620 621 # called from various widget update callbacks623 """A public function you can call that will set the dirty flag""" 624 if self.dirty or self.ignoredirty or not self.IsShown(): 625 # already dirty, no need to make it worse 626 return 627 self.setdirty(True)628630 """Set the dirty flag""" 631 if self.ignoredirty: 632 return 633 self.dirty=val 634 self._item_list.Enable(not self.dirty) 635 self._save_btn.Enable(self.dirty) 636 self._revert_btn.Enable(self.dirty)637 640 643 648650 # add a new memo item 651 if self.dirty: 652 # busy editing, cannot add now, just return 653 return 654 m=TodoEntry() 655 m.summary='New Task' 656 self._data[m.id]=m 657 self._populate() 658 self._save_to_db(self._data) 659 self._item_list.Select(self._data_map[m.id]) 660 self._populate_each(m.id)661663 sel_idx=self._item_list.GetSelection() 664 if sel_idx is None or sel_idx==-1: 665 # none selected 666 return False 667 return True668670 # delete the current selected item 671 sel_idx=self._item_list.GetSelection() 672 if sel_idx is None or sel_idx==-1: 673 # none selected 674 return 675 self.ignoredirty=True 676 k=self._item_list.GetClientData(sel_idx) 677 self._item_list.Delete(sel_idx) 678 self._clear_each() 679 del self._data[k] 680 del self._data_map[k] 681 self._save_to_db(self._data) 682 self.ignoredirty=False 683 self.setdirty(False)684 688 692694 db_rr={} 695 for k, e in todo_dict.items(): 696 db_rr[k]=TodoDataObject(e) 697 database.ensurerecordtype(db_rr, todoobjectfactory) 698 self._main_window.database.savemajordict('todo', db_rr) 699 self._publish_today_events() 700 self._publish_thisweek_events()701 705707 # read data from the database 708 todo_dict=self._main_window.database.\ 709 getmajordictvalues('todo',todoobjectfactory) 710 r={} 711 for k,e in todo_dict.items(): 712 ce=TodoEntry() 713 ce.set_db_dict(e) 714 r[ce.id]=ce 715 result.update({ 'todo': r }) 716 return result717719 # an item was clicked on/selected 720 self._populate_each(self._item_list.GetClientData(evt.GetInt())) 721 self.Refresh()722724 # save the current changes 725 self.ignoredirty=True 726 sel_idx=self._item_list.GetSelection() 727 k=self._item_list.GetClientData(sel_idx) 728 entry=self._data[k] 729 self._general_editor_w.Get(entry) 730 entry.note=self._memo_editor_w.Get().get('memo', None) 731 entry.categories=self._cat_editor_w.Get() 732 entry.check_completion() 733 self._general_editor_w.Set(entry) 734 self._item_list.SetString(sel_idx, entry.summary) 735 self._save_to_db(self._data) 736 self.ignoredirty=False 737 self.setdirty(False)738740 self.ignoredirty=True 741 self._item_list.Enable() 742 sel_idx=self._item_list.GetSelection() 743 if sel_idx!=wx.NOT_FOUND: 744 k=self._item_list.GetClientData(sel_idx) 745 self._populate_each(k) 746 self.ignoredirty=False 747 self.setdirty(False)
Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sun Jan 24 16:24:09 2010 | http://epydoc.sourceforge.net |