4 # scribble - scribble pad designed for Neo Freerunner
6 # Copyright (C) 2008 Neil Brown <neil@brown.name>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # The GNU General Public License, version 2, is available at
20 # http://www.fsf.org/licensing/licenses/info/GPLv2.html
21 # Or you can write to the Free Software Foundation, Inc., 51
22 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 # Email: <neil@brown.name>
34 ###########################################################
35 # Writing recognistion code
41 # Where they are like lowercase, we either double
42 # the last stroke (L, J, I) or draw backwards (S, Z, X)
43 # U V are a special case
45 dict.add('A', "R(4)6,8")
46 dict.add('B', "R(4)6,4.R(7)1,6")
47 dict.add('B', "R(4)6,4.L(4)2,8.R(7)1,6")
48 dict.add('B', "S(6)7,1.R(4)6,4.R(7)0,6")
49 dict.add('C', "R(4)8,2")
50 dict.add('D', "R(4)6,6")
51 dict.add('E', "L(1)2,8.L(7)2,8")
52 # double the stem for F
53 dict.add('F', "L(4)2,6.S(3)7,1")
54 dict.add('F', "S(1)5,3.S(3)1,7.S(3)7,1")
56 dict.add('G', "L(4)2,5.S(8)1,7")
57 dict.add('G', "L(4)2,5.R(8)6,8")
58 # FIXME I need better straight-curve alignment
59 dict.add('H', "S(3)1,7.R(7)6,8.S(5)7,1")
60 dict.add('H', "L(3)0,5.R(7)6,8.S(5)7,1")
61 # capital I is down/up
62 dict.add('I', "S(4)1,7.S(4)7,1")
64 # Capital J has a left/right tail
65 dict.add('J', "R(4)1,6.S(7)3,5")
67 dict.add('K', "L(4)0,2.R(4)6,6.L(4)2,8")
69 # Capital L, like J, doubles the foot
70 dict.add('L', "L(4)0,8.S(7)4,3")
72 dict.add('M', "R(3)6,5.R(5)3,8")
73 dict.add('M', "R(3)6,5.L(1)0,2.R(5)3,8")
75 dict.add('N', "R(3)6,8.L(5)0,2")
77 # Capital O is CW, but can be CCW in special dict
78 dict.add('O', "R(4)1,1", bot='0')
80 dict.add('P', "R(4)6,3")
81 dict.add('Q', "R(4)7,7.S(8)0,8")
83 dict.add('R', "R(4)6,4.S(8)0,8")
85 # S is drawn bottom to top.
86 dict.add('S', "L(7)6,1.R(1)7,2")
88 # Double the stem for capital T
89 dict.add('T', "R(4)0,8.S(5)7,1")
91 # U is L to R, V is R to L for now
92 dict.add('U', "L(4)0,2")
93 dict.add('V', "R(4)2,0")
95 dict.add('W', "R(5)2,3.L(7)8,6.R(3)5,0")
96 dict.add('W', "R(5)2,3.R(3)5,0")
98 dict.add('X', "R(4)6,0")
100 dict.add('Y',"L(1)0,2.R(5)4,6.S(5)6,2")
101 dict.add('Y',"L(1)0,2.S(5)2,7.S(5)7,2")
103 dict.add('Z', "R(4)8,2.L(4)6,0")
106 dict.add('a', "L(4)2,2.L(5)1,7")
107 dict.add('a', "L(4)2,2.L(5)0,8")
108 dict.add('a', "L(4)2,2.S(5)0,8")
109 dict.add('b', "S(3)1,7.R(7)6,3")
110 dict.add('c', "L(4)2,8", top='C')
111 dict.add('d', "L(4)5,2.S(5)1,7")
112 dict.add('d', "L(4)5,2.L(5)0,8")
113 dict.add('e', "S(4)3,5.L(4)5,8")
114 dict.add('e', "L(4)3,8")
115 dict.add('f', "L(4)2,6", top='F')
116 dict.add('f', "S(1)5,3.S(3)1,7", top='F')
117 dict.add('g', "L(1)2,2.R(4)1,6")
118 dict.add('h', "S(3)1,7.R(7)6,8")
119 dict.add('h', "L(3)0,5.R(7)6,8")
120 dict.add('i', "S(4)1,7", top='I', bot='1')
121 dict.add('j', "R(4)1,6", top='J')
122 dict.add('k', "L(3)0,5.L(7)2,8")
123 dict.add('k', "L(4)0,5.R(7)6,6.L(7)1,8")
124 dict.add('l', "L(4)0,8", top='L')
125 dict.add('l', "S(3)1,7.S(7)3,5", top='L')
126 dict.add('m', "S(3)1,7.R(3)6,8.R(5)6,8")
127 dict.add('m', "L(3)0,2.R(3)6,8.R(5)6,8")
128 dict.add('n', "S(3)1,7.R(4)6,8")
129 dict.add('o', "L(4)1,1", top='O', bot='0')
130 dict.add('p', "S(3)1,7.R(4)6,3")
131 dict.add('q', "L(1)2,2.L(5)1,5")
132 dict.add('q', "L(1)2,2.S(5)1,7.R(8)6,2")
133 dict.add('q', "L(1)2,2.S(5)1,7.S(5)1,7")
134 # FIXME this double 1,7 is due to a gentle where the
135 # second looks like a line because it is narrow.??
136 dict.add('r', "S(3)1,7.R(4)6,2")
137 dict.add('s', "L(1)2,7.R(7)1,6", top='S', bot='5')
138 dict.add('t', "R(4)0,8", top='T', bot='7')
139 dict.add('t', "S(1)3,5.S(5)1,7", top='T', bot='7')
140 dict.add('u', "L(4)0,2.S(5)1,7")
141 dict.add('v', "L(4)0,2.L(2)0,2")
142 dict.add('w', "L(3)0,2.L(5)0,2", top='W')
143 dict.add('w', "L(3)0,5.R(7)6,8.L(5)3,2", top='W')
144 dict.add('w', "L(3)0,5.L(5)3,2", top='W')
145 dict.add('x', "L(4)0,6", top='X')
146 dict.add('y', "L(1)0,2.R(5)4,6", top='Y') # if curved
147 dict.add('y', "L(1)0,2.S(5)2,7", top='Y')
148 dict.add('z', "R(4)0,6.L(4)2,8", top='Z', bot='2')
151 dict.add('0', "L(4)7,7")
152 dict.add('0', "R(4)7,7")
153 dict.add('1', "S(4)7,1")
154 dict.add('2', "R(4)0,6.S(7)3,5")
155 dict.add('2', "R(4)3,6.L(4)2,8")
156 dict.add('3', "R(1)0,6.R(7)1,6")
157 dict.add('4', "L(4)7,5")
158 dict.add('5', "L(1)2,6.R(7)0,3")
159 dict.add('5', "L(1)2,6.L(4)0,8.R(7)0,3")
160 dict.add('6', "L(4)2,3")
161 dict.add('7', "S(1)3,5.R(4)1,6")
162 dict.add('7', "R(4)0,6")
163 dict.add('7', "R(4)0,7")
164 dict.add('8', "L(4)2,8.R(4)4,2.L(3)6,1")
165 dict.add('8', "L(1)2,8.R(7)2,0.L(1)6,1")
166 dict.add('8', "L(0)2,6.R(7)0,1.L(2)6,0")
167 dict.add('8', "R(4)2,6.L(4)4,2.R(5)8,1")
168 dict.add('9', "L(1)2,2.S(5)1,7")
170 dict.add(' ', "S(4)3,5")
171 dict.add('<BS>', "S(4)5,3")
172 dict.add('-', "S(4)3,5.S(4)5,3")
173 dict.add('_', "S(4)3,5.S(4)5,3.S(4)3,5")
174 dict.add("<left>", "S(4)5,3.S(3)3,5")
175 dict.add("<right>","S(4)3,5.S(5)5,3")
176 dict.add("<newline>", "S(4)2,6")
180 # Each segment has for elements:
181 # direction: Right Straight Left (R=cw, L=ccw)
185 # Segments match if there difference at each element
186 # is 0, 1, or 3 (RSL coded as 012)
187 # A difference of 1 required both to be same / 3
188 # On a match, return number of 0s
189 # On non-match, return -1
190 def __init__(self, str):
196 if str[1] != '(' or str[3] != ')' or str[5] != ',':
207 self.e[1] = int(str[2])
208 self.e[2] = int(str[4])
209 self.e[3] = int(str[6])
211 def match(self, other):
214 diff = abs(self.e[i] - other.e[i])
219 elif diff == 1 and (self.e[i]/3 == other.e[i]/3):
226 # A Dict Pattern is a list of segments.
227 # A parsed pattern matches a dict pattern if
228 # the are the same nubmer of segments and they
229 # all match. The value of the match is the sum
230 # of the individual matches.
231 # A DictPattern is printers as segments joined by periods.
233 def __init__(self, str):
234 self.segs = map(DictSegment, str.split("."))
235 def match(self,other):
236 if len(self.segs) != len(other.segs):
239 for i in range(0,len(self.segs)):
240 m = self.segs[i].match(other.segs[i])
248 # The dictionary hold all the pattern for symbols and
250 # Each pattern in the directionary can be associated
251 # with 3 symbols. One when drawing in middle of screen,
252 # one for top of screen, one for bottom.
253 # Often these will all be the same.
254 # This allows e.g. s and S to have the same pattern in different
255 # location on the touchscreen.
256 # A match requires a unique entry with a match that is better
257 # than any other entry.
261 def add(self, sym, pat, top = None, bot = None):
262 if top == None: top = sym
263 if bot == None: bot = sym
264 self.dict.append((DictPattern(pat), sym, top, bot))
269 for (ptn, sym, top, bot) in self.dict:
273 val = (sym, top, bot)
278 def match(self, str, pos = "mid"):
283 (mid, top, bot) = self._match(p)
284 if pos == "top": return top
285 if pos == "bot": return bot
290 # This represents a point in the path and all the points leading
291 # up to it. It allows us to find the direction and curvature from
292 # one point to another
293 # We store x,y, and sum/cnt of points so far
294 def __init__(self,x,y) :
311 if self.x == x and self.y == y:
320 return abs(self.x - p.x)
322 return abs(self.y - p.y)
341 if self.cnt == p.cnt:
344 (x2,y2) = self.meanpoint(p)
345 x3 = self.x; y3 = self.y
347 curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
348 curve = curve * 100 / ((y3-y1)*(y3-y1)
357 if self.cnt == p.cnt:
360 (x2,y2) = self.meanpoint(p)
361 x3 = self.x; y3 = self.y
363 curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
364 curve = curve * 100 / ((y3-y1)*(y3-y1)
368 def meanpoint(self,p):
369 x = (self.xsum - p.xsum) / (self.cnt - p.cnt)
370 y = (self.ysum - p.ysum) / (self.cnt - p.cnt)
373 def is_sharp(self,A,C):
374 # Measure the cosine at self between A and C
375 # as A and C could be curve, we take the mean point on
376 # self.A and self.C as the points to find cosine between
377 (ax,ay) = self.meanpoint(A)
378 (cx,cy) = self.meanpoint(C)
379 a = ax-self.x; b=ay-self.y
380 c = cx-self.x; d=cy-self.y
383 h = math.sqrt(x*x+y*y)
391 # a BBox records min/max x/y of some Points and
392 # can subsequently report row, column, pos of each point
393 # can also locate one bbox in another
395 def __init__(self, p):
402 return self.maxx - self.minx
404 return self.maxy - self.miny
416 def finish(self, div = 3):
417 # if aspect ratio is bad, we adjust max/min accordingly
418 # before setting [xy][12]. We don't change self.min/max
419 # as they are used to place stroke in bigger bbox.
420 # Normally divisions are at 1/3 and 2/3. They can be moved
421 # by setting div e.g. 2 = 1/2 and 1/2
422 (minx,miny,maxx,maxy) = (self.minx,self.miny,self.maxx,self.maxy)
423 if (maxx - minx) * 3 < (maxy - miny) * 2:
425 mid = int((maxx + minx)/2)
426 halfwidth = int ((maxy - miny)/3)
427 minx = mid - halfwidth
428 maxx = mid + halfwidth
429 if (maxy - miny) * 3 < (maxx - minx) * 2:
431 mid = int((maxy + miny)/2)
432 halfheight = int ((maxx - minx)/3)
433 miny = mid - halfheight
434 maxy = mid + halfheight
437 self.x1 = int((div1*minx + maxx)/div)
438 self.x2 = int((minx + div1*maxx)/div)
439 self.y1 = int((div1*miny + maxy)/div)
440 self.y2 = int((miny + div1*maxy)/div)
443 # 0, 1, 2 - top to bottom
457 return self.row(p) * 3 + self.col(p)
460 # b is a box within self. find location 0-8
461 if b.maxx < self.x2 and b.minx < self.x1:
463 elif b.minx > self.x1 and b.maxx > self.x2:
467 if b.maxy < self.y2 and b.miny < self.y1:
469 elif b.miny > self.y1 and b.maxy > self.y2:
476 def different(*args):
479 if cur != 0 and i != 0 and cur != i:
492 # a PPath refines a list of x,y points into a list of Points
493 # The Points mark out segments which end at significant Points
494 # such as inflections and reversals.
496 def __init__(self, x,y):
498 self.start = Point(x,y)
499 self.mid = Point(x,y)
500 self.curr = Point(x,y)
501 self.list = [ self.start ]
506 if ( (abs(self.mid.xdir(self.start) - self.curr.xdir(self.mid)) == 2) or
507 (abs(self.mid.ydir(self.start) - self.curr.ydir(self.mid)) == 2) or
508 (abs(self.curr.Vcurve(self.start))+2 < abs(self.mid.Vcurve(self.start)))):
511 self.mid = self.curr.copy()
513 if self.curr.xlen(self.mid) > 4 or self.curr.ylen(self.mid) > 4:
514 self.start = self.mid.copy()
515 self.list.append(self.start)
516 self.mid = self.curr.copy()
519 self.list.append(self.curr)
521 def get_sectlist(self):
522 if len(self.list) <= 2:
523 return [[0,self.list]]
528 curcurve = B.curve(A)
529 for C in self.list[2:]:
533 if B.is_sharp(A,C) and not different(cabc, cab, cbc, curcurve):
534 # B is too pointy, must break here
535 l.append([curcurve, s])
538 elif not different(cabc, cab, cbc, curcurve):
542 curcurve = maxcurve(cab, cbc, cabc)
543 elif not different(cabc, cab, cbc) :
544 # gentle inflection along AB
545 # was: AB goes in old and new section
546 # now: AB only in old section, but curcurve
548 l.append([curcurve,s])
550 curcurve =maxcurve(cab, cbc, cabc)
552 # Change of direction at B
553 l.append([curcurve,s])
559 l.append([curcurve,s])
563 def remove_shorts(self, bbox):
564 # in self.list, if a point is close to the previous point,
566 if len(self.list) <= 2:
572 for p in self.list[1:]:
579 # OK, we have a list of points with curvature between.
580 # want to divide this into sections.
581 # for each 3 consectutive points ABC curve of ABC and AB and BC
582 # If all the same, they are all in a section.
583 # If not B starts a new section and the old ends on B or C...
584 BB = BBox(self.list[0])
589 self.remove_shorts(BB)
590 sectlist = self.get_sectlist()
592 for c, s in sectlist:
594 dr = "R" # clockwise is to the Right
596 dr = "L" # counterclockwise to the Left
603 # If all points are in some row or column, then
605 rwdiff = False; cldiff = False
606 rw = bb.row(s[0]); cl=bb.col(s[0])
608 if bb.row(p) != rw: rwdiff = True
609 if bb.col(p) != cl: cldiff = True
610 if not rwdiff or not cldiff: dr = "S"
613 t1 += "(%d)" % BB.relpos(bb)
614 t1 += "%d,%d" % (bb.box(s[0]), bb.box(s[-1]))
629 while l > 0 and a[l-1] >= '0' and a[l-1] <= '9':
631 # a[l:] is the last number
636 return a[0:l] + ("%d" % (num+1))
641 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
642 window.connect("destroy", self.close_application)
643 window.set_title("ScribblePad")
644 #window.set_size_request(480,640)
651 bar.set_size_request(-1, 40)
652 vb.pack_start(bar, expand=False)
655 page = gtk.DrawingArea()
656 page.set_size_request(480,540)
659 ctx = page.get_pango_context()
660 fd = ctx.get_font_description()
661 fd.set_absolute_size(25*pango.SCALE)
664 dflt = gtk.widget_get_default_style()
666 fd.set_absolute_size(25*pango.SCALE)
669 # < > R u r A D C name
670 #back = gtk.Button(stock = gtk.STOCK_GO_BACK) ; back.show()
671 #fore = gtk.Button(stock = gtk.STOCK_GO_FORWARD) ; fore.show()
672 #red = gtk.ToggleButton("red"); red.show()
673 #undo = gtk.Button(stock = gtk.STOCK_UNDO) ; undo.show()
674 #redo = gtk.Button(stock = gtk.STOCK_REDO) ; redo.show()
675 #add = gtk.Button(stock = gtk.STOCK_ADD) ; add.show()
676 #delete = gtk.Button(stock = gtk.STOCK_REMOVE) ; delete.show()
677 #clear = gtk.Button(stock = gtk.STOCK_CLEAR) ; clear.show()
678 #name = gtk.Label("1.2.3.4.5") ; name.show()
680 back = gtk.Button("<") ; back.show()
681 fore = gtk.Button(">") ; fore.show()
682 red = gtk.ToggleButton("red"); red.show()
683 undo = gtk.Button("u") ; undo.show()
684 redo = gtk.Button("r") ; redo.show()
685 add = gtk.Button("+") ; add.show()
686 delete = gtk.Button("-") ; delete.show()
687 clear = gtk.Button("C") ; clear.show()
688 text = gtk.ToggleButton("T") ; text.show(); text.set_sensitive(False)
689 name = gtk.Button("1.2.3.4.5") ; name.show()
702 back.connect("clicked", self.back)
703 fore.connect("clicked", self.fore)
704 red.connect("toggled", self.colour_change)
705 undo.connect("clicked", self.undo)
706 redo.connect("clicked", self.redo)
707 add.connect("clicked", self.add)
708 delete.connect("clicked", self.delete)
709 clear.connect("clicked", self.clear)
710 text.connect("toggled", self.text_change)
711 name.connect("clicked", self.setname)
717 self.hist = [] # undo history
718 self.texttoggle = text
721 page.connect("button_press_event", self.press)
722 page.connect("button_release_event", self.release)
723 page.connect("motion_notify_event", self.motion)
724 page.connect("expose-event", self.refresh)
725 page.set_events(gtk.gdk.EXPOSURE_MASK
726 | gtk.gdk.BUTTON_PRESS_MASK
727 | gtk.gdk.BUTTON_RELEASE_MASK
728 | gtk.gdk.POINTER_MOTION_MASK
729 | gtk.gdk.POINTER_MOTION_HINT_MASK)
732 colourmap = page.get_colormap()
733 black = gtk.gdk.color_parse("black")
734 red = gtk.gdk.color_parse("red")
735 blue = gtk.gdk.color_parse("blue")
736 self.colour_black = page.window.new_gc()
737 self.colour_black.line_width = 2
738 self.colour_black.set_foreground(colourmap.alloc_color(black))
740 self.colour_red = page.window.new_gc()
741 self.colour_red.line_width = 2
742 self.colour_red.set_foreground(colourmap.alloc_color(red))
744 self.colour_textmode = page.window.new_gc()
745 self.colour_textmode.line_width = 2
746 self.colour_textmode.set_foreground(colourmap.alloc_color(blue))
748 self.colour = self.colour_black
749 self.colourname = "black"
750 self.bg = page.get_style().bg_gc[gtk.STATE_NORMAL]
752 if 'HOME' in os.environ:
753 home = os.environ['HOME']
756 if home == "" or home == "/":
758 self.page_dir = home + '/Pages'
761 window.set_default_size(480,640)
765 self.dict = Dictionary()
770 ctx = page.get_pango_context()
771 fd = ctx.get_font_description()
772 met = ctx.get_metrics(fd)
773 self.lineheight = (met.get_ascent() + met.get_descent()) / pango.SCALE
774 self.lineascent = met.get_ascent() / pango.SCALE
778 def close_application(self, widget):
782 def load_pages(self):
784 os.mkdir(self.page_dir)
787 self.names = os.listdir(self.page_dir)
788 if len(self.names) == 0:
789 self.names.append("1")
790 self.names.sort(page_cmp)
796 def press(self, c, ev):
799 gobject.source_remove(self.timeout)
802 self.line = [ self.colourname, [int(ev.x), int(ev.y)] ]
804 def release(self, c, ev):
805 if self.line == None:
807 if self.timeout == None:
808 self.timeout = gobject.timeout_add(20*1000, self.tick)
810 if len(self.line) == 2:
813 (lineno,index) = self.find_text(self.line[1])
816 self.textpos = self.line[1]
817 self.texttoggle.set_sensitive(True)
818 self.texttoggle.set_active(True)
819 c.window.draw_rectangle(self.colour_textmode, True, int(ev.x),int(ev.y),
823 # clicked inside an old text.
824 # shuffle it to the top, open it, edit.
825 self.texttoggle.set_sensitive(True)
826 self.texttoggle.set_active(True)
827 ln = self.lines[lineno]
828 self.lines = self.lines[:lineno] + self.lines[lineno+1:]
832 self.colour = self.colour_red
833 self.redbutton.set_active(True)
835 self.colour = self.colour_black
836 self.redbutton.set_active(False)
837 self.textcurs = index + 1
841 if self.texttoggle.get_active():
850 self.lines.append(self.line)
851 self.texttoggle.set_sensitive(False)
854 def motion(self, c, ev):
857 x, y, state = ev.window.get_pointer()
864 if abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10:
866 if self.texttoggle.get_active():
867 c.window.draw_line(self.colour_textmode, prev[0],prev[1],x,y)
869 c.window.draw_line(self.colour, prev[0],prev[1],x,y)
870 self.line.append([x,y])
874 # nothing for 20 seconds, flush the page
876 gobject.source_remove(self.timeout)
879 def find_text(self, pos):
880 x = pos[0]; y = pos[1]
881 for i in range(0, len(self.lines)):
883 if type(p[2]) != str:
885 y = pos[1] + self.lineascent
886 if x >= p[1][0] and y >= p[1][1] and y < p[1][1] + self.lineheight:
887 # could be this line - check more precisely
888 layout = self.page.create_pango_layout(p[2])
889 (ink, log) = layout.get_pixel_extents()
891 if x < p[1][0] + ex or x > p[1][0] + ex + ew or \
892 y < p[1][1] + ey or \
893 y > p[1][1] + ey + self.lineheight :
895 # OK, it is in this one. Find out where.
896 (index, gr) = layout.xy_to_index((x - p[1][0] - ex) * pango.SCALE,
897 (y - p[1][1] - ey - self.lineheight) * pango.SCALE)
900 def flush_text(self):
901 if self.textstr == None:
903 if len(self.textstr) == 0:
906 l = [self.colourname, self.textpos, self.textstr]
909 self.texttoggle.set_active(False)
911 def draw_text(self, pos, colour, str, cursor = None):
912 layout = self.page.create_pango_layout(str)
913 self.page.window.draw_layout(colour, pos[0], pos[1] - self.lineascent,
916 (strong,weak) = layout.get_cursor_pos(cursor)
917 (x,y,width,height) = strong
918 self.page.window.draw_rectangle(self.colour_textmode, True,
919 pos[0] + x/pango.SCALE,
921 def add_sym(self, sym):
922 if self.textstr == None:
926 if self.textcurs > 0:
927 self.textstr = self.textstr[0:self.textcurs-1]+ \
928 self.textstr[self.textcurs:]
930 elif sym == "<left>":
931 if self.textcurs > 0:
933 elif sym == "<right>":
934 if self.textcurs < len(self.textstr):
936 elif sym == "<newline>":
937 tail = self.textstr[self.textcurs:]
938 self.textstr = self.textstr[:self.textcurs]
940 self.texttoggle.set_active(True)
941 self.textcurs = len(tail)
943 self.textpos = [ self.textpos[0], self.textpos[1] +
946 self.textstr = self.textstr[0:self.textcurs] + sym + \
947 self.textstr[self.textcurs:]
953 alloc = self.page.get_allocation()
954 pagebb = BBox(Point(0,0))
955 pagebb.add(Point(alloc.width, alloc.height))
956 pagebb.finish(div = 2)
958 p = PPath(self.line[1][0], self.line[1][1])
959 for pp in self.line[1:]:
963 pos = pagebb.relpos(p.bbox)
969 sym = self.dict.match(patn, tpos)
971 print "Failed to match pattern:", patn
974 def refresh(self, area, ev):
977 self.name.set_label(self.names[self.pagenum])
978 self.page.window.draw_rectangle(self.bg, True, 0, 0,
982 col = self.colour_red
984 col = self.colour_black
986 if type(l[2]) == list:
988 self.page.window.draw_line(col, st[0], st[1],
991 if type(l[2]) == str:
992 self.draw_text(st, col, l[2])
994 if self.textstr != None:
995 self.draw_text(self.textpos, self.colour, self.textstr,
1002 if self.pagenum <= 0:
1009 if self.pagenum >= len(self.names)-1:
1017 def colour_change(self,t):
1019 self.colour = self.colour_red
1020 self.colourname = "red"
1022 self.colour = self.colour_black
1023 self.colourname = "black"
1025 self.draw_text(self.textpos, self.colour, self.textstr,
1029 def text_change(self,t):
1033 if len(self.lines) == 0:
1035 self.hist.append(self.lines.pop())
1039 if len(self.hist) == 0:
1041 self.lines.append(self.hist.pop())
1045 # New name is either
1046 # - take last number and increment it
1049 if len(self.lines) == 0:
1050 # don't add after a blank page
1053 newname = self.choose_unique(self.names[self.pagenum])
1055 self.names = self.names[0:self.pagenum+1] + [ newname ] + \
1056 self.names[self.pagenum+1:]
1061 def choose_unique(self, newname):
1062 while newname in self.pages:
1063 new2 = inc_name(newname)
1064 if new2 not in self.pages:
1066 elif (newname + ".1") not in self.pages:
1067 newname = newname + ".1"
1069 newname = newname + ".0.1"
1073 if len(self.names) <= 1:
1075 if len(self.lines) > 0:
1078 nm = self.names[self.pagenum]
1079 if nm in self.pages:
1081 self.names = self.names[0:self.pagenum] + self.names[self.pagenum+1:]
1082 if self.pagenum >= len(self.names):
1088 def rename(self, newname):
1089 # Rename current page and rename the file
1090 # Maybe we should resort the name list, but then we need to update
1091 # pagenum which is awkward.
1092 if self.names[self.pagenum] == newname:
1094 newname = self.choose_unique(newname)
1095 oldpath = self.page_dir + "/" + self.names[self.pagenum]
1096 newpath = self.page_dir + "/" + newname
1098 os.rename(oldpath, newpath)
1099 self.names[self.pagenum] = newname
1100 self.name.set_label(self.names[self.pagenum])
1104 def setname(self,b):
1106 if len(self.textstr) > 0:
1107 self.rename(self.textstr)
1110 while len(self.lines) > 0:
1111 self.hist.append(self.lines.pop())
1115 def parseline(self, l):
1116 # string in "", or num,num. ':' separates words
1117 words = l.strip().split(':')
1122 elif w.find(',') >= 0:
1130 def load_page(self):
1131 nm = self.names[self.pagenum]
1132 if nm in self.pages:
1133 if self.names[self.pagenum] in self.pages:
1134 self.lines = self.pages[self.names[self.pagenum]]
1138 f = open(self.page_dir + "/" + self.names[self.pagenum], "r")
1144 self.lines.append(self.parseline(l))
1149 def save_page(self):
1151 self.pages[self.names[self.pagenum]] = self.lines
1152 fn = self.page_dir + "/" + self.names[self.pagenum]
1153 if len(self.lines) == 0:
1160 for l in self.lines:
1168 if isinstance(w, str):
1170 elif isinstance(w, list):
1171 f.write("%d,%d" %( w[0],w[1]))
1178 if __name__ == "__main__":