From 1ced2f1ee93188c147cf824005df7f5fba593ea1 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sun, 6 Feb 2011 20:28:40 +1100 Subject: [PATCH] scribble: add program Signed-off-by: NeilBrown --- scribble/Makefile | 9 + scribble/Sample-Pages/1 | 19 + scribble/Sample-Pages/2 | 23 + scribble/Sample-Pages/3 | 18 + scribble/scribble.desktop | 12 + scribble/scribble.png | Bin 0 -> 10593 bytes scribble/scribble.py | 1492 +++++++++++++++++++++++++++++++++++++ 7 files changed, 1573 insertions(+) create mode 100644 scribble/Makefile create mode 100755 scribble/Sample-Pages/1 create mode 100755 scribble/Sample-Pages/2 create mode 100755 scribble/Sample-Pages/3 create mode 100644 scribble/scribble.desktop create mode 100644 scribble/scribble.png create mode 100755 scribble/scribble.py 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 0000000000000000000000000000000000000000..a5b9cbc338e840319b681a3f67209c581c6f891f GIT binary patch literal 10593 zcmcgy^;cA3*BwCVl5RwlP+B^b6ln>`K|qF(?vRp{mhNr^q+y5wk?u}O>1ODBAOC=F zt@np_eTy}Vi964I?z!jez0W@LRaIFI51R@b0)gPkzma|ifuMk=C=g6^@K4{N2=?!l z<2yM?NZBCG7I=YftRN>1x&QZ<)m#`0-odhaqvHsHkfr{6pb(Eww1YP>oaB{cFs3mn z(C8oWbVvVNhSEt!+eymS+S=5{2_ofSYUE`4@v*Cgllfygc_r2Ne)!}N$YY4S^lJ^b z*}XsRpEV|K?~kU^lSLFXIR^Zr-Fh>SxX;CWZL#z z#)eg+=GKl?^ssR{jBEVxl@nvHt~?O6&nB#x*m!9se&p;34-5?O^6}^F=+-56z;K?h zvC(wAN`L3FJ)RNE{8l|Rb+4#|QbrBl{0d&S>d?TII)JPERubtUr1kDgP-IVD&V`Y` zg_*F1wDf}`EG%CBamR~81{!<}c)=y&`~@~_LeWZ}MqVmt)T&PX-MjD+^IzsX&YtP+ zqBiq30{*cB^RtmLVz>Z#11r8M3qW#h*X?J~6wuW?^Bm9CmwMBy8}tOvb2guHmx=kJ@c+ zV{;kR_Rh@prI|*$xED-sJDfK0?HltVpW65`bse3yY&65+@##0#*6fdNuego>t|hT2 z*szC9?W}3bb2Gn3)iBql0G9;r`j;F+GCF0uHzA9)u{kW#=TQY0-H*}+$x`-Q z(4R5oqzwFllI4!PQM6*iKwO|E_C*xe_y*PH9W(p4g_Jc8JLARnY@YK_;+pL|Y=Yvc zE?N0&p)#Njjz^7N*~VHPe@ahQ+)iv~%&#fC(F6;H6ntW#;G()+Od#<`SyS{fhX;-1 zU6m@z`ae<{)1B*g$75}Mpq1?8WFR*+c+*JS=i+jg+wPoq-8gvCI^*rJ5#O(L<1EWuGq*=0=0OtH@#}E8-K)IF@%itmk|U4ixVX6C;o;VwM(vnbSQc}! zuGN*i26AJ~Zq08S39OyLQ(h3vl!9!Q(#(-vJG+ZRBiX{d3k}=!k&rZHW=%~^5Zlq| z={J&+l4d79nRDtE7Oc!F86U@s)TE`Qo$6U=SrZB@Xhd9aL3x6Mx@Je;|C*f|8g_0j zy>+V2Oct)(80L+Li7^(x_`uAc8o_PS=~Se)ZOEEeo|#+nJU>7G^KlQeVw&JLuvdKR zdd{%C8afmfDi4tti0bM%M!6^=Iyz-3FEn(2l+*!PXJ@`OQc`_!hddSPlX>Ce)9$>A zif?Eb_=NQILp`y~U!$T9;u!f2dsj|9ggj1I@bK`$DPMM;*E&JX#=JNb?CrT9K76QM zsx>+~O2PB-%|`Vz`X~svR8mq>X<1pK&+BoYnqP1($RjO2<`cZ>ICvZ zpTle}?F(nH?uJ$ir|GCOZE!B764JOv7Yh#$z)Vf#qSp+XTl2T9v+pamH&4A$Gm*&Hdt@vLoFB{QXDlAl4 z%Xe*9o7T-0mzH)|X{l+(s+ih07?_()(kIR0t29G1iCCXJ`4Wh;J~lk;o16bIs>^?* zD?s%823}_2Cbnl-zdXUKD9gC2uKqP7r1c|D`qWpinVn2A;Sp!NJw?hny*+e)NjGf@ zj=I#)-nWvHU+f#_(}dlzmBxmfpuF_kd-H)$;boWA)6cqLj0_BZvnf=@#wK1+uCdwH z)7s{;_0#G`r7ieimY@I687o@ddPjbWp~)~#YplnD-JR3ixoVp6k5fz4XGdqw_WBO0 zblGvK`APh9F7(ODyT>&SpI}N961$xI+}ll>NhHDx`6^^PuZ+>z zLr!Cy$*S%t!7a{#ugX=-^y}9zu2fAcerhEprBW0l-rB&haz}%df=ey4P)P>|o-|6m zEBNgo`~A#b;ic-i{gFMbrCj<<0wf0mck`q+&q6oY+fc68!NwqI@cUzlGP|)MlKu~> zv-~aZ$AmSO)71*949nAK6*8>H_h?YlJiWy^_}817%X3()+g8+BPs@2rlr8i2N?#lZ zdFIuohtki+c^BNWSmQwnqHFUqUGrwY<$jG6bQMWflqIH%N`*DjR?YY*`2DdF0I#B> z2}wG9n~Sj$$G(o3pXen6J_$u-iv0$7EsY%?5GcDV%w+pbVU>0%1ui-p?4`4Fjdtmz zvbcfsRzyj0#iPFDgL4nD>C}{;+HiN)!{i3Sy1BpsV-%UT5v#75(Um&Vv{D5>JzbHs zk$@TFg6uN13_&cT1lwrUMpGS$>Gy82|lIpH++tnLOTEfrLr=? ztDKPFaMJzght_w)$$2-vQfKzY5+W+9USAaMG(oku*475EeyFGpvQ>YL2z-vh-!kF}ghXiDtF5C| z5_+-iI&e3HBa|YFn1}&ro0}z|&+WNrIG&e27xRpR*;W!idgM0%lM*t}X(%i%=B~+U zN?V6D8dBAvYM1ioU`P|wjS%?4XLX;ycwsNBcQe|C8!Bn{1{2T-;KA2pyi`=%#l^)( z?vlk-XpdP01Co*`G&BLusv2m@I6ISaNa*_&mOTIV?VI#zl&q%au98w*V&a&U?Xd-2 z7Nd2KaRrgs2M8dwY?j{ST8)uf@-sO-Y;1g3t8daIWVlHq-R6&$jq5qo?-MTtCJuqw zD@9zb@1MKkJl*a+l;OQF(Cc{%m03i`X5)%u;5b(hF|Mz2*$C{7- zyOV}Q3VpudvZ>R14)*_`a!BGnZQ}dluQdnfSu;9er;s^Ygnv%Xlj09pkVoD8HnYEN zGw$&~$){g60IqOrhW`76LE4r3a#f+ucCpR#pn1wPvz!HcYpW`m%jk=h(MUmAS>%+V zkN@{Fc<4y3dY-UFEXbahCr}=&lfzStmkTv()AEhy1G=MIo)K0 zzoOvQM8m5`KfQ0!e=$Dk?(dEs_=-pT*3kL_oOGbYT`ua?O5ykEd9*z)RKE9*PP*SV;`mYah){(!ELnb)bFV_e<;Evg16;g731u`uLHg zc(qd=4`*ZwHLIm10~8Gv6;*yv;N~Zk=)}yS3>9$(LBThc+954u5SPaEfB-kxU>YkQ zo3tSgL`Ln6ynLHE?_2QoMAR%eJVne?oB{R{ulgZuWMSh! z3B7DEKX7pw-MCSrVvdlbwZdwQxw`*!25hRMWbFuhp5LWn5i@fslc8WeIuv$hwR3cQ z`F+s?$4mhSsFMd67z+PR6$FWhhzO??32O7lP}svBA>RO*?ZpLI@VWQebrE@Q=o~-( zgV2i|a<(@YJht_ek5AK53lp62e^&z1t`B@i%%#;@x14fz;NxY4UaSxxGBC05m(2(y zB%x5cu1MPUol_9#5pvWI-O<_osS@iKd$zZ?LqbBLLpNFFAdr$0z~HQ$2Xl_?O4rMe{J?W?@fEBB8YlX5^&+>eb?RI zbbog(dec4RzP(kbQ~yDHW@ZKmj9ivbD-?S_ka*rleTEL(8k9bFNJCXk;`2pU`YC-! zJann4jQZI|S5CcJo71i4*RNlHG&3V6r37ph2Z4aieYm^13xV)~Mu+Z%3!e)Mbd5HY__3LuOhIe}q9(59zktnRHL7`=6&joq8k%??R@QT_g z)0^Etf3K-o;c=?hbUgKby563VK;nk0J(wUCBKYF@^H7n4CjaVcozI7D$}R^B11j`a zl(PPQFPN29;Q0#O-QCd%oP&;Qr0=Y{`e72Rv7!<&?`P|UVn6K ztU{Y@bi2cyjJH)lyH zc=);b3e*c|Npoc*j6YjGpW-we-2dYV!t5>M=vYdhc zzwF>FDlOfsjy$`=ppQ0GeKm5n=T!hz@_QFOk}LO;Su7pg#-71J7+A2R96{vM{1Cg{M7l?b$_|egK32e}Q()aqWt*oqq*D*pD*o}XO zd{aynRzl5c)G5{4Z`iPD5w!E2vuTXj-L)cP*OlI`xA$g^>RVfxQW5Kzvn{tc7uq~3 zE(RI)h?u%>oK+ouGp=c9R8g&rjoP2W7khExCVqEe0(G;02B3gVr!wjKps`$v;a^Mq z`E%L>UkQ-Xj(r0H>iYVMMn(*Cb+#K7q%V?$oh|DU*AAB%|E`xYI?oH1)vtuQG@rNn zp^3tcA68UUfOHDhLm+9dRd+`QxhT4PI|T*+RbkY&^{+=Onb83}d>qNKg2+O}W6UzWBFUlpUrS2%_5)}~ z=I8qncc+}n>B1fU1mdkR9bt1~PJ#}9F#`hw0Tujo>KP3~Z*B3q4vC6F{537VKHD=H zn63G{wx+J8rmU#AE8rBvMMO*tI+4Bj2#~}Yn$KRoM2qTb&rmr#Tn?spVS&V;6C^+| zAK-(d^$4Nq|u zAB()$q28e3s6igg)`Wp%t#aBT${hSvug?J*90JgW0SRSDxR2ZzG8@kFOH%Zk*}Xd5 zk^A`ZW2SDlCHCK61L-e=;j_Y8%{M%p_ggBEyh!S<>a8(tE+27F&FOdP82B_F$JPhf z8ux{5>W_l(BbZ}jW9Pms8C!m`v=cTU_Zk?Z^?k2V4flik{+Z>=DDzc&QJSQ*#vgOPP{2YdOPdV z{QSVmN}lH`nX*%-_<^r9HAzcKh)77Dq6|Mm_xIZ=Hyj)?^w^{MW-b2x#oXeZvX_@wiRBY_4W7`P!#&%3@+o!$kA>*YWjnjYTY{gi z2T;sbNcRxuht&e5dBA+M;;7wyegA^{@grLtWD^%e?I{xzkNt`?p!DvZfwhaH)tfs4 zM4??D8JVFcdlhWpDGyJj)S|jZb^7+UWro)oCCCs;LPT>;&X@QXJ_^eT?DMV%&GUBc zcqkRBy~}n#T?TZYJb9vByDQuoLED$-h>HAfdg3Pfkxjq5Q;Ay0?U0aBR-a7O`bSAg zDBy(J{H>Of;^Jiu9ZEGdHHQboGi+oyBO@c9&nNXpoYQ~)0=00hk9{2s*(9Nct1a8q$ao?VCZyf8q z_nh^8Lw2%a-hlGIIAFz7I+2MJ22wpG%F1DBRQfbPJsxTH5nW=2C()A4WjHy4BiBFnew z=5x@~XP3@@?iTOQeR_I&OxODCOfyX)dvzKQ9N!R&BzVUOjT@kWgV zu>ShkQyq16;#2`^CeU;C_bVcls@DLvT2Ayq*bLyjOPx0xvxs{feyjO^c2{}5tCX?v zlVr+dfU>~OU8b|AMf9V5_7CknGicFMS8vAbA!hr7=SY&yT{ zD@@Q@GMb^B$&{4d5+3uIY^;Kd z*5orK^lkXyM&^O($tH#5%_Z|Uz^ zS5-t&TK~$&YBycz0<{U>g$58o(jNl@M#ypWa+TiDa_UVh%nN$xBM>?rX>XiKaF zsgmNf=DyJ{l3G$i5xz&CeI?9lQKOqI>dv#;8DTP4>ndb-*qXdN=)DiWrrU4X;Q*Hv zp2GsP+)0RxI8f}v3lXD$vnpat!r#)ZO||<#CdR9%#-}SxzD7js#SDo%oUn`)Dt$Vy zp^*2-czJx>Cvx&f>wEkqa)aCFPBiPZ1;=&Xo;gbFjN<;HOX8JZzCuDwe0)bVgFF#L zOh;K6U&%!TP)5ATk_#pQfteNgfI8sa$)@I@+>Va8_;>?5&1C^0yCn$Fp1D<-^d!JQ z&yfiWiMZz3#m2^-*4OTCYH$P&A0_}&LlJi?UMjRZ8&xlUNN-G9M*0(1N=e%v|?>yG~lNnJ6iy|(@R3ugt^lX@F$%l?z0`v0PaB;G73szQ)kCDg* zhta1%;!UqD*4Ztif(G$nYgv@b(V&QsTk`@AbmPqJBPQXdaUSwIFEpv+&Mnhu7a_zE9l2&?+6D2XUAk&avlA` zO%L@|NI5qEsxr8xu>0jDD7%+H%8r^1wd9?*=xO{8rr&?U!NIXq-kb>8G`6p>4+spb zv|SXRsT2ztfH4XQCGX9JU3?s(Q;tv1DJWQ%Xc<~_sPMk^u;QP4_Qyv>Q*(3_<~%F* z)YBhInrvZF)ooB0S(xFGvJ_2NaJlUF*;7s53bN zJT^F%kq7N3-Nd9G+AB4Z^}+N$1J~OC;4D|#7-U#2(@{WVWMp`6gRgPTQ#vDQ57e}^ zyD^APh#(s&FU|0g>jSx!m7@pbmmy~W>He;5Ah*AluedZKj+{aJd-DZVR-gY17K%5L z1k2Y3#!J;R*t-&|BC$4Omf37BMMS%3a&WcDC~8Hejcq}wEh$8#P0)hi$lajiDY>S-pT7EbC&v5{WaC!M~-f1jCzs{-H z>H(H7P9SiOO}9xX>0K(avK|*GrS~4(A7Y^6z5Uz!!pk$Xs>9f5E9MylcyUS7)7qg5 zp`gfK&3y_DQ*g6idkcz32mTR@K-A8syn*rC1K-VWBr-bCu7rM!aI!H z#{HOI;L(ex0cR#+Bf~2aCi^HRgO49_xZf_3_y52a!{n@tfoZA6u>w60UJ7+MHTEc1H0sV z`|;@W@L+=MuFhruZyG}V+}l0ot6WmjB0Laxvd%X4q9)=z{=Y5PanJ8d}W?#JYO9B z*^y5x<9)+j+m+dRc7wxmdJ4Xkjq$^IQxyVwyw!Zo?=5H9m||+ib9qbcyYmgPiCU#O zO;Ks(%5UEqU!7Pu)X^Bai55`{(tqit78N%;o&Vci@^+_f$J zmgezjT^`IgPbX=56x;JV7IDW1Ef2=nM`QwexV%Zet2gxWWQTrc*U?FS@bKYj3%+Gt zuA68QHV*gR;^JlI;&_1jo=B<5T@uAfNC=%eA1$^oXmN3Ac>*G+1prTe4o7haR1YTD z2$xOZG?dlRq2w&n?^$jS=BD_3ZHEB4tz&vitR` zF5qTgxWxs(34lN_fByR|z`0GP1oM+8D>*OMwY~?M_9e!qrLhH%(q>yE@!NK4>(0*p zlvs#>o8quw3#QkTv$dut1ig5H)9iE4!TN=65?Q9Yd#K}!Ma;xyy{!Sn_qX_Xd60AE zlyW5{y%7Qnt>7_iy7-8WZcxm1@fmG>?wsYG$OEw};G+lX!*iB;yIWrxiNYsBMHm?$ zzh61MfPhKnvLD92*;?vJ@9!zG+zLDGO@b%mPA8vlWHuUmE#<)hB z#0843)ef3r&4t_6i2*HSSlGVy1fEawgY&Tj!WWDq^^d*&1lEr$mkZKRNL=G0MTCkT zJ~Uh~z!(l9$X(lSJSvEcg>UbJx)E?;s~Uq<*^e>(ixmRBHCrkeBItY^H;-Fg_{r+G zDl`(_K!_AM_)t`aUWfA@##Hu+%uDDlGE}lLpb+gV2Q~!c2>|%%W-|id%&`IkT8$)M z&!->AefOQcIv{5AYir4XchXP!G9)xKhW!sQAK#-Bq#U>HG=zgZ<3(Ak7!0?}9eJpn zHoOX(_jHCp@`f^$2L#WzS43$tAMufW;21qh!8MWakTx0-)t5Du`_ENa|pVA70U zdXCcgIw_9W3=FWGmevsn#KjwVuInf^x{f*YvAe+vFA{hy=-AA0qXvq>&6?u(g!57gTk0L!mCm*j>Z_AYsq4{q z;a!p0Hq#;Qw;mAOuBg>y!AH30G!mCs`++!+;UHYBq>XtTK#VwvIQkrljV{^yvw`+g za!F^m)CSy386xCj9Axam@Ka@Be!U$j4v!v#%zy{47XOUBpUN7a%qm;-1uKI}%qrQ; z&KC4OCKhF7bz@eJ8{|NF>_J*b&Bsjc_!?z9lD71`FO`pYC~2g=s`3z@@(UgDm zN{bD1U+uIN|65+#A!0?5q$rrk-tN4L=LUr3RV;*1EV3+DL(^!mr$bdY6coWb^$5>9 z)RWC}#9EC_%}Q^tB(>do%SCqfyC_N2EV)BBE*g=^GeoC~MEh|kb&-sWTW5H|nn>Le zEjQ{xo;=DeizV3TY~R|it6fKJ;Ng)Zem^aec!D}~p^-v=#k$|$KZ!g7)3y^HDCfG* z9nMZ4=W2!k`B3r(ySbVgfJ{iNJkuTsTzQ^__T35dj~6~+j+YL)-Zv&Ef;xtrPaz)k z*NKG(B#q714nt|N{$```)i+DmqnCjXUfJeoXyt%kJ!!l*alduO?RZw|Q|8X?b;7MH ziJE~$Y$tjF@pu=bkrWNt{6^a3$Q<-i<+*So-1NovU^-+I%DI1XQk(jrex0*5J=xEu zrnan?7+f{=`5-AN?H#W3j+SDh_^*?@Yl`h{4w{wVz~!bb+3I@1>LiMJ7UiSqEPNAT+Oxr9CO&{c^ z-aa)d>Ny*Uy$msGKp47;xVmpEt&|v0pFoz*DoFz|^B`Q-Gm?jY&h7TcQ^<&=`dn?BH;wAgkJa1? z6z>RviO`d)iJwHHwW=EUi?HKO)!9gfg4;C +# +# +# 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() -- 2.39.5