5 # Currently a 'contact' is a name, a number, and a speed-dial letter
7 # We have a content pane and a row of buttons at the bottom.
8 # The content is either:
9 # - list of contact names - highlighted minipane at bottom with number
10 # - detailed contact info that can be editted (name, number, speed-pos)
12 # When list of contacts are displayed, typing a char adds that char to
13 # a substring and only contacts containing that substring are listed.
14 # An extra entry at the start is given which matches exactly the substring
15 # and can be used to create a new entry.
16 # Alternately, the list can show only 'deleted' entries, still with substring
17 # matching. Colour is different in this case.
19 # Buttons for contact list are:
20 # When nothing selected (list_none):
22 # When contact selected (list_sel):
24 # When new-entry selected (list_new):
26 # When viewing deleted entries and nothing or first is selected (del_none):
28 # When viewing deleted entries and one is selected (del_sel):
29 # Undelete Open Return
31 # When the detailed contact info is displayed all fields can be
32 # edited and change background colour if they differ from stored value
33 # Fields are: Name Number
34 # Button for details are:
35 # When nothing is changed (edit_nil):
36 # Call SMS Close Delete
37 # When something is changed on new entry (edit_new)
39 # When something is changed on old entry (edit old)
42 # 'delete' doesn't actually delete, but adds '!delete$DATE-' to the name which
43 # causes most lookups to ignore the entry.
46 # - find way to properly reset 'current' pos after edit
47 # - have a global 'state' object which other things refer to
48 # It has an 'updated' state which other objects can connect to
49 # - save file after an update
51 import gtk, pango, time, gobject, os
52 from scrawl import Scrawl
53 from listselect import ListSelect
56 class Contacts(gtk.Window):
58 gtk.Window.__init__(self)
59 self.set_default_size(480,640)
60 self.set_title("Contacts")
61 self.connect('destroy', self.close_win)
66 self.load_book("/media/card/address-book")
72 # list of contacts -or-
75 # variable list of buttons.
77 ctx = self.get_pango_context()
78 fd = ctx.get_font_description()
79 fd.set_absolute_size(35*pango.SCALE)
82 v = gtk.VBox(); v.show()
87 v.pack_start(s, expand=True)
90 self.sc.set_colour('red')
93 v.pack_start(s, expand = True)
97 bv = gtk.VBox(); bv.show(); v.pack_start(bv, expand=False)
99 for c in w.get_children():
101 bv.hide_some = lambda : hide_some(bv)
104 b = self.buttonlist(bv)
105 self.button(b, 'New', self.open)
106 self.button(b, 'Undelete', self.undelete)
107 self.button(b, 'ALL', self.all)
110 b = self.buttonlist(bv)
111 self.button(b, 'Call', self.call)
112 self.button(b, 'SMS', self.sms)
113 self.button(b, 'Open', self.open)
114 self.button(b, 'Delete', self.delete)
117 b = self.buttonlist(bv)
118 self.button(b, 'Create', self.open)
119 self.button(b, 'ALL', self.all)
122 b = self.buttonlist(bv)
123 self.button(b, 'Return', self.all)
126 b = self.buttonlist(bv)
127 self.button(b, 'Undelete', self.delete)
128 self.button(b, 'Open', self.open)
129 self.button(b, 'Return', self.all)
132 b = self.buttonlist(bv)
133 self.button(b, 'Call', self.call)
134 self.button(b, 'SMS', self.sms)
135 self.button(b, 'Close', self.close)
136 self.button(b, 'Delete', self.delete)
139 b = self.buttonlist(bv)
140 self.button(b, 'Discard', self.close)
141 self.button(b, 'Create', self.create)
144 b = self.buttonlist(bv)
145 self.button(b, 'Restore', self.open)
146 self.button(b, 'Save', self.save)
147 self.button(b, 'Create', self.create)
150 self.list_none.show()
153 s = ListSelect(); s.show()
154 s.set_format("normal","black", background="grey", selected="white")
155 s.set_format("deleted","red", background="grey", selected="white")
156 s.set_format("virtual","blue", background="grey", selected="white")
157 s.connect('selected', self.selected)
159 self.clist = contact_list()
166 self.sc = Scrawl(s, self.gotsym, gottap, None, None)
168 s.set_events(s.get_events() | gtk.gdk.KEY_PRESS_MASK)
170 if len(ev.string) == 1:
171 self.gotsym(ev.string)
172 elif ev.keyval == 65288:
175 #print ev.keyval, len(ev.string), ev.string
177 s.connect('key_press_event', key)
178 s.set_flags(gtk.CAN_FOCUS)
181 v = gtk.VBox(); v.show()
182 v.pack_start(s, expand=True)
184 l.modify_font(self.fd)
185 self.number_label = l
186 v.pack_start(l, expand=False)
193 self.field(ui, 'Name')
194 self.field(ui, 'Number')
195 self.field(ui, 'Speed Number')
198 def field(self, v, lbl):
199 h = gtk.HBox(); h.show()
200 l = gtk.Label(lbl); l.show()
201 l.modify_font(self.fd)
202 h.pack_start(l, expand=False)
203 e = gtk.Entry(); e.show()
204 e.modify_font(self.fd)
205 h.pack_start(e, expand=True)
206 e.connect('changed', self.field_update, lbl)
207 v.pack_start(h, expand=True, fill=True)
211 def buttonlist(self, v):
213 b.set_homogeneous(True)
215 v.pack_end(b, expand=False)
218 def button(self, bl, label, func):
220 if label[0:3] == "gtk" :
221 img = gtk.image_new_from_stock(label, self.isize)
226 b.child.modify_font(self.fd)
228 b.connect('clicked', func)
229 b.set_focus_on_click(False)
230 bl.pack_start(b, expand = True)
232 def close_win(self, widget):
235 def selected(self, list, item):
236 self.buttons.hide_some()
240 if self.clist.show_deleted:
241 if item < 0 or (item == 0 and self.clist.str != ''):
245 i = self.clist.getitem(item)
248 self.list_none.show()
249 self.number_label.hide()
251 elif item == 0 and self.clist.str != '':
253 self.number_label.hide()
257 i = self.clist.getitem(item)
260 self.number_label.hide()
262 self.number_label.set_text(self.list[i][1])
263 self.number_label.show()
265 def field_update(self, ev, fld):
266 if self.flds[fld] == self.fields[fld].get_text():
267 self.fields[fld].modify_base(gtk.STATE_NORMAL,None)
269 self.fields[fld].modify_base(gtk.STATE_NORMAL,gtk.gdk.color_parse('yellow'))
272 for i in ['Name', 'Number', 'Speed Number']:
273 if self.fields[i].get_text() != self.flds[i]:
275 self.buttons.hide_some()
276 if self.current == None:
284 def load_book(self, file):
289 f = open('/home/neilb/home/mobile-numbers-jan-08')
290 self.fname = '/home/neilb/home/mobile-numbers-jan-08'
298 rv.append([x[0],x[1], ''])
300 for i in range(len(rv)):
301 if rv[i][0] in speed:
302 rv[i][2] = speed[rv[i][0]]
303 rv.sort(lambda x,y: cmp(x[0].lower(),y[0].lower()))
305 self.clist.set_list(rv)
309 f = open(self.fname + '.new', 'w')
314 f.write(name+';'+num+';\n')
316 f.write(speed+';'+name+';\n')
318 os.rename(self.fname+'.new', self.fname)
320 def queue_save(self):
322 gobject.source_remove(self.timer)
323 self.timer = gobject.timeout_add(15*1000, self.do_save)
326 gobject.source_remove(self.timer)
330 def gotsym(self,sym):
332 s = self.clist.str[:-1]
336 s = self.clist.str + sym
339 self.clist.set_str(s)
340 self.sel.list_changed()
341 self.sel.select(None)
347 self.buttons.hide_some()
348 if self.current == None:
353 self.flds['Name'] = ''
354 self.flds['Number'] = ''
355 self.flds['Speed Number'] = ''
356 if self.current != None:
357 self.flds['Name'] = self.list[self.current][0]
358 self.flds['Number'] = self.list[self.current][1]
359 self.flds['Speed Number'] = self.list[self.current][2]
361 self.flds['Name'] = self.clist.str
363 for f in ['Name', 'Number', 'Speed Number'] :
364 self.fields[f].set_text(self.flds[f])
365 self.fields[f].modify_base(gtk.STATE_NORMAL,None)
368 self.clist.set_str('')
369 self.clist.show_deleted = False
370 self.sel.select(None)
371 self.sel.list_changed()
373 def create(self, ev):
376 name = self.fields['Name'].get_text()
377 num = self.fields['Number'].get_text()
378 speed = self.fields['Speed Number'].get_text()
379 if name == '' or name.find(';') >= 0:
380 self.fields['Name'].modify_base(gtk.STATE_NORMAL,gtk.gdk.color_parse('pink'))
382 if num == '' or num.find(';') >= 0:
383 self.fields['Number'].modify_base(gtk.STATE_NORMAL,gtk.gdk.color_parse('pink'))
385 if len(speed) > 1 or speed.find(';') >= 0:
386 self.fields['Speed Number'].modify_base(gtk.STATE_NORMAL,gtk.gdk.color_parse('pink'))
388 if self.current == None or ev == None:
389 self.list.append([name, num, speed])
391 self.list[self.current] = [name, num, speed]
392 self.flds['Name'] = name
393 self.flds['Number'] = num
394 self.flds['Speed Number'] = speed
395 self.list.sort(lambda x,y: cmp(x[0].lower(),y[0].lower()))
396 self.clist.set_list(self.list)
397 self.sel.list_changed()
402 def delete(self, ev):
403 if self.current != None:
404 n = self.list[self.current][0]
405 if n[:8] == '!deleted':
411 n = time.strftime('!deleted.%Y.%m.%d-') + n
412 self.list[self.current][0] = n
413 self.list.sort(lambda x,y: cmp(x[0].lower(),y[0].lower()))
414 self.clist.set_list(self.list)
415 self.sel.list_changed()
420 def undelete(self, ev):
421 self.clist.show_deleted = True
422 self.clist.set_str('')
423 self.sel.list_changed()
424 self.sel.select(None)
432 self.sel.grab_focus()
434 if self.current != None and self.current >= len(self.clist):
436 self.selected(None, self.sel.selected)
441 self.match_start = []
447 self.match_deleted = False
449 self.show_deleted = False
450 def set_list(self, list):
455 for i in range(len(self.list)):
456 name,number,speed = self.list[i]
458 if name[:8] == '!deleted':
459 self.deleted.append(i)
463 def set_str(self,str):
467 if self.str == self.match_str and self.show_deleted == self.match_deleted:
469 if self.match_str != '' and self.str[:len(self.match_str)] == self.match_str and self.show_deleted == self.match_deleted:
470 # str is a bit longer
473 self.match_start = []
476 self.match_str = self.str
477 self.match_deleted = self.show_deleted
480 for i in self.deleted if self.match_deleted else self.valid:
481 name,number,speed = self.list[i]
484 self.match_start.append(i)
485 elif name.find(' '+s) >= 0:
486 self.match_word.append(i)
487 elif name.find(s) >= 0:
488 self.match_any.append(i)
489 self.total = len(self.match_start) + len(self.match_word) + len(self.match_any)
491 def recalc_quick(self):
492 # The string has been extended so we only look through what we already have
493 self.match_str = self.str
497 lst = self.match_start
498 self.match_start = []
500 name,number,speed = self.list[i]
503 self.match_start.append(i)
505 self.match_word.append(i)
506 self.match_word.sort()
507 lst = self.match_word
510 name, number, speed = self.list[i]
512 if name.find(' '+s) >= 0:
513 self.match_word.append(i)
515 self.match_any.append(i)
516 self.match_any.sort()
520 name, number, speed = self.list[i]
522 if name.find(s) >= 0:
523 self.match_any.append(i)
524 self.total = len(self.match_start) + len(self.match_word) + len(self.match_any)
529 if self.match_deleted:
530 return len(self.deleted)
532 return len(self.valid)
534 return self.total + 1
535 def getitem(self, ind):
539 if self.match_deleted:
540 return self.deleted[ind]
542 return self.valid[ind]
547 if ind < len(self.match_start):
548 return self.match_start[ind]
549 ind -= len(self.match_start)
550 if ind < len(self.match_word):
551 return self.match_word[ind]
552 ind -= len(self.match_word)
553 if ind < len(self.match_any):
554 return self.match_any[ind]
557 def __getitem__(self, ind):
558 i = self.getitem(ind)
560 return (self.str, 'virtual')
564 if s[:8] == '!deleted':
568 return (s[p+1:],'deleted')
572 if __name__ == "__main__":