From: NeilBrown Date: Sun, 6 Feb 2011 09:28:40 +0000 (+1100) Subject: scribble: add program X-Git-Url: http://git.neil.brown.name/?a=commitdiff_plain;h=1ced2f1ee93188c147cf824005df7f5fba593ea1;p=freerunner.git scribble: add program Signed-off-by: NeilBrown --- diff --git a/scribble/Makefile b/scribble/Makefile new file mode 100644 index 0000000..6633266 --- /dev/null +++ b/scribble/Makefile @@ -0,0 +1,9 @@ + +install: + cp scribble.py /usr/bin + chmod a+rx /usr/bin/scribble.py + cp scribble.desktop /usr/share/applications + chmod a+r /usr/share/applications/scribble.desktop + cp scribble.png /usr/share/pixmaps/scribble.png + chmod a+r /usr/share/pixmaps/scribble.png + if [ -d $$HOME/Pages ] ; then : ; else cp -r Sample-Pages $$HOME/Pages; fi diff --git a/scribble/Sample-Pages/1 b/scribble/Sample-Pages/1 new file mode 100755 index 0000000..aea83a1 --- /dev/null +++ b/scribble/Sample-Pages/1 @@ -0,0 +1,19 @@ +"black":64,81:"Welcome to" +"black":72,159:62,162:45,170:34,178:36,192:48,201:63,207:79,214:92,226:97,245:95,263:83,278:65,287:51,289:50,278 +"black":146,200:136,204:122,212:120,241:131,246:144,249:159,243:169,235 +"black":177,206:188,221:193,231:186,211:186,199:199,187:209,183:219,182:234,181:238,191 +"black":255,186:261,196:270,213 +"black":263,111:270,142:276,170:280,186:284,199:286,209:292,224:292,213:293,203:294,192:299,178:317,187:317,201:307,210:292,202 +"black":321,103:321,119:327,156:330,176:332,193:333,207:337,194:345,175:357,171:365,182:360,197:333,194 +"black":371,96:371,108:374,124:376,138:380,154:382,171:387,195 +"black":416,182:421,171:433,161:429,151:417,155:407,168:405,183:410,193:422,196:454,183 +"red":433,211:423,212:405,214:393,215:380,218:367,221:355,225:344,230:332,233:319,237:307,241:295,245:283,250:258,258:235,264:224,267:214,270:201,273:189,276:177,279:153,285:142,289:129,293:117,296:106,300:96,303:79,310:63,316 +"black":92,384:91,396:92,417:91,429:89,417:88,400:90,385:94,371:102,360:113,357:123,359:131,371:129,383:123,394:112,402:100,403 +"black":133,403:139,414:137,401:148,390:162,390:173,395 +"black":174,411:189,407:200,404:208,394:198,388:186,391:178,403:182,418:195,426:208,426:221,422 +"black":253,398:240,394:227,396:232,406:243,410:225,421 +"black":288,405:264,404:275,413:286,418:276,428:259,431:249,431 +"red":359,349:370,359:381,370:391,377:404,391:399,401:389,407:379,411:366,418:356,425:346,431 +"red":94,506:"to continue" +"red":65,48:71,36:70,23:69,12:59,23 +"red":68,7:76,17:85,29 diff --git a/scribble/Sample-Pages/2 b/scribble/Sample-Pages/2 new file mode 100755 index 0000000..bc60b71 --- /dev/null +++ b/scribble/Sample-Pages/2 @@ -0,0 +1,23 @@ +"black":31,42:"Here is a page number" +"black":350,36:361,38:372,40:383,36:395,35:405,33:416,29:426,25:441,18:450,7:440,8:451,7:454,17:453,27 +"black":33,103:"Use" +"black":202,82:192,80:180,90:168,101:158,106:171,118:189,129:199,135 +"black":241,113:"To go back" +"black":92,161:106,168:116,174:128,183:140,193:134,204:124,214:113,225:103,234:93,242 +"black":176,198:"for next" +"black":58,259:54,274:58,291:65,303:79,308:91,303:101,264:101,254 +"black":112,276:117,289:120,300:124,289:138,284:149,295 +"black":192,283:182,282:172,285:170,296:180,298:190,290:192,279:191,265:186,246:183,234:186,251:189,262:190,273:192,284:194,294:197,304 +"black":217,291:226,280:236,278:247,287:245,299:226,305:213,294:215,284:225,280:241,280 +"black":58,349:67,359:71,371:70,360:71,350:77,338:90,335:102,337:112,342 +"black":123,352:134,355:146,355:156,354:163,343:151,338:135,340:129,356:141,371:167,375:184,372 +"black":224,336:222,346:208,350:201,364:208,376:218,372:225,357:225,337:222,321:224,342:227,353:230,363:235,373 +"black":261,361:262,349:274,343:285,348:288,359:272,374:258,369:258,359:264,349 +"black":65,406:68,395:69,409:71,425:73,436 +"black":86,412:76,414:58,416:46,416 +"black":110,436:"add page" +"black":96,499:84,499:73,500:63,500:53,502 +"black":121,510:"remove page" +"black":365,503:375,500:378,510:368,514:360,504:371,503 +"black":402,510:402,500:408,510:397,515:390,504:407,499 +"black":429,507:440,504:444,516:430,515:422,503:435,502 diff --git a/scribble/Sample-Pages/3 b/scribble/Sample-Pages/3 new file mode 100755 index 0000000..e3ddef8 --- /dev/null +++ b/scribble/Sample-Pages/3 @@ -0,0 +1,18 @@ +"black":117,45:122,57:108,51:96,48:82,53:70,62:57,74:47,86:41,100:40,117:41,128:48,146:56,156:66,165:78,171:110,177:133,169 +"black":158,119:"Clear page" +"black":195,159:"Before removal" +"black":83,235:84,224:80,250:79,270:78,289:78,319:79,330 +"black":165,229:149,223:128,220:105,218:60,213:46,213:36,213 +"black":120,308:130,303:143,298:156,306:154,320:139,329:129,326:127,308 +"black":186,297:175,305:168,315:178,318:189,315:199,304:201,321:203,332:169,367:162,357:165,344 +"black":241,304:229,298:219,306:216,319:230,325:241,320:251,310:251,299:252,310:257,340:256,362:250,376:236,379:224,372:221,361 +"black":271,227:271,240:274,255:275,271:277,291:278,308:279,318 +"black":299,322:309,320:328,305:318,300:303,313:333,335:361,322 +"black":219,392:222,409:224,426:226,437 +"black":273,405:261,398:217,395:204,394:193,393:179,394 +"black":249,426:261,424:271,424:268,413:252,418:247,431:263,439:285,441:297,441 +"black":295,424:311,427:322,434 +"black":324,420:312,441:302,455 +"black":354,373:349,388:350,400:351,414:353,427:354,444 +"black":380,414:366,405:355,401:343,400:332,398:322,397 +"black":66,492:"Tap to enable" diff --git a/scribble/scribble.desktop b/scribble/scribble.desktop new file mode 100644 index 0000000..94385cc --- /dev/null +++ b/scribble/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/scribble.png b/scribble/scribble.png new file mode 100644 index 0000000..a5b9cbc Binary files /dev/null and b/scribble/scribble.png differ diff --git a/scribble/scribble.py b/scribble/scribble.py new file mode 100755 index 0000000..c969052 --- /dev/null +++ b/scribble/scribble.py @@ -0,0 +1,1492 @@ +#!/usr/bin/env python + + +# scribble - scribble pad designed for Neo Freerunner +# +# Copyright (C) 2008 Neil Brown +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The GNU General Public License, version 2, is available at +# http://www.fsf.org/licensing/licenses/info/GPLv2.html +# Or you can write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Author: Neil Brown +# Email: + + +# TODO +# - index page +# list of pages +# Buttons: select, delete, new +# - bigger buttons - fewer +# Need: undo, redo, colour, text/line +# When text is selected, these change to: +# align, join, make-title + +import pygtk +import gtk +import os +import pango +import gobject +import time +import listselect + +########################################################### +# 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(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('s', "L(5)1,8.R(7)2,3", 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") + dict.add("", "S(4)2,6") + + +class DictSegment: + # Each segment has four elements: + # direction: Right Straight Left (R=cw, L=ccw) + # location: 0-8. + # start: 0-8 + # finish: 0-8 + # Segments match if the 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, div = 3): + # 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. + # Normally divisions are at 1/3 and 2/3. They can be moved + # by setting div e.g. 2 = 1/2 and 1/2 + (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 + + div1 = div - 1 + self.x1 = int((div1*minx + maxx)/div) + self.x2 = int((minx + div1*maxx)/div) + self.y1 = int((div1*miny + maxy)/div) + self.y2 = int((miny + div1*maxy)/div) + + 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.lower() < b.lower(): + return -1 + if a.lower() > b.lower(): + return 1 + if a < b: + return -1 + if a > b: + return 1 + return 0 + +def inc_name(a): + l = len(a) + while l > 0 and a[l-1] >= '0' and a[l-1] <= '9': + l -= 1 + # a[l:] is the last number + if l == len(a): + # there is no number + return a + ".1" + num = 0 + int(a[l:]) + return a[0:l] + ("%d" % (num+1)) + +class ScribblePad: + + def __init__(self): + window = gtk.Window(gtk.WINDOW_TOPLEVEL) + window.connect("destroy", self.close_application) + window.set_title("ScribblePad") + #window.set_size_request(480,640) + self.window = window + + vb = gtk.VBox() + vb.show() + self.draw_box = vb + + bar = gtk.HBox(True) + bar.set_size_request(-1, 60) + vb.pack_end(bar, expand=False) + bar.show() + + l = gtk.Label('Page Name') + vb.pack_start(l, expand=False) + l.show() + self.name = l + + 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) + + l.modify_font(fd) + + dflt = gtk.widget_get_default_style() + fd = dflt.font_desc + fd.set_absolute_size(25*pango.SCALE) + + self.pixbuf = None + self.width = 0 + self.height = 0 + self.need_redraw = True + self.zoomx = None; self.zoomy = None + + # Now the widgets: + + done = gtk.Button("Done"); done.show() + colbtn = gtk.Button("colour"); colbtn.show() + undo = gtk.Button('Undo') ; undo.show() + redo = gtk.Button('Redo') ; redo.show() + + done.child.modify_font(fd) + colbtn.child.modify_font(fd) + undo.child.modify_font(fd) + redo.child.modify_font(fd) + + bar.add(done) + bar.add(colbtn) + bar.add(undo) + bar.add(redo) + + colbtn.connect("clicked", self.colour_change) + undo.connect("clicked", self.undo) + redo.connect("clicked", self.redo) + done.connect("clicked", self.done) + + self.col_align = colbtn + self.undo_join = undo + self.redo_rename = redo + + self.page = page + self.colbtn = colbtn + self.line = None + self.lines = [] + self.hist = [] # undo history + self.selecting = False + self.textcurs = 0 + self.textstr = None + self.textpos = None + + + page.connect("button_press_event", self.press) + page.connect("button_release_event", self.release) + page.connect("motion_notify_event", self.motion) + page.connect("expose-event", self.refresh) + page.connect("configure-event", self.reconfigure) + page.connect("key_press_event", self.type) + page.set_events(gtk.gdk.EXPOSURE_MASK + | gtk.gdk.STRUCTURE_MASK + | gtk.gdk.BUTTON_PRESS_MASK + | gtk.gdk.BUTTON_RELEASE_MASK + | gtk.gdk.KEY_PRESS_MASK + | gtk.gdk.KEY_RELEASE_MASK + | gtk.gdk.POINTER_MOTION_MASK + | gtk.gdk.POINTER_MOTION_HINT_MASK) + page.set_property('can-focus', True) + + + # Now create the index window + # A listselect of all the pages, with a row of buttons: + # open delete new undelete + ls = listselect.ListSelect(center = False) + self.listsel = ls + + ls.connect('selected', self.page_select) + ls.set_colour('page','blue') + ls.set_zoom(38) + ls.show() + + vb = gtk.VBox() + vb.show() + self.list_box = vb + + vb.add(ls) + bar = gtk.HBox(True); bar.show() + bar.set_size_request(-1, 60) + b= gtk.Button("Open"); b.child.modify_font(fd); b.show(); bar.add(b); b.connect('clicked', self.open_page) + b= gtk.Button("Del"); b.child.modify_font(fd); b.show(); bar.add(b); b.connect('clicked', self.del_page) + b= gtk.Button("New"); b.child.modify_font(fd); b.show(); bar.add(b); b.connect('clicked', self.new_page) + b= gtk.Button("Undelete"); b.child.modify_font(fd); b.show(); bar.add(b); b.connect('clicked', self.undelete_pages) + + vb.pack_end(bar, expand=False) + + window.add(self.draw_box) + + window.set_default_size(480,640) + + window.show() + + + if 'HOME' in os.environ: + home = os.environ['HOME'] + else: + home = "" + if home == "" or home == "/": + home = "/home/root" + self.page_dir = home + '/Pages' + self.load_pages() + + colourmap = page.get_colormap() + self.colourmap = {} + self.colnames = [ 'black', 'red', 'blue', 'green', 'purple', 'pink', 'yellow' ] + for col in self.colnames: + c = gtk.gdk.color_parse(col) + gc = page.window.new_gc() + gc.line_width = 2 + gc.set_foreground(colourmap.alloc_color(c)) + self.colourmap[col] = gc + self.reset_colour = False + + self.colour_textmode = self.colourmap['blue'] + + self.colourname = "black" + self.colour = self.colourmap['black'] + self.colbtn.child.set_text('black') + self.bg = page.get_style().bg_gc[gtk.STATE_NORMAL] + + self.dict = Dictionary() + LoadDict(self.dict) + self.textstr = None + + + ctx = page.get_pango_context() + fd = ctx.get_font_description() + met = ctx.get_metrics(fd) + self.lineheight = (met.get_ascent() + met.get_descent()) / pango.SCALE + self.lineascent = met.get_ascent() / pango.SCALE + + self.timeout = None + + window.remove(self.draw_box) + window.add(self.list_box) + + + def close_application(self, widget): + self.save_page() + gtk.main_quit() + + def load_pages(self): + try: + os.mkdir(self.page_dir) + except: + pass + self.names = os.listdir(self.page_dir) + if len(self.names) == 0: + self.names.append("1") + self.names.sort(page_cmp) + self.pages = {} + self.pagenum = 0 + self.load_page() + self.update_list() + + def update_list(self): + l = [] + for p in self.names: + l.append([p,'page']) + self.listsel.list = l + self.listsel.list_changed() + self.listsel.select(self.pagenum) + return + + def page_select(self, list, item): + if item == None: + return + self.pagenum = item + + def open_page(self, b): + self.load_page() + self.window.remove(self.list_box) + self.window.add(self.draw_box) + + def done(self, b): + self.flush_text() + self.save_page() + self.window.remove(self.draw_box) + self.window.add(self.list_box) + + def del_page(self, b): + pass + + def new_page(self, b): + newname = self.choose_unique(self.names[self.pagenum]) + self.names = self.names[0:self.pagenum+1] + [ newname ] + \ + self.names[self.pagenum+1:] + self.pagenum += 1; + self.update_list() + self.listsel.select(self.pagenum) + + return + + def undelete_pages(self, b): + pass + + + def type(self, c, ev): + if ev.keyval == 65288: + self.add_sym('') + elif ev.string == '\r': + self.add_sym('') + else: + self.add_sym(ev.string) + + def press(self, c, ev): + # Start a new line + if self.timeout: + gobject.source_remove(self.timeout) + self.timeout = None + self.taptime = time.time() + self.movetext = None + c.grab_focus() + self.movetimeout = None + self.selecting = False + if self.selection: + self.selection = None + self.need_redraw = True + self.name_buttons() + self.redraw() + + self.line = [ self.colourname, [int(ev.x), int(ev.y)] ] + return + def release(self, c, ev): + if self.movetimeout: + gobject.source_remove(self.movetimeout) + self.movetimeout = None + + if self.movetext: + self.movetext = None + self.line = None + self.need_redraw = True + self.redraw() + return + if self.line == None: + return + + if self.selecting: + self.selecting = False + self.line = None + return + if self.timeout == None: + self.timeout = gobject.timeout_add(20*1000, self.tick) + + if len(self.line) == 2: + # just set a cursor + need_redraw = ( self.textstr != None) + oldpos = None + if not self.textstr: + oldpos = self.textpos + self.flush_text() + if need_redraw: + self.redraw() + (lineno,index) = self.find_text(self.line[1]) + + if lineno == None: + # new text, + pos = self.align_text(self.line[1]) + if oldpos and abs(pos[0]-oldpos[0]) < 40 and \ + abs(pos[1] - oldpos[1]) < 40: + # turn of text mode + self.flush_text() + self.line = None + self.need_redraw = True + self.setlabel() + return + self.textpos = pos + self.textstr = "" + self.textcurs = 0 + # draw the cursor + self.draw_text(pos, self.colour, "", 0) + self.line = None + else: + # clicked inside an old text. + # shuffle it to the top, open it, edit. + ln = self.lines[lineno] + self.lines = self.lines[:lineno] + self.lines[lineno+1:] + self.textpos = ln[1] + self.textstr = ln[2] + if ln[0] in self.colourmap: + self.colourname = ln[0] + else: + self.colourname = "black" + self.colbtn.child.set_text(self.colourname) + self.colour = self.colourmap[self.colourname] + self.textcurs = index + 1 + self.need_redraw = True + self.redraw() + self.setlabel() + self.line = None + return + if self.textstr != None: + sym = self.getsym() + if sym: + self.add_sym(sym) + else: + self.redraw() + self.line = None + self.reset_colour = True + return + + self.lines.append(self.line) + self.line = None + self.reset_colour = True + self.need_redraw = True + return + def motion(self, c, ev): + if self.line: + if ev.is_hint: + x, y, state = ev.window.get_pointer() + else: + x = ev.x + y = ev.y + x = int(x) + y = int(y) + prev = self.line[-1] + if not self.movetext and abs(prev[0] - x) < 10 and abs(prev[1] - y) < 10: + return + if not self.movetext and len(self.line) == 2 and time.time() - self.taptime > 0.5: + self.flush_text() + (lineno, index) = self.find_text(prev) + if lineno != None: + self.movetext = self.lines[lineno] + self.moveoffset = [prev[0]-self.movetext[1][0], prev[1]-self.movetext[1][1]] + if self.movetext: + self.movetext[1] = [x-self.moveoffset[0],y-self.moveoffset[1]] + self.need_redraw = True + self.redraw() + return + + if self.movetimeout: + gobject.source_remove(self.movetimeout) + self.movetimeout = gobject.timeout_add(650, self.movetick) + + if self.textstr != None: + c.window.draw_line(self.colour_textmode, prev[0],prev[1],x,y) + else: + c.window.draw_line(self.colour, prev[0],prev[1],x,y) + self.line.append([x,y]) + return + + def movetick(self): + # longish pause while drawing + self.flush_text() + self.selecting = True + self.need_redraw = True + self.redraw() + + def tick(self): + # nothing for 20 seconds, flush the page + self.save_page() + gobject.source_remove(self.timeout) + self.timeout = None + + def find_text(self, pos): + x = pos[0]; y = pos[1] + self.lineascent + for i in range(0, len(self.lines)): + p = self.lines[i] + if type(p[2]) != str: + continue + if x >= p[1][0] and y >= p[1][1] and y < p[1][1] + self.lineheight: + # could be this line - check more precisely + layout = self.page.create_pango_layout(p[2]+' ') + (ink, log) = layout.get_pixel_extents() + (ex,ey,ew,eh) = log + if x < p[1][0] + ex or x > p[1][0] + ex + ew or \ + y < p[1][1] + ey or \ + y > p[1][1] + ey + self.lineheight : + continue + # OK, it is in this one. Find out where. + (index, gr) = layout.xy_to_index((x - p[1][0] - ex) * pango.SCALE, + (y - p[1][1] - ey - self.lineheight) * pango.SCALE) + if index >= len(p[2]): + index = len(p[2])-1 + return (i, index) + return (None, None) + + def align_text(self, pos): + # align pos to existing text. + # if pos is near one-line-past a previous text, move the exactly + # one-line-past + x = pos[0]; y = pos[1] + self.lineascent + for l in self.lines: + if type(l[2]) != str: + continue + if abs(x - l[1][0]) > self.lineheight: + continue + if abs(y - (l[1][1] + self.lineheight)) > self.lineheight: + continue + return [ l[1][0], l[1][1] + self.lineheight ] + return pos + + def flush_text(self): + if self.textstr == None: + self.textpos = None + return + self.setlabel() + if len(self.textstr) == 0: + self.textstr = None + self.textpos = None + return + l = [self.colourname, self.textpos, self.textstr] + self.lines.append(l) + self.need_redraw = True + self.textstr = None + self.textpos = None + + def draw_text(self, pos, colour, str, cursor = None, drawable = None, selected=False): + if drawable == None: + drawable = self.page.window + layout = self.page.create_pango_layout(str) + if self.zoomx != None: + xs = 2; xo = -self.zoomx + ys = 2; yo = -self.zoomy + else: + xs = 1 ; xo = 0 + ys = 1 ; yo = 0 + (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents() + if len(pos) == 2 or pos[2] != ew or pos[3] != eh: + while len(pos) > 2: + pos.pop() + pos.append(ew) + pos.append(eh) + if selected: + drawable.draw_rectangle(self.colourmap['yellow'], True, + ex+pos[0], ey+pos[1]-self.lineascent, ew, eh) + drawable.draw_layout(colour, pos[0]*xs+xo, + (pos[1] - self.lineascent)*ys + yo, + layout) + if cursor != None: + (strong,weak) = layout.get_cursor_pos(cursor) + (x,y,width,height) = strong + x = pos[0] + x / pango.SCALE + y = pos[1] + drawable.draw_line(self.colour_textmode, + x*xs+xo,y*ys+yo, (x-3)*xs+xo, (y+3)*ys+yo) + drawable.draw_line(self.colour_textmode, + x*xs+xo,y*ys+yo, (x+3)*xs+xo, (y+3)*ys+yo) + + def add_sym(self, sym): + if self.textstr == None: + return + 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 + elif sym == "": + tail = self.textstr[self.textcurs:] + self.textstr = self.textstr[:self.textcurs] + oldpos = self.textpos + self.flush_text() + self.textcurs = len(tail) + self.textstr = tail + self.textpos = [ oldpos[0], oldpos[1] + + self.lineheight ] + 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(div = 2) + + 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 setlabel(self): + if self.textstr == None: + self.name.set_label('Page: ' + self.names[self.pagenum] + ' (draw)') + else: + self.name.set_label('Page: ' + self.names[self.pagenum] + ' (text)') + + def name_buttons(self): + if self.selection: + self.col_align.child.set_text('Align') + self.undo_join.child.set_text('Join') + self.redo_rename.child.set_text('Rename') + else: + self.col_align.child.set_text(self.colourname) + self.undo_join.child.set_text('Undo') + self.redo_rename.child.set_text('Redo') + + def redraw(self): + self.setlabel() + + if self.need_redraw: + self.draw_buf() + self.page.window.draw_drawable(self.bg, self.pixbuf, 0, 0, 0, 0, + self.width, self.height) + + if self.textstr != None: + self.draw_text(self.textpos, self.colour, self.textstr, + self.textcurs) + + return + def draw_buf(self): + if self.pixbuf == None: + alloc = self.page.get_allocation() + self.pixbuf = gtk.gdk.Pixmap(self.page.window, alloc.width, alloc.height) + self.width = alloc.width + self.height = alloc.height + + self.pixbuf.draw_rectangle(self.bg, True, 0, 0, + self.width, self.height) + if self.zoomx != None: + xs = 2; xo = -self.zoomx + ys = 2; yo = -self.zoomy + else: + xs = 1 ; xo = 0 + ys = 1 ; yo = 0 + self.selection = [] + for l in self.lines: + if l[0] in self.colourmap: + col = self.colourmap[l[0]] + else: + col = self.colourmap['black'] + st = l[1] + if type(l[2]) == list: + for p in l[2:]: + self.pixbuf.draw_line(col, st[0]*xs + xo, st[1]*ys + yo, + p[0]*xs + xo,p[1]* ys + yo) + st = p + if type(l[2]) == str: + # if this text is 'near' the current line, make it red + selected = False + if self.selecting and self.line and len(st) == 4: + for p in self.line[1:]: + if p[0] > st[0] and \ + p[0] < st[0] + st[2] and \ + p[1] > st[1] - self.lineascent and \ + p[1] < st[1] - self.lineascent + st[3]: + selected = True + break + if selected: + self.selection.append(l) + self.draw_text(st, col, l[2], drawable = self.pixbuf, + selected = selected) + self.need_redraw = False + self.name_buttons() + + + def reconfigure(self, w, ev): + alloc = w.get_allocation() + if self.pixbuf == None: + return + if alloc.width != self.width or alloc.height != self.height: + self.pixbuf = None + self.need_redraw = True + + + def colour_change(self,t): + if self.selection: + # button is 'join' not 'colour' + return self.realign(t) + + if self.reset_colour and self.colourname != 'black': + next = 'black' + else: + next = 'black' + prev = '' + for c in self.colnames: + if self.colourname == prev: + next = c + prev = c + self.reset_colour = False + self.colourname = next + self.colour = self.colourmap[next] + t.child.set_text(next) + if self.textstr: + self.draw_text(self.textpos, self.colour, self.textstr, + self.textcurs) + + return + def text_change(self,t): + self.flush_text() + return + def undo(self,b): + if self.selection: + return self.join(b) + + if len(self.lines) == 0: + return + self.hist.append(self.lines.pop()) + self.need_redraw = True + self.redraw() + return + def redo(self,b): + if self.selection: + self.rename(self.selection[0][2]) + return + if len(self.hist) == 0: + return + self.lines.append(self.hist.pop()) + self.need_redraw = True + self.redraw() + return + def choose_unique(self, newname): + while newname in self.names: + new2 = inc_name(newname) + if new2 not in self.names: + newname = new2 + elif (newname + ".1") not in self.names: + newname = newname + ".1" + else: + newname = newname + ".0.1" + + return newname + def delete(self,b): + # hack + if self.selection: + return self.join(b) + self.flush_text() + if len(self.names) <= 1: + return + if len(self.lines) > 0: + return + self.save_page() + nm = self.names[self.pagenum] + if nm in self.pages: + del self.pages[nm] + self.names = self.names[0:self.pagenum] + self.names[self.pagenum+1:] + if self.pagenum >= len(self.names): + self.pagenum -= 1 + self.load_page() + self.need_redraw = True + self.redraw() + + return + + def cmplines(self, a,b): + pa = a[1] + pb = b[1] + if pa[1] != pb[1]: + return pa[1] - pb[1] + return pa[0] - pb[0] + + def realign(self, b): + self.selection.sort(self.cmplines) + x = self.selection[0][1][0] + y = self.selection[0][1][1] + for i in range(len(self.selection)): + self.selection[i][1][0] = x + self.selection[i][1][1] = y + y += self.lineheight + self.need_redraw = True + self.redraw() + + def join(self, b): + self.selection.sort(self.cmplines) + txt = "" + for i in range(len(self.selection)): + if txt: + txt = txt + ' ' + self.selection[i][2] + else: + txt = self.selection[i][2] + self.selection[i][2] = None + self.selection[0][2] = txt + i = 0; + while i < len(self.lines): + if len(self.lines[i]) > 2 and self.lines[i][2] == None: + self.lines = self.lines[:i] + self.lines[i+1:] + else: + i += 1 + self.need_redraw = True + self.redraw() + + + def rename(self, newname): + # Rename current page and rename the file + if self.names[self.pagenum] == newname: + return + self.save_page() + newname = self.choose_unique(newname) + oldpath = self.page_dir + "/" + self.names[self.pagenum] + newpath = self.page_dir + "/" + newname + try : + os.rename(oldpath, newpath) + self.names[self.pagenum] = newname + self.names.sort(page_cmp) + self.pagenum = self.names.index(newname) + self.setlabel() + except: + pass + self.update_list() + + def setname(self,b): + if self.textstr: + if len(self.textstr) > 0: + self.rename(self.textstr) + + def clear(self,b): + while len(self.lines) > 0: + self.hist.append(self.lines.pop()) + self.need_redraw = True + self.redraw() + return + + def parseline(self, l): + # string in "", or num,num. ':' separates words + words = l.strip().split(':') + line = [] + for w in words: + if w[0] == '"': + w = w[1:-1] + elif w.find(',') >= 0: + n = w.find(',') + x = int(w[:n]) + y = int(w[n+1:]) + w = [x,y] + line.append(w) + return line + + def load_page(self): + self.need_redraw = True + nm = self.names[self.pagenum] + if nm in self.pages: + self.lines = self.pages[nm] + return + self.lines = []; + try: + f = open(self.page_dir + "/" + self.names[self.pagenum], "r") + except: + f = None + if f: + l = f.readline() + while len(l) > 0: + self.lines.append(self.parseline(l)) + l = f.readline() + f.close() + return + + def save_page(self): + t = self.textstr; tc = self.textcurs + self.flush_text() + self.pages[self.names[self.pagenum]] = self.lines + tosave = self.lines + if t and len(t): + # restore the text + ln = self.lines[-1] + self.lines = self.lines[:-1] + self.textpos = ln[1] + self.textstr = ln[2] + self.textcurs = tc + + fn = self.page_dir + "/" + self.names[self.pagenum] + if len(tosave) == 0: + try: + os.unlink(fn) + except: + pass + return + f = open(fn, "w") + for l in tosave: + start = True + if not l: + continue + for w in l: + if not start: + f.write(":") + start = False + if isinstance(w, str): + f.write('"%s"' % w) + elif isinstance(w, list): + f.write("%d,%d" %( w[0],w[1])) + f.write("\n") + f.close() + +def main(): + gtk.main() + return 0 +if __name__ == "__main__": + ScribblePad() + main()