]> git.neil.brown.name Git - plato.git/blob - sms/sendsms.py
ca31918ae92aaebf753902855a41b5a617f80f44
[plato.git] / sms / sendsms.py
1 #!/usr/bin/env python
2
3 # Create/edit/send/display/search SMS messages.
4 # Two main displays:  Create and display
5 # Create:
6 #   Allow entry of recipient and text of SMS message and allow basic editting
7 #    When entering recipient, text box can show address matches for selection
8 #     Bottom buttons are "Select"..
9 #    When entering text, if there is no text, buttom buttons are:
10 #       "Browse", "Close"
11 #       If these is some text, bottom buttons are:
12 #        "Send", Save"
13 #
14 # Display:
15 #   We usually display a list of messages which can be selected from
16 #    There is a 'search' box to restrict message to those with a string
17 #   Options for selected message are:
18 #     Delete Reply View  Open(for draft)/Forward(for non-draft)
19 #   In View mode, the whole text is displayed and the 'View' button becomes "Index"
20 #     or "Show List"  or "ReadIt"
21 #   General options are:
22 #     New Config  List
23 #    New goes to Edit
24 #
25 #   Delete becomes Undelete and can undelete a whole stack.
26 #    Delete can become undelete without deleting be press-and-hold
27 #
28 #
29 # Messages are sent using a separate program. e.g. sms-gsm
30 # Different recipients can use different programs based on flag in address book.
31 # Somehow senders can be configured.
32 #   e.g. sms-exetel needs username, password, sender strings.
33 #   press-and-hold on the send button allows a sender to be selected.
34 #     
35 #
36 # Send an SMS message using some backend.
37
38
39 #
40 # TODO:
41 #   'del' to return to 'list' view
42 #   top buttons:  del, view/list, new/open/reply
43 #           so can only reply when viewing whole message
44 #   Bottom:
45 #      all:   sent recv
46 #      send:  all  draft
47 #      recv:  all new
48 #      draft:  all  sent
49 #      new:   all recv
50 #   DONE handle newline chars in summary
51 #   DONE cope properly when the month changes.
52 #   switch-to-'new' on 'expose'
53 #   'draft' button becomes 'cancel' when all is empty
54 #   DONE better display of name/number of destination
55 #   jump to list mode when change 'list'
56 #   'open' becomes 'reply' when current message was received.
57 #   new message becomes non-new when replied to
58 #   '<list>' button doesn't select, but just makes choice.
59 #      'new' becomes 'select' when <list> has been pressed.
60 #   DONE Start in 'read', preferrably 'new' 
61 #   DONE always report status from send
62 #   DONE draft/new/recv/sent/all  - 5 groups
63 #   DONE allow scrolling through list
64 #   DONE + prefix to work
65 #   DONE compose as 'GSM' or 'EXE' send
66 #   DONE somehow do addressbook lookup for compose
67 #   DONE addressbook lookup for display
68 #   On 'send' move to 'sent' (not draft) and display list
69 #   When open 'draft', delete from drafts... or later..
70 #   When 'reply' to new message, make it not 'new'
71 #
72 #   get 'cut' to work from phone number entry.
73 #   how to configure sender...
74 #   need to select 'number only' mode for entry
75 #   need drop-down of common numbers
76 #   DONE text wrapping
77 #   punctuation
78 #   faster text input!!!
79 #   DONE status message of transmission
80 #   DONE maybe go to 'past messages' on send - need to go somewhere
81 #   cut from other sources??
82 #   DONE scroll if message is too long!
83 #
84 #   DONE reread sms file when changing view
85 #   Don't add drafts that have not been changed... or
86 #   When opening a draft, delete it... or replace when re-add
87 #   DONE when sending a message, store - as draft if send failed
88 #   DONE show the 'send' status somewhere
89 #   DONE add a 'new' button from 'list' to 'send'
90 #   Need 'reply' button.. Make 'open' show 'reply' when 'to' me.
91 #   Scroll when near top or bottom
92 #   hide status line when not needed.
93 #   searching mesg list
94 #   'folder' view - by month or day
95 #   highlight 'new' and 'draft' messages in different colour
96 #   support 'sent' and 'received' distinction
97 #   when return from viewing a 'new' message, clear the 'new' status
98 #   enable starting in 'listing/New' mode
99
100 import gtk, pango
101 import sys, time, os, re
102 import struct
103 from subprocess import Popen, PIPE
104 from storesms import SMSstore, SMSmesg
105 import dnotify
106
107 ###########################################################
108 # Writing recognistion code
109 import math
110
111
112 def LoadDict(dict):
113     # Upper case.
114     # Where they are like lowercase, we either double
115     # the last stroke (L, J, I) or draw backwards (S, Z, X)
116     # U V are a special case
117
118     dict.add('A', "R(4)6,8")
119     dict.add('B', "R(4)6,4.R(7)1,6")
120     dict.add('B', "R(4)6,4.L(4)2,8.R(7)1,6")
121     dict.add('B', "S(6)7,1.R(4)6,4.R(7)0,6")
122     dict.add('C', "R(4)8,2")
123     dict.add('D', "R(4)6,6")
124     dict.add('E', "L(1)2,8.L(7)2,8")
125     # double the stem for F
126     dict.add('F', "L(4)2,6.S(3)7,1")
127     dict.add('F', "S(1)5,3.S(3)1,7.S(3)7,1")
128
129     dict.add('G', "L(4)2,5.S(8)1,7")
130     dict.add('G', "L(4)2,5.R(8)6,8")
131     # FIXME I need better straight-curve alignment
132     dict.add('H', "S(3)1,7.R(7)6,8.S(5)7,1")
133     dict.add('H', "L(3)0,5.R(7)6,8.S(5)7,1")
134     # capital I is down/up
135     dict.add('I', "S(4)1,7.S(4)7,1")
136
137     # Capital J has a left/right tail
138     dict.add('J', "R(4)1,6.S(7)3,5")
139
140     dict.add('K', "L(4)0,2.R(4)6,6.L(4)2,8")
141
142     # Capital L, like J, doubles the foot
143     dict.add('L', "L(4)0,8.S(7)4,3")
144
145     dict.add('M', "R(3)6,5.R(5)3,8")
146     dict.add('M', "R(3)6,5.L(1)0,2.R(5)3,8")
147
148     dict.add('N', "R(3)6,8.L(5)0,2")
149
150     # Capital O is CW, but can be CCW in special dict
151     dict.add('O', "R(4)1,1", bot='0')
152
153     dict.add('P', "R(4)6,3")
154     dict.add('Q', "R(4)7,7.S(8)0,8")
155
156     dict.add('R', "R(4)6,4.S(8)0,8")
157
158     # S is drawn bottom to top.
159     dict.add('S', "L(7)6,1.R(1)7,2")
160
161     # Double the stem for capital T
162     dict.add('T', "R(4)0,8.S(5)7,1")
163
164     # U is L to R, V is R to L for now
165     dict.add('U', "L(4)0,2")
166     dict.add('V', "R(4)2,0")
167
168     dict.add('W', "R(5)2,3.L(7)8,6.R(3)5,0")
169     dict.add('W', "R(5)2,3.R(3)5,0")
170
171     dict.add('X', "R(4)6,0")
172
173     dict.add('Y',"L(1)0,2.R(5)4,6.S(5)6,2")
174     dict.add('Y',"L(1)0,2.S(5)2,7.S(5)7,2")
175
176     dict.add('Z', "R(4)8,2.L(4)6,0")
177
178     # Lower case
179     dict.add('a', "L(4)2,2.L(5)1,7")
180     dict.add('a', "L(4)2,2.L(5)0,8")
181     dict.add('a', "L(4)2,2.S(5)0,8")
182     dict.add('b', "S(3)1,7.R(7)6,3")
183     dict.add('c', "L(4)2,8", top='C')
184     dict.add('d', "L(4)5,2.S(5)1,7")
185     dict.add('d', "L(4)5,2.L(5)0,8")
186     dict.add('e', "S(4)3,5.L(4)5,8")
187     dict.add('e', "L(4)3,8")
188     dict.add('f', "L(4)2,6", top='F')
189     dict.add('f', "S(1)5,3.S(3)1,7", top='F')
190     dict.add('g', "L(1)2,2.R(4)1,6")
191     dict.add('h', "S(3)1,7.R(7)6,8")
192     dict.add('h', "L(3)0,5.R(7)6,8")
193     dict.add('i', "S(4)1,7", top='I', bot='1')
194     dict.add('j', "R(4)1,6", top='J')
195     dict.add('k', "L(3)0,5.L(7)2,8")
196     dict.add('k', "L(4)0,5.R(7)6,6.L(7)1,8")
197     dict.add('l', "L(4)0,8", top='L')
198     dict.add('l', "S(3)1,7.S(7)3,5", top='L')
199     dict.add('m', "S(3)1,7.R(3)6,8.R(5)6,8")
200     dict.add('m', "L(3)0,2.R(3)6,8.R(5)6,8")
201     dict.add('n', "S(3)1,7.R(4)6,8")
202     dict.add('o', "L(4)1,1", top='O', bot='0')
203     dict.add('p', "S(3)1,7.R(4)6,3")
204     dict.add('q', "L(1)2,2.L(5)1,5")
205     dict.add('q', "L(1)2,2.S(5)1,7.R(8)6,2")
206     dict.add('q', "L(1)2,2.S(5)1,7.S(5)1,7")
207     # FIXME this double 1,7 is due to a gentle where the
208     # second looks like a line because it is narrow.??
209     dict.add('r', "S(3)1,7.R(4)6,2")
210     dict.add('s', "L(1)2,7.R(7)1,6", top='S', bot='5')
211     dict.add('t', "R(4)0,8", top='T', bot='7')
212     dict.add('t', "S(1)3,5.S(5)1,7", top='T', bot='7')
213     dict.add('u', "L(4)0,2.S(5)1,7")
214     dict.add('v', "L(4)0,2.L(2)0,2")
215     dict.add('w', "L(3)0,2.L(5)0,2", top='W')
216     dict.add('w', "L(3)0,5.R(7)6,8.L(5)3,2", top='W')
217     dict.add('w', "L(3)0,5.L(5)3,2", top='W')
218     dict.add('x', "L(4)0,6", top='X')
219     dict.add('y', "L(1)0,2.R(5)4,6", top='Y') # if curved
220     dict.add('y', "L(1)0,2.S(5)2,7", top='Y')
221     dict.add('z', "R(4)0,6.L(4)2,8", top='Z', bot='2')
222
223     # Digits
224     dict.add('0', "L(4)7,7")
225     dict.add('0', "R(4)7,7")
226     dict.add('1', "S(4)7,1")
227     dict.add('2', "R(4)0,6.S(7)3,5")
228     dict.add('2', "R(4)3,6.L(4)2,8")
229     dict.add('3', "R(1)0,6.R(7)1,6")
230     dict.add('4', "L(4)7,5")
231     dict.add('5', "L(1)2,6.R(7)0,3")
232     dict.add('5', "L(1)2,6.L(4)0,8.R(7)0,3")
233     dict.add('6', "L(4)2,3")
234     dict.add('7', "S(1)3,5.R(4)1,6")
235     dict.add('7', "R(4)0,6")
236     dict.add('7', "R(4)0,7")
237     dict.add('8', "L(4)2,8.R(4)4,2.L(3)6,1")
238     dict.add('8', "L(1)2,8.R(7)2,0.L(1)6,1")
239     dict.add('8', "L(0)2,6.R(7)0,1.L(2)6,0")
240     dict.add('8', "R(4)2,6.L(4)4,2.R(5)8,1")
241     dict.add('9', "L(1)2,2.S(5)1,7")
242
243     dict.add(' ', "S(4)3,5")
244     dict.add('<BS>', "S(4)5,3")
245     dict.add('-', "S(4)3,5.S(4)5,3")
246     dict.add('_', "S(4)3,5.S(4)5,3.S(4)3,5")
247     dict.add("<left>", "S(4)5,3.S(3)3,5")
248     dict.add("<right>","S(4)3,5.S(5)5,3")
249     dict.add("<left>", "S(4)7,1.S(1)1,7") # "<up>"
250     dict.add("<right>","S(4)1,7.S(7)7,1") # "<down>"
251     dict.add("<newline>", "S(4)2,6")
252
253
254 class DictSegment:
255     # Each segment has for elements:
256     #   direction: Right Straight Left (R=cw, L=ccw)
257     #   location: 0-8.
258     #   start: 0-8
259     #   finish: 0-8
260     # Segments match if there difference at each element
261     # is 0, 1, or 3 (RSL coded as 012)
262     # A difference of 1 required both to be same / 3
263     # On a match, return number of 0s
264     # On non-match, return -1
265     def __init__(self, str):
266         # D(L)S,R
267         # 0123456
268         self.e = [0,0,0,0]
269         if len(str) != 7:
270             raise ValueError
271         if str[1] != '(' or str[3] != ')' or str[5] != ',':
272             raise ValueError
273         if str[0] == 'R':
274             self.e[0] = 0
275         elif str[0] == 'L':
276             self.e[0] = 2
277         elif str[0] == 'S':
278             self.e[0] = 1
279         else:
280             raise ValueError
281
282         self.e[1] = int(str[2])
283         self.e[2] = int(str[4])
284         self.e[3] = int(str[6])
285
286     def match(self, other):
287         cnt = 0
288         for i in range(0,4):
289             diff = abs(self.e[i] - other.e[i])
290             if diff == 0:
291                 cnt += 1
292             elif diff == 3:
293                 pass
294             elif diff == 1 and (self.e[i]/3 == other.e[i]/3):
295                 pass
296             else:
297                 return -1
298         return cnt
299
300 class DictPattern:
301     # A Dict Pattern is a list of segments.
302     # A parsed pattern matches a dict pattern if
303     # the are the same nubmer of segments and they
304     # all match.  The value of the match is the sum
305     # of the individual matches.
306     # A DictPattern is printers as segments joined by periods.
307     #
308     def __init__(self, str):
309         self.segs = map(DictSegment, str.split("."))
310     def match(self,other):
311         if len(self.segs) != len(other.segs):
312             return -1
313         cnt = 0
314         for i in range(0,len(self.segs)):
315             m = self.segs[i].match(other.segs[i])
316             if m < 0:
317                 return m
318             cnt += m
319         return cnt
320
321
322 class Dictionary:
323     # The dictionary hold all the pattern for symbols and
324     # performs lookup
325     # Each pattern in the directionary can be associated
326     # with  3 symbols.  One when drawing in middle of screen,
327     # one for top of screen, one for bottom.
328     # Often these will all be the same.
329     # This allows e.g. s and S to have the same pattern in different
330     # location on the touchscreen.
331     # A match requires a unique entry with a match that is better
332     # than any other entry.
333     #
334     def __init__(self):
335         self.dict = []
336     def add(self, sym, pat, top = None, bot = None):
337         if top == None: top = sym
338         if bot == None: bot = sym
339         self.dict.append((DictPattern(pat), sym, top, bot))
340
341     def _match(self, p):
342         max = -1
343         val = None
344         for (ptn, sym, top, bot) in self.dict:
345             cnt = ptn.match(p)
346             if cnt > max:
347                 max = cnt
348                 val = (sym, top, bot)
349             elif cnt == max:
350                 val = None
351         return val
352
353     def match(self, str, pos = "mid"):
354         p = DictPattern(str)
355         m = self._match(p)
356         if m == None:
357             return m
358         (mid, top, bot) = self._match(p)
359         if pos == "top": return top
360         if pos == "bot": return bot
361         return mid
362
363
364 class Point:
365     # This represents a point in the path and all the points leading
366     # up to it.  It allows us to find the direction and curvature from
367     # one point to another
368     # We store x,y, and sum/cnt of points so far
369     def __init__(self,x,y) :
370         self.xsum = x
371         self.ysum = y
372         self.x = x
373         self.y = y
374         self.cnt = 1
375
376     def copy(self):
377         n = Point(0,0)
378         n.xsum = self.xsum
379         n.ysum = self.ysum
380         n.x = self.x
381         n.y = self.y
382         n.cnt = self.cnt
383         return n
384
385     def add(self,x,y):
386         if self.x == x and self.y == y:
387             return
388         self.x = x
389         self.y = y
390         self.xsum += x
391         self.ysum += y
392         self.cnt += 1
393
394     def xlen(self,p):
395         return abs(self.x - p.x)
396     def ylen(self,p):
397         return abs(self.y - p.y)
398     def sqlen(self,p):
399         x = self.x - p.x
400         y = self.y - p.y
401         return x*x + y*y
402
403     def xdir(self,p):
404         if self.x > p.x:
405             return 1
406         if self.x < p.x:
407             return -1
408         return 0
409     def ydir(self,p):
410         if self.y > p.y:
411             return 1
412         if self.y < p.y:
413             return -1
414         return 0
415     def curve(self,p):
416         if self.cnt == p.cnt:
417             return 0
418         x1 = p.x ; y1 = p.y
419         (x2,y2) = self.meanpoint(p)
420         x3 = self.x; y3 = self.y
421
422         curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
423         curve = curve * 100 / ((y3-y1)*(y3-y1)
424                                + (x3-x1)*(x3-x1))
425         if curve > 6:
426             return 1
427         if curve < -6:
428             return -1
429         return 0
430
431     def Vcurve(self,p):
432         if self.cnt == p.cnt:
433             return 0
434         x1 = p.x ; y1 = p.y
435         (x2,y2) = self.meanpoint(p)
436         x3 = self.x; y3 = self.y
437
438         curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
439         curve = curve * 100 / ((y3-y1)*(y3-y1)
440                                + (x3-x1)*(x3-x1))
441         return curve
442
443     def meanpoint(self,p):
444         x = (self.xsum - p.xsum) / (self.cnt - p.cnt)
445         y = (self.ysum - p.ysum) / (self.cnt - p.cnt)
446         return (x,y)
447
448     def is_sharp(self,A,C):
449         # Measure the cosine at self between A and C
450         # as A and C could be curve, we take the mean point on
451         # self.A and self.C as the points to find cosine between
452         (ax,ay) = self.meanpoint(A)
453         (cx,cy) = self.meanpoint(C)
454         a = ax-self.x; b=ay-self.y
455         c = cx-self.x; d=cy-self.y
456         x = a*c + b*d
457         y = a*d - b*c
458         h = math.sqrt(x*x+y*y)
459         if h > 0:
460             cs = x*1000/h
461         else:
462             cs = 0
463         return (cs > 900)
464
465 class BBox:
466     # a BBox records min/max x/y of some Points and
467     # can subsequently report row, column, pos of each point
468     # can also locate one bbox in another
469
470     def __init__(self, p):
471         self.minx = p.x
472         self.maxx = p.x
473         self.miny = p.y
474         self.maxy = p.y
475
476     def width(self):
477         return self.maxx - self.minx
478     def height(self):
479         return self.maxy - self.miny
480
481     def add(self, p):
482         if p.x > self.maxx:
483             self.maxx = p.x
484         if p.x < self.minx:
485             self.minx = p.x
486
487         if p.y > self.maxy:
488             self.maxy = p.y
489         if p.y < self.miny:
490             self.miny = p.y
491     def finish(self, div = 3):
492         # if aspect ratio is bad, we adjust max/min accordingly
493         # before setting [xy][12].  We don't change self.min/max
494         # as they are used to place stroke in bigger bbox.
495         # Normally divisions are at 1/3 and 2/3. They can be moved
496         # by setting div e.g. 2 = 1/2 and 1/2
497         (minx,miny,maxx,maxy) = (self.minx,self.miny,self.maxx,self.maxy)
498         if (maxx - minx) * 3 < (maxy - miny) * 2:
499             # too narrow
500             mid = int((maxx + minx)/2)
501             halfwidth = int ((maxy - miny)/3)
502             minx = mid - halfwidth
503             maxx = mid + halfwidth
504         if (maxy - miny) * 3 < (maxx - minx) * 2:
505             # too wide
506             mid = int((maxy + miny)/2)
507             halfheight = int ((maxx - minx)/3)
508             miny = mid - halfheight
509             maxy = mid + halfheight
510
511         div1 = div - 1
512         self.x1 = int((div1*minx + maxx)/div)
513         self.x2 = int((minx + div1*maxx)/div)
514         self.y1 = int((div1*miny + maxy)/div)
515         self.y2 = int((miny + div1*maxy)/div)
516
517     def row(self, p):
518         # 0, 1, 2 - top to bottom
519         if p.y <= self.y1:
520             return 0
521         if p.y < self.y2:
522             return 1
523         return 2
524     def col(self, p):
525         if p.x <= self.x1:
526             return 0
527         if p.x < self.x2:
528             return 1
529         return 2
530     def box(self, p):
531         # 0 to 9
532         return self.row(p) * 3 + self.col(p)
533
534     def relpos(self,b):
535         # b is a box within self.  find location 0-8
536         if b.maxx < self.x2 and b.minx < self.x1:
537             x = 0
538         elif b.minx > self.x1 and b.maxx > self.x2:
539             x = 2
540         else:
541             x = 1
542         if b.maxy < self.y2 and b.miny < self.y1:
543             y = 0
544         elif b.miny > self.y1 and b.maxy > self.y2:
545             y = 2
546         else:
547             y = 1
548         return y*3 + x
549
550
551 def different(*args):
552     cur = 0
553     for i in args:
554         if cur != 0 and i != 0 and cur != i:
555             return True
556         if cur == 0:
557             cur = i
558     return False
559
560 def maxcurve(*args):
561     for i in args:
562         if i != 0:
563             return i
564     return 0
565
566 class PPath:
567     # a PPath refines a list of x,y points into a list of Points
568     # The Points mark out segments which end at significant Points
569     # such as inflections and reversals.
570
571     def __init__(self, x,y):
572
573         self.start = Point(x,y)
574         self.mid = Point(x,y)
575         self.curr = Point(x,y)
576         self.list = [ self.start ]
577
578     def add(self, x, y):
579         self.curr.add(x,y)
580
581         if ( (abs(self.mid.xdir(self.start) - self.curr.xdir(self.mid)) == 2) or
582              (abs(self.mid.ydir(self.start) - self.curr.ydir(self.mid)) == 2) or
583              (abs(self.curr.Vcurve(self.start))+2 < abs(self.mid.Vcurve(self.start)))):
584             pass
585         else:
586             self.mid = self.curr.copy()
587
588         if self.curr.xlen(self.mid) > 4 or self.curr.ylen(self.mid) > 4:
589             self.start = self.mid.copy()
590             self.list.append(self.start)
591             self.mid = self.curr.copy()
592
593     def close(self):
594         self.list.append(self.curr)
595
596     def get_sectlist(self):
597         if len(self.list) <= 2:
598             return [[0,self.list]]
599         l = []
600         A = self.list[0]
601         B = self.list[1]
602         s = [A,B]
603         curcurve = B.curve(A)
604         for C in self.list[2:]:
605             cabc = C.curve(A)
606             cab = B.curve(A)
607             cbc = C.curve(B)
608             if B.is_sharp(A,C) and not different(cabc, cab, cbc, curcurve):
609                 # B is too pointy, must break here
610                 l.append([curcurve, s])
611                 s = [B, C]
612                 curcurve = cbc
613             elif not different(cabc, cab, cbc, curcurve):
614                 # all happy
615                 s.append(C)
616                 if curcurve == 0:
617                     curcurve = maxcurve(cab, cbc, cabc)
618             elif not different(cabc, cab, cbc)  :
619                 # gentle inflection along AB
620                 # was: AB goes in old and new section
621                 # now: AB only in old section, but curcurve
622                 #      preseved.
623                 l.append([curcurve,s])
624                 s = [A, B, C]
625                 curcurve =maxcurve(cab, cbc, cabc)
626             else:
627                 # Change of direction at B
628                 l.append([curcurve,s])
629                 s = [B, C]
630                 curcurve = cbc
631
632             A = B
633             B = C
634         l.append([curcurve,s])
635
636         return l
637
638     def remove_shorts(self, bbox):
639         # in self.list, if a point is close to the previous point,
640         # remove it.
641         if len(self.list) <= 2:
642             return
643         w = bbox.width()/10
644         h = bbox.height()/10
645         n = [self.list[0]]
646         leng = w*h*2*2
647         for p in self.list[1:]:
648             l = p.sqlen(n[-1])
649             if l > leng:
650                 n.append(p)
651         self.list = n
652
653     def text(self):
654         # OK, we have a list of points with curvature between.
655         # want to divide this into sections.
656         # for each 3 consectutive points ABC curve of ABC and AB and BC
657         # If all the same, they are all in a section.
658         # If not B starts a new section and the old ends on B or C...
659         BB = BBox(self.list[0])
660         for p in self.list:
661             BB.add(p)
662         BB.finish()
663         self.bbox = BB
664         self.remove_shorts(BB)
665         sectlist = self.get_sectlist()
666         t = ""
667         for c, s in sectlist:
668             if c > 0:
669                 dr = "R"  # clockwise is to the Right
670             elif c < 0:
671                 dr = "L"  # counterclockwise to the Left
672             else:
673                 dr = "S"  # straight
674             bb = BBox(s[0])
675             for p in s:
676                 bb.add(p)
677             bb.finish()
678             # If  all points are in some row or column, then
679             # line is S
680             rwdiff = False; cldiff = False
681             rw = bb.row(s[0]); cl=bb.col(s[0])
682             for p in s:
683                 if bb.row(p) != rw: rwdiff = True
684                 if bb.col(p) != cl: cldiff = True
685             if not rwdiff or not cldiff: dr = "S"
686
687             t1 = dr
688             t1 += "(%d)" % BB.relpos(bb)
689             t1 += "%d,%d" % (bb.box(s[0]), bb.box(s[-1]))
690             t += t1 + '.'
691         return t[:-1]
692
693
694 class text_input:
695     def __init__(self, page, callout):
696
697         self.page = page
698         self.callout = callout
699         self.colour = None
700         self.line = None
701         self.dict = Dictionary()
702         self.active = True
703         LoadDict(self.dict)
704
705         page.connect("button_press_event", self.press)
706         page.connect("button_release_event", self.release)
707         page.connect("motion_notify_event", self.motion)
708         page.set_events(page.get_events()
709                         | gtk.gdk.BUTTON_PRESS_MASK
710                         | gtk.gdk.BUTTON_RELEASE_MASK
711                         |  gtk.gdk.POINTER_MOTION_MASK
712                         | gtk.gdk.POINTER_MOTION_HINT_MASK)
713
714     def set_colour(self, col):
715         self.colour = col
716     
717     def press(self, c, ev):
718         if not self.active:
719             return
720         # Start a new line
721         self.line = [ [int(ev.x), int(ev.y)] ]
722         if not ev.send_event:
723             self.page.stop_emission('button_press_event')
724         return
725     def release(self, c, ev):
726         if self.line == None:
727             return
728         if len(self.line) == 1:
729             self.callout('click', ev)
730             self.line = None
731             return
732
733         sym = self.getsym()
734         if sym:
735             self.callout('sym', sym)
736         self.callout('redraw', None)
737         self.line = None
738         return
739
740     def motion(self, c, ev):
741         if self.line:
742             if ev.is_hint:
743                 x, y, state = ev.window.get_pointer()
744             else:
745                 x = ev.x
746                 y = ev.y
747             x = int(x)
748             y = int(y)
749             prev = self.line[-1]
750             if abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10:
751                 return
752             if self.colour:
753                 c.window.draw_line(self.colour, prev[0],prev[1],x,y)
754             self.line.append([x,y])
755         return
756
757     def getsym(self):
758         alloc = self.page.get_allocation()
759         pagebb = BBox(Point(0,0))
760         pagebb.add(Point(alloc.width, alloc.height))
761         pagebb.finish(div = 2)
762
763         p = PPath(self.line[1][0], self.line[1][1])
764         for pp in self.line[1:]:
765             p.add(pp[0], pp[1])
766         p.close()
767         patn = p.text()
768         pos = pagebb.relpos(p.bbox)
769         tpos = "mid"
770         if pos < 3:
771             tpos = "top"
772         if pos >= 6:
773             tpos = "bot"
774         sym = self.dict.match(patn, tpos)
775         if sym == None:
776             print "Failed to match pattern:", patn
777         return sym
778
779
780
781
782
783 ########################################################################
784
785
786
787 class FingerText(gtk.TextView):
788     def __init__(self):
789         gtk.TextView.__init__(self)
790         self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
791         self.exphan = self.connect('expose-event', self.config)
792         self.input = text_input(self, self.stylus)
793
794     def config(self, *a):
795         self.disconnect(self.exphan)
796         c = gtk.gdk.color_parse('red')
797         gc = self.window.new_gc()
798         gc.set_foreground(self.get_colormap().alloc_color(c))
799         #gc.set_line_attributes(2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)
800         gc.set_subwindow(gtk.gdk.INCLUDE_INFERIORS)
801         self.input.set_colour(gc)
802
803     def stylus(self, cmd, info):
804         if cmd == "sym":
805             tl = self.get_toplevel()
806             w = tl.get_focus()
807             if w == None:
808                 w = self
809             ev = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
810             ev.window = w.window
811             if info == '<BS>':
812                 ev.keyval = 65288
813                 ev.hardware_keycode = 22
814             else:
815                 (ev.keyval,) = struct.unpack_from("b", info)
816             w.emit('key_press_event', ev)
817             #self.get_buffer().insert_at_cursor(info)
818         if cmd == 'click':
819             self.grab_focus()
820             if not info.send_event:
821                 info.send_event = True
822                 ev2 = gtk.gdk.Event(gtk.gdk.BUTTON_PRESS)
823                 ev2.send_event = True
824                 ev2.window = info.window
825                 ev2.time = info.time
826                 ev2.x = info.x
827                 ev2.y = info.y
828                 ev2.button = info.button
829                 self.emit('button_press_event', ev2)
830                 self.emit('button_release_event', info)
831         if cmd == 'redraw':
832             self.queue_draw()
833
834     def insert_at_cursor(self, text):
835         self.get_buffer().insert_at_cursor(text)
836         
837 class FingerEntry(gtk.Entry):
838     def __init__(self):
839         gtk.Entry.__init__(self)
840
841     def insert_at_cursor(self, text):
842         c = self.get_property('cursor-position')
843         t = self.get_text()
844         t = t[0:c]+text+t[c:]
845         self.set_text(t)
846
847 class SMSlist(gtk.DrawingArea):
848     def __init__(self, getlist):
849         gtk.DrawingArea.__init__(self)
850         self.pixbuf = None
851         self.width = self.height = 0
852         self.need_redraw = True
853         self.colours = None
854         self.collist = {}
855         self.get_list = getlist
856
857         self.connect("expose-event", self.redraw)
858         self.connect("configure-event", self.reconfig)
859         
860         self.connect("button_release_event", self.release)
861         self.connect("button_press_event", self.press)
862         self.set_events(gtk.gdk.EXPOSURE_MASK
863                         | gtk.gdk.BUTTON_PRESS_MASK
864                         | gtk.gdk.BUTTON_RELEASE_MASK
865                         | gtk.gdk.STRUCTURE_MASK)
866
867         # choose a font
868         fd = pango.FontDescription('sans 10')
869         fd.set_absolute_size(25 * pango.SCALE)
870         self.font = fd
871         met = self.get_pango_context().get_metrics(fd)
872         self.lineheight = (met.get_ascent() + met.get_descent()) / pango.SCALE
873         fd = pango.FontDescription('sans 5')
874         fd.set_absolute_size(15 * pango.SCALE)
875         self.smallfont = fd
876         self.selected = 0
877         self.top = 0
878         self.book = None
879
880         self.smslist = []
881
882         self.queue_draw()
883
884
885     def set_book(self, book):
886         self.book = book
887
888     def lines(self):
889         alloc = self.get_allocation()
890         lines = alloc.height / self.lineheight
891         return lines
892
893     def reset_list(self):
894         self.selected = 0
895         self.smslist = None
896         self.size_requested = 0
897         self.refresh()
898
899     def refresh(self):
900         self.need_redraw = True
901         self.queue_draw()
902
903     def assign_colour(self, purpose, name):
904         self.collist[purpose] = name
905
906     def reconfig(self, w, ev):
907         alloc = w.get_allocation()
908         if not self.pixbuf:
909             return
910         if alloc.width != self.width or alloc.height != self.height:
911             self.pixbuf = None
912             self.need_redraw = True
913
914     def add_col(self, sym, col):
915         c = gtk.gdk.color_parse(col)
916         gc = self.window.new_gc()
917         gc.set_foreground(self.get_colormap().alloc_color(c))
918         self.colours[sym] = gc
919
920     def redraw(self, w, ev):
921         if self.colours == None:
922             self.colours = {}
923             for p in self.collist:
924                 self.add_col(p, self.collist[p])
925             self.bg = self.get_style().bg_gc[gtk.STATE_NORMAL]
926
927         if self.need_redraw:
928             self.draw_buf()
929
930         self.window.draw_drawable(self.bg, self.pixbuf, 0, 0, 0, 0,
931                                          self.width, self.height)
932
933     def draw_buf(self):
934         self.need_redraw = False
935         if self.pixbuf == None:
936             alloc = self.get_allocation()
937             self.pixbuf = gtk.gdk.Pixmap(self.window, alloc.width, alloc.height)
938             self.width = alloc.width
939             self.height = alloc.height
940         self.pixbuf.draw_rectangle(self.bg, True, 0, 0,
941                                    self.width, self.height)
942
943         if self.top > self.selected:
944             self.top = 0
945         max = self.lines()
946         if self.smslist == None or \
947                (self.top + max > len(self.smslist) and self.size_requested < self.top + max):
948             self.size_requested = self.top + max
949             self.smslist = self.get_list(self.top + max)
950         for i in range(len(self.smslist)):
951             if i < self.top:
952                 continue
953             if i > self.top + max:
954                 break
955             if i == self.selected:
956                 col = self.colours['bg-selected']
957             else:
958                 col = self.colours['bg-%d'%(i%2)]
959
960             self.pixbuf.draw_rectangle(col,
961                                        True, 0, (i-self.top)*self.lineheight,
962                                        self.width, self.lineheight)
963             self.draw_sms(self.smslist[i], (i - self.top) * self.lineheight)
964
965
966     def draw_sms(self, sms, yoff):
967         
968         self.modify_font(self.smallfont)
969         tm = time.strftime("%Y-%m-%d %H:%M:%S", sms.time[0:6]+(0,0,0))
970         then = time.mktime(sms.time[0:6]+(0,0,-1))
971         now = time.time()
972         if now > then:
973             diff = now - then
974             if diff < 99:
975                 delta = "%02d sec ago" % diff
976             elif diff < 99*60:
977                 delta = "%02d min ago" % (diff/60)
978             elif diff < 48*60*60:
979                 delta = "%02dh%02dm ago" % ((diff/60/60), (diff/60)%60)
980             else:
981                 delta = tm[0:10]
982             tm = delta + tm[10:]
983
984         l = self.create_pango_layout(tm)
985         self.pixbuf.draw_layout(self.colours['time'],
986                                 0, yoff, l)
987         co = sms.correspondent
988         if self.book:
989             cor = book_name(self.book, co)
990             if cor:
991                 co = cor[0]
992         if sms.source == 'LOCAL':
993             col = self.colours['recipient']
994             co = 'To ' + co
995         else:
996             col = self.colours['sender']
997             co = 'From '+co
998         l = self.create_pango_layout(co)
999         self.pixbuf.draw_layout(col,
1000                                 0, yoff + self.lineheight/2, l)
1001         self.modify_font(self.font)
1002         t = sms.text.replace("\n", " ")
1003         t = t.replace("\n", " ")
1004         l = self.create_pango_layout(t)
1005         if sms.state in ['DRAFT', 'NEW']:
1006             col = self.colours['mesg-new']
1007         else:
1008             col = self.colours['mesg']
1009         self.pixbuf.draw_layout(col,
1010                                 180, yoff, l)
1011
1012     def press(self,w,ev):
1013         row = int(ev.y / self.lineheight)
1014         self.selected = self.top + row
1015         if self.selected >= len(self.smslist):
1016             self.selected = len(self.smslist) - 1
1017         if self.selected < 0:
1018             self.selected = 0
1019
1020         l = self.lines()
1021         self.top += row - l / 2
1022         if self.top >= len(self.smslist) - l:
1023             self.top = len(self.smslist) - l + 1
1024         if self.top < 0:
1025             self.top = 0
1026
1027         self.refresh()
1028         
1029     def release(self,w,ev):
1030         pass
1031
1032 def load_book(file):
1033     try:
1034         f = open(file)
1035     except:
1036         f = open('/home/neilb/home/mobile-numbers-jan-08')
1037     rv = []
1038     for l in f:
1039         x = l.split(';')
1040         rv.append([x[0],x[1]])
1041     rv.sort(lambda x,y: cmp(x[0],y[0]))
1042     return rv
1043
1044 def book_lookup(book, name, num):
1045     st=[]; mid=[]
1046     for l in book:
1047         if name.lower() == l[0][0:len(name)].lower():
1048             st.append(l)
1049         elif l[0].lower().find(name.lower()) >= 0:
1050             mid.append(l)
1051     st += mid
1052     if len(st) == 0:
1053         return [None, None]
1054     if num >= len(st):
1055         num = -1
1056     return st[num]
1057
1058 def book_parse(book, name):
1059     if not book:
1060         return None
1061     cnt = 0
1062     while len(name) and name[-1] == '.':
1063         cnt += 1
1064         name = name[0:-1]
1065     return book_lookup(book, name, cnt)
1066     
1067
1068
1069 def book_name(book, num):
1070     if len(num) < 8:
1071         return None
1072     for ad in book:
1073         if len(ad[1]) >= 8 and num[-8:] == ad[1][-8:]:
1074             return ad
1075     return None
1076
1077 def book_speed(book, sym):
1078     i = book_lookup(book, sym, 0)
1079     if i[0] == None or i[0] != sym:
1080         return None
1081     j = book_lookup(book, i[1], 0)
1082     if j[0] == None:
1083         return (i[1], i[0])
1084     return (j[1], j[0])
1085
1086 def name_lookup(book, str):
1087     # We need to report
1088     #  - a number - to dial
1089     #  - optionally a name that is associated with that number
1090     #  - optionally a new name to save the number as
1091     # The name is normally alpha, but can be a single digit for
1092     # speed-dial
1093     # Dots following a name allow us to stop through multiple matches.
1094     # So input can be:
1095     # A single symbol.
1096     #         This is a speed dial.  It maps to name, then number
1097     # A string of >1 digits
1098     #         This is a literal number, we look up name if we can
1099     # A string of dots
1100     #         This is a look up against recent incoming calls
1101     #         We look up name in phone book
1102     # A string starting with alpha, possibly ending with dots
1103     #         This is a regular lookup in the phone book
1104     # A number followed by a string
1105     #         This provides the string as a new name for saving
1106     # A string of dots followed by a string
1107     #         This also provides the string as a newname
1108     # An alpha string, with dots, followed by '+'then a single symbol
1109     #         This saves the match as a speed dial
1110     #
1111     # We return a triple of (number,oldname,newname)
1112     if re.match('^[A-Za-z0-9]$', str):
1113         # Speed dial lookup
1114         s = book_speed(book, str)
1115         if s:
1116             return (s[0], s[1], None)
1117         return None
1118     m = re.match('^(\+?\d+)([A-Za-z][A-Za-z0-9 ]*)?$', str)
1119     if m:
1120         # Number and possible newname
1121         s = book_name(book, m.group(1))
1122         if s:
1123             return (m.group(1), s[0], m.group(2))
1124         else:
1125             return (m.group(1), None, m.group(2))
1126     m = re.match('^([A-Za-z][A-Za-z0-9 ]*)(\.*)(\+[A-Za-z0-9])?$', str)
1127     if m:
1128         # name and dots
1129         speed = None
1130         if m.group(3):
1131             speed = m.group(3)[1]
1132         i = book_lookup(book, m.group(1), len(m.group(2)))
1133         if i[0]:
1134             return (i[1], i[0], speed)
1135         return None
1136
1137 class SendSMS(gtk.Window):
1138     def __init__(self, store):
1139         gtk.Window.__init__(self)
1140         self.set_default_size(480,640)
1141         self.set_title("SendSMS")
1142         self.store = store
1143         self.connect('destroy', self.close_win)
1144         
1145         self.selecting = False
1146         self.viewing = False
1147         self.book = None
1148         self.create_ui()
1149
1150         self.show()
1151         self.reload_book = True
1152         self.number = None
1153         self.cutbuffer = None
1154
1155         d = dnotify.dir(store.dirname)
1156         self.watcher = d.watch('newmesg', lambda f : self.got_new())
1157
1158         self.watch_clip('sms-new')
1159
1160         self.connect('property-notify-event', self.newprop)
1161         self.add_events(gtk.gdk.PROPERTY_CHANGE_MASK)
1162     def newprop(self, w, ev):
1163         if ev.atom == '_INPUT_TEXT':
1164             str = self.window.property_get('_INPUT_TEXT')
1165             self.numentry.set_text(str[2])
1166
1167     def close_win(self, *a):
1168         # FIXME save draft
1169         gtk.main_quit()
1170
1171     def create_ui(self):
1172
1173         fd = pango.FontDescription("sans 10")
1174         fd.set_absolute_size(25*pango.SCALE)
1175         self.button_font = fd
1176         v = gtk.VBox() ;v.show() ; self.add(v)
1177
1178         self.sender = self.send_ui()
1179         v.add(self.sender)
1180         self.sender.hide()
1181
1182         self.listing = self.list_ui()
1183         v.add(self.listing)
1184         self.listing.show()
1185
1186         self.book = load_book("/data/address-book")
1187         self.listview.set_book(self.book)
1188
1189         self.rotate_list(self, target='All')
1190
1191     def send_ui(self):
1192         v = gtk.VBox()
1193         main = v
1194
1195         h = gtk.HBox()
1196         h.show()
1197         v.pack_start(h, expand=False)
1198         l = gtk.Label('To:')
1199         l.modify_font(self.button_font)
1200         l.show()
1201         h.pack_start(l, expand=False)
1202         
1203         self.numentry = FingerEntry()
1204         h.pack_start(self.numentry)
1205         self.numentry.modify_font(self.button_font)
1206         self.numentry.show()
1207         self.numentry.connect('changed', self.update_to);
1208
1209         h = gtk.HBox()
1210         l = gtk.Label('')
1211         l.modify_font(self.button_font)
1212         l.show()
1213         h.pack_start(l)
1214         h.show()
1215         v.pack_start(h, expand=False)
1216         h = gtk.HBox()
1217         self.to_label = l
1218         l = gtk.Label('0 chars')
1219         l.modify_font(self.button_font)
1220         l.show()
1221         self.cnt_label = l
1222         h.pack_end(l)
1223         h.show()
1224         v.pack_start(h, expand=False)
1225
1226         h = gtk.HBox()
1227         h.set_size_request(-1,80)
1228         h.set_homogeneous(True)
1229         h.show()
1230         v.pack_start(h, expand=False)
1231
1232         self.add_button(h, 'select', self.select)
1233         self.add_button(h, 'clear', self.clear)
1234         self.add_button(h, 'paste', self.paste)
1235
1236         self.message = FingerText()
1237         self.message.show()
1238         self.message.modify_font(self.button_font)
1239         sw = gtk.ScrolledWindow() ; sw.show()
1240         sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1241         #v.add(self.message)
1242         v.add(sw)
1243         sw.add(self.message)
1244         self.message.get_buffer().connect('changed', self.buff_changed)
1245
1246         h = gtk.HBox()
1247         h.set_size_request(-1,80)
1248         h.set_homogeneous(True)
1249         h.show()
1250         v.pack_end(h, expand=False)
1251
1252         self.add_button(h, 'Send GSM', self.send, 'GSM')
1253         self.draft_button = self.add_button(h, 'Draft', self.draft)
1254         self.add_button(h, 'Send EXE', self.send, 'EXE')
1255
1256         return main
1257
1258     def list_ui(self):
1259         v = gtk.VBox() ; main = v
1260
1261         h = gtk.HBox() ; h.show()
1262         h.set_size_request(-1,80)
1263         h.set_homogeneous(True)
1264         v.pack_start(h, expand = False)
1265         self.add_button(h, 'Del', self.delete)
1266         self.view_button = self.add_button(h, 'View', self.view)
1267         self.reply = self.add_button(h, 'New', self.open)
1268
1269         h = gtk.HBox() ; h.show()
1270         v.pack_start(h, expand=False)
1271         b = gtk.Button("clr") ; b.show()
1272         b.connect('clicked', self.clear_search)
1273         h.pack_end(b, expand=False)
1274         l = gtk.Label('search:') ; l.show()
1275         h.pack_start(l, expand=False)
1276         
1277         e = gtk.Entry() ; e.show()
1278         self.search_entry = e
1279         h.pack_start(e)
1280
1281         self.listview = SMSlist(self.load_list)
1282         self.listview.show()
1283         self.listview.assign_colour('time', 'blue')
1284         self.listview.assign_colour('sender', 'red')
1285         self.listview.assign_colour('recipient', 'black')
1286         self.listview.assign_colour('mesg', 'black')
1287         self.listview.assign_colour('mesg-new', 'red')
1288         self.listview.assign_colour('bg-0', 'yellow')
1289         self.listview.assign_colour('bg-1', 'pink')
1290         self.listview.assign_colour('bg-selected', 'white')
1291         
1292         self.listbox = gtk.VBox()
1293         self.listbox.add(self.listview)
1294         v.add(self.listbox)
1295         self.listbox.show()
1296
1297
1298         bb = gtk.HBox() ; bb.show()
1299         bb.set_size_request(-1,80)
1300         bb.set_homogeneous(True)
1301         self.listbox.pack_end(bb, expand=False)
1302         self.buttonA = self.add_button(bb, 'Sent', self.rotate_list, 'A')
1303         self.buttonB = self.add_button(bb, 'Recv', self.rotate_list, 'B')
1304
1305
1306         self.last_response = gtk.Label('')
1307         self.listbox.pack_end(self.last_response, expand = False)
1308
1309         self.singleview = gtk.TextView()
1310         self.singleview.modify_font(self.button_font)
1311         self.singleview.show()
1312         self.singleview.set_wrap_mode(gtk.WRAP_WORD_CHAR)
1313         sw = gtk.ScrolledWindow()
1314         sw.add(self.singleview)
1315         sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1316         sw.show()
1317         self.singlescroll = sw
1318
1319         self.singlebox = gtk.VBox()
1320         self.singlebox.add(sw)
1321         self.singlebox.hide()
1322         v.add(self.singlebox)
1323
1324         bb = gtk.HBox() ; bb.show()
1325         bb.set_size_request(-1,80)
1326         bb.set_homogeneous(True)
1327         self.singlebox.pack_end(bb, expand=False)
1328         self.add_button(bb, 'Call', self.docall)
1329         self.add_button(bb, 'Contacts', self.contacts)
1330
1331         main.show()
1332         return main
1333
1334     def add_button(self, parent, label, func, *args):
1335         b = gtk.Button(label)
1336         b.child.modify_font(self.button_font)
1337         b.connect('clicked', func, *args)
1338         b.set_property('can-focus', False)
1339         parent.add(b)
1340         b.show()
1341         return b
1342
1343     def update_to(self, w):
1344         n = w.get_text()
1345         if n == '':
1346             self.reload_book = True
1347             self.to_label.set_text('')
1348         else:
1349             if self.reload_book:
1350                 self.reload_book = False
1351                 self.book = load_book("/data/address-book")
1352                 self.listview.set_book(self.book)
1353             e = name_lookup(self.book, n)
1354             if e and e[1]:
1355                 self.to_label.set_text(e[1] + 
1356                                        ' '+
1357                                        e[0])
1358                 self.number = e[0]
1359             else:
1360                 self.to_label.set_text('??')
1361                 self.number = n
1362         self.buff_changed(None)
1363
1364     def buff_changed(self, w):
1365         if self.numentry.get_text() == '' and self.message.get_buffer().get_property('text') == '':
1366             self.draft_button.child.set_text('Cancel')
1367         else:
1368             self.draft_button.child.set_text('SaveDraft')
1369         l = len(self.message.get_buffer().get_property('text'))
1370         if l <= 160:
1371             m = 1
1372         else:
1373             m = (l+152)/153
1374         self.cnt_label.set_text('%d chars / %d msgs' % (l, m))
1375
1376     def select(self, w, *a):
1377         if not self.selecting:
1378             self.message.input.active = False
1379             w.child.set_text('Cut')
1380             self.selecting = True
1381         else:
1382             self.message.input.active = True
1383             w.child.set_text('Select')
1384             self.selecting = False
1385             b = self.message.get_buffer()
1386             bound = b.get_selection_bounds()
1387             if bound:
1388                 (s,e) = bound
1389                 t = b.get_text(s,e)
1390                 self.cutbuffer = t
1391                 b.delete_selection(True, True)
1392
1393     def clear(self, *a):
1394         w = self.get_toplevel().get_focus()
1395         if w == None:
1396             w = self.message
1397         if w == self.message:
1398             self.cutbuffer = self.message.get_buffer().get_property('text')
1399             b = self.message.get_buffer()
1400             b.set_text('')
1401         else:
1402             self.cutbuffer = w.get_text()
1403             w.set_text('')
1404             
1405     def paste(self, *a):
1406         w = self.get_toplevel().get_focus()
1407         if w == None:
1408             w = self.message
1409         if self.cutbuffer:
1410             w.insert_at_cursor(self.cutbuffer)
1411         pass
1412
1413     def watch_clip(self, board):
1414         self.cb = gtk.Clipboard(selection=board)
1415         self.targets = [ (gtk.gdk.SELECTION_TYPE_STRING, 0, 0) ]
1416         self.cb.set_with_data(self.targets, self.get, self.got_clip, None)
1417
1418     def got_clip(self, clipb, data):
1419         a = clipb.wait_for_text()
1420         print "sms got clip", a
1421         self.numentry.set_text(a)
1422         self.listing.hide()
1423         self.sender.show()
1424         self.cb.set_with_data(self.targets, self.get, self.got_clip, None)
1425         self.present()
1426
1427     def get(self, sel, info, data):
1428         sel.set_text("Number Please")
1429
1430     def send(self, w, style):
1431         sender = '0403463349'
1432         recipient = self.number
1433         mesg = self.message.get_buffer().get_property('text')
1434         if not mesg or not recipient:
1435             return
1436         try:
1437             if style == 'EXE':
1438                 p = Popen(['exesms', sender, recipient, mesg], stdout = PIPE)
1439             else:
1440                 p = Popen(['gsm-sms', sender, recipient, mesg], stdout = PIPE)
1441         except:
1442             rv = 1
1443             line = 'Fork Failed'
1444         else:
1445             line = 'no response'
1446             rv = p.wait()
1447             for l in p.stdout:
1448                 if l:
1449                     line = l
1450         
1451         s = SMSmesg(to = recipient, text = mesg)
1452
1453         if rv or line[0:2] != 'OK':
1454             s.state = 'DRAFT'
1455             target = 'Draft'
1456         else:
1457             target = 'All'
1458         self.store.store(s)
1459         self.last_response.set_text('Mess Send: '+ line.strip())
1460         self.last_response.show()
1461
1462         self.sender.hide()
1463         self.listing.show()
1464         self.rotate_list(target=target)
1465
1466     def draft(self, *a):
1467         sender = '0403463349'
1468         recipient = self.numentry.get_text()
1469         if recipient:
1470             rl = [recipient]
1471         else:
1472             rl = []
1473         mesg = self.message.get_buffer().get_property('text')
1474         if mesg:
1475             s = SMSmesg(to = recipient, text = mesg, state = 'DRAFT')
1476             self.store.store(s)
1477         self.sender.hide()
1478         self.listing.show()
1479         self.rotate_list(target='Draft')
1480     def config(self, *a):
1481         pass
1482     def delete(self, *a):
1483         if len(self.listview.smslist ) < 1:
1484             return
1485         s = self.listview.smslist[self.listview.selected]
1486         self.store.delete(s)
1487         sel = self.listview.selected
1488         self.rotate_list(target=self.display_list)
1489         self.listview.selected = sel
1490         if self.viewing:
1491             self.view(self.view_button)
1492
1493     def view(self, w, *a):
1494         if self.viewing:
1495             w.child.set_text('View')
1496             self.viewing = False
1497             self.singlebox.hide()
1498             self.listbox.show()
1499             if self.listview.smslist and  len(self.listview.smslist ) >= 1:
1500                 s = self.listview.smslist[self.listview.selected]
1501                 if s.state == 'NEW':
1502                     self.store.setstate(s, None)
1503                     if self.display_list == 'New':
1504                         self.rotate_list(target='New')
1505             self.reply.child.set_text('New')
1506         else:
1507             if not self.listview.smslist or len(self.listview.smslist ) < 1:
1508                 return
1509             s = self.listview.smslist[self.listview.selected]
1510             w.child.set_text('List')
1511             self.viewing = True
1512             self.last_response.hide()
1513             self.listbox.hide()
1514             if self.book:
1515                 n = book_name(self.book, s.correspondent)
1516                 if n and n[0]:
1517                     n = n[0] + ' ['+s.correspondent+']'
1518                 else:
1519                     n = s.correspondent
1520             else:
1521                 n = s.correspondent
1522             if s.source == 'LOCAL':
1523                 t = 'To: ' + n + '\n'
1524             else:
1525                 t = 'From: %s (%s)\n' % (n, s.source)
1526             tm = time.strftime('%d%b%Y %H:%M:%S', s.time[0:6]+(0,0,0))
1527             t += 'Time: ' + tm + '\n'
1528             t += '\n'
1529             t += s.text
1530             self.singleview.get_buffer().set_text(t)
1531             self.singlebox.show()
1532
1533             if s.source == 'LOCAL':
1534                 self.reply.child.set_text('Open')
1535             else:
1536                 self.reply.child.set_text('Reply')
1537             
1538     def open(self, *a):
1539         if self.viewing:
1540             if len(self.listview.smslist) < 1:
1541                 return
1542             s = self.listview.smslist[self.listview.selected]
1543             if s.state == 'NEW':
1544                 self.store.setstate(s, None)
1545         
1546             self.numentry.set_text(s.correspondent)
1547             self.message.get_buffer().set_text(s.text)
1548             self.draft_button.child.set_text('SaveDraft')
1549         else:
1550             self.numentry.set_text('')
1551             self.message.get_buffer().set_text('')
1552             self.draft_button.child.set_text('Cancel')
1553         self.listing.hide()
1554         self.sender.show()
1555
1556     def load_list(self, lines):
1557         now = time.time()
1558         l = []
1559         target = self.display_list
1560         patn = self.search_entry.get_text()
1561         #print 'pattern is', patn
1562         if target == 'New':
1563             (now, l) = self.store.lookup(now, 'NEW')
1564         elif target == 'Draft':
1565             (now, l) = self.store.lookup(now, 'DRAFT')
1566         else:
1567             if lines == 0: lines = 20
1568             while now and len(l) < lines:
1569                 (now, l2) = self.store.lookup(now)
1570                 for e in l2:
1571                     if patn and patn not in e.correspondent:
1572                         continue
1573                     if target == 'All':
1574                         l.append(e)
1575                     elif target == 'Sent' and e.source == 'LOCAL':
1576                         l.append(e)
1577                     elif target == 'Recv' and e.source != 'LOCAL':
1578                         l.append(e)
1579         return l
1580         
1581     def rotate_list(self, w=None, ev=None, which = None, target=None):
1582         # lists are:
1583         #   All, Recv, New, Sent, Draft
1584         # When one is current, two others can be selected
1585
1586         if target == None:
1587             if w == None:
1588                 target = self.display_list
1589             else:
1590                 target = w.child.get_text()
1591
1592         if target == 'All':
1593             self.buttonA.child.set_text('Sent')
1594             self.buttonB.child.set_text('Recv')
1595         if target == 'Sent':
1596             self.buttonA.child.set_text('All')
1597             self.buttonB.child.set_text('Draft')
1598         if target == 'Draft':
1599             self.buttonA.child.set_text('All')
1600             self.buttonB.child.set_text('Sent')
1601         if target == 'Recv':
1602             self.buttonA.child.set_text('All')
1603             self.buttonB.child.set_text('New')
1604         if target == 'New':
1605             self.buttonA.child.set_text('All')
1606             self.buttonB.child.set_text('Recv')
1607                 
1608         self.display_list = target
1609         self.listview.reset_list()
1610
1611     def clear_search(self, *a):
1612         pass
1613
1614     def got_new(self):
1615         self.rotate_list(self, target = 'New')
1616
1617     def get_contact(self):
1618         if not self.listview.smslist or len(self.listview.smslist ) < 1:
1619             return None
1620         s = self.listview.smslist[self.listview.selected]
1621         return s.correspondent
1622
1623     def docall(self, b):
1624         send_number('voice-dial', self.get_contact())
1625
1626     def contacts(self, b):
1627         send_number('contact-find', self.get_contact())
1628
1629 sel_number = None
1630 clips = {}
1631 def clip_get_data(clip, sel, info, data):
1632     global sel_number
1633     print 'get clip data', sel_number
1634     if sel_number:
1635         sel.set_text(sel_number)
1636 def clip_clear_data(clip, data):
1637     global sel_number
1638     print 'clear clip data', clip.wait_for_text()
1639     sel_number = None
1640
1641 def send_number(sel, num):
1642     global sel_number, clips
1643     if not num:
1644         return
1645     sel_number = num
1646     if sel not in clips:
1647         clips[sel] = gtk.Clipboard(selection = sel)
1648     c = clips[sel]
1649     c.set_with_data([(gtk.gdk.SELECTION_TYPE_STRING, 0, 0)],
1650                     clip_get_data, clip_clear_data, None)
1651
1652
1653
1654 def main(args):
1655     for p in ['/data','/media/card','/var/tmp']:
1656         if os.path.exists(p):
1657             pth = p
1658             break
1659     w = SendSMS(SMSstore(pth+'/SMS'))
1660     gtk.settings_get_default().set_long_property("gtk-cursor-blink", 0, "main")
1661
1662     gtk.main()
1663
1664 if __name__ == '__main__':
1665     main(sys.argv)