1
2
3
4
5
6
7
8
9
10
11 """A widget for displaying/editting the phone information
12
13 The format for a phonebook entry is standardised. It is a
14 dict with the following fields. Each field is a list, most
15 important first, with each item in the list being a dict.
16
17 names:
18
19 - title ??Job title or salutation??
20 - first
21 - middle
22 - last
23 - full You should specify the fullname or the 4 above
24 - nickname (This could also be an alias)
25
26 categories:
27
28 - category User defined category name
29 - ringtone (optional) Ringtone name for this category
30
31 emails:
32
33 - email Email address
34 - type (optional) 'home' or 'business'
35 - speeddial (optional) Speed dial for this entry
36 - ringtone (optional) ringtone name for this entry
37 - wallpaper (optional) wallpaper name for this entry
38
39 maillist:
40
41 - entry string of '\x00\x00' separated of number or email entries.
42 - speeddial (optional) Speed dial for this entry
43 - ringtone (optional) ringtone name for this entry
44 - wallpaper (optional) wallpaper name for this entry
45
46 urls:
47
48 - url URL
49 - type (optional) 'home' or 'business'
50
51 ringtones:
52
53 - ringtone Name of a ringtone
54 - use 'call', 'message'
55
56 addresses:
57
58 - type 'home' or 'business'
59 - company (only for type of 'business')
60 - street Street part of address
61 - street2 Second line of street address
62 - city
63 - state
64 - postalcode
65 - country Can also be the region
66
67 wallpapers:
68
69 - wallpaper Name of wallpaper
70 - use see ringtones.use
71
72 flags:
73
74 - secret Boolean if record is private/secret (if not present - value is false)
75 - sim Boolean if record should be stored on SIM card of GSM phones.
76
77 memos:
78
79 - memo Note
80
81 numbers:
82
83 - number Phone number as ascii string
84 - type 'home', 'office', 'cell', 'fax', 'pager', 'data', 'none' (if you have home2 etc, list
85 them without the digits. The second 'home' is implicitly home2 etc)
86 - speeddial (optional) Speed dial number
87 - ringtone (optional) ringtone name for this entry
88 - wallpaper (optional) wallpaper name for this entry
89
90 serials:
91
92 - sourcetype identifies source driver in bitpim (eg "lgvx4400", "windowsaddressbook")
93 - sourceuniqueid (optional) identifier for where the serial came from (eg ESN of phone, wab host/username)
94 (imagine having multiple phones of the same model to see why this is needed)
95 - * other names of use to sourcetype
96 """
97
98
99 from __future__ import with_statement
100 import os
101 import cStringIO
102 import re
103 import time
104 import copy
105
106
107 import wx
108 import wx.grid
109 import wx.html
110
111
112 import common
113 import xyaptu
114 import guihelper
115 import phonebookentryeditor
116 import pubsub
117 import nameparser
118 import bphtml
119 import guihelper
120 import guiwidgets
121 import phonenumber
122 import helpids
123 import database
124 import widgets
133
134 _knownlistproperties=database.basedataobject._knownlistproperties.copy()
135 _knownlistproperties.update( {'names': ['title', 'first', 'middle', 'last', 'full', 'nickname'],
136 'categories': ['category'],
137 'emails': ['email', 'type', 'speeddial',
138 'ringtone', 'wallpaper' ],
139 'urls': ['url', 'type'],
140 'ringtones': ['ringtone', 'use'],
141 'addresses': ['type', 'company', 'street', 'street2', 'city', 'state', 'postalcode', 'country'],
142 'wallpapers': ['wallpaper', 'use'],
143 'flags': ['secret', 'sim'],
144 'memos': ['memo'],
145 'numbers': ['number', 'type', 'speeddial',
146 'ringtone', 'wallpaper' ],
147 'ice': [ 'iceindex' ],
148 'favorite': [ 'favoriteindex' ],
149 'ims': [ 'type', 'username' ],
150
151
152
153 })
154
155 phonebookobjectfactory=database.dataobjectfactory(phonebookdataobject)
156
157
158
159
160
161 -class PhoneEntryDetailsView(bphtml.HTMLWindow):
162
163 - def __init__(self, parent, id, stylesfile="styles.xy", layoutfile="pblayout.xy"):
164 bphtml.HTMLWindow.__init__(self, parent, id)
165 self.stylesfile=guihelper.getresourcefile(stylesfile)
166 self.pblayoutfile=guihelper.getresourcefile(layoutfile)
167 self.xcp=None
168 self.xcpstyles=None
169 self.ShowEntry({})
170
171 - def ShowEntry(self, entry):
172 if self.xcp is None:
173 template=open(self.pblayoutfile, "rt").read()
174 self.xcp=xyaptu.xcopier(None)
175 self.xcp.setupxcopy(template)
176 if self.xcpstyles is None:
177 self.xcpstyles={}
178 try:
179 execfile(self.stylesfile, self.xcpstyles, self.xcpstyles)
180 except UnicodeError:
181 common.unicode_execfile(self.stylesfile, self.xcpstyles, self.xcpstyles)
182 self.xcpstyles['entry']=entry
183 text=self.xcp.xcopywithdns(self.xcpstyles)
184 try:
185 text=bphtml.applyhtmlstyles(text, self.xcpstyles['styles'])
186 except:
187 if __debug__:
188 open("debug.html", "wt").write(common.forceascii(text))
189 raise
190 self.SetPage(text)
191
198
201
206
213
221
227
230
233
234
235
236
237
238 _getdatalist=[
239
240 'Name', ("names", 0, None, nameparser.formatfullname, True),
241 'First', ("names", 0, None, nameparser.getfirst, False),
242 'Middle', ("names", 0, None, nameparser.getmiddle, False),
243 'Last', ("names", 0, None, nameparser.getlast, False),
244
245 'Category', ("categories", 0, None, "category", False),
246 'Category2', ("categories", 1, None, "category", False),
247 'Category3', ("categories", 2, None, "category", False),
248 'Category4', ("categories", 3, None, "category", False),
249 'Category5', ("categories", 4, None, "category", False),
250 'Categories', ("categories", None, None, formatcategories, True),
251
252 "Phone", ("numbers", 0, None, formattypenumber, False),
253 "Phone2", ("numbers", 1, None, formattypenumber, False),
254 "Phone3", ("numbers", 2, None, formattypenumber, False),
255 "Phone4", ("numbers", 3, None, formattypenumber, False),
256 "Phone5", ("numbers", 4, None, formattypenumber, False),
257 "Phone6", ("numbers", 5, None, formattypenumber, False),
258 "Phone7", ("numbers", 6, None, formattypenumber, False),
259 "Phone8", ("numbers", 7, None, formattypenumber, False),
260 "Phone9", ("numbers", 8, None, formattypenumber, False),
261 "Phone10", ("numbers", 9, None, formattypenumber, False),
262
263
264
265 'Email', ("emails", 0, None, "email", True),
266 'Email2', ("emails", 1, None, "email", True),
267 'Email3', ("emails", 2, None, "email", True),
268 'Email4', ("emails", 3, None, "email", True),
269 'Email5', ("emails", 4, None, "email", True),
270 'Business Email', ("emails", 0, ("type", "business"), "email", False),
271 'Business Email2', ("emails", 1, ("type", "business"), "email", False),
272 'Home Email', ("emails", 0, ("type", "home"), "email", False),
273 'Home Email2', ("emails", 1, ("type", "home"), "email", False),
274
275 'URL', ("urls", 0, None, "url", True),
276 'URL2', ("urls", 1, None, "url", True),
277 'URL3', ("urls", 2, None, "url", True),
278 'URL4', ("urls", 3, None, "url", True),
279 'URL5', ("urls", 4, None, "url", True),
280 'Business URL', ("urls", 0, ("type", "business"), "url", False),
281 'Business URL2', ("urls", 1, ("type", "business"), "url", False),
282 'Home URL', ("urls", 0, ("type", "home"), "url", False),
283 'Home URL2', ("urls", 1, ("type", "home"), "url", False),
284
285 'Ringtone', ("ringtones", 0, ("use", "call"), "ringtone", True),
286 'Message Ringtone', ("ringtones", 0, ("use", "message"), "ringtone", True),
287
288 'Address', ("addresses", 0, None, formataddress, True),
289 'Address2', ("addresses", 1, None, formataddress, True),
290 'Address3', ("addresses", 2, None, formataddress, True),
291 'Address4', ("addresses", 3, None, formataddress, True),
292 'Address5', ("addresses", 4, None, formataddress, True),
293 'Home Address', ("addresses", 0, ("type", "home"), formataddress, False),
294 'Home Address2', ("addresses", 1, ("type", "home"), formataddress, False),
295 'Business Address', ("addressess", 0, ("type", "business"), formataddress, False),
296 'Business Address2', ("addressess", 1, ("type", "business"), formataddress, False),
297
298 "Wallpaper", ("wallpapers", 0, None, "wallpaper", True),
299
300 "Secret", ("flags", 0, ("secret", True), formatsecret, True),
301 "Storage", ("flags", 0,('sim', True), formatstorage, True),
302 "Memo", ("memos", 0, None, "memo", True),
303 "Memo2", ("memos", 1, None, "memo", True),
304 "Memo3", ("memos", 2, None, "memo", True),
305 "Memo4", ("memos", 3, None, "memo", True),
306 "Memo5", ("memos", 4, None, "memo", True),
307
308 "ICE #", ("ice", 0, None, formatICE, False),
309 "Favorite #", ("favorite", 0, None, formatFav, False)
310
311 ]
312
313 ll=[]
314 for pretty, actual in ("Home", "home"), ("Office", "office"), ("Cell", "cell"), ("Fax", "fax"), ("Pager", "pager"), ("Data", "data"):
315 for suf,n in ("", 0), ("2", 1), ("3", 2):
316 ll.append(pretty+suf)
317 ll.append(("numbers", n, ("type", actual), formatnumber, True))
318 _getdatalist[40:40]=ll
319
320 _getdatatable={}
321 AvailableColumns=[]
322 DefaultColumns=['Name', 'Phone', 'Phone2', 'Phone3', 'Email', 'Categories', 'Memo', 'Secret']
323 ImportColumns=[_getdatalist[x*2] for x in range(len(_getdatalist)/2) if _getdatalist[x*2+1][4]]
324
325 for n in range(len(_getdatalist)/2):
326 AvailableColumns.append(_getdatalist[n*2])
327 _getdatatable[_getdatalist[n*2]]=_getdatalist[n*2+1]
328
329 del _getdatalist
330
331 -def getdata(column, entry, default=None):
332 """Returns the value in a particular column.
333 Note that the data is appropriately formatted.
334
335 @param column: column name
336 @param entry: the dict representing a phonebook entry
337 @param default: what to return if the entry has no data for that column
338 """
339 key, count, prereq, formatter, _ =_getdatatable[column]
340
341
342 if key not in entry:
343 return default
344
345 if count is None:
346
347 thevalue=entry[key]
348 elif prereq is None:
349
350 if len(entry[key])<=count:
351 return default
352 thevalue=entry[key][count]
353 else:
354
355 ptr=0
356 togo=count+1
357 l=entry[key]
358 k,v=prereq
359 while togo:
360 if ptr==len(l):
361 return default
362 if k not in l[ptr]:
363 ptr+=1
364 continue
365 if l[ptr][k]!=v:
366 ptr+=1
367 continue
368 togo-=1
369 if togo!=0:
370 ptr+=1
371 continue
372 thevalue=entry[key][ptr]
373 break
374
375
376 if callable(formatter):
377 return formatter(thevalue)
378
379 return thevalue.get(formatter, default)
380
382 """Similar to L{getdata} except returning higher level information.
383
384 Returns the key name and which index from the list corresponds to
385 the column.
386
387 @param column: Column name
388 @param entry: The dict representing a phonebook entry
389 @returns: (keyname, index) tuple. index will be None if the entry doesn't
390 have the relevant column value and -1 if all of them apply
391 """
392 key, count, prereq, formatter, _ =_getdatatable[column]
393
394
395 if key not in entry:
396 return (key, None)
397
398
399 if count is None:
400 return (key, -1)
401 elif prereq is None:
402
403 if len(entry[key])<=count:
404 return (key, None)
405 return (key, count)
406 else:
407
408 ptr=0
409 togo=count+1
410 l=entry[key]
411 k,v=prereq
412 while togo:
413 if ptr==len(l):
414 return (key,None)
415 if k not in l[ptr]:
416 ptr+=1
417 continue
418 if l[ptr][k]!=v:
419 ptr+=1
420 continue
421 togo-=1
422 if togo!=0:
423 ptr+=1
424 continue
425 return (key, ptr)
426 return (key, None)
427
429
430
431
432 __publisher=pubsub.Publisher
433
445
450
453
455 self.group_wps=msg.data[:]
456 self.OnGroupWPRequest()
457
465
471
482
484 new_groups=msg.data[:]
485 gwp=self.group_wps[:]
486 temp_dict={}
487 for entry in gwp:
488 l=entry.split(":", 1)
489 name=l[0]
490 wp=l[1]
491 temp_dict[name]=wp
492 for entry in new_groups:
493 l=entry.split(":", 1)
494 name=l[0]
495 wp=l[1]
496 temp_dict[name]=wp
497 out_list=[]
498 for k, v in temp_dict.items():
499 out_list.append(str(k)+":"+str(v))
500 self.group_wps=out_list
501 self.OnGroupWPRequest()
502
503
504 CategoryManager=CategoryManager()
511
513 self.main=widget
514 self.rowkeys=self.main._data.keys()
515 wx.grid.PyGridTableBase.__init__(self)
516 self.oddattr=wx.grid.GridCellAttr()
517 self.oddattr.SetBackgroundColour("OLDLACE")
518 self.evenattr=wx.grid.GridCellAttr()
519 self.evenattr.SetBackgroundColour("ALICE BLUE")
520 self.columns=columns
521 assert len(self.rowkeys)==0
522
524 return self.columns[col]
525
527 newkeys=self.main._data.keys()
528 newkeys.sort()
529 oldrows=self.rowkeys
530 self.rowkeys=newkeys
531 lo=len(oldrows)
532 ln=len(self.rowkeys)
533 if ln>lo:
534 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, ln-lo)
535 elif lo>ln:
536 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 0, lo-ln)
537 else:
538 msg=None
539 if msg is not None:
540 self.GetView().ProcessTableMessage(msg)
541 self.Sort()
542 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
543 self.GetView().ProcessTableMessage(msg)
544 self.GetView().AutoSizeColumns()
545
547 oldcols=self.columns
548 self.columns=columns
549 lo=len(oldcols)
550 ln=len(self.columns)
551 if ln>lo:
552 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED, ln-lo)
553 elif lo>ln:
554 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, 0, lo-ln)
555 else:
556 msg=None
557 if msg is not None:
558 self.GetView().ProcessTableMessage(msg)
559 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
560 self.GetView().ProcessTableMessage(msg)
561 self.GetView().AutoSizeColumns()
562
564 bycol=self.main.sortedColumn
565 descending=self.main.sortedColumnDescending
566
567 l=[ (getdata(self.columns[bycol], self.main._data[key]), key) for key in self.rowkeys]
568 l.sort()
569 if descending:
570 l.reverse()
571 self.rowkeys=[key for val,key in l]
572 msg=wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
573 self.GetView().ProcessTableMessage(msg)
574
577
579 return len(self.rowkeys)
580
582 return len(self.columns)
583
585 try:
586 entry=self.main._data[self.rowkeys[row]]
587 except:
588 print "bad row", row
589 return "<error>"
590
591 return getdata(self.columns[col], entry, "")
592
594 r=[self.evenattr, self.oddattr][row%2]
595 r.IncRef()
596 return r
597