]> git.neil.brown.name Git - freerunner.git/commitdiff
scribble: add program
authorNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 09:28:40 +0000 (20:28 +1100)
committerNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 09:28:40 +0000 (20:28 +1100)
Signed-off-by: NeilBrown <neilb@suse.de>
scribble/Makefile [new file with mode: 0644]
scribble/Sample-Pages/1 [new file with mode: 0755]
scribble/Sample-Pages/2 [new file with mode: 0755]
scribble/Sample-Pages/3 [new file with mode: 0755]
scribble/scribble.desktop [new file with mode: 0644]
scribble/scribble.png [new file with mode: 0644]
scribble/scribble.py [new file with mode: 0755]

diff --git a/scribble/Makefile b/scribble/Makefile
new file mode 100644 (file)
index 0000000..6633266
--- /dev/null
@@ -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 (executable)
index 0000000..aea83a1
--- /dev/null
@@ -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 (executable)
index 0000000..bc60b71
--- /dev/null
@@ -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 (executable)
index 0000000..e3ddef8
--- /dev/null
@@ -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 (file)
index 0000000..94385cc
--- /dev/null
@@ -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 (file)
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 (executable)
index 0000000..c969052
--- /dev/null
@@ -0,0 +1,1492 @@
+#!/usr/bin/env python
+
+
+# scribble - scribble pad designed for Neo Freerunner
+#
+# Copyright (C) 2008 Neil Brown <neil@brown.name>
+#
+#
+#    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: <neil@brown.name>
+
+
+# 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('<BS>', "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("<left>", "S(4)5,3.S(3)3,5")
+    dict.add("<right>","S(4)3,5.S(5)5,3")
+    dict.add("<newline>", "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('<BS>')
+        elif ev.string == '\r':
+            self.add_sym('<newline>')
+        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 == "<BS>":
+            if self.textcurs > 0:
+                self.textstr = self.textstr[0:self.textcurs-1]+ \
+                               self.textstr[self.textcurs:]
+                self.textcurs -= 1
+        elif sym == "<left>":
+            if self.textcurs > 0:
+                self.textcurs -= 1
+        elif sym == "<right>":
+            if self.textcurs < len(self.textstr):
+                self.textcurs += 1
+        elif sym == "<newline>":
+            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()