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>
33 ###########################################################
34 # Writing recognistion code
40 # Where they are like lowercase, we either double
41 # the last stroke (L, J, I) or draw backwards (S, Z, X)
42 # U V are a special case
44 dict.add('A', "R(4)6,8")
45 dict.add('B', "R(4)6,4.R(7)1,6")
46 dict.add('B', "R(4)6,4.L(4)2,8.R(7)1,6")
47 dict.add('B', "S(6)7,1.R(4)6,4.R(7)0,6")
48 dict.add('C', "R(4)8,2")
49 dict.add('D', "R(4)6,6")
50 dict.add('E', "L(1)2,8.L(7)2,8")
51 # double the stem for F
52 dict.add('F', "L(4)2,6.S(3)7,1")
53 dict.add('F', "S(1)5,3.S(3)1,7.S(3)7,1")
55 dict.add('G', "L(4)2,5.S(8)1,7")
56 dict.add('G', "L(4)2,5.R(8)6,8")
57 # FIXME I need better straight-curve alignment
58 dict.add('H', "S(3)1,7.R(7)6,8.S(5)7,1")
59 dict.add('H', "L(3)0,5.R(7)6,8.S(5)7,1")
60 # capital I is down/up
61 dict.add('I', "S(4)1,7.S(4)7,1")
63 # Capital J has a left/right tail
64 dict.add('J', "R(4)1,6.S(7)3,5")
66 dict.add('K', "L(4)0,2.R(4)6,6.L(4)2,8")
68 # Capital L, like J, doubles the foot
69 dict.add('L', "L(4)0,8.S(7)4,3")
71 dict.add('M', "R(3)6,5.R(5)3,8")
72 dict.add('M', "R(3)6,5.L(1)0,2.R(5)3,8")
74 dict.add('N', "R(3)6,8.L(5)0,2")
76 # Capital O is CW, but can be CCW in special dict
77 dict.add('O', "R(4)1,1", bot='0')
79 dict.add('P', "R(4)6,3")
80 dict.add('Q', "R(4)7,7.S(8)0,8")
82 dict.add('R', "R(4)6,4.S(8)0,8")
84 # S is drawn bottom to top.
85 dict.add('S', "L(7)6,1.R(1)7,2")
87 # Double the stem for capital T
88 dict.add('T', "R(4)0,8.S(5)7,1")
90 # U is L to R, V is R to L for now
91 dict.add('U', "L(4)0,2")
92 dict.add('V', "R(4)2,0")
94 dict.add('W', "R(5)2,3.L(7)8,6.R(3)5,0")
95 dict.add('W', "R(5)2,3.R(3)5,0")
97 dict.add('X', "R(4)6,0")
99 dict.add('Y',"L(1)0,2.R(5)4,6.S(5)6,2")
100 dict.add('Y',"L(1)0,2.S(5)2,7.S(5)7,2")
102 dict.add('Z', "R(4)8,2.L(4)6,0")
105 dict.add('a', "L(4)2,2.L(5)1,7")
106 dict.add('a', "L(4)2,2.L(5)0,8")
107 dict.add('a', "L(4)2,2.S(5)0,8")
108 dict.add('b', "S(3)1,7.R(7)6,3")
109 dict.add('c', "L(4)2,8", top='C')
110 dict.add('d', "L(4)5,2.S(5)1,7")
111 dict.add('d', "L(4)5,2.L(5)0,8")
112 dict.add('e', "S(4)3,5.L(4)5,8")
113 dict.add('e', "L(4)3,8")
114 dict.add('f', "L(4)2,6", top='F')
115 dict.add('f', "S(1)5,3.S(3)1,7", top='F')
116 dict.add('g', "L(1)2,2.R(4)1,6")
117 dict.add('h', "S(3)1,7.R(7)6,8")
118 dict.add('h', "L(3)0,5.R(7)6,8")
119 dict.add('i', "S(4)1,7", top='I', bot='1')
120 dict.add('j', "R(4)1,6", top='J')
121 dict.add('k', "L(3)0,5.L(7)2,8")
122 dict.add('k', "L(4)0,5.R(7)6,6.L(7)1,8")
123 dict.add('l', "L(4)0,8", top='L')
124 dict.add('l', "S(3)1,7.S(7)3,5", top='L')
125 dict.add('m', "S(3)1,7.R(3)6,8.R(5)6,8")
126 dict.add('m', "L(3)0,2.R(3)6,8.R(5)6,8")
127 dict.add('n', "S(3)1,7.R(4)6,8")
128 dict.add('o', "L(4)1,1", top='O', bot='0')
129 dict.add('p', "S(3)1,7.R(4)6,3")
130 dict.add('q', "L(1)2,2.L(5)1,5")
131 dict.add('q', "L(1)2,2.S(5)1,7.R(8)6,2")
132 dict.add('q', "L(1)2,2.S(5)1,7.S(5)1,7")
133 # FIXME this double 1,7 is due to a gentle where the
134 # second looks like a line because it is narrow.??
135 dict.add('r', "S(3)1,7.R(4)6,2")
136 dict.add('s', "L(1)2,7.R(7)1,6", top='S', bot='5')
137 dict.add('t', "R(4)0,8", top='T', bot='7')
138 dict.add('t', "S(1)3,5.S(5)1,7", top='T', bot='7')
139 dict.add('u', "L(4)0,2.S(5)1,7")
140 dict.add('v', "L(4)0,2.L(2)0,2")
141 dict.add('w', "L(3)0,2.L(5)0,2", top='W')
142 dict.add('w', "L(3)0,5.R(7)6,8.L(5)3,2", top='W')
143 dict.add('w', "L(3)0,5.L(5)3,2", top='W')
144 dict.add('x', "L(4)0,6", top='X')
145 dict.add('y', "L(1)0,2.R(5)4,6", top='Y') # if curved
146 dict.add('y', "L(1)0,2.S(5)2,7", top='Y')
147 dict.add('z', "R(4)0,6.L(4)2,8", top='Z', bot='2')
150 dict.add('0', "L(4)7,7")
151 dict.add('0', "R(4)7,7")
152 dict.add('1', "S(4)7,1")
153 dict.add('2', "R(4)0,6.S(7)3,5")
154 dict.add('2', "R(4)3,6.L(4)2,8")
155 dict.add('3', "R(1)0,6.R(7)1,6")
156 dict.add('4', "L(4)7,5")
157 dict.add('5', "L(1)2,6.R(7)0,3")
158 dict.add('5', "L(1)2,6.L(4)0,8.R(7)0,3")
159 dict.add('6', "L(4)2,3")
160 dict.add('7', "S(1)3,5.R(4)1,6")
161 dict.add('7', "R(4)0,6")
162 dict.add('7', "R(4)0,7")
163 dict.add('8', "L(4)2,8.R(4)4,2.L(3)6,1")
164 dict.add('8', "L(1)2,8.R(7)2,0.L(1)6,1")
165 dict.add('8', "L(0)2,6.R(7)0,1.L(2)6,0")
166 dict.add('8', "R(4)2,6.L(4)4,2.R(5)8,1")
167 dict.add('9', "L(1)2,2.S(5)1,7")
169 dict.add(' ', "S(4)3,5")
170 dict.add('<BS>', "S(4)5,3")
171 dict.add('-', "S(4)3,5.S(4)5,3")
172 dict.add('_', "S(4)3,5.S(4)5,3.S(4)3,5")
173 dict.add("<left>", "S(4)5,3.S(3)3,5")
174 dict.add("<right>","S(4)3,5.S(5)5,3")
175 dict.add("<newline>", "S(4)2,6")
179 # Each segment has for elements:
180 # direction: Right Straight Left (R=cw, L=ccw)
184 # Segments match if there difference at each element
185 # is 0, 1, or 3 (RSL coded as 012)
186 # A difference of 1 required both to be same / 3
187 # On a match, return number of 0s
188 # On non-match, return -1
189 def __init__(self, str):
195 if str[1] != '(' or str[3] != ')' or str[5] != ',':
206 self.e[1] = int(str[2])
207 self.e[2] = int(str[4])
208 self.e[3] = int(str[6])
210 def match(self, other):
213 diff = abs(self.e[i] - other.e[i])
218 elif diff == 1 and (self.e[i]/3 == other.e[i]/3):
225 # A Dict Pattern is a list of segments.
226 # A parsed pattern matches a dict pattern if
227 # the are the same nubmer of segments and they
228 # all match. The value of the match is the sum
229 # of the individual matches.
230 # A DictPattern is printers as segments joined by periods.
232 def __init__(self, str):
233 self.segs = map(DictSegment, str.split("."))
234 def match(self,other):
235 if len(self.segs) != len(other.segs):
238 for i in range(0,len(self.segs)):
239 m = self.segs[i].match(other.segs[i])
247 # The dictionary hold all the pattern for symbols and
249 # Each pattern in the directionary can be associated
250 # with 3 symbols. One when drawing in middle of screen,
251 # one for top of screen, one for bottom.
252 # Often these will all be the same.
253 # This allows e.g. s and S to have the same pattern in different
254 # location on the touchscreen.
255 # A match requires a unique entry with a match that is better
256 # than any other entry.
260 def add(self, sym, pat, top = None, bot = None):
261 if top == None: top = sym
262 if bot == None: bot = sym
263 self.dict.append((DictPattern(pat), sym, top, bot))
268 for (ptn, sym, top, bot) in self.dict:
272 val = (sym, top, bot)
277 def match(self, str, pos = "mid"):
282 (mid, top, bot) = self._match(p)
283 if pos == "top": return top
284 if pos == "bot": return bot
289 # This represents a point in the path and all the points leading
290 # up to it. It allows us to find the direction and curvature from
291 # one point to another
292 # We store x,y, and sum/cnt of points so far
293 def __init__(self,x,y) :
310 if self.x == x and self.y == y:
319 return abs(self.x - p.x)
321 return abs(self.y - p.y)
340 if self.cnt == p.cnt:
343 (x2,y2) = self.meanpoint(p)
344 x3 = self.x; y3 = self.y
346 curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
347 curve = curve * 100 / ((y3-y1)*(y3-y1)
356 if self.cnt == p.cnt:
359 (x2,y2) = self.meanpoint(p)
360 x3 = self.x; y3 = self.y
362 curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1)
363 curve = curve * 100 / ((y3-y1)*(y3-y1)
367 def meanpoint(self,p):
368 x = (self.xsum - p.xsum) / (self.cnt - p.cnt)
369 y = (self.ysum - p.ysum) / (self.cnt - p.cnt)
372 def is_sharp(self,A,C):
373 # Measure the cosine at self between A and C
374 # as A and C could be curve, we take the mean point on
375 # self.A and self.C as the points to find cosine between
376 (ax,ay) = self.meanpoint(A)
377 (cx,cy) = self.meanpoint(C)
378 a = ax-self.x; b=ay-self.y
379 c = cx-self.x; d=cy-self.y
382 h = math.sqrt(x*x+y*y)
390 # a BBox records min/max x/y of some Points and
391 # can subsequently report row, column, pos of each point
392 # can also locate one bbox in another
394 def __init__(self, p):
401 return self.maxx - self.minx
403 return self.maxy - self.miny
415 def finish(self, div = 3):
416 # if aspect ratio is bad, we adjust max/min accordingly
417 # before setting [xy][12]. We don't change self.min/max
418 # as they are used to place stroke in bigger bbox.
419 # Normally divisions are at 1/3 and 2/3. They can be moved
420 # by setting div e.g. 2 = 1/2 and 1/2
421 (minx,miny,maxx,maxy) = (self.minx,self.miny,self.maxx,self.maxy)
422 if (maxx - minx) * 3 < (maxy - miny) * 2:
424 mid = int((maxx + minx)/2)
425 halfwidth = int ((maxy - miny)/3)
426 minx = mid - halfwidth
427 maxx = mid + halfwidth
428 if (maxy - miny) * 3 < (maxx - minx) * 2:
430 mid = int((maxy + miny)/2)
431 halfheight = int ((maxx - minx)/3)
432 miny = mid - halfheight
433 maxy = mid + halfheight
436 self.x1 = int((div1*minx + maxx)/div)
437 self.x2 = int((minx + div1*maxx)/div)
438 self.y1 = int((div1*miny + maxy)/div)
439 self.y2 = int((miny + div1*maxy)/div)
442 # 0, 1, 2 - top to bottom
456 return self.row(p) * 3 + self.col(p)
459 # b is a box within self. find location 0-8
460 if b.maxx < self.x2 and b.minx < self.x1:
462 elif b.minx > self.x1 and b.maxx > self.x2:
466 if b.maxy < self.y2 and b.miny < self.y1:
468 elif b.miny > self.y1 and b.maxy > self.y2:
475 def different(*args):
478 if cur != 0 and i != 0 and cur != i:
491 # a PPath refines a list of x,y points into a list of Points
492 # The Points mark out segments which end at significant Points
493 # such as inflections and reversals.
495 def __init__(self, x,y):
497 self.start = Point(x,y)
498 self.mid = Point(x,y)
499 self.curr = Point(x,y)
500 self.list = [ self.start ]
505 if ( (abs(self.mid.xdir(self.start) - self.curr.xdir(self.mid)) == 2) or
506 (abs(self.mid.ydir(self.start) - self.curr.ydir(self.mid)) == 2) or
507 (abs(self.curr.Vcurve(self.start))+2 < abs(self.mid.Vcurve(self.start)))):
510 self.mid = self.curr.copy()
512 if self.curr.xlen(self.mid) > 4 or self.curr.ylen(self.mid) > 4:
513 self.start = self.mid.copy()
514 self.list.append(self.start)
515 self.mid = self.curr.copy()
518 self.list.append(self.curr)
520 def get_sectlist(self):
521 if len(self.list) <= 2:
522 return [[0,self.list]]
527 curcurve = B.curve(A)
528 for C in self.list[2:]:
532 if B.is_sharp(A,C) and not different(cabc, cab, cbc, curcurve):
533 # B is too pointy, must break here
534 l.append([curcurve, s])
537 elif not different(cabc, cab, cbc, curcurve):
541 curcurve = maxcurve(cab, cbc, cabc)
542 elif not different(cabc, cab, cbc) :
543 # gentle inflection along AB
544 # was: AB goes in old and new section
545 # now: AB only in old section, but curcurve
547 l.append([curcurve,s])
549 curcurve =maxcurve(cab, cbc, cabc)
551 # Change of direction at B
552 l.append([curcurve,s])
558 l.append([curcurve,s])
562 def remove_shorts(self, bbox):
563 # in self.list, if a point is close to the previous point,
565 if len(self.list) <= 2:
571 for p in self.list[1:]:
578 # OK, we have a list of points with curvature between.
579 # want to divide this into sections.
580 # for each 3 consectutive points ABC curve of ABC and AB and BC
581 # If all the same, they are all in a section.
582 # If not B starts a new section and the old ends on B or C...
583 BB = BBox(self.list[0])
588 self.remove_shorts(BB)
589 sectlist = self.get_sectlist()
591 for c, s in sectlist:
593 dr = "R" # clockwise is to the Right
595 dr = "L" # counterclockwise to the Left
602 # If all points are in some row or column, then
604 rwdiff = False; cldiff = False
605 rw = bb.row(s[0]); cl=bb.col(s[0])
607 if bb.row(p) != rw: rwdiff = True
608 if bb.col(p) != cl: cldiff = True
609 if not rwdiff or not cldiff: dr = "S"
612 t1 += "(%d)" % BB.relpos(bb)
613 t1 += "%d,%d" % (bb.box(s[0]), bb.box(s[-1]))
628 while l > 0 and a[l-1] >= '0' and a[l-1] <= '9':
630 # a[l:] is the last number
635 return a[0:l] + ("%d" % (num+1))
640 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
641 window.connect("destroy", self.close_application)
642 window.set_title("ScribblePad")
643 #window.set_size_request(480,640)
650 bar.set_size_request(-1, 40)
651 vb.pack_start(bar, expand=False)
654 page = gtk.DrawingArea()
655 page.set_size_request(480,540)
658 ctx = page.get_pango_context()
659 fd = ctx.get_font_description()
660 fd.set_absolute_size(25*pango.SCALE)
663 dflt = gtk.widget_get_default_style()
665 fd.set_absolute_size(25*pango.SCALE)
668 # < > R u r A D C name
669 #back = gtk.Button(stock = gtk.STOCK_GO_BACK) ; back.show()
670 #fore = gtk.Button(stock = gtk.STOCK_GO_FORWARD) ; fore.show()
671 #red = gtk.ToggleButton("red"); red.show()
672 #undo = gtk.Button(stock = gtk.STOCK_UNDO) ; undo.show()
673 #redo = gtk.Button(stock = gtk.STOCK_REDO) ; redo.show()
674 #add = gtk.Button(stock = gtk.STOCK_ADD) ; add.show()
675 #delete = gtk.Button(stock = gtk.STOCK_REMOVE) ; delete.show()
676 #clear = gtk.Button(stock = gtk.STOCK_CLEAR) ; clear.show()
677 #name = gtk.Label("1.2.3.4.5") ; name.show()
679 back = gtk.Button("<") ; back.show()
680 fore = gtk.Button(">") ; fore.show()
681 red = gtk.ToggleButton("red"); red.show()
682 undo = gtk.Button("u") ; undo.show()
683 redo = gtk.Button("r") ; redo.show()
684 add = gtk.Button("+") ; add.show()
685 delete = gtk.Button("-") ; delete.show()
686 clear = gtk.Button("C") ; clear.show()
687 text = gtk.ToggleButton("T") ; text.show(); text.set_sensitive(False)
688 name = gtk.Button("1.2.3.4.5") ; name.show()
701 back.connect("clicked", self.back)
702 fore.connect("clicked", self.fore)
703 red.connect("toggled", self.colour_change)
704 undo.connect("clicked", self.undo)
705 redo.connect("clicked", self.redo)
706 add.connect("clicked", self.add)
707 delete.connect("clicked", self.delete)
708 clear.connect("clicked", self.clear)
709 text.connect("toggled", self.text_change)
710 name.connect("clicked", self.setname)
715 self.hist = [] # undo history
716 self.texttoggle = text
719 page.connect("button_press_event", self.press)
720 page.connect("button_release_event", self.release)
721 page.connect("motion_notify_event", self.motion)
722 page.connect("expose-event", self.refresh)
723 page.set_events(gtk.gdk.EXPOSURE_MASK
724 | gtk.gdk.BUTTON_PRESS_MASK
725 | gtk.gdk.BUTTON_RELEASE_MASK
726 | gtk.gdk.POINTER_MOTION_MASK
727 | gtk.gdk.POINTER_MOTION_HINT_MASK)
730 colourmap = page.get_colormap()
731 black = gtk.gdk.color_parse("black")
732 red = gtk.gdk.color_parse("red")
733 blue = gtk.gdk.color_parse("blue")
734 self.colour_black = page.window.new_gc()
735 self.colour_black.line_width = 2
736 self.colour_black.set_foreground(colourmap.alloc_color(black))
738 self.colour_red = page.window.new_gc()
739 self.colour_red.line_width = 2
740 self.colour_red.set_foreground(colourmap.alloc_color(red))
742 self.colour_textmode = page.window.new_gc()
743 self.colour_textmode.line_width = 2
744 self.colour_textmode.set_foreground(colourmap.alloc_color(blue))
746 self.colour = self.colour_black
747 self.colourname = "black"
748 self.bg = page.get_style().bg_gc[gtk.STATE_NORMAL]
750 if 'HOME' in os.environ:
751 home = os.environ['HOME']
754 if home == "" or home == "/":
756 self.page_dir = home + '/Pages'
759 window.set_default_size(480,640)
763 self.dict = Dictionary()
768 ctx = page.get_pango_context()
769 fd = ctx.get_font_description()
770 met = ctx.get_metrics(fd)
771 self.lineheight = (met.get_ascent() + met.get_descent()) / pango.SCALE
772 self.lineascent = met.get_ascent() / pango.SCALE
774 def close_application(self, widget):
778 def load_pages(self):
780 os.mkdir(self.page_dir)
783 self.names = os.listdir(self.page_dir)
784 if len(self.names) == 0:
785 self.names.append("1")
786 self.names.sort(page_cmp)
792 def press(self, c, ev):
795 self.line = [ self.colourname, [int(ev.x), int(ev.y)] ]
797 def release(self, c, ev):
798 if self.line == None:
800 if len(self.line) == 2:
803 self.textpos = self.line[1]
804 self.texttoggle.set_sensitive(True)
805 c.window.draw_rectangle(self.colour_textmode, True, int(ev.x),int(ev.y),
809 if self.texttoggle.get_active():
818 self.lines.append(self.line)
819 self.texttoggle.set_sensitive(False)
822 def motion(self, c, ev):
825 x, y, state = ev.window.get_pointer()
832 if abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10:
834 if self.texttoggle.get_active():
835 c.window.draw_line(self.colour_textmode, prev[0],prev[1],x,y)
837 c.window.draw_line(self.colour, prev[0],prev[1],x,y)
838 self.line.append([x,y])
841 def flush_text(self):
842 if self.textstr == None:
844 if len(self.textstr) == 0:
847 l = [self.colourname, self.textpos, self.textstr]
851 def draw_text(self, pos, colour, str, cursor = None):
852 layout = self.page.create_pango_layout(str)
853 self.page.window.draw_layout(colour, pos[0], pos[1] - self.lineascent,
856 (strong,weak) = layout.get_cursor_pos(cursor)
857 (x,y,width,height) = strong
858 self.page.window.draw_rectangle(self.colour_textmode, True,
859 pos[0] + x/pango.SCALE,
861 def add_sym(self, sym):
862 if self.textstr == None:
866 if self.textcurs > 0:
867 self.textstr = self.textstr[0:self.textcurs-1]+ \
868 self.textstr[self.textcurs:]
870 elif sym == "<left>":
871 if self.textcurs > 0:
873 elif sym == "<right>":
874 if self.textcurs < len(self.textstr):
876 elif sym == "<newline>":
880 self.textpos = [ self.textpos[0], self.textpos[1] +
883 self.textstr = self.textstr[0:self.textcurs] + sym + \
884 self.textstr[self.textcurs:]
890 alloc = self.page.get_allocation()
891 pagebb = BBox(Point(0,0))
892 pagebb.add(Point(alloc.width, alloc.height))
893 pagebb.finish(div = 2)
895 p = PPath(self.line[1][0], self.line[1][1])
896 for pp in self.line[1:]:
900 pos = pagebb.relpos(p.bbox)
906 sym = self.dict.match(patn, tpos)
908 print "Failed to match pattern:", patn
911 def refresh(self, area, ev):
914 self.name.set_label(self.names[self.pagenum])
915 self.page.window.draw_rectangle(self.bg, True, 0, 0,
919 col = self.colour_red
921 col = self.colour_black
923 if type(l[2]) == list:
925 self.page.window.draw_line(col, st[0], st[1],
928 if type(l[2]) == str:
929 self.draw_text(st, col, l[2])
931 if self.textstr != None:
932 self.draw_text(self.textpos, self.colour, self.textstr,
939 if self.pagenum <= 0:
946 if self.pagenum >= len(self.names)-1:
954 def colour_change(self,t):
956 self.colour = self.colour_red
957 self.colourname = "red"
959 self.colour = self.colour_black
960 self.colourname = "black"
962 self.draw_text(self.textpos, self.colour, self.textstr,
966 def text_change(self,t):
970 if len(self.lines) == 0:
972 self.hist.append(self.lines.pop())
976 if len(self.hist) == 0:
978 self.lines.append(self.hist.pop())
983 # - take last number and increment it
986 if len(self.lines) == 0:
987 # don't add after a blank page
990 newname = self.choose_unique(self.names[self.pagenum])
992 self.names = self.names[0:self.pagenum+1] + [ newname ] + \
993 self.names[self.pagenum+1:]
998 def choose_unique(self, newname):
999 while newname in self.pages:
1000 new2 = inc_name(newname)
1001 if new2 not in self.pages:
1003 elif (newname + ".1") not in self.pages:
1004 newname = newname + ".1"
1006 newname = newname + ".0.1"
1010 if len(self.names) <= 1:
1012 if len(self.lines) > 0:
1015 nm = self.names[self.pagenum]
1016 if nm in self.pages:
1018 self.names = self.names[0:self.pagenum] + self.names[self.pagenum+1:]
1019 if self.pagenum >= len(self.names):
1025 def rename(self, newname):
1026 # Rename current page and rename the file
1027 # Maybe we should resort the name list, but then we need to update
1028 # pagenum which is awkward.
1029 if self.names[self.pagenum] == newname:
1031 newname = self.choose_unique(newname)
1032 oldpath = self.page_dir + "/" + self.names[self.pagenum]
1033 newpath = self.page_dir + "/" + newname
1035 os.rename(oldpath, newpath)
1036 self.names[self.pagenum] = newname
1037 self.name.set_label(self.names[self.pagenum])
1041 def setname(self,b):
1043 if len(self.textstr) > 0:
1044 self.rename(self.textstr)
1047 while len(self.lines) > 0:
1048 self.hist.append(self.lines.pop())
1052 def parseline(self, l):
1053 # string in "", or num,num. ':' separates words
1054 words = l.strip().split(':')
1059 elif w.find(',') >= 0:
1067 def load_page(self):
1068 nm = self.names[self.pagenum]
1069 if nm in self.pages:
1070 if self.names[self.pagenum] in self.pages:
1071 self.lines = self.pages[self.names[self.pagenum]]
1075 f = open(self.page_dir + "/" + self.names[self.pagenum], "r")
1081 self.lines.append(self.parseline(l))
1086 def save_page(self):
1088 self.pages[self.names[self.pagenum]] = self.lines
1089 fn = self.page_dir + "/" + self.names[self.pagenum]
1090 if len(self.lines) == 0:
1097 for l in self.lines:
1105 if isinstance(w, str):
1107 elif isinstance(w, list):
1108 f.write("%d,%d" %( w[0],w[1]))
1115 if __name__ == "__main__":