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 c.window.draw_rectangle(self.colour_textmode, True, int(ev.x),int(ev.y),
822 # clicked inside an old text.
823 # shuffle it to the top, open it, edit.
824 self.texttoggle.set_sensitive(True)
825 self.texttoggle.set_active(True)
826 ln = self.lines[lineno]
827 self.lines = self.lines[:lineno] + self.lines[lineno+1:]
831 self.colour = self.colour_red
832 self.redbutton.set_active(True)
834 self.colour = self.colour_black
835 self.redbutton.set_active(False)
836 self.textcurs = index + 1
840 if self.texttoggle.get_active():
849 self.lines.append(self.line)
850 self.texttoggle.set_sensitive(False)
853 def motion(self, c, ev):
856 x, y, state = ev.window.get_pointer()
863 if abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10:
865 if self.texttoggle.get_active():
866 c.window.draw_line(self.colour_textmode, prev[0],prev[1],x,y)
868 c.window.draw_line(self.colour, prev[0],prev[1],x,y)
869 self.line.append([x,y])
873 # nothing for 20 seconds, flush the page
875 gobject.source_remove(self.timeout)
878 def find_text(self, pos):
879 x = pos[0]; y = pos[1]
880 for i in range(0, len(self.lines)):
882 if type(p[2]) != str:
884 y = pos[1] + self.lineascent
885 if x >= p[1][0] and y >= p[1][1] and y < p[1][1] + self.lineheight:
886 # could be this line - check more precisely
887 layout = self.page.create_pango_layout(p[2])
888 (ink, log) = layout.get_pixel_extents()
890 if x < p[1][0] + ex or x > p[1][0] + ex + ew or \
891 y < p[1][1] + ey or \
892 y > p[1][1] + ey + self.lineheight :
894 # OK, it is in this one. Find out where.
895 (index, gr) = layout.xy_to_index((x - p[1][0] - ex) * pango.SCALE,
896 (y - p[1][1] - ey - self.lineheight) * pango.SCALE)
899 def flush_text(self):
900 if self.textstr == None:
902 if len(self.textstr) == 0:
905 l = [self.colourname, self.textpos, self.textstr]
909 def draw_text(self, pos, colour, str, cursor = None):
910 layout = self.page.create_pango_layout(str)
911 self.page.window.draw_layout(colour, pos[0], pos[1] - self.lineascent,
914 (strong,weak) = layout.get_cursor_pos(cursor)
915 (x,y,width,height) = strong
916 self.page.window.draw_rectangle(self.colour_textmode, True,
917 pos[0] + x/pango.SCALE,
919 def add_sym(self, sym):
920 if self.textstr == None:
924 if self.textcurs > 0:
925 self.textstr = self.textstr[0:self.textcurs-1]+ \
926 self.textstr[self.textcurs:]
928 elif sym == "<left>":
929 if self.textcurs > 0:
931 elif sym == "<right>":
932 if self.textcurs < len(self.textstr):
934 elif sym == "<newline>":
935 tail = self.textstr[self.textcurs:]
936 self.textstr = self.textstr[:self.textcurs]
938 self.textcurs = len(tail)
940 self.textpos = [ self.textpos[0], self.textpos[1] +
943 self.textstr = self.textstr[0:self.textcurs] + sym + \
944 self.textstr[self.textcurs:]
950 alloc = self.page.get_allocation()
951 pagebb = BBox(Point(0,0))
952 pagebb.add(Point(alloc.width, alloc.height))
953 pagebb.finish(div = 2)
955 p = PPath(self.line[1][0], self.line[1][1])
956 for pp in self.line[1:]:
960 pos = pagebb.relpos(p.bbox)
966 sym = self.dict.match(patn, tpos)
968 print "Failed to match pattern:", patn
971 def refresh(self, area, ev):
974 self.name.set_label(self.names[self.pagenum])
975 self.page.window.draw_rectangle(self.bg, True, 0, 0,
979 col = self.colour_red
981 col = self.colour_black
983 if type(l[2]) == list:
985 self.page.window.draw_line(col, st[0], st[1],
988 if type(l[2]) == str:
989 self.draw_text(st, col, l[2])
991 if self.textstr != None:
992 self.draw_text(self.textpos, self.colour, self.textstr,
999 if self.pagenum <= 0:
1006 if self.pagenum >= len(self.names)-1:
1014 def colour_change(self,t):
1016 self.colour = self.colour_red
1017 self.colourname = "red"
1019 self.colour = self.colour_black
1020 self.colourname = "black"
1022 self.draw_text(self.textpos, self.colour, self.textstr,
1026 def text_change(self,t):
1030 if len(self.lines) == 0:
1032 self.hist.append(self.lines.pop())
1036 if len(self.hist) == 0:
1038 self.lines.append(self.hist.pop())
1042 # New name is either
1043 # - take last number and increment it
1046 if len(self.lines) == 0:
1047 # don't add after a blank page
1050 newname = self.choose_unique(self.names[self.pagenum])
1052 self.names = self.names[0:self.pagenum+1] + [ newname ] + \
1053 self.names[self.pagenum+1:]
1058 def choose_unique(self, newname):
1059 while newname in self.pages:
1060 new2 = inc_name(newname)
1061 if new2 not in self.pages:
1063 elif (newname + ".1") not in self.pages:
1064 newname = newname + ".1"
1066 newname = newname + ".0.1"
1070 if len(self.names) <= 1:
1072 if len(self.lines) > 0:
1075 nm = self.names[self.pagenum]
1076 if nm in self.pages:
1078 self.names = self.names[0:self.pagenum] + self.names[self.pagenum+1:]
1079 if self.pagenum >= len(self.names):
1085 def rename(self, newname):
1086 # Rename current page and rename the file
1087 # Maybe we should resort the name list, but then we need to update
1088 # pagenum which is awkward.
1089 if self.names[self.pagenum] == newname:
1091 newname = self.choose_unique(newname)
1092 oldpath = self.page_dir + "/" + self.names[self.pagenum]
1093 newpath = self.page_dir + "/" + newname
1095 os.rename(oldpath, newpath)
1096 self.names[self.pagenum] = newname
1097 self.name.set_label(self.names[self.pagenum])
1101 def setname(self,b):
1103 if len(self.textstr) > 0:
1104 self.rename(self.textstr)
1107 while len(self.lines) > 0:
1108 self.hist.append(self.lines.pop())
1112 def parseline(self, l):
1113 # string in "", or num,num. ':' separates words
1114 words = l.strip().split(':')
1119 elif w.find(',') >= 0:
1127 def load_page(self):
1128 nm = self.names[self.pagenum]
1129 if nm in self.pages:
1130 if self.names[self.pagenum] in self.pages:
1131 self.lines = self.pages[self.names[self.pagenum]]
1135 f = open(self.page_dir + "/" + self.names[self.pagenum], "r")
1141 self.lines.append(self.parseline(l))
1146 def save_page(self):
1148 self.pages[self.names[self.pagenum]] = self.lines
1149 fn = self.page_dir + "/" + self.names[self.pagenum]
1150 if len(self.lines) == 0:
1157 for l in self.lines:
1165 if isinstance(w, str):
1167 elif isinstance(w, list):
1168 f.write("%d,%d" %( w[0],w[1]))
1175 if __name__ == "__main__":