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

Source Code for Module call_history

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2005 Joe Pham <djpham@bitpim.org> 
  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: call_history.py 4601 2008-03-04 22:37:48Z djpham $ 
  9   
 10  """ 
 11  Code to handle Call History data storage and display. 
 12   
 13  The format of the Call History is standardized.  It is an object with the 
 14  following attributes: 
 15   
 16  folder: string (where this item belongs) 
 17  datetime: string 'YYYYMMDDThhmmss' or (y,m,d,h,m,s) 
 18  number: string (the phone number of this call) 
 19  name: string (optional name associated with this number) 
 20  duration: int (optional duration of the call in minutes) 
 21   
 22  To implement Call History feature for a phone module: 
 23   
 24    Add an entry into Profile._supportedsyncs: 
 25    ('call_history', 'read', None), 
 26   
 27    Implement the following method in your Phone class: 
 28    def getcallhistory(self, result, merge): 
 29       ... 
 30       return result 
 31   
 32  The result dict key is 'call_history'. 
 33   
 34  """ 
 35   
 36  # standard modules 
 37  from __future__ import with_statement 
 38  import copy 
 39  import sha 
 40  import time 
 41   
 42  # wx modules 
 43  import wx 
 44  import wx.lib.scrolledpanel as scrolled 
 45   
 46  # BitPim modules 
 47  import database 
 48  import guiwidgets 
 49  import guihelper 
 50  import helpids 
 51  import phonenumber 
 52  import pubsub 
 53  import today 
 54  import widgets 
 55   
 56  #------------------------------------------------------------------------------- 
57 -class CallHistoryDataobject(database.basedataobject):
58 _knownproperties=['folder', 'datetime', 'number', 'name', 'duration' ] 59 _knownlistproperties=database.basedataobject._knownlistproperties.copy()
60 - def __init__(self, data=None):
61 if data is None or not isinstance(data, CallHistoryEntry): 62 return; 63 self.update(data.get_db_dict())
64 callhistoryobjectfactory=database.dataobjectfactory(CallHistoryDataobject) 65 66 #-------------------------------------------------------------------------------
67 -def GetDurationStr(duration):
68 """convert duration int into an h:mm:ss formatted string""" 69 if duration is None: 70 return '' 71 else: 72 sec=duration%60 73 min=duration/60 74 hr=min/60 75 min=min%60 76 return "%d:%02d:%02d" % (hr, min, sec)
77 78 #-------------------------------------------------------------------------------
79 -class CallHistoryEntry(object):
80 Folder_Incoming='Incoming' 81 Folder_Outgoing='Outgoing' 82 Folder_Missed='Missed' 83 Folder_Data='Data' 84 Valid_Folders=(Folder_Incoming, Folder_Outgoing, Folder_Missed, Folder_Data) 85 _folder_key='folder' 86 _datetime_key='datetime' 87 _number_key='number' 88 _name_key='name' 89 _duration_key='duration' 90 _unknown_datetime='YYYY-MM-DD hh:mm:ss' 91 _id_index=0 92 _max_id_index=999
93 - def __init__(self):
94 self._data={ 'serials': [] } 95 self._create_id()
96
97 - def __eq__(self, rhs):
98 return self.folder==rhs.folder and self.datetime==rhs.datetime and\ 99 self.number==rhs.number
100 - def __ne__(self, rhs):
101 return self.folder!=rhs.folder or self.datetime!=rhs.datetime or\ 102 self.number!=rhs.number
103 - def get(self):
104 return copy.deepcopy(self._data, {})
105 - def set(self, d):
106 self._data={} 107 self._data.update(d)
108
109 - def get_db_dict(self):
110 return self.get()
111 - def set_db_dict(self, d):
112 self.set(d)
113
114 - def _create_id(self):
115 "Create a BitPim serial for this entry" 116 self._data.setdefault("serials", []).append(\ 117 {"sourcetype": "bitpim", 118 "id": '%.3f%03d'%(time.time(), CallHistoryEntry._id_index) }) 119 if CallHistoryEntry._id_index<CallHistoryEntry._max_id_index: 120 CallHistoryEntry._id_index+=1 121 else: 122 CallHistoryEntry._id_index=0
123 - def _get_id(self):
124 s=self._data.get('serials', []) 125 for n in s: 126 if n.get('sourcetype', None)=='bitpim': 127 return n.get('id', None) 128 return None
129 - def _set_id(self, id):
130 s=self._data.get('serials', []) 131 for n in s: 132 if n.get('sourcetype', None)=='bitpim': 133 n['id']=id 134 return 135 self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } )
136 id=property(fget=_get_id, fset=_set_id) 137
138 - def _set_or_del(self, key, v, v_list=[]):
139 if v is None or v in v_list: 140 if self._data.has_key(key): 141 del self._data[key] 142 else: 143 self._data[key]=v
144
145 - def _get_folder(self):
146 return self._data.get(self._folder_key, '')
147 - def _set_folder(self, v):
148 if v is None: 149 if self._data.has_key(self._folder_key): 150 del self._data[self._folder_key] 151 return 152 if not isinstance(v, (str, unicode)): 153 raise TypeError,'not a string or unicode type' 154 if v not in self.Valid_Folders: 155 raise ValueError,'not a valid folder' 156 self._data[self._folder_key]=v
157 folder=property(fget=_get_folder, fset=_set_folder) 158
159 - def _get_number(self):
160 return self._data.get(self._number_key, '')
161 - def _set_number(self, v):
162 self._set_or_del(self._number_key, v, [''])
163 number=property(fget=_get_number, fset=_set_number) 164
165 - def _get_name(self):
166 return self._data.get(self._name_key, '')
167 - def _set_name(self, v):
168 self._set_or_del(self._name_key, v, ('',))
169 name=property(fget=_get_name, fset=_set_name) 170
171 - def _get_duration(self):
172 return self._data.get(self._duration_key, None)
173 - def _set_duration(self, v):
174 if v is not None and not isinstance(v, int): 175 raise TypeError('duration property is an int arg') 176 self._set_or_del(self._duration_key, v)
177 - def _get_durationstr(self):
178 return GetDurationStr(self.duration)
179 duration=property(fget=_get_duration, fset=_set_duration) 180 durationstr=property(fget=_get_durationstr) 181 182
183 - def _get_datetime(self):
184 return self._data.get(self._datetime_key, '')
185 - def _set_datetime(self, v):
186 # this routine supports 2 formats: 187 # (y,m,d,h,m,s) and 'YYYYMMDDThhmmss' 188 # check for None and delete manually 189 if v is None: 190 if self._data.has_key(self._datetime_key): 191 del self._data[self._datetime_key] 192 return 193 if isinstance(v, (tuple, list)): 194 if len(v)!=6: 195 raise ValueError,'(y, m, d, h, m, s)' 196 s='%04d%02d%02dT%02d%02d%02d'%tuple(v) 197 elif isinstance(v, (str, unicode)): 198 # some primitive validation 199 if len(v)!=15 or v[8]!='T': 200 raise ValueError,'value must be in format YYYYMMDDThhmmss' 201 s=v 202 else: 203 raise TypeError 204 self._data[self._datetime_key]=s
205 datetime=property(fget=_get_datetime, fset=_set_datetime)
206 - def get_date_time_str(self):
207 # return a string representing this date/time in the format of 208 # YYYY-MM-DD hh:mm:ss 209 s=self.datetime 210 if not len(s): 211 s=self._unknown_datetime 212 else: 213 s=s[:4]+'-'+s[4:6]+'-'+s[6:8]+' '+s[9:11]+':'+s[11:13]+':'+s[13:] 214 return s
215 - def summary(self, name=None):
216 # return a short summary for this entry in the format of 217 # MM/DD hh:mm <Number/Name> 218 s=self.datetime 219 if s: 220 s=s[4:6]+'/'+s[6:8]+' '+s[9:11]+':'+s[11:13]+' ' 221 else: 222 s='**/** **:** ' 223 if name: 224 s+=name 225 elif self.name: 226 s+=self.name 227 else: 228 s+=phonenumber.format(self.number) 229 return s
230 231 #-------------------------------------------------------------------------------
232 -class CallHistoryWidget(scrolled.ScrolledPanel, widgets.BitPimWidget):
233 _data_key='call_history' 234 stat_list=("Data", "Missed", "Incoming", "Outgoing", "All")
235 - def __init__(self, mainwindow, parent):
236 super(CallHistoryWidget, self).__init__(parent, -1) 237 self._main_window=mainwindow 238 self.call_history_tree_nodes={} 239 self._parent=parent 240 self.read_only=False 241 self.historical_date=None 242 self._data={} 243 self._name_map={} 244 pubsub.subscribe(self._OnPBLookup, pubsub.RESPONSE_PB_LOOKUP) 245 self.list_widget=CallHistoryList(self._main_window, self._parent, self) 246 # main box sizer 247 vbs=wx.BoxSizer(wx.VERTICAL) 248 # data date adjuster 249 hbs=wx.BoxSizer(wx.HORIZONTAL) 250 static_bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, 251 'Historical Data Status:'), 252 wx.VERTICAL) 253 self.historical_data_label=wx.StaticText(self, -1, 'Current Data') 254 static_bs.Add(self.historical_data_label, 1, wx.EXPAND|wx.ALL, 5) 255 hbs.Add(static_bs, 1, wx.EXPAND|wx.ALL, 5) 256 vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5) 257 # main stats display 258 self.total_calls=today.HyperLinkCtrl(self, -1, ' Total Calls: 0') 259 today.EVT_HYPERLINK_LEFT(self, self.total_calls.GetId(), 260 self.OnNodeSelection) 261 self.total_in=today.HyperLinkCtrl(self, -1, ' Incoming Calls: 0') 262 today.EVT_HYPERLINK_LEFT(self, self.total_in.GetId(), 263 self.OnNodeSelection) 264 self.total_out=today.HyperLinkCtrl(self, -1, ' Outgoing Calls: 0') 265 today.EVT_HYPERLINK_LEFT(self, self.total_out.GetId(), 266 self.OnNodeSelection) 267 self.total_missed=today.HyperLinkCtrl(self, -1, ' Missed Calls: 0') 268 today.EVT_HYPERLINK_LEFT(self, self.total_missed.GetId(), 269 self.OnNodeSelection) 270 self.total_data=today.HyperLinkCtrl(self, -1, ' Data Calls: 0') 271 today.EVT_HYPERLINK_LEFT(self, self.total_data.GetId(), 272 self.OnNodeSelection) 273 self.duration_all=wx.StaticText(self, -1, ' Total Duration(h:m:s): 0') 274 self.duration_in=wx.StaticText(self, -1, ' Incoming Duration(h:m:s): 0') 275 self.duration_out=wx.StaticText(self, -1, ' Outgoing Duration(h:m:s): 0') 276 self.duration_data=wx.StaticText(self, -1, ' Data Duration(h:m:s): 0') 277 self._id_dict={ 278 self.total_calls.GetId(): self.stat_list[4], 279 self.total_in.GetId(): self.stat_list[2], 280 self.total_out.GetId(): self.stat_list[3], 281 self.total_missed.GetId(): self.stat_list[1], 282 self.total_data.GetId(): self.stat_list[0], 283 } 284 vbs.Add(wx.StaticText(self, -1, ''), 0, wx.ALIGN_LEFT|wx.ALL, 2) 285 vbs.Add(self.total_calls, 0, wx.ALIGN_LEFT|wx.ALL, 2) 286 vbs.Add(self.total_in, 0, wx.ALIGN_LEFT|wx.ALL, 2) 287 vbs.Add(self.total_out, 0, wx.ALIGN_LEFT|wx.ALL, 2) 288 vbs.Add(self.total_missed, 0, wx.ALIGN_LEFT|wx.ALL, 2) 289 vbs.Add(self.total_data, 0, wx.ALIGN_LEFT|wx.ALL, 2) 290 vbs.Add(wx.StaticText(self, -1, ''), 0, wx.ALIGN_LEFT|wx.ALL, 2) 291 vbs.Add(self.duration_all, 0, wx.ALIGN_LEFT|wx.ALL, 2) 292 vbs.Add(self.duration_in, 0, wx.ALIGN_LEFT|wx.ALL, 2) 293 vbs.Add(self.duration_out, 0, wx.ALIGN_LEFT|wx.ALL, 2) 294 vbs.Add(self.duration_data, 0, wx.ALIGN_LEFT|wx.ALL, 2) 295 # all done 296 self.SetSizer(vbs) 297 self.SetAutoLayout(True) 298 vbs.Fit(self) 299 self.SetupScrolling() 300 self.SetBackgroundColour(wx.WHITE) 301 # populate data 302 self._populate()
303
304 - def OnNodeSelection(self, evt):
305 # Request to select a subnode 306 _node=self._id_dict.get(evt.GetId(), None) 307 if _node and self.call_history_tree_nodes.get(_node, None): 308 self.ActivateSelf(self.call_history_tree_nodes[_node])
309
310 - def populate(self, dict, force=False):
311 if self.read_only and not force: 312 # historical data, bail 313 return 314 self._data=dict.get(self._data_key, {}) 315 self._populate()
316
317 - def OnInit(self):
318 for stat in self.stat_list: 319 self.call_history_tree_nodes[stat]=self.AddSubPage(self.list_widget, stat, self._tree.calls)
320
321 - def GetRightClickMenuItems(self, node):
322 result=[] 323 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EXPORT_CSV_CALL_HISTORY, "Export to CSV ...", "Export the call history to a csv file")) 324 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_DATAHISTORICAL, "Historical Data ...", "Display Historical Data")) 325 return result
326
327 - def HasHistoricalData(self):
328 return True
329
330 - def OnHistoricalData(self):
331 """Display current or historical data""" 332 if self.read_only: 333 current_choice=guiwidgets.HistoricalDataDialog.Historical_Data 334 else: 335 current_choice=guiwidgets.HistoricalDataDialog.Current_Data 336 with guihelper.WXDialogWrapper(guiwidgets.HistoricalDataDialog(self, 337 current_choice=current_choice, 338 historical_date=self.historical_date, 339 historical_events=\ 340 self._main_window.database.getchangescount(self._data_key)), 341 True) as (dlg, retcode): 342 if retcode==wx.ID_OK: 343 with guihelper.MWBusyWrapper(self._main_window): 344 current_choice, self.historical_date=dlg.GetValue() 345 r={} 346 if current_choice==guiwidgets.HistoricalDataDialog.Current_Data: 347 self.read_only=False 348 msg_str='Current Data' 349 self.getfromfs(r) 350 else: 351 self.read_only=True 352 msg_str='Historical Data as of %s'%\ 353 str(wx.DateTimeFromTimeT(self.historical_date)) 354 self.getfromfs(r, self.historical_date) 355 self.populate(r, True) 356 self.historical_data_label.SetLabel(msg_str) 357 self.list_widget.historical_data_label.SetLabel(msg_str)
358
359 - def _publish_today_data(self):
360 keys=[(x.datetime, k) for k,x in self._data.items()] 361 keys.sort() 362 keys.reverse() 363 today_event_in=today.TodayIncomingCallsEvent() 364 today_event_miss=today.TodayMissedCallsEvent() 365 for _,k in keys: 366 if self._data[k].folder==CallHistoryEntry.Folder_Incoming: 367 today_event_in.append(self._data[k].summary(self._name_map.get(self._data[k].number, None)), {'id':k}) 368 if self._data[k].folder==CallHistoryEntry.Folder_Missed: 369 today_event_miss.append(self._data[k].summary(self._name_map.get(self._data[k].number, None)), {'id':k}) 370 today_event_in.broadcast() 371 today_event_miss.broadcast()
372
373 - def _populate(self):
374 # lookup phone book for names 375 for k,e in self._data.items(): 376 if e.name: 377 if not self._name_map.has_key(e.number): 378 self._name_map[e.number]=e.name 379 else: 380 if not self._name_map.has_key(e.number): 381 pubsub.publish(pubsub.REQUEST_PB_LOOKUP, 382 { 'item': e.number } ) 383 self.list_widget.populate() 384 #update stats 385 self.CalculateStats() 386 # tell today 'bout it 387 self._publish_today_data()
388
389 - def CalculateStats(self):
390 total=inc=out=miss=data=0 391 total_d=in_d=out_d=data_d=0 392 for k, e in self._data.items(): 393 total+=1 394 if e.duration==None: 395 dur=0 396 else: 397 dur=e.duration 398 total_d+=dur 399 if e.folder==CallHistoryEntry.Folder_Incoming: 400 inc+=1 401 in_d+=dur 402 elif e.folder==CallHistoryEntry.Folder_Outgoing: 403 out+=1 404 out_d+=dur 405 elif e.folder==CallHistoryEntry.Folder_Missed: 406 miss+=1 407 elif e.folder==CallHistoryEntry.Folder_Data: 408 data+=1 409 data_d+=dur 410 self.total_calls.SetLabel(' Total Calls: '+`total`) 411 self.total_in.SetLabel(' Incoming Calls: '+`inc`) 412 self.total_out.SetLabel(' Outgoing Calls: '+`out`) 413 self.total_missed.SetLabel(' Missed Calls: '+`miss`) 414 self.total_data.SetLabel(' Data Calls: '+`data`) 415 self.duration_all.SetLabel(' Total Duration(h:m:s): '+GetDurationStr(total_d)) 416 self.duration_in.SetLabel(' Incoming Duration(h:m:s): '+GetDurationStr(in_d)) 417 self.duration_out.SetLabel(' Outgoing Duration(h:m:s): '+GetDurationStr(out_d)) 418 self.duration_data.SetLabel(' Data Duration(h:m:s): '+GetDurationStr(data_d))
419
420 - def _OnPBLookup(self, msg):
421 d=msg.data 422 k=d.get('item', None) 423 name=d.get('name', None) 424 if k is None: 425 return 426 self._name_map[k]=name
427
428 - def getdata(self, dict, want=None):
429 dict[self._data_key]=copy.deepcopy(self._data)
430
431 - def _save_to_db(self, dict):
432 if self.read_only: 433 return 434 db_rr={} 435 for k,e in dict.items(): 436 db_rr[k]=CallHistoryDataobject(e) 437 database.ensurerecordtype(db_rr, callhistoryobjectfactory) 438 self._main_window.database.savemajordict(self._data_key, db_rr)
439
440 - def populatefs(self, dict):
441 if self.read_only: 442 wx.MessageBox('You are viewing historical data which cannot be changed or saved', 443 'Cannot Save Call History Data', 444 style=wx.OK|wx.ICON_ERROR) 445 else: 446 self._save_to_db(dict.get(self._data_key, {})) 447 return dict
448
449 - def getfromfs(self, result, timestamp=None):
450 dict=self._main_window.database.\ 451 getmajordictvalues(self._data_key, 452 callhistoryobjectfactory, 453 at_time=timestamp) 454 r={} 455 for k,e in dict.items(): 456 ce=CallHistoryEntry() 457 ce.set_db_dict(e) 458 r[ce.id]=ce 459 result.update({ self._data_key: r}) 460 return result
461
462 - def merge(self, dict):
463 if self.read_only: 464 wx.MessageBox('You are viewing historical data which cannot be changed or saved', 465 'Cannot Save Call History Data', 466 style=wx.OK|wx.ICON_ERROR) 467 return 468 d=dict.get(self._data_key, {}) 469 l=[e for k,e in self._data.items()] 470 for k,e in d.items(): 471 if e not in l: 472 self._data[e.id]=e 473 self._save_to_db(self._data) 474 self._populate()
475
476 - def get_selected_data(self):
477 # return a dict of selected items 478 res={} 479 for sel_idx in self.list_widget._item_list.GetSelections(): 480 k=self.list_widget._item_list.GetItemData(sel_idx) 481 if k: 482 res[k]=self._data[k] 483 return res
484
485 - def get_data(self):
486 return self._data
487 488 #-------------------------------------------------------------------------------
489 -class CallHistoryList(wx.Panel, widgets.BitPimWidget):
490 _by_type=0 491 _by_date=1 492 _by_number=2
493 - def __init__(self, mainwindow, parent, stats):
494 super(CallHistoryList, self).__init__(parent, -1) 495 self._main_window=mainwindow 496 self._stats=stats 497 self.nodes={} 498 self.nodes_keys={} 499 self._display_filter="All" 500 # main box sizer 501 vbs=wx.BoxSizer(wx.VERTICAL) 502 # data date adjuster 503 hbs=wx.BoxSizer(wx.HORIZONTAL) 504 static_bs=wx.StaticBoxSizer(wx.StaticBox(self, -1, 505 'Historical Data Status:'), 506 wx.VERTICAL) 507 self.historical_data_label=wx.StaticText(self, -1, 'Current Data') 508 static_bs.Add(self.historical_data_label, 1, wx.EXPAND|wx.ALL, 5) 509 hbs.Add(static_bs, 1, wx.EXPAND|wx.ALL, 5) 510 vbs.Add(hbs, 0, wx.EXPAND|wx.ALL, 5) 511 # main list 512 column_info=[] 513 column_info.append(("Call Type", 80, False)) 514 column_info.append(("Date", 120, False)) 515 column_info.append(("Number", 100, False)) 516 column_info.append(("Duration", 80, False)) 517 column_info.append(("Name", 130, False)) 518 self._item_list=guiwidgets.BitPimListCtrl(self, column_info) 519 self._item_list.ResetView(self.nodes, self.nodes_keys) 520 vbs.Add(self._item_list, 1, wx.EXPAND|wx.ALL, 5) 521 vbs.Add(wx.StaticText(self, -1, ' Note: Click column headings to sort data'), 0, wx.ALIGN_CENTRE|wx.BOTTOM, 10) 522 # all done 523 today.bind_notification_event(self.OnTodaySelectionIncoming, 524 today.Today_Group_IncomingCalls) 525 today.bind_notification_event(self.OnTodaySelectionMissed, 526 today.Today_Group_MissedCalls) 527 self.today_data=None 528 self.SetSizer(vbs) 529 self.SetAutoLayout(True) 530 vbs.Fit(self)
531
532 - def OnSelected(self, node):
533 for stat in self._stats.stat_list: 534 if self._stats.call_history_tree_nodes[stat]==node: 535 if self._display_filter!=stat: 536 self._display_filter=stat 537 # for some reason GetTopItem return 0 (instead of -1) 538 # when the list is empty 539 if self._item_list.GetItemCount(): 540 item=self._item_list.GetTopItem() 541 # deselect all the items when changing view 542 while item!=-1: 543 self._item_list.Select(item, 0) 544 item=self._item_list.GetNextItem(item) 545 self.populate() 546 self._on_today_selection() 547 return
548
549 - def OnTodaySelectionIncoming(self, evt):
550 node=self._stats.call_history_tree_nodes["Incoming"] 551 self.today_data=evt.data 552 self.ActivateSelf(node)
553
554 - def OnTodaySelectionMissed(self, evt):
555 node=self._stats.call_history_tree_nodes["Missed"] 556 self.today_data=evt.data 557 self.ActivateSelf(node)
558
559 - def _on_today_selection(self):
560 if self.today_data and self._item_list.GetItemCount(): 561 item=self._item_list.GetTopItem() 562 while item!=-1: 563 if self.today_data['id']==self._item_list.GetItemData(item): 564 self._item_list.Select(item, 1) 565 self._item_list.EnsureVisible(item) 566 else: 567 self._item_list.Select(item, 0) 568 item=self._item_list.GetNextItem(item) 569 self.today_data=None
570
571 - def GetRightClickMenuItems(self, node):
572 result=[] 573 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITSELECTALL, "Select All", "Select All Items")) 574 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITDELETEENTRY, "Delete Selected", "Delete Selected Items")) 575 result.append((widgets.BitPimWidget.MENU_SPACER, 0, "", "")) 576 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EXPORT_CSV_CALL_HISTORY, "Export to CSV ...", "Export the call history to a csv file")) 577 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_DATAHISTORICAL, "Historical Data ...", "Display Historical Data")) 578 return result
579
580 - def CanSelectAll(self):
581 if self._item_list.GetItemCount(): 582 return True 583 return False
584
585 - def OnSelectAll(self, _):
586 item=self._item_list.GetTopItem() 587 while item!=-1: 588 self._item_list.Select(item) 589 item=self._item_list.GetNextItem(item)
590
591 - def HasHistoricalData(self):
592 return self._stats.HasHistoricalData()
593
594 - def OnHistoricalData(self):
595 return self._stats.OnHistoricalData()
596
597 - def populate(self):
598 self.nodes={} 599 self.nodes_keys={} 600 index=0 601 for k,e in self._stats._data.items(): 602 if self._display_filter=="All" or e.folder==self._display_filter: 603 name=e.name 604 if name==None or name=="": 605 temp=self._stats._name_map.get(e.number, None) 606 if temp !=None: 607 name=temp 608 else: 609 name="" 610 self.nodes[index]=(e.folder, e.get_date_time_str(), 611 phonenumber.format(e.number), 612 e.durationstr, name) 613 self.nodes_keys[index]=k 614 index+=1 615 self._item_list.ResetView(self.nodes, self.nodes_keys)
616
617 - def CanDelete(self):
618 if self._stats.read_only: 619 return False 620 sels_idx=self._item_list.GetFirstSelected() 621 if sels_idx==-1: 622 return False 623 return True
624
625 - def GetDeleteInfo(self):
626 return wx.ART_DEL_BOOKMARK, "Delete Call Record"
627
628 - def OnDelete(self, _):
629 if self._stats.read_only: 630 return 631 sels_idx=self._item_list.GetSelections() 632 if len(sels_idx): 633 # delete them from the data list 634 for i,item in sels_idx.items(): 635 del self._stats._data[self._item_list.GetItemData(item)] 636 self._item_list.Select(item, 0) 637 self.populate() 638 self._stats._save_to_db(self._stats._data) 639 self._stats.CalculateStats()
640 - def GetHelpID(self):
642