From 3937d7cb5858be660a97ba0bc9fe2d149758afd4 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Sun, 24 Aug 2008 18:56:03 +1000 Subject: [PATCH] Text entry largely working. Also a .desktop file. Note: there are still lots of imprefections with text entry. --- scribble.desktop | 12 + scribble.py | 717 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 721 insertions(+), 8 deletions(-) create mode 100644 scribble.desktop diff --git a/scribble.desktop b/scribble.desktop new file mode 100644 index 0000000..94385cc --- /dev/null +++ b/scribble.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=Scribble pad +Comment=Note pad for scibbles and note taking +Encoding=UTF-8 +Version=1.0 +Type=Application +Exec=scribble.py +Icon=scribble +Terminal=false +Categories=GTK;Application;PIM;Office +SingleInstance=true +StartupNotify=true diff --git a/scribble.py b/scribble.py index 486088c..41c5fd7 100755 --- a/scribble.py +++ b/scribble.py @@ -3,6 +3,589 @@ import pygtk import gtk import os +import pango + +########################################################### +# Writing recognistion code +import math + + +def LoadDict(dict): + # Upper case. + # Where they are like lowercase, we either double + # the last stroke (L, J, I) or draw backwards (S, Z, X) + # U V are a special case + + dict.add('A', "R(4)6,8") + dict.add('B', "R(4)6,4.R(7)1,6") + dict.add('B', "R(4)6,4.L(4)2,8.R(7)1,6") + dict.add('B', "S(6)7,1.R(4)6,4.R(7)0,6") + dict.add('C', "R(4)8,2") + dict.add('D', "R(4)6,6") + dict.add('E', "L(1)2,8.L(7)2,8") + # double the stem for F + dict.add('F', "L(4)2,6.S(3)7,1") + dict.add('F', "S(1)5,3.S(3)1,7.S(3)7,1") + + dict.add('G', "L(4)2,5.S(8)1,7") + dict.add('G', "L(4)2,5.R(8)6,8") + # FIXME I need better straight-curve alignment + dict.add('H', "S(3)1,7.R(7)6,8.S(5)7,1") + dict.add('H', "L(3)0,5.R(7)6,8.S(5)7,1") + # capital I is down/up + dict.add('I', "S(4)1,7.S(4)7,1") + + # Capital J has a left/right tail + dict.add('J', "R(4)1,6.S(7)3,5") + + dict.add('K', "L(4)0,2.R(4)6,6.L(4)2,8") + + # Capital L, like J, doubles the foot + dict.add('L', "L(4)0,8.S(7)4,3") + + dict.add('M', "R(3)6,5.R(5)3,8") + dict.add('M', "R(3)6,5.L(1)0,2.R(5)3,8") + + dict.add('N', "R(3)6,8.L(5)0,2") + + # Capital O is CW, but can be CCW in special dict + dict.add('O', "R(4)1,1", bot='0') + + dict.add('P', "R(4)6,3") + dict.add('Q', "R(4)7,7.S(8)0,8") + + dict.add('R', "R(4)6,4.S(8)0,8") + + # S is drawn bottom to top. + dict.add('S', "L(7)6,1.R(1)7,2") + + # Double the stem for capital T + dict.add('T', "R(4)0,8.S(5)7,1") + + # U is L to R, V is R to L for now + dict.add('U', "L(4)0,2") + dict.add('V', "R(4)2,0") + + dict.add('W', "R(5)2,3.L(7)8,6.R(3)5,0") + dict.add('W', "R(5)2,3.R(3)5,0") + + dict.add('X', "R(4)6,0") + + dict.add('Y',"L(1)0,2.R(5)4,6.S(5)6,2") + dict.add('Y',"L(1)0,2.S(5)2,7.S(5)7,2") + + dict.add('Z', "R(4)8,2.L(4)6,0") + + # Lower case + dict.add('a', "L(4)2,2.L(5)1,7") + dict.add('a', "L(4)2,2.L(5)0,8") + dict.add('a', "L(4)2,2.S(5)0,8") + dict.add('b', "S(3)1,7.R(7)6,3") + dict.add('c', "L(4)2,8", top='C') + dict.add('d', "L(4)5,2.S(5)1,7") + dict.add('d', "L(4)5,2.L(5)0,8") + dict.add('e', "S(4)3,5.L(4)5,8") + dict.add('e', "L(4)3,8") + dict.add('f', "L(4)2,6", top='F') + dict.add('f', "S(1)5,3.S(3)1,7", top='F') + dict.add('g', "L(1)2,2.R(4)1,6") + dict.add('h', "S(3)1,7.R(7)6,8") + dict.add('h', "L(3)0,5.R(7)6,8") + dict.add('i', "S(4)1,7", top='I', bot='1') + dict.add('j', "R(4)1,6", top='J') + dict.add('k', "L(3)0,5.L(7)2,8") + dict.add('k', "L(4)0,5.R(7)6,6.L(7)1,8") + dict.add('l', "L(4)0,8", top='L') + dict.add('l', "S(3)1,7.S(7)3,5", top='L') + dict.add('m', "S(3)1,7.R(3)6,8.R(5)6,8") + dict.add('m', "L(3)0,2.R(3)6,8.R(5)6,8") + dict.add('n', "S(3)1,7.R(4)6,8") + dict.add('o', "L(4)1,1", top='O', bot='0') + dict.add('p', "S(3)1,7.R(4)6,3") + dict.add('q', "L(1)2,2.L(5)1,5") + dict.add('q', "L(1)2,2.S(5)1,7.R(8)6,2") + dict.add('q', "L(1)2,2.S(5)1,7.S(5)1,7") + # FIXME this double 1,7 is due to a gentle where the + # second looks like a line because it is narrow.?? + dict.add('r', "S(3)1,7.R(4)6,2") + dict.add('s', "L(1)2,7.R(7)1,6", top='S', bot='5') + dict.add('t', "R(4)0,8", top='T', bot='7') + dict.add('t', "S(1)3,5.S(5)1,7", top='T', bot='7') + dict.add('u', "L(4)0,2.S(5)1,7") + dict.add('v', "L(4)0,2.L(2)0,2") + dict.add('w', "L(3)0,2.L(5)0,2", top='W') + dict.add('w', "L(3)0,5.R(7)6,8.L(5)3,2", top='W') + dict.add('w', "L(3)0,5.L(5)3,2", top='W') + dict.add('x', "L(4)0,6", top='X') + dict.add('y', "L(1)0,2.R(5)4,6", top='Y') # if curved + dict.add('y', "L(1)0,2.S(5)2,7", top='Y') + dict.add('z', "R(4)0,6.L(4)2,8", top='Z', bot='2') + + # Digits + dict.add('0', "L(4)7,7") + dict.add('0', "R(4)7,7") + dict.add('1', "S(4)7,1") + dict.add('2', "R(4)0,6.S(7)3,5") + dict.add('2', "R(4)3,6.L(4)2,8") + dict.add('3', "R(1)0,6.R(7)1,6") + dict.add('4', "L(4)7,5") + dict.add('5', "L(1)2,6.R(7)0,3") + dict.add('5', "L(1)2,6.L(4)0,8.R(7)0,3") + dict.add('6', "L(4)2,3") + dict.add('7', "S(1)3,5.R(4)1,6") + dict.add('7', "R(4)0,6") + dict.add('7', "R(4)0,7") + dict.add('8', "L(4)2,8.R(4)4,2.L(3)6,1") + dict.add('8', "L(1)2,8.R(7)2,0.L(1)6,1") + dict.add('8', "L(0)2,6.R(7)0,1.L(2)6,0") + dict.add('8', "R(4)2,6.L(4)4,2.R(5)8,1") + dict.add('9', "L(1)2,2.S(5)1,7") + + dict.add(' ', "S(4)3,5") + dict.add('', "S(4)5,3") + dict.add('-', "S(4)3,5.S(4)5,3") + dict.add('_', "S(4)3,5.S(4)5,3.S(4)3,5") + dict.add("", "S(4)5,3.S(3)3,5") + dict.add("","S(4)3,5.S(5)5,3") + + +class DictSegment: + # Each segment has for elements: + # direction: Right Straight Left (R=cw, L=ccw) + # location: 0-8. + # start: 0-8 + # finish: 0-8 + # Segments match if there difference at each element + # is 0, 1, or 3 (RSL coded as 012) + # A difference of 1 required both to be same / 3 + # On a match, return number of 0s + # On non-match, return -1 + def __init__(self, str): + # D(L)S,R + # 0123456 + self.e = [0,0,0,0] + if len(str) != 7: + raise ValueError + if str[1] != '(' or str[3] != ')' or str[5] != ',': + raise ValueError + if str[0] == 'R': + self.e[0] = 0 + elif str[0] == 'L': + self.e[0] = 2 + elif str[0] == 'S': + self.e[0] = 1 + else: + raise ValueError + + self.e[1] = int(str[2]) + self.e[2] = int(str[4]) + self.e[3] = int(str[6]) + + def match(self, other): + cnt = 0 + for i in range(0,4): + diff = abs(self.e[i] - other.e[i]) + if diff == 0: + cnt += 1 + elif diff == 3: + pass + elif diff == 1 and (self.e[i]/3 == other.e[i]/3): + pass + else: + return -1 + return cnt + +class DictPattern: + # A Dict Pattern is a list of segments. + # A parsed pattern matches a dict pattern if + # the are the same nubmer of segments and they + # all match. The value of the match is the sum + # of the individual matches. + # A DictPattern is printers as segments joined by periods. + # + def __init__(self, str): + self.segs = map(DictSegment, str.split(".")) + def match(self,other): + if len(self.segs) != len(other.segs): + return -1 + cnt = 0 + for i in range(0,len(self.segs)): + m = self.segs[i].match(other.segs[i]) + if m < 0: + return m + cnt += m + return cnt + + +class Dictionary: + # The dictionary hold all the pattern for symbols and + # performs lookup + # Each pattern in the directionary can be associated + # with 3 symbols. One when drawing in middle of screen, + # one for top of screen, one for bottom. + # Often these will all be the same. + # This allows e.g. s and S to have the same pattern in different + # location on the touchscreen. + # A match requires a unique entry with a match that is better + # than any other entry. + # + def __init__(self): + self.dict = [] + def add(self, sym, pat, top = None, bot = None): + if top == None: top = sym + if bot == None: bot = sym + self.dict.append((DictPattern(pat), sym, top, bot)) + + def _match(self, p): + max = -1 + val = None + for (ptn, sym, top, bot) in self.dict: + cnt = ptn.match(p) + if cnt > max: + max = cnt + val = (sym, top, bot) + elif cnt == max: + val = None + return val + + def match(self, str, pos = "mid"): + p = DictPattern(str) + m = self._match(p) + if m == None: + return m + (mid, top, bot) = self._match(p) + if pos == "top": return top + if pos == "bot": return bot + return mid + + +class Point: + # This represents a point in the path and all the points leading + # up to it. It allows us to find the direction and curvature from + # one point to another + # We store x,y, and sum/cnt of points so far + def __init__(self,x,y) : + self.xsum = x + self.ysum = y + self.x = x + self.y = y + self.cnt = 1 + + def copy(self): + n = Point(0,0) + n.xsum = self.xsum + n.ysum = self.ysum + n.x = self.x + n.y = self.y + n.cnt = self.cnt + return n + + def add(self,x,y): + if self.x == x and self.y == y: + return + self.x = x + self.y = y + self.xsum += x + self.ysum += y + self.cnt += 1 + + def xlen(self,p): + return abs(self.x - p.x) + def ylen(self,p): + return abs(self.y - p.y) + def sqlen(self,p): + x = self.x - p.x + y = self.y - p.y + return x*x + y*y + + def xdir(self,p): + if self.x > p.x: + return 1 + if self.x < p.x: + return -1 + return 0 + def ydir(self,p): + if self.y > p.y: + return 1 + if self.y < p.y: + return -1 + return 0 + def curve(self,p): + if self.cnt == p.cnt: + return 0 + x1 = p.x ; y1 = p.y + (x2,y2) = self.meanpoint(p) + x3 = self.x; y3 = self.y + + curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1) + curve = curve * 100 / ((y3-y1)*(y3-y1) + + (x3-x1)*(x3-x1)) + if curve > 6: + return 1 + if curve < -6: + return -1 + return 0 + + def Vcurve(self,p): + if self.cnt == p.cnt: + return 0 + x1 = p.x ; y1 = p.y + (x2,y2) = self.meanpoint(p) + x3 = self.x; y3 = self.y + + curve = (y3-y1)*(x2-x1) - (y2-y1)*(x3-x1) + curve = curve * 100 / ((y3-y1)*(y3-y1) + + (x3-x1)*(x3-x1)) + return curve + + def meanpoint(self,p): + x = (self.xsum - p.xsum) / (self.cnt - p.cnt) + y = (self.ysum - p.ysum) / (self.cnt - p.cnt) + return (x,y) + + def is_sharp(self,A,C): + # Measure the cosine at self between A and C + # as A and C could be curve, we take the mean point on + # self.A and self.C as the points to find cosine between + (ax,ay) = self.meanpoint(A) + (cx,cy) = self.meanpoint(C) + a = ax-self.x; b=ay-self.y + c = cx-self.x; d=cy-self.y + x = a*c + b*d + y = a*d - b*c + h = math.sqrt(x*x+y*y) + if h > 0: + cs = x*1000/h + else: + cs = 0 + return (cs > 900) + +class BBox: + # a BBox records min/max x/y of some Points and + # can subsequently report row, column, pos of each point + # can also locate one bbox in another + + def __init__(self, p): + self.minx = p.x + self.maxx = p.x + self.miny = p.y + self.maxy = p.y + + def width(self): + return self.maxx - self.minx + def height(self): + return self.maxy - self.miny + + def add(self, p): + if p.x > self.maxx: + self.maxx = p.x + if p.x < self.minx: + self.minx = p.x + + if p.y > self.maxy: + self.maxy = p.y + if p.y < self.miny: + self.miny = p.y + def finish(self): + # if aspect ratio is bad, we adjust max/min accordingly + # before setting [xy][12]. We don't change self.min/max + # as they are used to place stroke in bigger bbox. + (minx,miny,maxx,maxy) = (self.minx,self.miny,self.maxx,self.maxy) + if (maxx - minx) * 3 < (maxy - miny) * 2: + # too narrow + mid = int((maxx + minx)/2) + halfwidth = int ((maxy - miny)/3) + minx = mid - halfwidth + maxx = mid + halfwidth + if (maxy - miny) * 3 < (maxx - minx) * 2: + # too wide + mid = int((maxy + miny)/2) + halfheight = int ((maxx - minx)/3) + miny = mid - halfheight + maxy = mid + halfheight + + self.x1 = int((2*minx + maxx)/3) + self.x2 = int((minx + 2*maxx)/3) + self.y1 = int((2*miny + maxy)/3) + self.y2 = int((miny + 2*maxy)/3) + + def row(self, p): + # 0, 1, 2 - top to bottom + if p.y <= self.y1: + return 0 + if p.y < self.y2: + return 1 + return 2 + def col(self, p): + if p.x <= self.x1: + return 0 + if p.x < self.x2: + return 1 + return 2 + def box(self, p): + # 0 to 9 + return self.row(p) * 3 + self.col(p) + + def relpos(self,b): + # b is a box within self. find location 0-8 + if b.maxx < self.x2 and b.minx < self.x1: + x = 0 + elif b.minx > self.x1 and b.maxx > self.x2: + x = 2 + else: + x = 1 + if b.maxy < self.y2 and b.miny < self.y1: + y = 0 + elif b.miny > self.y1 and b.maxy > self.y2: + y = 2 + else: + y = 1 + return y*3 + x + + +def different(*args): + cur = 0 + for i in args: + if cur != 0 and i != 0 and cur != i: + return True + if cur == 0: + cur = i + return False + +def maxcurve(*args): + for i in args: + if i != 0: + return i + return 0 + +class PPath: + # a PPath refines a list of x,y points into a list of Points + # The Points mark out segments which end at significant Points + # such as inflections and reversals. + + def __init__(self, x,y): + + self.start = Point(x,y) + self.mid = Point(x,y) + self.curr = Point(x,y) + self.list = [ self.start ] + + def add(self, x, y): + self.curr.add(x,y) + + if ( (abs(self.mid.xdir(self.start) - self.curr.xdir(self.mid)) == 2) or + (abs(self.mid.ydir(self.start) - self.curr.ydir(self.mid)) == 2) or + (abs(self.curr.Vcurve(self.start))+2 < abs(self.mid.Vcurve(self.start)))): + pass + else: + self.mid = self.curr.copy() + + if self.curr.xlen(self.mid) > 4 or self.curr.ylen(self.mid) > 4: + self.start = self.mid.copy() + self.list.append(self.start) + self.mid = self.curr.copy() + + def close(self): + self.list.append(self.curr) + + def get_sectlist(self): + if len(self.list) <= 2: + return [[0,self.list]] + l = [] + A = self.list[0] + B = self.list[1] + s = [A,B] + curcurve = B.curve(A) + for C in self.list[2:]: + cabc = C.curve(A) + cab = B.curve(A) + cbc = C.curve(B) + if B.is_sharp(A,C) and not different(cabc, cab, cbc, curcurve): + # B is too pointy, must break here + l.append([curcurve, s]) + s = [B, C] + curcurve = cbc + elif not different(cabc, cab, cbc, curcurve): + # all happy + s.append(C) + if curcurve == 0: + curcurve = maxcurve(cab, cbc, cabc) + elif not different(cabc, cab, cbc) : + # gentle inflection along AB + # was: AB goes in old and new section + # now: AB only in old section, but curcurve + # preseved. + l.append([curcurve,s]) + s = [A, B, C] + curcurve =maxcurve(cab, cbc, cabc) + else: + # Change of direction at B + l.append([curcurve,s]) + s = [B, C] + curcurve = cbc + + A = B + B = C + l.append([curcurve,s]) + + return l + + def remove_shorts(self, bbox): + # in self.list, if a point is close to the previous point, + # remove it. + if len(self.list) <= 2: + return + w = bbox.width()/10 + h = bbox.height()/10 + n = [self.list[0]] + leng = w*h*2*2 + for p in self.list[1:]: + l = p.sqlen(n[-1]) + if l > leng: + n.append(p) + self.list = n + + def text(self): + # OK, we have a list of points with curvature between. + # want to divide this into sections. + # for each 3 consectutive points ABC curve of ABC and AB and BC + # If all the same, they are all in a section. + # If not B starts a new section and the old ends on B or C... + BB = BBox(self.list[0]) + for p in self.list: + BB.add(p) + BB.finish() + self.bbox = BB + self.remove_shorts(BB) + sectlist = self.get_sectlist() + t = "" + for c, s in sectlist: + if c > 0: + dr = "R" # clockwise is to the Right + elif c < 0: + dr = "L" # counterclockwise to the Left + else: + dr = "S" # straight + bb = BBox(s[0]) + for p in s: + bb.add(p) + bb.finish() + # If all points are in some row or column, then + # line is S + rwdiff = False; cldiff = False + rw = bb.row(s[0]); cl=bb.col(s[0]) + for p in s: + if bb.row(p) != rw: rwdiff = True + if bb.col(p) != cl: cldiff = True + if not rwdiff or not cldiff: dr = "S" + + t1 = dr + t1 += "(%d)" % BB.relpos(bb) + t1 += "%d,%d" % (bb.box(s[0]), bb.box(s[-1])) + t += t1 + '.' + return t[:-1] + + def page_cmp(a,b): if a < b: @@ -35,13 +618,22 @@ class ScribblePad: vb.show() bar = gtk.HBox() - vb.add(bar) + bar.set_size_request(-1, 40) + vb.pack_start(bar, expand=False) bar.show() page = gtk.DrawingArea() page.set_size_request(480,540) vb.add(page) page.show() + ctx = page.get_pango_context() + fd = ctx.get_font_description() + fd.set_absolute_size(25*pango.SCALE) + page.modify_font(fd) + + dflt = gtk.widget_get_default_style() + fd = dflt.font_desc + fd.set_absolute_size(25*pango.SCALE) # Now the widgets: # < > R u r A D C name @@ -63,6 +655,7 @@ class ScribblePad: add = gtk.Button("+") ; add.show() delete = gtk.Button("-") ; delete.show() clear = gtk.Button("C") ; clear.show() + text = gtk.ToggleButton("T") ; text.show(); text.set_sensitive(False) name = gtk.Label("1.2.3.4.5") ; name.show() bar.add(back) @@ -73,6 +666,7 @@ class ScribblePad: bar.add(add) bar.add(delete) bar.add(clear) + bar.add(text) bar.add(name) back.connect("clicked", self.back) @@ -83,11 +677,13 @@ class ScribblePad: add.connect("clicked", self.add) delete.connect("clicked", self.delete) clear.connect("clicked", self.clear) + text.connect("toggled", self.text_change) self.name = name self.page = page self.line = None self.lines = [] self.hist = [] # undo history + self.texttoggle = text page.connect("button_press_event", self.press) @@ -119,8 +715,14 @@ class ScribblePad: self.page_dir = os.environ['HOME'] + '/Pages' self.load_pages() + window.set_default_size(480,640) + window.show() + self.dict = Dictionary() + LoadDict(self.dict) + self.textstr = None + def close_application(self, widget): self.save_page() gtk.main_quit() @@ -145,9 +747,28 @@ class ScribblePad: self.line = [ self.colourname, [int(ev.x), int(ev.y)] ] return def release(self, c, ev): - #FIXME ignore line if only one dot. - if self.line: - self.lines.append(self.line) + if self.line == None: + return + if len(self.line) == 2: + # just set a cursor + self.flush_text() + self.textpos = self.line[1] + self.texttoggle.set_sensitive(True) + c.window.draw_rectangle(self.colour_black, True, int(ev.x),int(ev.y), + 2,2) + self.line = None + return + if self.texttoggle.get_active(): + sym = self.getsym() + if sym: + self.add_sym(sym) + else: + self.redraw() + self.line = None + return + + self.lines.append(self.line) + self.texttoggle.set_sensitive(False) self.line = None return def motion(self, c, ev): @@ -160,10 +781,78 @@ class ScribblePad: x = int(x) y = int(y) prev = self.line[-1] + if abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10: + return c.window.draw_line(self.colour, prev[0],prev[1],x,y) self.line.append([x,y]) return + def flush_text(self): + if self.textstr == None: + return + if len(self.textstr) == 0: + self.textstr = None + return + l = [self.colourname, self.textpos, self.textstr] + self.lines.append(l) + self.textstr = None + + def draw_text(self, pos, colour, str, cursor = None): + layout = self.page.create_pango_layout(str) + ctx = self.page.get_pango_context() + ascent = ctx.get_metrics(ctx.get_font_description()).get_ascent() + self.page.window.draw_layout(colour, pos[0], pos[1] - ascent/pango.SCALE, + layout) + if cursor != None: + (strong,weak) = layout.get_cursor_pos(cursor) + (x,y,width,height) = strong + self.page.window.draw_rectangle(self.colour_black, True, + pos[0] + x/pango.SCALE, + pos[1], 2,2) + def add_sym(self, sym): + if self.textstr == None: + self.textstr = "" + self.textcurs = 0 + if sym == "": + if self.textcurs > 0: + self.textstr = self.textstr[0:self.textcurs-1]+ \ + self.textstr[self.textcurs:] + self.textcurs -= 1 + elif sym == "": + if self.textcurs > 0: + self.textcurs -= 1 + elif sym == "": + if self.textcurs < len(self.textstr): + self.textcurs += 1 + else: + self.textstr = self.textstr[0:self.textcurs] + sym + \ + self.textstr[self.textcurs:] + self.textcurs += 1 + self.redraw() + + + def getsym(self): + alloc = self.page.get_allocation() + pagebb = BBox(Point(0,0)) + pagebb.add(Point(alloc.width, alloc.height)) + pagebb.finish() + + p = PPath(self.line[1][0], self.line[1][1]) + for pp in self.line[1:]: + p.add(pp[0], pp[1]) + p.close() + patn = p.text() + pos = pagebb.relpos(p.bbox) + tpos = "mid" + if pos < 3: + tpos = "top" + if pos >= 6: + tpos = "bot" + sym = self.dict.match(patn, tpos) + if sym == None: + print "Failed to match pattern:", patn + return sym + def refresh(self, area, ev): self.redraw() def redraw(self): @@ -176,10 +865,18 @@ class ScribblePad: else: col = self.colour_black st = l[1] - for p in l[2:]: - self.page.window.draw_line(col, st[0], st[1], - p[0],p[1]) - st = p + if type(l[2]) == list: + for p in l[2:]: + self.page.window.draw_line(col, st[0], st[1], + p[0],p[1]) + st = p + if type(l[2]) == str: + self.draw_text(st, col, l[2]) + + if self.textstr: + self.draw_text(self.textpos, self.colour, self.textstr, + self.textcurs) + return def back(self,b): @@ -207,6 +904,9 @@ class ScribblePad: self.colour = self.colour_black self.colourname = "black" return + def text_change(self,t): + self.flush_text() + return def undo(self,b): if len(self.lines) == 0: return @@ -300,6 +1000,7 @@ class ScribblePad: return def save_page(self): + self.flush_text() self.pages[self.names[self.pagenum]] = self.lines fn = self.page_dir + "/" + self.names[self.pagenum] if len(self.lines) == 0: -- 2.39.5