Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xConvert.py238
-rwxr-xr-xLICENSE.txt674
-rwxr-xr-xactivity.py9
-rwxr-xr-xactivity/MANIFEST~2
-rwxr-xr-xactivity/activity-imagequiz.svg355
-rwxr-xr-xactivity/activity.info7
-rwxr-xr-xactivity/manifest159
-rwxr-xr-xactivity/permissions.info1
-rwxr-xr-xbackend.py326
-rwxr-xr-xcheckphrase.py39
-rwxr-xr-xcheckrenyi.py50
-rw-r--r--data/themes/default/Vera.ttfbin0 -> 65932 bytes
-rw-r--r--data/themes/default/box.down.pngbin0 -> 312 bytes
-rw-r--r--data/themes/default/box.hover.pngbin0 -> 324 bytes
-rw-r--r--data/themes/default/box.normal.pngbin0 -> 242 bytes
-rw-r--r--data/themes/default/box.xcfbin0 -> 2004 bytes
-rw-r--r--data/themes/default/button.down.tgabin0 -> 786 bytes
-rw-r--r--data/themes/default/button.hover.tgabin0 -> 776 bytes
-rw-r--r--data/themes/default/button.normal.tgabin0 -> 729 bytes
-rw-r--r--data/themes/default/check.pngbin0 -> 205 bytes
-rw-r--r--data/themes/default/checkbox.off.hover.tgabin0 -> 442 bytes
-rw-r--r--data/themes/default/checkbox.off.normal.tgabin0 -> 320 bytes
-rw-r--r--data/themes/default/checkbox.on.hover.tgabin0 -> 535 bytes
-rw-r--r--data/themes/default/checkbox.on.normal.tgabin0 -> 500 bytes
-rw-r--r--data/themes/default/config.txt291
-rw-r--r--data/themes/default/console.input.focus.pngbin0 -> 224 bytes
-rw-r--r--data/themes/default/console.input.normal.pngbin0 -> 224 bytes
-rw-r--r--data/themes/default/console.pngbin0 -> 224 bytes
-rw-r--r--data/themes/default/desktop.pngbin0 -> 181 bytes
-rw-r--r--data/themes/default/desktop.xcfbin0 -> 1975 bytes
-rw-r--r--data/themes/default/dialog.bar.pngbin0 -> 329 bytes
-rw-r--r--data/themes/default/dialog.close.down.tgabin0 -> 677 bytes
-rw-r--r--data/themes/default/dialog.close.hover.tgabin0 -> 668 bytes
-rw-r--r--data/themes/default/dialog.close.normal.tgabin0 -> 582 bytes
-rw-r--r--data/themes/default/dialog.pngbin0 -> 322 bytes
-rw-r--r--data/themes/default/dot.down.pngbin0 -> 549 bytes
-rw-r--r--data/themes/default/dot.hover.pngbin0 -> 548 bytes
-rw-r--r--data/themes/default/dot.normal.pngbin0 -> 366 bytes
-rw-r--r--data/themes/default/dot.xcfbin0 -> 1930 bytes
-rw-r--r--data/themes/default/down.pngbin0 -> 202 bytes
-rw-r--r--data/themes/default/filebrowser.folder.pngbin0 -> 634 bytes
-rw-r--r--data/themes/default/generate.py98
-rw-r--r--data/themes/default/hslider.bar.hover.tgabin0 -> 776 bytes
-rw-r--r--data/themes/default/hslider.bar.normal.tgabin0 -> 729 bytes
-rw-r--r--data/themes/default/hslider.left.tgabin0 -> 372 bytes
-rw-r--r--data/themes/default/hslider.right.tgabin0 -> 372 bytes
-rw-r--r--data/themes/default/hslider.tgabin0 -> 1231 bytes
-rw-r--r--data/themes/default/idot.normal.pngbin0 -> 408 bytes
-rw-r--r--data/themes/default/input.focus.pngbin0 -> 181 bytes
-rw-r--r--data/themes/default/input.normal.pngbin0 -> 208 bytes
-rw-r--r--data/themes/default/left.pngbin0 -> 209 bytes
-rw-r--r--data/themes/default/list.item.down.pngbin0 -> 190 bytes
-rw-r--r--data/themes/default/list.item.hover.pngbin0 -> 172 bytes
-rw-r--r--data/themes/default/list.item.normal.pngbin0 -> 172 bytes
-rw-r--r--data/themes/default/list.pngbin0 -> 129 bytes
-rw-r--r--data/themes/default/listitem.down.tgabin0 -> 2226 bytes
-rw-r--r--data/themes/default/listitem.hover.tgabin0 -> 2226 bytes
-rw-r--r--data/themes/default/listitem.normal.tgabin0 -> 2322 bytes
-rw-r--r--data/themes/default/menu.down.tgabin0 -> 528 bytes
-rw-r--r--data/themes/default/menu.hover.tgabin0 -> 441 bytes
-rw-r--r--data/themes/default/menu.normal.tgabin0 -> 78 bytes
-rw-r--r--data/themes/default/notes.txt8
-rw-r--r--data/themes/default/out.tgabin0 -> 795 bytes
-rw-r--r--data/themes/default/progressbar.bar.tgabin0 -> 563 bytes
-rw-r--r--data/themes/default/progressbar.tgabin0 -> 412 bytes
-rw-r--r--data/themes/default/radio.off.hover.tgabin0 -> 582 bytes
-rw-r--r--data/themes/default/radio.off.normal.tgabin0 -> 537 bytes
-rw-r--r--data/themes/default/radio.on.hover.tgabin0 -> 637 bytes
-rw-r--r--data/themes/default/radio.on.normal.tgabin0 -> 646 bytes
-rw-r--r--data/themes/default/radio.pngbin0 -> 197 bytes
-rw-r--r--data/themes/default/rdot.down.pngbin0 -> 525 bytes
-rw-r--r--data/themes/default/rdot.hover.pngbin0 -> 529 bytes
-rw-r--r--data/themes/default/rdot.normal.pngbin0 -> 370 bytes
-rw-r--r--data/themes/default/right.pngbin0 -> 206 bytes
-rw-r--r--data/themes/default/sbox.normal.pngbin0 -> 232 bytes
-rw-r--r--data/themes/default/scroller.slide.bar.hover.tgabin0 -> 342 bytes
-rw-r--r--data/themes/default/scroller.slide.bar.normal.tgabin0 -> 342 bytes
-rw-r--r--data/themes/default/scroller.slide.h.tgabin0 -> 1278 bytes
-rw-r--r--data/themes/default/scroller.slide.v.tgabin0 -> 1278 bytes
-rw-r--r--data/themes/default/select.arrow.down.tgabin0 -> 344 bytes
-rw-r--r--data/themes/default/select.arrow.hover.tgabin0 -> 363 bytes
-rw-r--r--data/themes/default/select.arrow.normal.tgabin0 -> 363 bytes
-rw-r--r--data/themes/default/select.arrow.pngbin0 -> 156 bytes
-rw-r--r--data/themes/default/select.option.hover.pngbin0 -> 190 bytes
-rw-r--r--data/themes/default/select.option.normal.pngbin0 -> 172 bytes
-rw-r--r--data/themes/default/select.options.pngbin0 -> 181 bytes
-rw-r--r--data/themes/default/select.selected.down.tgabin0 -> 322 bytes
-rw-r--r--data/themes/default/select.selected.hover.tgabin0 -> 338 bytes
-rw-r--r--data/themes/default/select.selected.normal.tgabin0 -> 282 bytes
-rw-r--r--data/themes/default/slider.bar.hover.tgabin0 -> 776 bytes
-rw-r--r--data/themes/default/slider.bar.normal.tgabin0 -> 729 bytes
-rw-r--r--data/themes/default/slider.tgabin0 -> 1231 bytes
-rw-r--r--data/themes/default/tool.down.tgabin0 -> 528 bytes
-rw-r--r--data/themes/default/tool.hover.tgabin0 -> 563 bytes
-rw-r--r--data/themes/default/tool.normal.tgabin0 -> 441 bytes
-rw-r--r--data/themes/default/up.pngbin0 -> 195 bytes
-rw-r--r--data/themes/default/vbox.normal.pngbin0 -> 233 bytes
-rw-r--r--data/themes/default/vdot.down.pngbin0 -> 540 bytes
-rw-r--r--data/themes/default/vdot.hover.pngbin0 -> 527 bytes
-rw-r--r--data/themes/default/vdot.normal.pngbin0 -> 354 bytes
-rw-r--r--data/themes/default/vsbox.normal.pngbin0 -> 224 bytes
-rw-r--r--data/themes/default/vslider.bar.hover.tgabin0 -> 1412 bytes
-rw-r--r--data/themes/default/vslider.bar.normal.tgabin0 -> 1412 bytes
-rw-r--r--data/themes/default/vslider.down.tgabin0 -> 864 bytes
-rw-r--r--data/themes/default/vslider.tgabin0 -> 1231 bytes
-rw-r--r--data/themes/default/vslider.up.tgabin0 -> 864 bytes
-rw-r--r--data/themes/default/x.pngbin0 -> 199 bytes
-rw-r--r--data/themes/gray/Vera.ttfbin0 -> 65932 bytes
-rw-r--r--data/themes/gray/box.down.pngbin0 -> 225 bytes
-rw-r--r--data/themes/gray/box.normal.pngbin0 -> 230 bytes
-rw-r--r--data/themes/gray/button.down.pngbin0 -> 191 bytes
-rw-r--r--data/themes/gray/button.normal.pngbin0 -> 199 bytes
-rw-r--r--data/themes/gray/checkbox.off.down.pngbin0 -> 165 bytes
-rw-r--r--data/themes/gray/checkbox.off.normal.pngbin0 -> 178 bytes
-rw-r--r--data/themes/gray/checkbox.on.down.pngbin0 -> 213 bytes
-rw-r--r--data/themes/gray/checkbox.on.normal.pngbin0 -> 216 bytes
-rw-r--r--data/themes/gray/config.txt244
-rw-r--r--data/themes/gray/console.input.focus.pngbin0 -> 224 bytes
-rw-r--r--data/themes/gray/console.input.normal.pngbin0 -> 224 bytes
-rw-r--r--data/themes/gray/console.pngbin0 -> 224 bytes
-rw-r--r--data/themes/gray/desktop.pngbin0 -> 131 bytes
-rw-r--r--data/themes/gray/dialog.bar.pngbin0 -> 138 bytes
-rw-r--r--data/themes/gray/dialog.close.down.pngbin0 -> 270 bytes
-rw-r--r--data/themes/gray/dialog.close.normal.pngbin0 -> 217 bytes
-rw-r--r--data/themes/gray/dialog.pngbin0 -> 138 bytes
-rw-r--r--data/themes/gray/filebrowser.folder.pngbin0 -> 634 bytes
-rw-r--r--data/themes/gray/input.focus.pngbin0 -> 181 bytes
-rw-r--r--data/themes/gray/input.normal.pngbin0 -> 208 bytes
-rw-r--r--data/themes/gray/list.item.normal.pngbin0 -> 172 bytes
-rw-r--r--data/themes/gray/list.pngbin0 -> 129 bytes
-rw-r--r--data/themes/gray/menu.down.pngbin0 -> 149 bytes
-rw-r--r--data/themes/gray/menu.hover.pngbin0 -> 150 bytes
-rw-r--r--data/themes/gray/menu.normal.pngbin0 -> 120 bytes
-rw-r--r--data/themes/gray/menu.option.hover.pngbin0 -> 147 bytes
-rw-r--r--data/themes/gray/menu.option.normal.pngbin0 -> 131 bytes
-rw-r--r--data/themes/gray/radio.off.down.pngbin0 -> 224 bytes
-rw-r--r--data/themes/gray/radio.off.normal.pngbin0 -> 238 bytes
-rw-r--r--data/themes/gray/radio.on.down.pngbin0 -> 248 bytes
-rw-r--r--data/themes/gray/radio.on.normal.pngbin0 -> 250 bytes
-rw-r--r--data/themes/gray/select.arrow.down.pngbin0 -> 149 bytes
-rw-r--r--data/themes/gray/select.arrow.normal.pngbin0 -> 168 bytes
-rw-r--r--data/themes/gray/select.arrow.pngbin0 -> 156 bytes
-rw-r--r--data/themes/gray/select.option.normal.pngbin0 -> 172 bytes
-rw-r--r--data/themes/gray/select.options.pngbin0 -> 181 bytes
-rw-r--r--data/themes/gray/select.selected.normal.pngbin0 -> 135 bytes
-rw-r--r--data/themes/gray/slider.bar.normal.pngbin0 -> 181 bytes
-rw-r--r--data/themes/gray/slider.pngbin0 -> 145 bytes
-rw-r--r--data/themes/gray/tool.down.pngbin0 -> 168 bytes
-rw-r--r--data/themes/gray/tool.normal.pngbin0 -> 168 bytes
-rw-r--r--data/themes/tools/config.txt11
-rw-r--r--data/themes/tools/icons48.bkgr.tgabin0 -> 1595 bytes
-rw-r--r--data/themes/tools/icons48.code.tgabin0 -> 1360 bytes
-rw-r--r--data/themes/tools/icons48.draw.tgabin0 -> 1443 bytes
-rw-r--r--data/themes/tools/icons48.eraser.tgabin0 -> 1381 bytes
-rw-r--r--data/themes/tools/icons48.fill.tgabin0 -> 1221 bytes
-rw-r--r--data/themes/tools/icons48.line.tgabin0 -> 1141 bytes
-rw-r--r--data/themes/tools/icons48.pixel.tgabin0 -> 1210 bytes
-rw-r--r--data/themes/tools/icons48.select.tgabin0 -> 1457 bytes
-rw-r--r--data/themes/tools/icons48.tile.tgabin0 -> 1268 bytes
-rwxr-xr-xdb/Africa.csv1
-rwxr-xr-xdb/Animals.csv0
-rwxr-xr-xdb/Arts.csv1
-rwxr-xr-xdb/Astronomy.csv1
-rwxr-xr-xdb/Bears.csv1
-rwxr-xr-xdb/Cars_&_Motorbikes.csv1
-rwxr-xr-xdb/Difference_Game.csv1
-rwxr-xr-xdb/Dogs.csv1
-rwxr-xr-xdb/Europe.csv1
-rwxr-xr-xdb/Geography.csv1
-rwxr-xr-xdb/United_Mexican_States.csv1
-rwxr-xr-xdb/XO_Laptop.csv1
-rwxr-xr-xdb/main.dbbin0 -> 45056 bytes
-rwxr-xr-xfrontend.py880
-rwxr-xr-ximagequiz_library/Africa.csv1
-rwxr-xr-ximagequiz_library/Astronomy.csv1
-rwxr-xr-ximagequiz_library/Cars_&_Motorbikes.csv1
-rwxr-xr-ximagequiz_library/Difference_Game.csv1
-rwxr-xr-ximagequiz_library/Europe.csv1
-rwxr-xr-ximagequiz_library/Geography.csv1
-rwxr-xr-ximagequiz_library/Quiz0001.xml131
-rwxr-xr-ximagequiz_library/United_Mexican_States.csv1
-rwxr-xr-ximagequiz_library/XO_Laptop.csv1
-rwxr-xr-ximagequiz_library/image/21arts1.jpgbin0 -> 43228 bytes
-rwxr-xr-ximagequiz_library/image/5104352.jpgbin0 -> 119455 bytes
-rwxr-xr-ximagequiz_library/image/800px-Regions_of_Europe.pngbin0 -> 164203 bytes
-rwxr-xr-ximagequiz_library/image/Mexico_states.pngbin0 -> 67275 bytes
-rwxr-xr-ximagequiz_library/image/africa2.jpgbin0 -> 39194 bytes
-rwxr-xr-ximagequiz_library/image/bears.jpgbin0 -> 374267 bytes
-rwxr-xr-ximagequiz_library/image/different1.pngbin0 -> 66799 bytes
-rwxr-xr-ximagequiz_library/image/different2.pngbin0 -> 50656 bytes
-rwxr-xr-ximagequiz_library/image/different3.pngbin0 -> 50919 bytes
-rwxr-xr-ximagequiz_library/image/different4.pngbin0 -> 30757 bytes
-rwxr-xr-ximagequiz_library/image/dogs.jpgbin0 -> 28991 bytes
-rwxr-xr-ximagequiz_library/image/europe.gifbin0 -> 61947 bytes
-rwxr-xr-ximagequiz_library/image/k0061.pngbin0 -> 49838 bytes
-rwxr-xr-ximagequiz_library/image/k0168.pngbin0 -> 88025 bytes
-rwxr-xr-ximagequiz_library/image/k0177.pngbin0 -> 80010 bytes
-rwxr-xr-ximagequiz_library/image/k0286.pngbin0 -> 91714 bytes
-rwxr-xr-ximagequiz_library/image/k0670.pngbin0 -> 51736 bytes
-rwxr-xr-ximagequiz_library/image/k0762.pngbin0 -> 83219 bytes
-rwxr-xr-ximagequiz_library/image/k0820.pngbin0 -> 43259 bytes
-rwxr-xr-ximagequiz_library/image/k0963.pngbin0 -> 68979 bytes
-rwxr-xr-ximagequiz_library/image/k0993.pngbin0 -> 61195 bytes
-rwxr-xr-ximagequiz_library/image/k1030.pngbin0 -> 71307 bytes
-rwxr-xr-ximagequiz_library/image/k1094.pngbin0 -> 60996 bytes
-rwxr-xr-ximagequiz_library/image/k1267.pngbin0 -> 76631 bytes
-rwxr-xr-ximagequiz_library/image/k1391.pngbin0 -> 71562 bytes
-rwxr-xr-ximagequiz_library/image/k1524.pngbin0 -> 71153 bytes
-rwxr-xr-ximagequiz_library/image/k1527.pngbin0 -> 71152 bytes
-rwxr-xr-ximagequiz_library/image/k1702.pngbin0 -> 64770 bytes
-rwxr-xr-ximagequiz_library/image/k1809.pngbin0 -> 69380 bytes
-rwxr-xr-ximagequiz_library/image/k1873.pngbin0 -> 50996 bytes
-rwxr-xr-ximagequiz_library/image/k1960.pngbin0 -> 89953 bytes
-rwxr-xr-ximagequiz_library/image/k2006.pngbin0 -> 39136 bytes
-rwxr-xr-ximagequiz_library/image/k2361.pngbin0 -> 80400 bytes
-rwxr-xr-ximagequiz_library/image/k2364.pngbin0 -> 71702 bytes
-rwxr-xr-ximagequiz_library/image/k2372.pngbin0 -> 72972 bytes
-rwxr-xr-ximagequiz_library/image/k2488.pngbin0 -> 52044 bytes
-rwxr-xr-ximagequiz_library/image/k2532.pngbin0 -> 90747 bytes
-rwxr-xr-ximagequiz_library/image/k2579.pngbin0 -> 53576 bytes
-rwxr-xr-ximagequiz_library/image/k2621.pngbin0 -> 62463 bytes
-rwxr-xr-ximagequiz_library/image/k2701.pngbin0 -> 51135 bytes
-rwxr-xr-ximagequiz_library/image/k2702.pngbin0 -> 72623 bytes
-rwxr-xr-ximagequiz_library/image/k2849.pngbin0 -> 65540 bytes
-rwxr-xr-ximagequiz_library/image/k2889.pngbin0 -> 65995 bytes
-rwxr-xr-ximagequiz_library/image/k3184.pngbin0 -> 62534 bytes
-rwxr-xr-ximagequiz_library/image/latinamerica.jpgbin0 -> 44668 bytes
-rwxr-xr-ximagequiz_library/image/olpc-xo-2.jpgbin0 -> 22659 bytes
-rwxr-xr-ximagequiz_library/image/planets_iau_big.jpgbin0 -> 29176 bytes
-rwxr-xr-ximagequiz_library/image/xo.jpgbin0 -> 253295 bytes
-rwxr-xr-ximagequiz_library/rld001.xml103
-rwxr-xr-ximagequiz_library/sound/k0061.oggbin0 -> 24841 bytes
-rwxr-xr-ximagequiz_library/sound/k0168.oggbin0 -> 23005 bytes
-rwxr-xr-ximagequiz_library/sound/k0177.oggbin0 -> 24888 bytes
-rwxr-xr-ximagequiz_library/sound/k0286.oggbin0 -> 34282 bytes
-rwxr-xr-ximagequiz_library/sound/k0670.oggbin0 -> 27718 bytes
-rwxr-xr-ximagequiz_library/sound/k0762.oggbin0 -> 28448 bytes
-rwxr-xr-ximagequiz_library/sound/k0820.oggbin0 -> 31937 bytes
-rwxr-xr-ximagequiz_library/sound/k0963.oggbin0 -> 28261 bytes
-rwxr-xr-ximagequiz_library/sound/k0993.oggbin0 -> 20962 bytes
-rwxr-xr-ximagequiz_library/sound/k1030.oggbin0 -> 25378 bytes
-rwxr-xr-ximagequiz_library/sound/k1094.oggbin0 -> 32262 bytes
-rwxr-xr-ximagequiz_library/sound/k1267.oggbin0 -> 22008 bytes
-rwxr-xr-ximagequiz_library/sound/k1391.oggbin0 -> 33153 bytes
-rwxr-xr-ximagequiz_library/sound/k1391r.oggbin0 -> 51633 bytes
-rwxr-xr-ximagequiz_library/sound/k1391r1.oggbin0 -> 60699 bytes
-rwxr-xr-ximagequiz_library/sound/k1524.oggbin0 -> 55062 bytes
-rwxr-xr-ximagequiz_library/sound/k1527.oggbin0 -> 51227 bytes
-rwxr-xr-ximagequiz_library/sound/k1702.oggbin0 -> 27500 bytes
-rwxr-xr-ximagequiz_library/sound/k1809.oggbin0 -> 22630 bytes
-rwxr-xr-ximagequiz_library/sound/k1873.oggbin0 -> 23823 bytes
-rwxr-xr-ximagequiz_library/sound/k1960.oggbin0 -> 36638 bytes
-rwxr-xr-ximagequiz_library/sound/k2006.oggbin0 -> 28092 bytes
-rwxr-xr-ximagequiz_library/sound/k2361.oggbin0 -> 30360 bytes
-rwxr-xr-ximagequiz_library/sound/k2364.oggbin0 -> 61997 bytes
-rwxr-xr-ximagequiz_library/sound/k2372.oggbin0 -> 41875 bytes
-rwxr-xr-ximagequiz_library/sound/k2488.oggbin0 -> 23667 bytes
-rwxr-xr-ximagequiz_library/sound/k2532.oggbin0 -> 33493 bytes
-rwxr-xr-ximagequiz_library/sound/k2579.oggbin0 -> 24136 bytes
-rwxr-xr-ximagequiz_library/sound/k2621.oggbin0 -> 26044 bytes
-rwxr-xr-ximagequiz_library/sound/k2701.oggbin0 -> 24364 bytes
-rwxr-xr-ximagequiz_library/sound/k2702.oggbin0 -> 32067 bytes
-rwxr-xr-ximagequiz_library/sound/k2849.oggbin0 -> 40620 bytes
-rwxr-xr-ximagequiz_library/sound/k2889.oggbin0 -> 30647 bytes
-rwxr-xr-ximagequiz_library/sound/k3184.oggbin0 -> 24479 bytes
-rwxr-xr-ximages/Africa/africa2.jpgbin0 -> 39194 bytes
-rwxr-xr-ximages/Astronomy/planets_iau_big.jpgbin0 -> 29176 bytes
-rwxr-xr-ximages/Bears/bears.jpgbin0 -> 374267 bytes
-rwxr-xr-ximages/Cars_&_Motorbikes/5104352.jpgbin0 -> 119455 bytes
-rwxr-xr-ximages/Difference_Game/different1.pngbin0 -> 66799 bytes
-rwxr-xr-ximages/Difference_Game/different2.pngbin0 -> 50656 bytes
-rwxr-xr-ximages/Difference_Game/different3.pngbin0 -> 50919 bytes
-rwxr-xr-ximages/Difference_Game/different4.pngbin0 -> 30757 bytes
-rwxr-xr-ximages/Dogs/dogs.jpgbin0 -> 28991 bytes
-rwxr-xr-ximages/Emotes/face-crying.pngbin0 -> 1933 bytes
-rwxr-xr-ximages/Emotes/face-devil-grin.pngbin0 -> 1600 bytes
-rwxr-xr-ximages/Emotes/face-devil-grin_24px.pngbin0 -> 1066 bytes
-rwxr-xr-ximages/Emotes/face-glasses.pngbin0 -> 2275 bytes
-rwxr-xr-ximages/Emotes/face-grin.pngbin0 -> 1536 bytes
-rwxr-xr-ximages/Emotes/face-grin_24px.pngbin0 -> 1038 bytes
-rwxr-xr-ximages/Emotes/face-monkey.pngbin0 -> 2184 bytes
-rwxr-xr-ximages/Europe/800px-Regions_of_Europe.pngbin0 -> 164203 bytes
-rwxr-xr-ximages/Europe/europe.gifbin0 -> 61947 bytes
-rwxr-xr-ximages/Geography/latinamerica.jpgbin0 -> 44668 bytes
-rwxr-xr-ximages/Icons/bulb.pngbin0 -> 1910 bytes
-rwxr-xr-ximages/Icons/no1.pngbin0 -> 880 bytes
-rwxr-xr-ximages/Icons/no2.pngbin0 -> 734 bytes
-rwxr-xr-ximages/Icons/ok1.pngbin0 -> 1019 bytes
-rwxr-xr-ximages/Icons/ok2.pngbin0 -> 988 bytes
-rwxr-xr-ximages/United_Mexican_States/Mexico_states.pngbin0 -> 67275 bytes
-rwxr-xr-ximages/XO_Laptop/olpc-xo-2.jpgbin0 -> 22659 bytes
-rwxr-xr-ximages/XO_Laptop/xo.jpgbin0 -> 253295 bytes
-rwxr-xr-ximages/bubble.gifbin0 -> 1822 bytes
-rwxr-xr-ximages/cursor_big.pngbin0 -> 585 bytes
-rwxr-xr-ximages/cursor_big.xbm20
-rwxr-xr-ximages/cursor_big_mask.xbm17
-rwxr-xr-ximages/logo.jpgbin0 -> 6504 bytes
-rwxr-xr-ximages/ooops.pngbin0 -> 177089 bytes
-rwxr-xr-ximages/play.png105
-rwxr-xr-ximages/play.svg105
-rwxr-xr-ximages/play_button.pngbin0 -> 5205 bytes
-rwxr-xr-xink.py61
-rwxr-xr-xlayout.py146
-rwxr-xr-xlocale/de/activity.linfo2
-rwxr-xr-xmanifest159
-rwxr-xr-xolpcgames/__init__.py34
-rwxr-xr-xolpcgames/__init__.pycbin0 -> 1337 bytes
-rwxr-xr-xolpcgames/activity.py162
-rwxr-xr-xolpcgames/activity.pycbin0 -> 7385 bytes
-rwxr-xr-xolpcgames/camera.py235
-rwxr-xr-xolpcgames/camera.pycbin0 -> 10079 bytes
-rwxr-xr-xolpcgames/canvas.py111
-rwxr-xr-xolpcgames/canvas.pycbin0 -> 4258 bytes
-rwxr-xr-xolpcgames/eventwrap.py160
-rwxr-xr-xolpcgames/eventwrap.pycbin0 -> 6107 bytes
-rwxr-xr-xolpcgames/gtkEvent.py264
-rwxr-xr-xolpcgames/gtkEvent.pycbin0 -> 10233 bytes
-rwxr-xr-xolpcgames/mesh.py398
-rwxr-xr-xolpcgames/mesh.pycbin0 -> 15798 bytes
-rwxr-xr-xolpcgames/pangofont.py293
-rwxr-xr-xolpcgames/pangofont.pycbin0 -> 12032 bytes
-rwxr-xr-xolpcgames/tubeconn.py107
-rwxr-xr-xolpcgames/util.py67
-rwxr-xr-xolpcgames/util.pycbin0 -> 3148 bytes
-rwxr-xr-xolpcgames/video.py71
-rwxr-xr-xolpcgames/video.pycbin0 -> 3721 bytes
-rwxr-xr-xpath.py971
-rw-r--r--pgu/__init__.py7
-rw-r--r--pgu/__init__.pycbin0 -> 189 bytes
-rw-r--r--pgu/algo.py135
-rw-r--r--pgu/ani.py90
-rw-r--r--pgu/engine.py154
-rw-r--r--pgu/engine.pycbin0 -> 5501 bytes
-rw-r--r--pgu/fonts.py130
-rw-r--r--pgu/gui/__init__.py32
-rw-r--r--pgu/gui/__init__.pycbin0 -> 1856 bytes
-rw-r--r--pgu/gui/app.py237
-rw-r--r--pgu/gui/app.pycbin0 -> 6685 bytes
-rw-r--r--pgu/gui/area.py434
-rw-r--r--pgu/gui/area.pycbin0 -> 13562 bytes
-rw-r--r--pgu/gui/basic.py124
-rw-r--r--pgu/gui/basic.pycbin0 -> 4473 bytes
-rw-r--r--pgu/gui/button.py351
-rw-r--r--pgu/gui/button.pycbin0 -> 11854 bytes
-rw-r--r--pgu/gui/const.py45
-rw-r--r--pgu/gui/const.pycbin0 -> 1133 bytes
-rw-r--r--pgu/gui/container.py455
-rw-r--r--pgu/gui/container.pycbin0 -> 12229 bytes
-rw-r--r--pgu/gui/deprecated.py76
-rw-r--r--pgu/gui/deprecated.pycbin0 -> 3300 bytes
-rw-r--r--pgu/gui/dialog.py157
-rw-r--r--pgu/gui/dialog.pycbin0 -> 4744 bytes
-rw-r--r--pgu/gui/document.py112
-rw-r--r--pgu/gui/document.pycbin0 -> 3731 bytes
-rw-r--r--pgu/gui/form.py79
-rw-r--r--pgu/gui/form.pycbin0 -> 2767 bytes
-rw-r--r--pgu/gui/group.py43
-rw-r--r--pgu/gui/group.pycbin0 -> 1827 bytes
-rw-r--r--pgu/gui/input.py166
-rw-r--r--pgu/gui/input.pycbin0 -> 4938 bytes
-rw-r--r--pgu/gui/keysym.py72
-rw-r--r--pgu/gui/keysym.pycbin0 -> 2638 bytes
-rw-r--r--pgu/gui/layout.py172
-rw-r--r--pgu/gui/layout.pycbin0 -> 5499 bytes
-rw-r--r--pgu/gui/menus.py121
-rw-r--r--pgu/gui/menus.pycbin0 -> 4614 bytes
-rw-r--r--pgu/gui/misc.py43
-rw-r--r--pgu/gui/misc.pycbin0 -> 1797 bytes
-rw-r--r--pgu/gui/pguglobals.py7
-rw-r--r--pgu/gui/pguglobals.pycbin0 -> 134 bytes
-rw-r--r--pgu/gui/select.py191
-rw-r--r--pgu/gui/select.pycbin0 -> 5208 bytes
-rw-r--r--pgu/gui/slider.py279
-rw-r--r--pgu/gui/slider.pycbin0 -> 9981 bytes
-rw-r--r--pgu/gui/style.py41
-rw-r--r--pgu/gui/style.pycbin0 -> 2164 bytes
-rw-r--r--pgu/gui/surface.py142
-rw-r--r--pgu/gui/surface.pycbin0 -> 7067 bytes
-rw-r--r--pgu/gui/table.py331
-rw-r--r--pgu/gui/table.pycbin0 -> 9131 bytes
-rw-r--r--pgu/gui/textarea.py287
-rw-r--r--pgu/gui/textarea.pycbin0 -> 7099 bytes
-rw-r--r--pgu/gui/theme.py486
-rw-r--r--pgu/gui/theme.pycbin0 -> 15188 bytes
-rw-r--r--pgu/gui/widget.py352
-rw-r--r--pgu/gui/widget.pycbin0 -> 11782 bytes
-rw-r--r--pgu/hexvid.py127
-rw-r--r--pgu/hexvid.pycbin0 -> 4090 bytes
-rw-r--r--pgu/high.py154
-rw-r--r--pgu/html.py571
-rw-r--r--pgu/html.pycbin0 -> 22344 bytes
-rw-r--r--pgu/isovid.py182
-rw-r--r--pgu/isovid.pycbin0 -> 6262 bytes
-rw-r--r--pgu/layout.py4
-rw-r--r--pgu/layout.pycbin0 -> 206 bytes
-rw-r--r--pgu/text.py61
-rw-r--r--pgu/text.pycbin0 -> 2535 bytes
-rw-r--r--pgu/tilevid.py195
-rw-r--r--pgu/tilevid.pycbin0 -> 5305 bytes
-rw-r--r--pgu/timer.py68
-rw-r--r--pgu/timer.pycbin0 -> 2173 bytes
-rw-r--r--pgu/vid.py560
-rw-r--r--pgu/vid.pycbin0 -> 17893 bytes
-rw-r--r--pgutest.py168
-rw-r--r--pgutest1.py148
-rwxr-xr-xplugins/_extract.py896
-rwxr-xr-xplugins/_flashcard.py728
-rwxr-xr-xplugins/_library.py198
-rwxr-xr-xplugins/_quizsocket.py247
-rwxr-xr-xplugins/dbtool.py99
-rwxr-xr-xplugins/demoplugin.py25
-rwxr-xr-xplugins/ink.py61
-rwxr-xr-xplugins/make.py422
-rwxr-xr-xplugins/multi_player.py552
-rwxr-xr-xplugins/path.py971
-rwxr-xr-xplugins/readme11
-rwxr-xr-xplugins/single_player.py744
-rwxr-xr-xplugins/tools.py100
-rwxr-xr-xquiz.py167
-rwxr-xr-xsetup.py11
-rwxr-xr-xsounds/accessed.wavbin0 -> 17510 bytes
-rwxr-xr-xsounds/alf_wrong.wavbin0 -> 54829 bytes
-rwxr-xr-xsounds/awh_man.wavbin0 -> 10037 bytes
-rwxr-xr-xsounds/brain-yes.wavbin0 -> 8312 bytes
-rwxr-xr-xsounds/bt-excellent.wavbin0 -> 10728 bytes
-rwxr-xr-xsounds/bunny_awful.wavbin0 -> 11390 bytes
-rwxr-xr-xsounds/burns-excellent.wavbin0 -> 6744 bytes
-rwxr-xr-xsounds/compute.wavbin0 -> 13724 bytes
-rwxr-xr-xsounds/db_forgetaboutit.wavbin0 -> 66294 bytes
-rwxr-xr-xsounds/doh.wavbin0 -> 6108 bytes
-rwxr-xr-xsounds/dramatic_chord.wavbin0 -> 18370 bytes
-rwxr-xr-xsounds/excuse_me.wavbin0 -> 69272 bytes
-rwxr-xr-xsounds/groovy.wavbin0 -> 14069 bytes
-rwxr-xr-xsounds/gwarsh.wavbin0 -> 8972 bytes
-rwxr-xr-xsounds/haha.wavbin0 -> 9369 bytes
-rwxr-xr-xsounds/hoo-ah.wavbin0 -> 190076 bytes
-rwxr-xr-xsounds/i-like-it.wavbin0 -> 22096 bytes
-rwxr-xr-xsounds/impressive.wavbin0 -> 248552 bytes
-rwxr-xr-xsounds/metal_clang_2.wavbin0 -> 2432 bytes
-rwxr-xr-xsounds/mk-excellent.wavbin0 -> 12086 bytes
-rwxr-xr-xsounds/negative.wavbin0 -> 23224 bytes
-rwxr-xr-xsounds/not.wavbin0 -> 14802 bytes
-rwxr-xr-xsounds/oh-yeah.wavbin0 -> 17182 bytes
-rwxr-xr-xsounds/oh_no.wavbin0 -> 37108 bytes
-rwxr-xr-xsounds/ooh-yeah.wavbin0 -> 81736 bytes
-rwxr-xr-xsounds/sorry.wavbin0 -> 13568 bytes
-rwxr-xr-xsounds/sweet.wavbin0 -> 9472 bytes
-rwxr-xr-xsounds/that-was-cool.wavbin0 -> 12723 bytes
-rwxr-xr-xsounds/transfer_data.wavbin0 -> 64464 bytes
-rwxr-xr-xsounds/yes-i-like-it.wavbin0 -> 60590 bytes
-rwxr-xr-xwiki105
-rwxr-xr-xxmlio.py56
452 files changed, 20115 insertions, 0 deletions
diff --git a/Convert.py b/Convert.py
new file mode 100755
index 0000000..fae30f7
--- /dev/null
+++ b/Convert.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+
+#----------------------------------------------------------------------
+# Convert.py
+# Dave Reed
+# 01/23/2008
+#----------------------------------------------------------------------
+
+import sys, os
+
+try:
+ from xml.etree import ElementTree as etree
+except:
+ sys.exit()
+
+from GladeWindow import *
+
+#----------------------------------------------------------------------
+
+class Window(GladeWindow):
+
+ #----------------------------------------------------------------------
+
+ def __init__(self):
+
+ ''' '''
+
+ self.init()
+
+ #----------------------------------------------------------------------
+
+ def init(self):
+
+ filename = 'Convert.glade'
+
+ widget_list = [
+ 'window1',
+ 'entry1',
+ 'entry2',
+ ]
+
+ handlers = [
+ 'on_entry2_activate',
+ ]
+
+ top_window = 'window1'
+ GladeWindow.__init__(self, filename, top_window, widget_list, handlers)
+ #----------------------------------------------------------------------
+
+ def on_entry2_activate(self, *args):
+ output_folder = self.widgets['entry2'].get_text()
+ convert_main(output_folder)
+
+ def main_window_destroy():
+ self.destroy()
+
+#----------------------------------------------------------------------
+class InTree():
+
+ def __init__(self, filename):
+ self.tree = etree.parse(filename)
+ self.xml = self.tree.getroot()
+
+ def get(self, node):
+ idcard = node.attrib.get("id")
+ if not idcard:
+ idcard = '0'
+ question = node.findtext("front")
+ if not question:
+ question = ''
+ hint = node.findtext('comment')
+ if not hint:
+ hint = ''
+ sound = node.findtext('sound')
+ if not sound:
+ sound = ''
+ answer = node.findtext("back")
+ if not answer:
+ answer = ''
+ more = node.findtext('backexample')
+ if not more:
+ more = ''
+ image = node.findtext('image')
+ if not image:
+ image = ''
+ return (idcard, question, hint, answer, more, sound, image)
+
+#----------------------------------------------------------------------
+class SaveDeck():
+ def __init__(self):
+ self.xml = etree.Element("quiz")
+
+ def put(self, card):
+ if len(card) < 7:
+ print 'fatal error in put:', len(card)
+ sys.exit()
+ xmlcard = etree.Element("card")
+ xmlcard.set("id",str(card[0]))
+ question = etree.SubElement(xmlcard, "question")
+ question.text = card[1]
+ if len(card[2]) > 0:
+ hint = etree.SubElement(question, "hint")
+ hint.text = card[2]
+ if len(card[5]) > 0:
+ sound = etree.SubElement(question, 'sound')
+ sound.text = card[5]
+ answer = etree.SubElement(xmlcard, "answer")
+ answer.text = card[3]
+ if len(card[4]) > 0:
+ more = etree.SubElement(answer, "more")
+ more.text = card[4]
+ if len(card[6]) > 0:
+ image = etree.SubElement(answer, 'image')
+ image.text = card[6]
+ self.xml.append(xmlcard)
+
+ # in-place prettyprint formatter
+
+ def indent(self, elem, level=0):
+ i = "\n" + level*" "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+ def printtree(self, filename, last):
+ self.xml.set("last",str(last))
+ #prettyprint
+ tree = etree.ElementTree(self.xml)
+ self.indent(self.xml)
+ tree.write(filename, encoding = 'utf-8')
+
+#----------------------------------------------------------------------
+def get_filenames():
+ #select decks using file chooser dialog [ user must select in correct order ]
+ chooser = gtk.FileChooserDialog(title=None,action=gtk.FILE_CHOOSER_ACTION_OPEN, \
+ buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
+ chooser.set_select_multiple(True)
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ response = chooser.run()
+ if response != gtk.RESPONSE_OK:
+ sys.exit()
+ decks = chooser.get_filenames()
+ chooser.destroy()
+ return decks
+
+def fix_file(filename, outfilename):
+ infile = open(filename)
+ s = unicode(infile.read(),'ISO 8859-1')
+ infile.close()
+ t = s.replace('<?xml version="1.0" ?>','<?xml version="1.0" encoding= "utf-16" ?>')
+ s = t
+ t = s.replace('utf-16', 'utf-8')
+ s = t
+ s = t
+ t = s.replace('front example', 'frontexample')
+ s = t
+ s = t
+ t = s.replace('back example', 'backexample')
+ s = t
+ outfile = open(outfilename, 'w')
+ outfile.write(t)
+ outfile.close
+
+def get_deck(tree):
+ deck = []
+ for node in tree.xml:
+ card = tree.get(node)
+ deck.append(card)
+ return deck
+
+def get_tree(filename):
+ global w
+ tree = InTree(filename)
+ return tree
+
+def convert_deck(deck):
+ cards = []
+ for card in deck:
+ #card = [id, question, hint, answer, more, sound, image]
+ id = str(len(cards))
+ #make newcard [id, card[1], card[2], card[3], card[4], card[5], card[6]]
+ #sound = card[5]
+ #if len(sound) > 0:
+ # newcard = [id, card[1], card[2], sound[:-4], card[4], card[5], card[6]]
+ #else:
+ newcard = [id, card[1], card[2], card[3], card[4], card[5], card[6]]
+ #add to cards
+ cards.append(newcard)
+ return cards
+
+def save_deck(filename, cards):
+ #create tree from cards
+ out = SaveDeck()
+ for card in cards:
+ out.put(card)
+ #write tree with pretty print
+ number = len(cards)
+ out.printtree(filename, number)
+
+def path_strip(pathname):
+ # get part after last /, if any
+ filename = os.path.basename(pathname)
+ return filename
+
+
+def convert_main(outfolder):
+ global w
+ decks = get_filenames()
+ for filename in decks:
+ w.widgets['entry1'].set_text(path_strip(filename))
+ outfilename = './' + outfolder + '/' + path_strip(filename)
+ #fix_file(filename, outfilename)
+ tree = get_tree(filename)
+ deck = get_deck(tree)
+ cards = convert_deck(deck)
+ save_deck(outfilename, cards)
+ sys.exit()
+
+#----------------------------------------------------------------------
+
+def main(argv):
+ global w
+ w = Window()
+ w.show()
+ w.widgets['entry2'].grab_focus()
+ gtk.main()
+
+#----------------------------------------------------------------------
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100755
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 3 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/activity.py b/activity.py
new file mode 100755
index 0000000..8fae348
--- /dev/null
+++ b/activity.py
@@ -0,0 +1,9 @@
+import olpcgames
+from gettext import gettext as _
+
+class Activity(olpcgames.PyGameActivity):
+ """Your Sugar activity"""
+
+ game_name = 'quiz'
+ game_title = _('ImageQuiz')
+ game_size = None
diff --git a/activity/MANIFEST~ b/activity/MANIFEST~
new file mode 100755
index 0000000..316aa14
--- /dev/null
+++ b/activity/MANIFEST~
@@ -0,0 +1,2 @@
+HelloWorldActivity.py
+
diff --git a/activity/activity-imagequiz.svg b/activity/activity-imagequiz.svg
new file mode 100755
index 0000000..78bf5a2
--- /dev/null
+++ b/activity/activity-imagequiz.svg
@@ -0,0 +1,355 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ id="svg1"
+ sodipodi:version="0.32"
+ inkscape:version="0.39"
+ width="48pt"
+ height="48pt"
+ sodipodi:docbase="/home/stefan/software/Lila-themes/lila-gnome/Lila/scalable/mimetypes"
+ sodipodi:docname="x-gdesklets-display.svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs3">
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient1330"
+ x1="-0.00000000"
+ y1="-0.00000000"
+ x2="0.99270076"
+ y2="0.95714283" />
+ <linearGradient
+ id="linearGradient1304">
+ <stop
+ style="stop-color:#9fec67;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop1305" />
+ <stop
+ style="stop-color:#a7d585;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop1306" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient1304"
+ id="linearGradient587"
+ x1="0.00000000"
+ y1="0.00826446"
+ x2="0.99176955"
+ y2="0.98760331" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient624"
+ x1="0.00330033"
+ y1="0.00413223"
+ x2="0.99339932"
+ y2="0.99173552" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient626"
+ x1="-0.00000000"
+ y1="0.00413223"
+ x2="1.00607908"
+ y2="0.99586779" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient628"
+ x1="0.00000000"
+ y1="0.00413223"
+ x2="0.93548387"
+ y2="0.99586779" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient630"
+ x1="-0.00000000"
+ y1="0.01239669"
+ x2="1.00000000"
+ y2="0.98347110" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient632"
+ x1="0.00289855"
+ y1="-0.00000000"
+ x2="0.98840582"
+ y2="1.01111114" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient622"
+ x1="0.00392157"
+ y1="0.00826446"
+ x2="1.00000000"
+ y2="1.00413227" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient635"
+ x1="0.00289855"
+ y1="0.00900901"
+ x2="0.99130434"
+ y2="1.00450456" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient637"
+ x1="0.00000000"
+ y1="0.01136364"
+ x2="0.99130434"
+ y2="0.99431819" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient639"
+ x1="0.00289855"
+ y1="0.01923077"
+ x2="1.00289857"
+ y2="0.98557693" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient641"
+ x1="0.01714286"
+ y1="0.00413223"
+ x2="0.98857141"
+ y2="0.99173552" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient643"
+ x1="-0.00000000"
+ y1="0.00826446"
+ x2="0.99492383"
+ y2="0.99173552" />
+ <linearGradient
+ id="linearGradient866">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0.00000000"
+ id="stop867" />
+ <stop
+ style="stop-color:#e0ecff;stop-opacity:1;"
+ offset="1.00000000"
+ id="stop868" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient584">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0.00000000"
+ id="stop585" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1.00000000"
+ id="stop586" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient578">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop579" />
+ <stop
+ style="stop-color:#e0ecff;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop580" />
+ </linearGradient>
+ <linearGradient
+ xlink:href="#linearGradient866"
+ id="linearGradient581"
+ x1="0.02631579"
+ y1="0.07031250"
+ x2="0.98245615"
+ y2="0.96093750" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient583"
+ x1="0.01094890"
+ y1="0.01030928"
+ x2="1.01459849"
+ y2="0.96907216" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient860"
+ x1="0.02380952"
+ y1="0.04687500"
+ x2="1.00793648"
+ y2="0.99218750" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient861"
+ x1="0.04069768"
+ y1="0.04687500"
+ x2="0.99418604"
+ y2="0.96875000" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient862"
+ x1="0.01824817"
+ y1="0.02542373"
+ x2="0.98540145"
+ y2="0.94915253" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient869"
+ x1="0.06481481"
+ y1="0.04687500"
+ x2="0.95370370"
+ y2="0.94531250" />
+ <radialGradient
+ xlink:href="#linearGradient584"
+ id="radialGradient975"
+ cx="0.50000000"
+ cy="0.50000000"
+ r="0.50000000"
+ fx="0.50000000"
+ fy="0.50000000" />
+ <linearGradient
+ xlink:href="#linearGradient584"
+ id="linearGradient983"
+ x1="0.00000000"
+ y1="0.04687500"
+ x2="0.99462366"
+ y2="1.00000000" />
+ <linearGradient
+ xlink:href="#linearGradient578"
+ id="linearGradient984"
+ x1="0.05303030"
+ y1="0.05468750"
+ x2="0.96212119"
+ y2="0.96875000" />
+ <linearGradient
+ xlink:href="#linearGradient1304"
+ id="linearGradient586"
+ x1="1.1227998e-15"
+ y1="-8.4567769e-18"
+ x2="0.99420291"
+ y2="0.99576271" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6423387"
+ inkscape:cx="29.916903"
+ inkscape:cy="24.206299"
+ inkscape:window-width="922"
+ inkscape:window-height="644"
+ inkscape:window-x="33"
+ inkscape:window-y="222" />
+ <path
+ d="M 4.4389304 48.086315 C 5.2483181 50.50443 38.765774 61.377487 40.966438 59.55288 C 42.936267 57.902492 57.675565 8.7785888 56.688667 6.8303779 C 55.662821 4.6824777 21.657054 -0.6675724 20.042945 0.80153134 C 18.054709 2.621752 3.5888672 45.311841 4.4389304 48.086315 z "
+ style="font-size:12;opacity:0.302675;fill-rule:evenodd;stroke-width:12.5;"
+ id="path639" />
+ <path
+ d="M 4.5541516 46.08493 C 5.3114351 48.34726 36.66955 58.519843 38.728432 56.812781 C 40.571358 55.268717 54.36111 9.3095333 53.437795 7.4867937 C 52.478034 5.477268 20.663028 0.47192513 19.152904 1.8463842 C 17.292759 3.5493405 3.7588508 43.489198 4.5541516 46.08493 z "
+ style="font-size:12;fill:url(#linearGradient581);fill-rule:evenodd;stroke:#261933;stroke-width:1.87726;"
+ id="path634" />
+ <path
+ d="M 6.6876568 43.965773 C 6.360392 43.13667 19.363758 4.5872629 20.579427 3.7922731 C 22.09876 2.7554118 46.659814 7.3454032 46.98631 8.172561 C 47.28557 8.9306762 27.823671 5.3906629 23.493845 9.2635774 C 15.189619 16.482895 6.9861475 44.721984 6.6876568 43.965773 z "
+ style="font-size:12;opacity:0.699936;fill:url(#linearGradient583);fill-rule:evenodd;stroke:#ffffff;stroke-width:0.100121pt;"
+ id="path640" />
+ <path
+ style="fill:url(#linearGradient1330);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;"
+ d="M 41.235318,18.253482 C 41.581346,17.972342 46.938667,17.974476 48.222246,18.377972 C 48.654754,18.513932 47.091741,19.884468 46.383581,19.871851 C 44.762765,19.842971 40.968458,18.470302 41.235318,18.253482 z "
+ id="path1328"
+ sodipodi:nodetypes="csss" />
+ <path
+ style="font-size:12;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.4375;opacity:0.300000;"
+ d="M 28.733730,24.316844 C 28.733730,22.032108 28.733730,19.747371 28.733730,17.462634 C 30.983371,17.462634 33.233013,17.462634 35.482655,17.462634 C 34.357834,18.605003 34.902936,21.443345 35.482655,22.032108 C 36.607476,23.174476 39.981939,23.174476 41.106759,22.032108 C 41.686478,21.443345 42.231580,18.605003 41.106759,17.462634 C 43.356401,17.462634 45.606043,17.462634 47.855685,17.462634 C 47.855685,19.747371 47.855685,22.032108 47.855685,24.316844 C 48.980506,23.174476 51.411849,23.359012 52.354968,24.316844 C 53.479789,25.459212 53.479789,27.743949 52.354968,28.886317 C 51.411849,29.844149 48.980506,30.028685 47.855685,28.886317 C 47.855685,31.551843 47.855685,34.217368 47.855685,36.882895 C 45.606043,36.882895 43.356401,36.882895 41.106759,36.882895 C 42.231580,35.740526 41.939960,32.990381 41.106759,32.313422 C 39.981939,31.171054 36.607476,31.171054 35.482655,32.313422 C 34.545304,33.075000 34.357834,35.740526 35.482655,36.882895 C 33.233013,36.882895 30.983371,36.882895 28.733730,36.882895 C 28.733730,34.217368 28.733730,31.551843 28.733730,28.886317 C 27.608909,30.028685 25.171797,29.647896 24.234446,28.886317 C 23.109625,27.743949 23.109625,25.459212 24.234446,24.316844 C 25.275947,23.470645 27.608909,23.174476 28.733730,24.316844 z "
+ id="path644"
+ sodipodi:nodetypes="cccsscccssccccccccccc" />
+ <path
+ style="font-size:12.000000;fill:url(#linearGradient586);fill-opacity:1.00000000;fill-rule:evenodd;stroke:#261933;stroke-width:1.1335607;"
+ d="M 27.140220,22.947455 C 27.140220,20.662718 27.140220,18.377983 27.140220,16.093246 C 29.389861,16.093246 31.639503,16.093246 33.889145,16.093246 C 32.764324,17.235614 33.309426,20.073956 33.889145,20.662718 C 35.013966,21.805087 38.388428,21.805087 39.513249,20.662718 C 40.092968,20.073956 40.638070,17.235614 39.513249,16.093246 C 41.762891,16.093246 44.012533,16.093246 46.262175,16.093246 C 46.262175,18.377983 46.262175,20.662718 46.262175,22.947455 C 47.386995,21.805087 49.818339,21.989623 50.761458,22.947455 C 51.886279,24.089823 51.886279,26.374559 50.761458,27.516928 C 49.818339,28.474760 47.386995,28.659296 46.262175,27.516928 C 46.262175,30.182454 46.262175,32.847979 46.262175,35.513506 C 44.012533,35.513506 41.762891,35.513506 39.513249,35.513506 C 40.638070,34.371137 40.346450,31.620992 39.513249,30.944033 C 38.388428,29.801664 35.013966,29.801664 33.889145,30.944033 C 32.951794,31.705611 32.764324,34.371137 33.889145,35.513506 C 31.639503,35.513506 29.389861,35.513506 27.140220,35.513506 C 27.140220,32.847979 27.140220,30.182454 27.140220,27.516928 C 26.015399,28.659296 23.578287,28.278506 22.640936,27.516928 C 21.516115,26.374559 21.516115,24.089823 22.640936,22.947455 C 23.682437,22.101256 26.015399,21.805087 27.140220,22.947455 z "
+ id="path619"
+ sodipodi:nodetypes="cccsscccssccccccccccc" />
+ <path
+ style="font-size:12;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.4375;opacity:0.300000;"
+ d="M 17.036391,35.780014 C 18.627127,34.164461 20.217862,32.548909 21.808599,30.933356 C 23.399336,32.548909 24.990073,34.164461 26.580810,35.780014 C 24.990073,35.780014 23.399336,38.178483 23.399336,39.011119 C 23.399336,40.626672 25.785442,43.050001 27.376178,43.050001 C 28.196025,43.050001 30.557652,41.434448 30.557652,39.818896 C 32.148389,41.434448 33.739126,43.050001 35.329863,44.665554 C 33.739126,46.281107 32.148389,47.896659 30.557652,49.512212 C 32.148389,49.512212 33.739126,51.388738 33.739126,52.743317 C 33.739126,54.358870 32.148389,55.974423 30.557652,55.974423 C 29.223881,55.974423 27.376178,54.358870 27.376178,52.743317 C 25.520318,54.628129 23.664459,56.512940 21.808599,58.397752 C 20.217862,56.782199 18.627127,55.166646 17.036391,53.551094 C 18.627127,53.551094 20.335695,51.397023 20.217862,50.319988 C 20.217862,48.704435 17.831755,46.281107 16.241020,46.281107 C 15.047966,46.146477 13.059549,47.896659 13.059549,49.512212 C 11.468806,47.896659 9.8780703,46.281107 8.2873353,44.665554 C 10.143192,42.780742 11.999056,40.895932 13.854913,39.011119 C 12.264177,39.011119 10.806003,36.991678 10.673442,35.780014 C 10.673442,34.164461 12.264177,32.548909 13.854913,32.548909 C 15.180527,32.698496 17.036391,34.164461 17.036391,35.780014 z "
+ id="path645"
+ sodipodi:nodetypes="cccsscccssccccccccccc" />
+ <path
+ style="font-size:12.000000;fill:url(#linearGradient587);fill-opacity:1.00000000;fill-rule:evenodd;stroke:#261933;stroke-width:1.1335607;"
+ d="M 15.442878,34.161645 C 17.033614,32.546092 18.624349,30.930539 20.215089,29.314987 C 21.805826,30.930539 23.396563,32.546092 24.987300,34.161645 C 23.396563,34.161645 21.805826,36.560114 21.805826,37.392750 C 21.805826,39.008303 24.191931,41.431632 25.782668,41.431632 C 26.602515,41.431632 28.964142,39.816079 28.964142,38.200527 C 30.554879,39.816079 32.145616,41.431632 33.736353,43.047185 C 32.145616,44.662737 30.554879,46.278290 28.964142,47.893843 C 30.554879,47.893843 32.145616,49.770369 32.145616,51.124948 C 32.145616,52.740501 30.554879,54.356054 28.964142,54.356054 C 27.630371,54.356054 25.782668,52.740501 25.782668,51.124948 C 23.926808,53.009760 22.070949,54.894571 20.215089,56.779383 C 18.624349,55.163830 17.033614,53.548277 15.442878,51.932725 C 17.033614,51.932725 18.742183,49.778654 18.624349,48.701619 C 18.624349,47.086066 16.238250,44.662737 14.647507,44.662737 C 13.454461,44.528108 11.466036,46.278290 11.466036,47.893843 C 9.8753003,46.278290 8.2845653,44.662737 6.6938223,43.047185 C 8.5496863,41.162373 10.405543,39.277563 12.261407,37.392750 C 10.670664,37.392750 9.2124903,35.373309 9.0799293,34.161645 C 9.0799293,32.546092 10.670664,30.930539 12.261407,30.930539 C 13.587022,31.080127 15.442878,32.546092 15.442878,34.161645 z "
+ id="path620"
+ sodipodi:nodetypes="cccsscccssccccccccccc" />
+ <path
+ style="fill:url(#linearGradient622);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 22.907363,25.853964 C 22.726340,25.768168 22.622974,24.046350 23.048327,23.634932 C 23.609724,23.091928 25.663193,22.893812 25.867614,22.990696 C 26.015604,23.060835 24.601196,23.656606 24.105560,24.136004 C 23.614445,24.611028 23.078751,25.935193 22.907363,25.853964 z "
+ id="path621"
+ sodipodi:nodetypes="cssss" />
+ <path
+ style="fill:url(#linearGradient624);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 28.052561,20.342174 C 27.804925,20.090675 27.305680,17.415524 27.911596,16.906253 C 28.668336,16.270216 31.900462,16.662443 32.140527,16.906253 C 32.323356,17.091933 29.961583,17.538038 29.180276,18.194724 C 28.598928,18.683345 28.227818,20.520165 28.052561,20.342174 z "
+ id="path623"
+ sodipodi:nodetypes="cssss" />
+ <path
+ style="fill:url(#linearGradient626);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 40.386940,21.201155 C 40.159272,21.008471 41.074692,18.558765 41.021279,17.192579 C 40.986964,16.222873 45.005194,16.913632 45.250209,17.120998 C 45.428086,17.271541 43.060103,17.578366 42.360440,18.123142 C 41.650461,18.675951 40.582164,21.366380 40.386940,21.201155 z "
+ id="path625"
+ sodipodi:nodetypes="cssss" />
+ <path
+ style="fill:url(#linearGradient628);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 28.052561,34.515350 C 27.886531,34.281876 27.645920,28.516920 27.841114,26.712944 C 27.880861,26.345609 28.884589,28.352734 28.898347,28.860396 C 28.955071,30.953536 28.191954,34.711367 28.052561,34.515350 z "
+ id="path627"
+ sodipodi:nodetypes="csss" />
+ <path
+ style="fill:url(#linearGradient630);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 40.457422,30.506774 C 40.885558,31.677060 41.020600,33.517186 40.950798,34.586932 C 40.950943,34.815057 41.854928,34.082058 41.796583,33.513206 C 41.737459,32.936750 40.368641,30.298397 40.457422,30.506774 z "
+ id="path629"
+ sodipodi:nodetypes="csss" />
+ <path
+ style="fill:url(#linearGradient632);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 46.236960,24.136004 C 45.992144,24.050268 47.578377,23.123789 48.280942,23.062278 C 48.941032,23.004487 50.560048,23.654584 50.324925,23.778095 C 49.878721,24.012492 46.673472,24.288874 46.236960,24.136004 z "
+ id="path631"
+ sodipodi:nodetypes="csss" />
+ <path
+ style="fill:url(#linearGradient622);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 10.297099,34.863628 C 9.7636633,33.990433 10.365120,32.698105 10.790470,32.286687 C 11.351871,31.743683 12.489071,31.545567 13.186867,32.071942 C 13.324310,32.161521 11.990926,32.666269 11.495293,33.145667 C 11.004182,33.620691 10.386763,35.032751 10.297099,34.863628 z "
+ id="path633"
+ sodipodi:nodetypes="cssss" />
+ <path
+ style="fill:url(#linearGradient635);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 16.570007,34.004648 C 16.665752,33.685164 18.893758,31.202910 20.094117,30.711889 C 20.585329,30.510954 21.724359,31.663038 21.644725,31.928779 C 21.559901,32.211831 20.239875,32.096825 19.600740,32.358269 C 18.548307,32.788780 16.501290,34.233957 16.570007,34.004648 z "
+ id="path949"
+ sodipodi:nodetypes="cssss" />
+ <path
+ style="fill:url(#linearGradient637);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 26.085101,42.093379 C 25.940985,42.093379 27.500211,41.608463 27.917638,41.234398 C 28.299009,40.892646 28.827803,39.792924 29.186317,39.802764 C 29.697083,39.816785 30.891644,40.960747 30.948370,41.234398 C 30.985621,41.414098 30.160713,41.008434 29.679692,41.091236 C 28.774564,41.247040 26.316890,42.093379 26.085101,42.093379 z "
+ id="path636"
+ sodipodi:nodetypes="csssss" />
+ <path
+ style="fill:url(#linearGradient639);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 27.988119,48.750478 C 27.839073,48.567237 29.325250,48.734924 29.820656,49.036804 C 30.382483,49.379159 31.381211,50.717775 31.159817,50.683183 C 30.770366,50.622333 28.285459,49.116030 27.988119,48.750478 z "
+ id="path638"
+ sodipodi:nodetypes="csss" />
+ <path
+ style="fill:url(#linearGradient641);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 16.851936,52.329562 C 16.719016,52.183315 18.318632,51.156515 18.754953,50.396856 C 19.140922,49.724881 19.200422,47.904404 19.318811,48.034661 C 19.482351,48.214590 19.747668,50.723931 19.318811,51.470582 C 18.925378,52.155565 17.000945,52.493503 16.851936,52.329562 z "
+ id="path952"
+ sodipodi:nodetypes="cssss" />
+ <path
+ style="fill:url(#linearGradient643);fill-rule:evenodd;stroke:none;stroke-opacity:1;stroke-width:1pt;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1.00;"
+ d="M 9.6627543,42.666033 C 9.4056853,42.917618 7.6266843,43.287038 7.8302243,42.952360 C 8.6134353,41.664519 12.262315,38.410980 12.411558,38.585876 C 12.567734,38.768889 10.697863,41.653008 9.6627543,42.666033 z "
+ id="path642"
+ sodipodi:nodetypes="csss" />
+ <metadata
+ id="metadata59">
+ <rdf:RDF
+ id="RDF60">
+ <cc:Work
+ rdf:about=""
+ id="Work61">
+ <dc:format
+ id="format62">image/svg+xml</dc:format>
+ <dc:type
+ id="type64"
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100755
index 0000000..04abcfa
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,7 @@
+[Activity]
+name = ImageQuiz
+activity_version = 1
+host_version = 1
+service_name = org.laptop.ImageQuiz
+icon = activity-imagequiz
+exec = sugar-activity activity.Activity
diff --git a/activity/manifest b/activity/manifest
new file mode 100755
index 0000000..efb34b6
--- /dev/null
+++ b/activity/manifest
@@ -0,0 +1,159 @@
+ImageQuizPlus.activity/
+ImageQuizPlus.activity/plugins
+ImageQuizPlus.activity/plugins/questions.py
+ImageQuizPlus.activity/plugins/single_player.py
+ImageQuizPlus.activity/plugins/tools.py
+ImageQuizPlus.activity/plugins/demoplugin.py
+ImageQuizPlus.activity/plugins/multi_player.py
+ImageQuizPlus.activity/plugins/README
+ImageQuizPlus.activity/plugins/_quizsocket.py
+ImageQuizPlus.activity/plugins/flashcard.py
+ImageQuizPlus.activity/db
+ImageQuizPlus.activity/db/main.db
+ImageQuizPlus.activity/db/Geography.csv
+ImageQuizPlus.activity/db/Animals.csv
+ImageQuizPlus.activity/db/Arts.csv
+ImageQuizPlus.activity/db/Cars_&_Motorbikes.csv
+ImageQuizPlus.activity/db/Africa.csv
+ImageQuizPlus.activity/db/Europe.csv
+ImageQuizPlus.activity/db/XO_Laptop.csv
+ImageQuizPlus.activity/db/Astronomy.csv
+ImageQuizPlus.activity/db/Dogs.csv
+ImageQuizPlus.activity/db/Bears.csv
+ImageQuizPlus.activity/activity
+ImageQuizPlus.activity/activity/activity-ImageQuizPlus.svg
+ImageQuizPlus.activity/activity/activity.info
+ImageQuizPlus.activity/locale
+ImageQuizPlus.activity/locale/de
+ImageQuizPlus.activity/locale/de/activity.linfo
+ImageQuizPlus.activity/images
+ImageQuizPlus.activity/images/Animals
+ImageQuizPlus.activity/images/Bears
+ImageQuizPlus.activity/images/Bears/bears.jpg
+ImageQuizPlus.activity/images/Dogs
+ImageQuizPlus.activity/images/Dogs/dogs.jpg
+ImageQuizPlus.activity/images/Africa
+ImageQuizPlus.activity/images/Africa/africa2.jpg
+ImageQuizPlus.activity/images/bubble.gif
+ImageQuizPlus.activity/images/logo.jpg
+ImageQuizPlus.activity/images/cursor_big.png
+ImageQuizPlus.activity/images/cursor_big.xbm
+ImageQuizPlus.activity/images/cursor_big_mask.xbm
+ImageQuizPlus.activity/images/Icons
+ImageQuizPlus.activity/images/Icons/no1.png
+ImageQuizPlus.activity/images/Icons/no2.png
+ImageQuizPlus.activity/images/Icons/ok1.png
+ImageQuizPlus.activity/images/Icons/ok2.png
+ImageQuizPlus.activity/images/Icons/bulb.png
+ImageQuizPlus.activity/images/Emotes
+ImageQuizPlus.activity/images/Emotes/face-crying.png
+ImageQuizPlus.activity/images/Emotes/face-devil-grin.png
+ImageQuizPlus.activity/images/Emotes/face-glasses.png
+ImageQuizPlus.activity/images/Emotes/face-grin.png
+ImageQuizPlus.activity/images/Emotes/face-monkey.png
+ImageQuizPlus.activity/images/Emotes/face-devil-grin_24px.png
+ImageQuizPlus.activity/images/Emotes/face-grin_24px.png
+ImageQuizPlus.activity/images/Cars_&_Motorbikes
+ImageQuizPlus.activity/images/Cars_&_Motorbikes/5104352.JPG
+ImageQuizPlus.activity/images/xclock.svg
+ImageQuizPlus.activity/images/xclock.png
+ImageQuizPlus.activity/images/Astronomy
+ImageQuizPlus.activity/images/Astronomy/planets_iau_big.jpg
+ImageQuizPlus.activity/images/Geography
+ImageQuizPlus.activity/images/Geography/latinamerica.jpg
+ImageQuizPlus.activity/images/Europe
+ImageQuizPlus.activity/images/Europe/europe.gif
+ImageQuizPlus.activity/images/Europe/800px-Regions_of_Europe.png
+ImageQuizPlus.activity/images/XO_Laptop
+ImageQuizPlus.activity/images/XO_Laptop/xo.jpg
+ImageQuizPlus.activity/images/XO_Laptop/olpc-xo-2.jpg
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.5.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.7.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.3.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.4.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.3.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.5.2.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.4.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.4.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.5.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.7.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.5.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.6.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.4.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.3.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.5.2.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.6.xml
+ImageQuizPlus.activity/flashcards/russian/RLD524.xml
+ImageQuizPlus.activity/flashcards/russian/RLD053.xml
+ImageQuizPlus.activity/flashcards/russian/RLD697.xml
+ImageQuizPlus.activity/flashcards/russian/RLD261.xml
+ImageQuizPlus.activity/flashcards/russian/RLD551.xml
+ImageQuizPlus.activity/flashcards/russian/RLD180.xml
+ImageQuizPlus.activity/flashcards/russian/RLD601.xml
+ImageQuizPlus.activity/flashcards/russian/RLD577.xml
+ImageQuizPlus.activity/flashcards/russian/RLD449.xml
+ImageQuizPlus.activity/flashcards/russian/RLD206.xml
+ImageQuizPlus.activity/flashcards/russian/RLD235.xml
+ImageQuizPlus.activity/flashcards/russian/RLD317.xml
+ImageQuizPlus.activity/flashcards/russian/RLD770.xml
+ImageQuizPlus.activity/flashcards/russian/RLD661.xml
+ImageQuizPlus.activity/flashcards/russian/RLD474.xml
+ImageQuizPlus.activity/flashcards/russian/RLD001.xml
+ImageQuizPlus.activity/flashcards/russian/RLD374.xml
+ImageQuizPlus.activity/flashcards/russian/RLD080.xml
+ImageQuizPlus.activity/flashcards/russian/RLD026.xml
+ImageQuizPlus.activity/flashcards/russian/RLD425.xml
+ImageQuizPlus.activity/flashcards/russian/RLD290.xml
+ImageQuizPlus.activity/flashcards/russian/RLD156.xml
+ImageQuizPlus.activity/flashcards/russian/RLD398.xml
+ImageQuizPlus.activity/flashcards/russian/RLD634.xml
+ImageQuizPlus.activity/flashcards/russian/RLD346.xml
+ImageQuizPlus.activity/flashcards/russian/RLD500.xml
+ImageQuizPlus.activity/flashcards/russian/RLD105.xml
+ImageQuizPlus.activity/flashcards/russian/RLD131.xml
+ImageQuizPlus.activity/flashcards/russian/RLD735.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/street traffic.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/travel.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/vehicles.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/rail, plane, ship.xml
+ImageQuizPlus.activity/flashcards/german/colors.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/house and apartment.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/clothing and jewelry.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/furnishings.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/doctor and hospital.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/basic commodities.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/groceries, food.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/fruits and vegetables.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/drinking and smoking.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/meals, restaurant.xml
+ImageQuizPlus.activity/flashcards/german/Countries and People/geographical names.xml
+ImageQuizPlus.activity/flashcards/german/Countries and People/nationalities, inhabitants, languages.xml
+ImageQuizPlus.activity/olpcgames
+ImageQuizPlus.activity/olpcgames/camera.py
+ImageQuizPlus.activity/olpcgames/canvas.py
+ImageQuizPlus.activity/olpcgames/eventwrap.py
+ImageQuizPlus.activity/olpcgames/pangofont.py
+ImageQuizPlus.activity/olpcgames/activity.py
+ImageQuizPlus.activity/olpcgames/mesh.py
+ImageQuizPlus.activity/olpcgames/tubeconn.py
+ImageQuizPlus.activity/olpcgames/util.py
+ImageQuizPlus.activity/olpcgames/__init__.py
+ImageQuizPlus.activity/olpcgames/video.py
+ImageQuizPlus.activity/olpcgames/gtkEvent.py
+ImageQuizPlus.activity/sounds
+ImageQuizPlus.activity/sounds/accessed.wav
+ImageQuizPlus.activity/sounds/transfer_data.wav
+ImageQuizPlus.activity/sounds/sorry.wav
+ImageQuizPlus.activity/po
+ImageQuizPlus.activity/quiz.py
+ImageQuizPlus.activity/activity.py
+ImageQuizPlus.activity/setup.py
+ImageQuizPlus.activity/LICENSE.txt
+ImageQuizPlus.activity/layout.py
+ImageQuizPlus.activity/frontend.py
+ImageQuizPlus.activity/backend.py
+ImageQuizPlus.activity/MANIFEST
diff --git a/activity/permissions.info b/activity/permissions.info
new file mode 100755
index 0000000..585d713
--- /dev/null
+++ b/activity/permissions.info
@@ -0,0 +1 @@
+constant-uid
diff --git a/backend.py b/backend.py
new file mode 100755
index 0000000..b019a0c
--- /dev/null
+++ b/backend.py
@@ -0,0 +1,326 @@
+import os
+import sqlite3
+import random
+import sys
+from sugar.activity import activity
+from xmlio import Xmlio
+from path import path
+import subprocess
+
+import traceback
+
+random.seed()
+
+global debug_info
+debug_info = True
+
+'''
+Handles pysqlite Interaction: query("SELECT..."), commit("UPDATE...")
+'''
+class Question:
+ imgfn = u''
+ sndfn = u''
+ map = u''
+ cat = 0
+ subcat = 0
+ lang = 1 # 1 = English
+ text = u''
+ answer = u''
+ answer_link = ''
+
+class Database:
+
+ db_filename = "main.db"
+ cur = '';
+ con = '';
+
+ def __init__(self):
+ pass
+
+ def query(self, q):
+ dataList = []
+ try:
+ self.cur.execute(q)
+ except:
+ # If DB connection get's lost on Activity-Startup, then Reset
+ print 'query reset=', q
+ self.con = sqlite3.connect(self.db_fn)
+ self.cur = self.con.cursor()
+ self.cur.execute("-- types unicode")
+ try:
+ self.cur.execute(q)
+ except:
+ print 'execute failed', q
+
+ data = self.cur.fetchall()
+ #if data:
+ #print 'data', len(data)
+ #else:
+ #print 'data = None'
+ if data: dataList = [list(row) for row in data]
+ #print 'dbmgr return dataList', len(dataList)
+ return dataList
+
+ def commit(self, q):
+ try:
+ self.cur.execute(q)
+ except:
+ # If DB connection get's lost on Activity-Startup, then Reset
+ print 'execute reset=', q
+ self.con = sqlite3.connect(self.db_fn)
+ self.cur = self.con.cursor()
+ self.cur.execute("-- types unicode")
+ self.cur.execute(q)
+
+ try:
+ self.con.commit()
+ except:
+ print 'failure on commit'
+
+ def load(self, services):
+ # Init DB
+ db = os.path.join(activity.get_activity_root(), "data")
+ fullname = os.path.join(db, self.db_filename)
+ self.db_fn = fullname
+ #for testing, remove db
+ #subprocess.call("rm -rf " + fullname, shell=True)
+ if os.path.isfile(fullname):
+ self.con = sqlite3.connect(fullname)
+ self.cur = self.con.cursor()
+ self.cur.execute("-- types unicode")
+ else:
+ # Check for Database Setup
+ self.con = sqlite3.connect(fullname)
+ self.cur = self.con.cursor()
+ self.cur.execute("-- types unicode")
+ # Create image, sound folders
+ self.imagepath = os.path.join(db,'image')
+ subprocess.call("mkdir -p " + self.imagepath, shell=True)
+ self.soundpath = os.path.join(db,'sound')
+ subprocess.call("mkdir -p " + self.soundpath, shell=True)
+ # Setup New Database
+ self.cur.execute("CREATE TABLE 'categories' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'text' TEXT);")
+ self.cur.execute("CREATE TABLE 'questions' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'prompt' TEXT, 'response' TEXT, 'image_fn' TEXT, 'sound_fn' TEXT, 'map' TEXT, 'answer_link' VARCHAR ( 1024 ));")
+ self.cur.execute("CREATE TABLE 'Leitner' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'question_id' INT, 'count_found' INT, 'count notfound' INT, 'box' INT, 'time' INT, 'day' INT);")
+ self.cur.execute("CREATE TABLE 'catlink' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'parent_id' INT, 'child_id' INT);")
+ self.cur.execute("CREATE TABLE 'quizlink' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'quiz_id' INT, 'question_id' INT);")
+
+ # Add questions, quizzes from builtins
+ self.load_builtins()
+
+ self.con.commit()
+
+ print "* database created"
+
+ if debug_info: print "- database loaded"
+ return True
+
+ #loads preinstalled quizzes from activity-bundle/imagequiz_library into db
+ def load_builtins(self):
+ self.builtins = path(activity.get_bundle_path()) / 'imagequiz_library'
+ categorypaths = self.builtins.listdir()
+ categories = []
+ for categorypath in categorypaths:
+ categories.append(categorypath.name)
+ categories.sort()
+
+ for quiz in categories:
+ if not (quiz == 'image' or quiz == 'sound'):
+ #create category entry in db
+ self.cat_id = self.add_cat(path(quiz).namebase)
+ if 'xml' in path(quiz).name:
+ self.add_questions_xml(quiz)
+ else:
+ self.add_questions_csv(quiz)
+
+ def add_questions_xml(self, quizname):
+ quiztree = Xmlio(path(self.builtins) / quizname)
+ quiz = quiztree.getroot()
+ question = Question()
+ #what we need to do:
+ for card in quiz:
+ question.prompt = card.findtext('question')
+ question.response = card.findtext('answer')
+ if card.find('answer').findtext('image'):
+ question.imgfn = card.find('answer').findtext('image')
+ else:
+ question.imgfn = ""
+ if card.find('question').findtext('sound'):
+ question.sndfn = card.find('question').findtext('sound')
+ else:
+ question.sndfn = ""
+ question.map = ""
+ question.answer_link = ""
+ self.add_question(question)
+ if question.imgfn and len(question.imgfn) > 0:
+ srcpath = path(self.builtins) / 'image' / question.imgfn
+ dstpath = path(self.imagepath) / question.imgfn
+ if srcpath.exists():
+ path.copy(srcpath,dstpath)
+ if question.sndfn and len(question.sndfn) > 0:
+ srcpath = path(self.builtins) / 'sound' / question.sndfn
+ dstpath = path(self.soundpath) / question.sndfn
+ if srcpath.exists():
+ path.copy(srcpath,dstpath)
+
+ def add_questions_csv(self, quiz):
+ question = Question()
+ csvquiz = open(path(self.builtins) / quiz)
+ csvtext = csvquiz.read()
+ csvquiz.close()
+
+ questions = csvtext.split(";;;<br>")
+ for q in questions:
+ csv = q.split(";;")
+ if len(csv) < 2:
+ #no more questions
+ return
+ question.prompt = csv[1].replace("'", "")
+ question.response = csv[4].replace("'", "")
+ question.imgfn = "%s" % (csv[0])
+ question.sndfn = ""
+ question.map = csv[2]
+ if len(csv) > 5:
+ question.answer_link = csv[5].replace("'", "")
+ else:
+ question.answer_link = ""
+ self.add_question(question)
+ #copy resources to resource folders
+ if question.imgfn and len(question.imgfn) > 0:
+ srcpath = path(self.builtins) / 'image' / question.imgfn
+ dstpath = path(self.imagepath) / question.imgfn
+ if srcpath.exists():
+ path.copy(srcpath,dstpath)
+ if question.sndfn and len(question.sndfn) > 0:
+ srcpath = path(self.builtins) / 'sound' / question.sndfn
+ dstpath = path(self.soundpath) / question.sndfn
+ if srcpath.exists():
+ path.copy(srcpath,dstpath)
+
+ def add_cat(self, cat_namebase):
+ # returns new cat_id
+ # Category exists?
+ q = u"SELECT count(*) FROM categories WHERE text='%s'" % (cat_namebase)
+ res = self.query(q)
+ if res[0][0] == 0:
+ # No, insert
+ q = u"INSERT INTO categories (text) VALUES ('%s')" % (cat_namebase)
+ self.commit(q)
+ q = u"SELECT id FROM categories WHERE text='%s'" % (cat_namebase)
+ res = self.query(q)
+ id = res[0][0]
+ return id;
+
+
+ def add_question(self, question):
+
+ if len(question.answer_link) > 0:
+ question.answer_link = question.answer_link.replace("'", '"')
+
+ # Insert Question
+ q = u'INSERT INTO questions (prompt, response, image_fn, sound_fn, map, answer_link) VALUES ("%s", "%s", "%s", "%s", "%s", "%s")' % (question.prompt, question.response, question.imgfn, question.sndfn, question.map, question.answer_link)
+ self.commit(q)
+
+ # Get Question_ID of last question inserted
+ question_id = self.cur.lastrowid
+
+ # Link question to quiz
+ q = u"INSERT INTO quizlink (quiz_id, question_id) VALUES (%i, %i)" % (self.cat_id, question_id)
+ self.commit(q)
+
+ return True
+
+'''
+Main Game Backend (Kernel)
+'''
+
+class Kernel:
+ services = []
+ def __init__(self):
+ pass
+
+ def load(self, s):
+ pass
+
+ def add_service(self, descriptor, function):
+ print "* registering service:",descriptor,
+ self.services.append([descriptor, function])
+ print "... ok"
+
+ def start_service(self, descriptor, params=False):
+ for s in self.services:
+ if s[0] == descriptor:
+ print "* starting service:",s[0],"(",params,")"
+
+ try:
+ print s[1](params)
+ except:
+ try:
+ print s[1]()
+ except:
+ print "! starting service %s failed" % s[0]
+ traceback.print_exc()
+ def hex2rgb(hex):
+ if hex[0:1] == '#': hex = hex[1:];
+ return (hex2dec(hex[:2]), hex2dec(hex[2:4]), hex2dec(hex[4:6]))
+
+'''
+ Class PluginManager:
+ ====================
+ This class loads the plugins and hooks in the services
+ (Loads all .py files from plugin_dir/ not starting with _)
+ - plugin1.py will be loaded
+ - _plugin1.py will not be loaded
+ Usage:
+ plugger = PluginManager()
+ plugger.load_plugins()
+ plugger.debug()
+'''
+def empty(): pass
+
+class PluginManager:
+ plugins = []
+ plugin_dir = 'plugins'
+
+ services = ''
+
+ def __init__(self):
+ pass
+
+ def load(self, services):
+ self.services = services
+ if debug_info: print "- plugger loaded"
+
+ def load_plugins(self):
+ filenames = os.listdir(self.plugin_dir)
+ for fn in filenames:
+ # All .py files not starting with _
+ if fn[-3:] == '.py' and fn[:1] != '_':
+ # Extract File without Extension
+ plugin_fn = os.path.splitext(fn)[0]
+
+ # Import
+ p = __import__(os.path.join(self.plugin_dir, plugin_fn))
+ self.plugins.append(p)
+
+
+ if debug_info:
+ try:
+ print "- plugin import:", p.__PLUGIN_NAME__
+ except:
+ print "no plugin name for ", plugin_fn
+
+ p.__SERVICES__ = self.services
+
+ try: p.load()
+ except: traceback.print_exc()
+
+ def close_plugins(self):
+ for p in self.plugins:
+ try: p.close()
+ except: traceback.print_exc()
+
+ def debug(self):
+ print "Activated Plugins:"
+ for p in self.plugins:
+ print "-", p.__PLUGIN_NAME__
diff --git a/checkphrase.py b/checkphrase.py
new file mode 100755
index 0000000..ef10b3f
--- /dev/null
+++ b/checkphrase.py
@@ -0,0 +1,39 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+from path import path
+from xmlio import Xmlio
+NEWLINE = '\n'
+
+#open file for output
+outfile = open('errorrptp','w')
+errorcount = 0
+#for each directory in Renyi
+try:
+ d = path('/home/tonya/Desktop/ImageQuizPlus.activity/flashcards/russian/Phrases')
+ s = path('/home/tonya/Desktop/ImageQuizPlus.activity/flashcards/russian/Phrases/sound')
+except:
+ print 'invalid path'
+
+for cat in d.dirs():
+ outfile.write(cat + NEWLINE)
+ for f in cat.files('*.xml'):
+ #write directory name
+ deck = Xmlio(f)
+ cards = deck.getroot()
+ outfile.write(f + ' ' + str(len(cards)) + NEWLINE)
+ #if img not in image:
+ for card in cards:
+ question_node = card.find('question')
+ if question_node:
+ sound = question_node.findtext('sound')
+ if sound:
+ sounds = sound.split('/')
+ for item in sounds:
+ temp = item[:-4] + '.ogg'
+ if not path(s/temp).isfile():
+ outfile.write(temp + ' not found' + NEWLINE)
+ errorcount += 1
+outfile.write(str(errorcount) + ' errors')
+print (errorcount, ' errors')
diff --git a/checkrenyi.py b/checkrenyi.py
new file mode 100755
index 0000000..162cfcb
--- /dev/null
+++ b/checkrenyi.py
@@ -0,0 +1,50 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+from path import path
+from xmlio import Xmlio
+NEWLINE = '\n'
+
+#open file for output
+outfile = open('errorrpt','w')
+errorcount = 0
+#for each directory in Renyi
+try:
+ d = path('/home/olpc/Activities/ImageQuizPlus.activity/flashcards/library/russian/renyi')
+ i = path('/home/olpc/Activities/ImageQuizPlus.activity/flashcards/library/russian/renyi/image')
+ s = path('/home/olpc/Activities/ImageQuizPlus.activity/flashcards/library/russian/renyi/sound')
+except:
+ print 'invalid path'
+print d
+print i
+print s
+for cat in d.dirs():
+ outfile.write(cat + NEWLINE)
+ for f in cat.files('*.xml'):
+ #write directory name
+ deck = Xmlio(f)
+ cards = deck.getroot()
+ outfile.write(f + ' ' + str(len(cards)) + NEWLINE)
+ #if img not in image:
+ for card in cards:
+ question_node = card.find('question')
+ if question_node:
+ sound = question_node.findtext('sound')
+ answer_node = card.find('answer')
+ if answer_node:
+ temp = answer_node.findtext('image')
+ if temp:
+ image = temp[:-4] + '.png'
+ if image and not path(i/image).isfile():
+ outfile.write(image + ' not found' + NEWLINE)
+ errorcount += 1
+ if sound:
+ sounds = sound.split('/')
+ for item in sounds:
+ temp = item[:-4] + '.ogg'
+ if not path(s/temp).isfile():
+ outfile.write(temp + ' not found' + NEWLINE)
+ errorcount += 1
+outfile.write(str(errorcount) + ' errors')
+print (errorcount, ' errors')
diff --git a/data/themes/default/Vera.ttf b/data/themes/default/Vera.ttf
new file mode 100644
index 0000000..58cd6b5
--- /dev/null
+++ b/data/themes/default/Vera.ttf
Binary files differ
diff --git a/data/themes/default/box.down.png b/data/themes/default/box.down.png
new file mode 100644
index 0000000..b9e965d
--- /dev/null
+++ b/data/themes/default/box.down.png
Binary files differ
diff --git a/data/themes/default/box.hover.png b/data/themes/default/box.hover.png
new file mode 100644
index 0000000..ef3c225
--- /dev/null
+++ b/data/themes/default/box.hover.png
Binary files differ
diff --git a/data/themes/default/box.normal.png b/data/themes/default/box.normal.png
new file mode 100644
index 0000000..90f8d40
--- /dev/null
+++ b/data/themes/default/box.normal.png
Binary files differ
diff --git a/data/themes/default/box.xcf b/data/themes/default/box.xcf
new file mode 100644
index 0000000..960ca8d
--- /dev/null
+++ b/data/themes/default/box.xcf
Binary files differ
diff --git a/data/themes/default/button.down.tga b/data/themes/default/button.down.tga
new file mode 100644
index 0000000..64873c1
--- /dev/null
+++ b/data/themes/default/button.down.tga
Binary files differ
diff --git a/data/themes/default/button.hover.tga b/data/themes/default/button.hover.tga
new file mode 100644
index 0000000..5e5c53a
--- /dev/null
+++ b/data/themes/default/button.hover.tga
Binary files differ
diff --git a/data/themes/default/button.normal.tga b/data/themes/default/button.normal.tga
new file mode 100644
index 0000000..e9371c7
--- /dev/null
+++ b/data/themes/default/button.normal.tga
Binary files differ
diff --git a/data/themes/default/check.png b/data/themes/default/check.png
new file mode 100644
index 0000000..4ef58a3
--- /dev/null
+++ b/data/themes/default/check.png
Binary files differ
diff --git a/data/themes/default/checkbox.off.hover.tga b/data/themes/default/checkbox.off.hover.tga
new file mode 100644
index 0000000..9a4d8a8
--- /dev/null
+++ b/data/themes/default/checkbox.off.hover.tga
Binary files differ
diff --git a/data/themes/default/checkbox.off.normal.tga b/data/themes/default/checkbox.off.normal.tga
new file mode 100644
index 0000000..de59f19
--- /dev/null
+++ b/data/themes/default/checkbox.off.normal.tga
Binary files differ
diff --git a/data/themes/default/checkbox.on.hover.tga b/data/themes/default/checkbox.on.hover.tga
new file mode 100644
index 0000000..4940c26
--- /dev/null
+++ b/data/themes/default/checkbox.on.hover.tga
Binary files differ
diff --git a/data/themes/default/checkbox.on.normal.tga b/data/themes/default/checkbox.on.normal.tga
new file mode 100644
index 0000000..9cf658e
--- /dev/null
+++ b/data/themes/default/checkbox.on.normal.tga
Binary files differ
diff --git a/data/themes/default/config.txt b/data/themes/default/config.txt
new file mode 100644
index 0000000..e4ef48b
--- /dev/null
+++ b/data/themes/default/config.txt
@@ -0,0 +1,291 @@
+desktop background desktop.png
+
+input font Vera.ttf 16
+input background input.normal.png
+input color #000000
+input:focus background input.focus.png
+input padding_left 6
+input padding_right 6
+input padding_top 3
+input padding_bottom 3
+
+link font Vera.ttf 24
+link color #0000FF
+link:hover color #FF0000
+link:down color #00FF00
+
+label font Vera.ttf 16
+label color #000000
+
+document font Vera.ttf 16
+document color #000000
+div font Vera.ttf 16
+div color #000000
+
+td font Vera.ttf 16
+td color #000000
+th font Vera.ttf 16
+th color #000000
+
+h1 font Vera.ttf 24
+h1 color #000000
+h2 font Vera.ttf 20
+h2 color #000000
+h3 font Vera.ttf 16
+h3 color #000000
+h4 font Vera.ttf 14
+h4 color #000000
+h5 font Vera.ttf 12
+h5 color #000000
+h6 font Vera.ttf 10
+h6 color #000000
+
+ul font Vera.ttf 16
+ul color #000000
+ol font Vera.ttf 16
+ol color #000000
+li font Vera.ttf 16
+li color #000000
+li padding_left 32
+
+pre font mono 16
+pre color #000000
+code font mono 16
+code color #000000
+
+checkbox off checkbox.off.normal.tga
+checkbox on checkbox.on.normal.tga
+checkbox:hover off checkbox.off.hover.tga
+checkbox:hover on checkbox.on.hover.tga
+checkbox:down off checkbox.off.hover.tga
+checkbox:down on checkbox.on.hover.tga
+
+switch off checkbox.off.normal.tga
+switch on checkbox.on.normal.tga
+switch:hover off checkbox.off.hover.tga
+switch:hover on checkbox.on.hover.tga
+switch:down off checkbox.off.hover.tga
+switch:down on checkbox.on.hover.tga
+
+radio off radio.off.normal.tga
+radio on radio.on.normal.tga
+radio:hover off radio.off.hover.tga
+radio:hover on radio.on.hover.tga
+radio:down off radio.off.hover.tga
+radio:down on radio.on.hover.tga
+
+button background button.normal.tga
+button:hover background button.hover.tga
+button:down background button.down.tga
+button padding_left 8
+button padding_right 8
+button padding_top 1
+button padding_bottom 1
+button.label font Vera.ttf 16
+button.label color #000000
+
+slider background slider.tga
+slider bar slider.bar.normal.tga
+slider:hover bar slider.bar.hover.tga
+slider width 16
+slider height 16
+
+hslider background hslider.tga
+hslider bar hslider.bar.normal.tga
+hslider:hover bar hslider.bar.hover.tga
+hslider:down bar hslider.bar.hover.tga
+hslider width 16
+hslider height 16
+
+vslider background vslider.tga
+vslider bar vslider.bar.normal.tga
+vslider:hover bar vslider.bar.hover.tga
+vslider:down bar vslider.bar.hover.tga
+vslider width 16
+vslider height 16
+
+xhscrollbar height 16
+xhscrollbar background scroller.slide.h.tga
+xhscrollbar bar scroller.slide.bar.normal.tga
+xhscrollbar:hover bar scroller.slide.bar.hover.tga
+
+xvscrollbar width 16
+xvscrollbar background scroller.slide.v.tga
+xvscrollbar bar scroller.slide.bar.normal.tga
+xvscrollbar:hover bar scroller.slide.bar.hover.tga
+
+hscrollbar.slider background hslider.tga
+hscrollbar.slider bar hslider.bar.normal.tga
+hscrollbar.slider:hover bar hslider.bar.hover.tga
+hscrollbar.slider:down bar hslider.bar.hover.tga
+hscrollbar.slider width 16
+hscrollbar.slider height 16
+hscrollbar minus hslider.left.tga
+hscrollbar plus hslider.right.tga
+
+vscrollbar.slider background vslider.tga
+vscrollbar.slider bar vslider.bar.normal.tga
+vscrollbar.slider:hover bar vslider.bar.hover.tga
+vscrollbar.slider:down bar vslider.bar.hover.tga
+vscrollbar.slider width 16
+vscrollbar.slider height 16
+vscrollbar minus vslider.up.tga
+vscrollbar plus vslider.down.tga
+
+
+select.selected background select.selected.normal.tga
+select.selected:hover background select.selected.hover.tga
+select.selected:down background select.selected.down.tga
+select.selected padding_left 4
+select.selected padding_right 4
+select.selected padding_top 1
+select.selected padding_bottom 1
+select.arrow background select.arrow.normal.tga
+select.arrow:hover background select.arrow.hover.tga
+select.arrow:down background select.arrow.down.tga
+select.arrow padding_left 1
+select.arrow padding_right 1
+
+select.options background select.options.png
+select.option background select.option.normal.png
+select.option:hover background select.option.hover.png
+select.option:down background select.option.hover.png
+select.option padding_left 4
+select.option padding_right 4
+select.option padding_top 1
+select.option padding_bottom 1
+#select.option border_top 1
+#select.option border_right 1
+#select.option border_bottom 1
+#select.option border_left 1
+
+select.option.label font Vera.ttf 16
+select.option.label color #000000
+select.options padding_left 1
+select.options padding_right 1
+select.options padding_top 1
+select.options padding_bottom 1
+select arrow select.arrow.png
+
+
+dialog background dialog.bar.png
+xdialog.bar background dialog.bar.png
+dialog.bar padding_left 8
+dialog.bar padding_right 8
+dialog.bar padding_top 2
+dialog.bar padding_bottom 1
+dialog.bar.close image dialog.close.normal.tga
+dialog.bar.close:hover image dialog.close.hover.tga
+dialog.bar.close:down image dialog.close.down.tga
+dialog.main background dialog.png
+dialog.main padding_left 8
+dialog.main padding_right 8
+dialog.main padding_top 4
+dialog.main padding_bottom 4
+
+keysym font Vera.ttf 16
+keysym background input.normal.png
+keysym color #000000
+keysym:focus background input.focus.png
+keysym padding_left 6
+keysym padding_right 6
+keysym padding_top 3
+keysym padding_bottom 3
+
+tool background tool.normal.tga
+tool:hover background tool.hover.tga
+tool:down background tool.down.tga
+tool padding_left 4
+tool padding_right 4
+tool padding_top 1
+tool padding_bottom 1
+tool.label font Vera.ttf 16
+tool.label color #000000
+
+menu background menu.normal.tga
+menu:hover background menu.hover.tga
+menu:down background menu.down.tga
+menu padding_left 6
+menu padding_right 6
+menu padding_top 3
+menu padding_bottom 3
+menu.label font Vera.ttf 16
+menu.label color #000000
+
+menu-open background menu.down.tga
+menu-open:hover background menu.down.tga
+menu-open:down background menu.down.tga
+menu-open padding_left 6
+menu-open padding_right 6
+menu-open padding_top 3
+menu-open padding_bottom 3
+
+menu.options background select.options.png
+menu.option background select.option.normal.png
+menu.option:hover background select.option.hover.png
+menu.option:down background select.option.hover.png
+menu.option padding_left 6
+menu.option padding_right 6
+menu.option padding_top 1
+menu.option padding_bottom 1
+menu.option.label font Vera.ttf 16
+menu.option.label color #000000
+menu.options padding_left 1
+menu.options padding_right 1
+menu.options padding_top 1
+menu.options padding_bottom 1
+menu arrow select.arrow.tga
+
+
+scrollarea.content background #ffffff
+scrollarea.content padding_left 1
+scrollarea.content padding_right 1
+scrollarea.content padding_top 1
+scrollarea.content padding_bottom 1
+
+
+list.item background list.item.normal.png
+list.item:hover background list.item.down.png
+list.item:down background list.item.down.png
+list.item padding_left 4
+list.item padding_right 4
+list.item padding_top 2
+list.item padding_bottom 2
+list.item margin_bottom 1
+list.item align -1
+list.item.label font Vera.ttf 14
+list.item.label color #000000
+
+list background list.png
+list padding_left 1
+list padding_right 1
+list padding_top 1
+list padding_bottom 1
+list.content background #eeeeee
+list.content padding_left 1
+list.content padding_right 1
+list.content padding_top 1
+list.content padding_bottom 1
+
+filedialog.folder image filebrowser.folder.png
+filedialog.label font Vera.ttf 14
+filedialog.label color #000000
+filedialog.title.label font Vera.ttf 16
+filedialog.title.label color #000000
+filedialog.input font Vera.ttf 14
+filedialog.input background input.normal.png
+filedialog.input color #000000
+filedialog.input:focus background input.focus.png
+filedialog.input padding_left 6
+filedialog.input padding_right 6
+filedialog.input padding_top 3
+filedialog.input padding_bottom 3
+
+dialog.title.label font Vera.ttf 16
+dialog.title.label color #000000
+
+
+progressbar background progressbar.tga
+progressbar bar progressbar.bar.tga
+progressbar width 16
+progressbar height 16
diff --git a/data/themes/default/console.input.focus.png b/data/themes/default/console.input.focus.png
new file mode 100644
index 0000000..819d835
--- /dev/null
+++ b/data/themes/default/console.input.focus.png
Binary files differ
diff --git a/data/themes/default/console.input.normal.png b/data/themes/default/console.input.normal.png
new file mode 100644
index 0000000..a14e329
--- /dev/null
+++ b/data/themes/default/console.input.normal.png
Binary files differ
diff --git a/data/themes/default/console.png b/data/themes/default/console.png
new file mode 100644
index 0000000..a14e329
--- /dev/null
+++ b/data/themes/default/console.png
Binary files differ
diff --git a/data/themes/default/desktop.png b/data/themes/default/desktop.png
new file mode 100644
index 0000000..c83f5cd
--- /dev/null
+++ b/data/themes/default/desktop.png
Binary files differ
diff --git a/data/themes/default/desktop.xcf b/data/themes/default/desktop.xcf
new file mode 100644
index 0000000..2c504ab
--- /dev/null
+++ b/data/themes/default/desktop.xcf
Binary files differ
diff --git a/data/themes/default/dialog.bar.png b/data/themes/default/dialog.bar.png
new file mode 100644
index 0000000..e014e04
--- /dev/null
+++ b/data/themes/default/dialog.bar.png
Binary files differ
diff --git a/data/themes/default/dialog.close.down.tga b/data/themes/default/dialog.close.down.tga
new file mode 100644
index 0000000..ade4813
--- /dev/null
+++ b/data/themes/default/dialog.close.down.tga
Binary files differ
diff --git a/data/themes/default/dialog.close.hover.tga b/data/themes/default/dialog.close.hover.tga
new file mode 100644
index 0000000..9f36bb7
--- /dev/null
+++ b/data/themes/default/dialog.close.hover.tga
Binary files differ
diff --git a/data/themes/default/dialog.close.normal.tga b/data/themes/default/dialog.close.normal.tga
new file mode 100644
index 0000000..ee3a5d4
--- /dev/null
+++ b/data/themes/default/dialog.close.normal.tga
Binary files differ
diff --git a/data/themes/default/dialog.png b/data/themes/default/dialog.png
new file mode 100644
index 0000000..26ae2a6
--- /dev/null
+++ b/data/themes/default/dialog.png
Binary files differ
diff --git a/data/themes/default/dot.down.png b/data/themes/default/dot.down.png
new file mode 100644
index 0000000..ab117a7
--- /dev/null
+++ b/data/themes/default/dot.down.png
Binary files differ
diff --git a/data/themes/default/dot.hover.png b/data/themes/default/dot.hover.png
new file mode 100644
index 0000000..090f07d
--- /dev/null
+++ b/data/themes/default/dot.hover.png
Binary files differ
diff --git a/data/themes/default/dot.normal.png b/data/themes/default/dot.normal.png
new file mode 100644
index 0000000..55bd736
--- /dev/null
+++ b/data/themes/default/dot.normal.png
Binary files differ
diff --git a/data/themes/default/dot.xcf b/data/themes/default/dot.xcf
new file mode 100644
index 0000000..3100750
--- /dev/null
+++ b/data/themes/default/dot.xcf
Binary files differ
diff --git a/data/themes/default/down.png b/data/themes/default/down.png
new file mode 100644
index 0000000..7532249
--- /dev/null
+++ b/data/themes/default/down.png
Binary files differ
diff --git a/data/themes/default/filebrowser.folder.png b/data/themes/default/filebrowser.folder.png
new file mode 100644
index 0000000..4a3bd2c
--- /dev/null
+++ b/data/themes/default/filebrowser.folder.png
Binary files differ
diff --git a/data/themes/default/generate.py b/data/themes/default/generate.py
new file mode 100644
index 0000000..a161556
--- /dev/null
+++ b/data/themes/default/generate.py
@@ -0,0 +1,98 @@
+import pygame
+from pygame.locals import *
+pygame.display.init()
+pygame.display.set_mode((80,80),32)
+
+def prep(name):
+ fname = name+".png"
+ img = pygame.image.load(fname)
+ w,h = img.get_width()/2,img.get_height()/2
+
+ out = pygame.Surface((w*3,h*3),SWSURFACE|SRCALPHA,32)
+ out.fill((0,0,0,0))
+ out.blit(img.subsurface(0,0,w,h),(0,0))
+ out.blit(img.subsurface(w,0,w,h),(w*2,0))
+ out.blit(img.subsurface(0,h,w,h),(0,h*2))
+ out.blit(img.subsurface(w,h,w,h),(w*2,h*2))
+ for i in range(0,w):
+ img = out.subsurface((w-1,0,1,h*3)).convert_alpha()
+ out.blit(img,(w+i,0))
+ for i in range(0,h):
+ img = out.subsurface((0,h-1,w*3,1)).convert_alpha()
+ out.blit(img,(0,h+i))
+
+ return out,w,h
+
+todo = [
+ ('button.normal','dot.normal',None,3,3,'789456123'),
+ ('button.hover','dot.hover',None,3,3,'789456123'),
+ ('button.down','dot.down',None,3,3,'789456123'),
+
+ ('checkbox.off.normal','box.normal',None,2,2,'7913'),
+ ('checkbox.on.normal','box.down','check',2,2,'7913'),
+ ('checkbox.off.hover','box.hover',None,2,2,'7913'),
+ ('checkbox.on.hover','box.hover','check',2,2,'7913'),
+
+ ('radio.off.normal','dot.normal',None,2,2,'7913'),
+ ('radio.on.normal','dot.down','radio',2,2,'7913'),
+ ('radio.off.hover','dot.hover',None,2,2,'7913'),
+ ('radio.on.hover','dot.hover','radio',2,2,'7913'),
+
+ ('tool.normal','box.normal',None,3,3,'789456123'),
+ ('tool.hover','box.hover',None,3,3,'789456123'),
+ ('tool.down','box.down',None,3,3,'789456123'),
+
+ ('hslider','idot.normal',None,3,3,'789456123'),
+ ('hslider.bar.normal','dot.normal',None,3,3,'789456123'),
+ ('hslider.bar.hover','dot.hover',None,3,3,'789456123'),
+ ('hslider.left','sbox.normal','left',2,2,'7913'),
+ ('hslider.right','sbox.normal','right',2,2,'7913'),
+
+
+ ('vslider','idot.normal',None,3,3,'789456123'),
+ ('vslider.bar.normal','vdot.normal',None,3,3,'789456123'),
+ ('vslider.bar.hover','vdot.hover',None,3,3,'789456123'),
+ ('vslider.up','vsbox.normal','up',2,2,'7913'),
+ ('vslider.down','vsbox.normal','down',2,2,'7913'),
+
+ ('dialog.close.normal','rdot.hover',None,2,2,'7913'),
+ ('dialog.close.hover','rdot.hover','x',2,2,'7913'),
+ ('dialog.close.down','rdot.down','x',2,2,'7913'),
+
+ ('menu.normal','desktop',None,1,1,'7'),
+ ('menu.hover','box.normal',None,3,3,'789456123'),
+ ('menu.down','box.down',None,3,3,'789456123'),
+
+ ('select.selected.normal','box.normal',None,3,3,'788455122'),
+ ('select.selected.hover','box.hover',None,3,3,'788455122'),
+ ('select.selected.down','box.down',None,3,3,'788455122'),
+
+ ('select.arrow.normal','box.hover',None,3,3,'889556223'),
+ ('select.arrow.hover','box.hover',None,3,3,'889556223'),
+ ('select.arrow.down','box.down',None,3,3,'889556223'),
+
+ ('progressbar','sbox.normal',None,3,3,'789456123'),
+ ('progressbar.bar','box.hover',None,3,3,'789456123'),
+ ]
+
+for fname,img,over,ww,hh,s in todo:
+ print fname
+ img,w,h = prep(img)
+ out = pygame.Surface((ww*w,hh*h),SWSURFACE|SRCALPHA,32)
+ out.fill((0,0,0,0))
+ n = 0
+ for y in range(0,hh):
+ for x in range(0,ww):
+ c = int(s[n])
+ xx,yy = (c-1)%3,2-(c-1)/3
+ out.blit(img.subsurface((xx*w,yy*h,w,h)),(x*w,y*h))
+ n += 1
+ if over != None:
+ over = pygame.image.load(over+".png")
+ out.blit(over,(0,0))
+ pygame.image.save(out,fname+".tga")
+
+
+
+
+
diff --git a/data/themes/default/hslider.bar.hover.tga b/data/themes/default/hslider.bar.hover.tga
new file mode 100644
index 0000000..5e5c53a
--- /dev/null
+++ b/data/themes/default/hslider.bar.hover.tga
Binary files differ
diff --git a/data/themes/default/hslider.bar.normal.tga b/data/themes/default/hslider.bar.normal.tga
new file mode 100644
index 0000000..e9371c7
--- /dev/null
+++ b/data/themes/default/hslider.bar.normal.tga
Binary files differ
diff --git a/data/themes/default/hslider.left.tga b/data/themes/default/hslider.left.tga
new file mode 100644
index 0000000..2fe406c
--- /dev/null
+++ b/data/themes/default/hslider.left.tga
Binary files differ
diff --git a/data/themes/default/hslider.right.tga b/data/themes/default/hslider.right.tga
new file mode 100644
index 0000000..86a9ca5
--- /dev/null
+++ b/data/themes/default/hslider.right.tga
Binary files differ
diff --git a/data/themes/default/hslider.tga b/data/themes/default/hslider.tga
new file mode 100644
index 0000000..ff3b4b2
--- /dev/null
+++ b/data/themes/default/hslider.tga
Binary files differ
diff --git a/data/themes/default/idot.normal.png b/data/themes/default/idot.normal.png
new file mode 100644
index 0000000..4e22195
--- /dev/null
+++ b/data/themes/default/idot.normal.png
Binary files differ
diff --git a/data/themes/default/input.focus.png b/data/themes/default/input.focus.png
new file mode 100644
index 0000000..477a826
--- /dev/null
+++ b/data/themes/default/input.focus.png
Binary files differ
diff --git a/data/themes/default/input.normal.png b/data/themes/default/input.normal.png
new file mode 100644
index 0000000..8519a98
--- /dev/null
+++ b/data/themes/default/input.normal.png
Binary files differ
diff --git a/data/themes/default/left.png b/data/themes/default/left.png
new file mode 100644
index 0000000..b965666
--- /dev/null
+++ b/data/themes/default/left.png
Binary files differ
diff --git a/data/themes/default/list.item.down.png b/data/themes/default/list.item.down.png
new file mode 100644
index 0000000..fd9dc21
--- /dev/null
+++ b/data/themes/default/list.item.down.png
Binary files differ
diff --git a/data/themes/default/list.item.hover.png b/data/themes/default/list.item.hover.png
new file mode 100644
index 0000000..627790d
--- /dev/null
+++ b/data/themes/default/list.item.hover.png
Binary files differ
diff --git a/data/themes/default/list.item.normal.png b/data/themes/default/list.item.normal.png
new file mode 100644
index 0000000..627790d
--- /dev/null
+++ b/data/themes/default/list.item.normal.png
Binary files differ
diff --git a/data/themes/default/list.png b/data/themes/default/list.png
new file mode 100644
index 0000000..99ad5bc
--- /dev/null
+++ b/data/themes/default/list.png
Binary files differ
diff --git a/data/themes/default/listitem.down.tga b/data/themes/default/listitem.down.tga
new file mode 100644
index 0000000..13e2e57
--- /dev/null
+++ b/data/themes/default/listitem.down.tga
Binary files differ
diff --git a/data/themes/default/listitem.hover.tga b/data/themes/default/listitem.hover.tga
new file mode 100644
index 0000000..8bdf60a
--- /dev/null
+++ b/data/themes/default/listitem.hover.tga
Binary files differ
diff --git a/data/themes/default/listitem.normal.tga b/data/themes/default/listitem.normal.tga
new file mode 100644
index 0000000..a2994aa
--- /dev/null
+++ b/data/themes/default/listitem.normal.tga
Binary files differ
diff --git a/data/themes/default/menu.down.tga b/data/themes/default/menu.down.tga
new file mode 100644
index 0000000..f89d4b4
--- /dev/null
+++ b/data/themes/default/menu.down.tga
Binary files differ
diff --git a/data/themes/default/menu.hover.tga b/data/themes/default/menu.hover.tga
new file mode 100644
index 0000000..b304b87
--- /dev/null
+++ b/data/themes/default/menu.hover.tga
Binary files differ
diff --git a/data/themes/default/menu.normal.tga b/data/themes/default/menu.normal.tga
new file mode 100644
index 0000000..d3eb2d0
--- /dev/null
+++ b/data/themes/default/menu.normal.tga
Binary files differ
diff --git a/data/themes/default/notes.txt b/data/themes/default/notes.txt
new file mode 100644
index 0000000..f6541e4
--- /dev/null
+++ b/data/themes/default/notes.txt
@@ -0,0 +1,8 @@
+dot and box.xcf:
+
+color -170
+
+.down
+.hover +64 brightness
+.normal, grayscale +127 brightness, +48 contrast
+
diff --git a/data/themes/default/out.tga b/data/themes/default/out.tga
new file mode 100644
index 0000000..7ed46cc
--- /dev/null
+++ b/data/themes/default/out.tga
Binary files differ
diff --git a/data/themes/default/progressbar.bar.tga b/data/themes/default/progressbar.bar.tga
new file mode 100644
index 0000000..184ae9c
--- /dev/null
+++ b/data/themes/default/progressbar.bar.tga
Binary files differ
diff --git a/data/themes/default/progressbar.tga b/data/themes/default/progressbar.tga
new file mode 100644
index 0000000..d459763
--- /dev/null
+++ b/data/themes/default/progressbar.tga
Binary files differ
diff --git a/data/themes/default/radio.off.hover.tga b/data/themes/default/radio.off.hover.tga
new file mode 100644
index 0000000..6b0f737
--- /dev/null
+++ b/data/themes/default/radio.off.hover.tga
Binary files differ
diff --git a/data/themes/default/radio.off.normal.tga b/data/themes/default/radio.off.normal.tga
new file mode 100644
index 0000000..3da51d8
--- /dev/null
+++ b/data/themes/default/radio.off.normal.tga
Binary files differ
diff --git a/data/themes/default/radio.on.hover.tga b/data/themes/default/radio.on.hover.tga
new file mode 100644
index 0000000..d26764b
--- /dev/null
+++ b/data/themes/default/radio.on.hover.tga
Binary files differ
diff --git a/data/themes/default/radio.on.normal.tga b/data/themes/default/radio.on.normal.tga
new file mode 100644
index 0000000..42515fe
--- /dev/null
+++ b/data/themes/default/radio.on.normal.tga
Binary files differ
diff --git a/data/themes/default/radio.png b/data/themes/default/radio.png
new file mode 100644
index 0000000..7596f48
--- /dev/null
+++ b/data/themes/default/radio.png
Binary files differ
diff --git a/data/themes/default/rdot.down.png b/data/themes/default/rdot.down.png
new file mode 100644
index 0000000..35cd4fe
--- /dev/null
+++ b/data/themes/default/rdot.down.png
Binary files differ
diff --git a/data/themes/default/rdot.hover.png b/data/themes/default/rdot.hover.png
new file mode 100644
index 0000000..5cd77a2
--- /dev/null
+++ b/data/themes/default/rdot.hover.png
Binary files differ
diff --git a/data/themes/default/rdot.normal.png b/data/themes/default/rdot.normal.png
new file mode 100644
index 0000000..636a207
--- /dev/null
+++ b/data/themes/default/rdot.normal.png
Binary files differ
diff --git a/data/themes/default/right.png b/data/themes/default/right.png
new file mode 100644
index 0000000..613779e
--- /dev/null
+++ b/data/themes/default/right.png
Binary files differ
diff --git a/data/themes/default/sbox.normal.png b/data/themes/default/sbox.normal.png
new file mode 100644
index 0000000..00be882
--- /dev/null
+++ b/data/themes/default/sbox.normal.png
Binary files differ
diff --git a/data/themes/default/scroller.slide.bar.hover.tga b/data/themes/default/scroller.slide.bar.hover.tga
new file mode 100644
index 0000000..d0b85a9
--- /dev/null
+++ b/data/themes/default/scroller.slide.bar.hover.tga
Binary files differ
diff --git a/data/themes/default/scroller.slide.bar.normal.tga b/data/themes/default/scroller.slide.bar.normal.tga
new file mode 100644
index 0000000..84ff6bb
--- /dev/null
+++ b/data/themes/default/scroller.slide.bar.normal.tga
Binary files differ
diff --git a/data/themes/default/scroller.slide.h.tga b/data/themes/default/scroller.slide.h.tga
new file mode 100644
index 0000000..0281567
--- /dev/null
+++ b/data/themes/default/scroller.slide.h.tga
Binary files differ
diff --git a/data/themes/default/scroller.slide.v.tga b/data/themes/default/scroller.slide.v.tga
new file mode 100644
index 0000000..cbaa875
--- /dev/null
+++ b/data/themes/default/scroller.slide.v.tga
Binary files differ
diff --git a/data/themes/default/select.arrow.down.tga b/data/themes/default/select.arrow.down.tga
new file mode 100644
index 0000000..d721002
--- /dev/null
+++ b/data/themes/default/select.arrow.down.tga
Binary files differ
diff --git a/data/themes/default/select.arrow.hover.tga b/data/themes/default/select.arrow.hover.tga
new file mode 100644
index 0000000..162d8e7
--- /dev/null
+++ b/data/themes/default/select.arrow.hover.tga
Binary files differ
diff --git a/data/themes/default/select.arrow.normal.tga b/data/themes/default/select.arrow.normal.tga
new file mode 100644
index 0000000..162d8e7
--- /dev/null
+++ b/data/themes/default/select.arrow.normal.tga
Binary files differ
diff --git a/data/themes/default/select.arrow.png b/data/themes/default/select.arrow.png
new file mode 100644
index 0000000..19de760
--- /dev/null
+++ b/data/themes/default/select.arrow.png
Binary files differ
diff --git a/data/themes/default/select.option.hover.png b/data/themes/default/select.option.hover.png
new file mode 100644
index 0000000..fd9dc21
--- /dev/null
+++ b/data/themes/default/select.option.hover.png
Binary files differ
diff --git a/data/themes/default/select.option.normal.png b/data/themes/default/select.option.normal.png
new file mode 100644
index 0000000..627790d
--- /dev/null
+++ b/data/themes/default/select.option.normal.png
Binary files differ
diff --git a/data/themes/default/select.options.png b/data/themes/default/select.options.png
new file mode 100644
index 0000000..477a826
--- /dev/null
+++ b/data/themes/default/select.options.png
Binary files differ
diff --git a/data/themes/default/select.selected.down.tga b/data/themes/default/select.selected.down.tga
new file mode 100644
index 0000000..7d952a0
--- /dev/null
+++ b/data/themes/default/select.selected.down.tga
Binary files differ
diff --git a/data/themes/default/select.selected.hover.tga b/data/themes/default/select.selected.hover.tga
new file mode 100644
index 0000000..91dd794
--- /dev/null
+++ b/data/themes/default/select.selected.hover.tga
Binary files differ
diff --git a/data/themes/default/select.selected.normal.tga b/data/themes/default/select.selected.normal.tga
new file mode 100644
index 0000000..54b8927
--- /dev/null
+++ b/data/themes/default/select.selected.normal.tga
Binary files differ
diff --git a/data/themes/default/slider.bar.hover.tga b/data/themes/default/slider.bar.hover.tga
new file mode 100644
index 0000000..5e5c53a
--- /dev/null
+++ b/data/themes/default/slider.bar.hover.tga
Binary files differ
diff --git a/data/themes/default/slider.bar.normal.tga b/data/themes/default/slider.bar.normal.tga
new file mode 100644
index 0000000..e9371c7
--- /dev/null
+++ b/data/themes/default/slider.bar.normal.tga
Binary files differ
diff --git a/data/themes/default/slider.tga b/data/themes/default/slider.tga
new file mode 100644
index 0000000..ff3b4b2
--- /dev/null
+++ b/data/themes/default/slider.tga
Binary files differ
diff --git a/data/themes/default/tool.down.tga b/data/themes/default/tool.down.tga
new file mode 100644
index 0000000..f89d4b4
--- /dev/null
+++ b/data/themes/default/tool.down.tga
Binary files differ
diff --git a/data/themes/default/tool.hover.tga b/data/themes/default/tool.hover.tga
new file mode 100644
index 0000000..184ae9c
--- /dev/null
+++ b/data/themes/default/tool.hover.tga
Binary files differ
diff --git a/data/themes/default/tool.normal.tga b/data/themes/default/tool.normal.tga
new file mode 100644
index 0000000..b304b87
--- /dev/null
+++ b/data/themes/default/tool.normal.tga
Binary files differ
diff --git a/data/themes/default/up.png b/data/themes/default/up.png
new file mode 100644
index 0000000..d42c324
--- /dev/null
+++ b/data/themes/default/up.png
Binary files differ
diff --git a/data/themes/default/vbox.normal.png b/data/themes/default/vbox.normal.png
new file mode 100644
index 0000000..9229c87
--- /dev/null
+++ b/data/themes/default/vbox.normal.png
Binary files differ
diff --git a/data/themes/default/vdot.down.png b/data/themes/default/vdot.down.png
new file mode 100644
index 0000000..e9e781e
--- /dev/null
+++ b/data/themes/default/vdot.down.png
Binary files differ
diff --git a/data/themes/default/vdot.hover.png b/data/themes/default/vdot.hover.png
new file mode 100644
index 0000000..74e043b
--- /dev/null
+++ b/data/themes/default/vdot.hover.png
Binary files differ
diff --git a/data/themes/default/vdot.normal.png b/data/themes/default/vdot.normal.png
new file mode 100644
index 0000000..f64089b
--- /dev/null
+++ b/data/themes/default/vdot.normal.png
Binary files differ
diff --git a/data/themes/default/vsbox.normal.png b/data/themes/default/vsbox.normal.png
new file mode 100644
index 0000000..2deca17
--- /dev/null
+++ b/data/themes/default/vsbox.normal.png
Binary files differ
diff --git a/data/themes/default/vslider.bar.hover.tga b/data/themes/default/vslider.bar.hover.tga
new file mode 100644
index 0000000..0a3f70a
--- /dev/null
+++ b/data/themes/default/vslider.bar.hover.tga
Binary files differ
diff --git a/data/themes/default/vslider.bar.normal.tga b/data/themes/default/vslider.bar.normal.tga
new file mode 100644
index 0000000..07ee06e
--- /dev/null
+++ b/data/themes/default/vslider.bar.normal.tga
Binary files differ
diff --git a/data/themes/default/vslider.down.tga b/data/themes/default/vslider.down.tga
new file mode 100644
index 0000000..61c75a6
--- /dev/null
+++ b/data/themes/default/vslider.down.tga
Binary files differ
diff --git a/data/themes/default/vslider.tga b/data/themes/default/vslider.tga
new file mode 100644
index 0000000..ff3b4b2
--- /dev/null
+++ b/data/themes/default/vslider.tga
Binary files differ
diff --git a/data/themes/default/vslider.up.tga b/data/themes/default/vslider.up.tga
new file mode 100644
index 0000000..ce73c30
--- /dev/null
+++ b/data/themes/default/vslider.up.tga
Binary files differ
diff --git a/data/themes/default/x.png b/data/themes/default/x.png
new file mode 100644
index 0000000..d00f36b
--- /dev/null
+++ b/data/themes/default/x.png
Binary files differ
diff --git a/data/themes/gray/Vera.ttf b/data/themes/gray/Vera.ttf
new file mode 100644
index 0000000..58cd6b5
--- /dev/null
+++ b/data/themes/gray/Vera.ttf
Binary files differ
diff --git a/data/themes/gray/box.down.png b/data/themes/gray/box.down.png
new file mode 100644
index 0000000..0009fe7
--- /dev/null
+++ b/data/themes/gray/box.down.png
Binary files differ
diff --git a/data/themes/gray/box.normal.png b/data/themes/gray/box.normal.png
new file mode 100644
index 0000000..e4599d9
--- /dev/null
+++ b/data/themes/gray/box.normal.png
Binary files differ
diff --git a/data/themes/gray/button.down.png b/data/themes/gray/button.down.png
new file mode 100644
index 0000000..efb67bc
--- /dev/null
+++ b/data/themes/gray/button.down.png
Binary files differ
diff --git a/data/themes/gray/button.normal.png b/data/themes/gray/button.normal.png
new file mode 100644
index 0000000..7393150
--- /dev/null
+++ b/data/themes/gray/button.normal.png
Binary files differ
diff --git a/data/themes/gray/checkbox.off.down.png b/data/themes/gray/checkbox.off.down.png
new file mode 100644
index 0000000..656f779
--- /dev/null
+++ b/data/themes/gray/checkbox.off.down.png
Binary files differ
diff --git a/data/themes/gray/checkbox.off.normal.png b/data/themes/gray/checkbox.off.normal.png
new file mode 100644
index 0000000..70943f1
--- /dev/null
+++ b/data/themes/gray/checkbox.off.normal.png
Binary files differ
diff --git a/data/themes/gray/checkbox.on.down.png b/data/themes/gray/checkbox.on.down.png
new file mode 100644
index 0000000..fa61a2b
--- /dev/null
+++ b/data/themes/gray/checkbox.on.down.png
Binary files differ
diff --git a/data/themes/gray/checkbox.on.normal.png b/data/themes/gray/checkbox.on.normal.png
new file mode 100644
index 0000000..5ee17b1
--- /dev/null
+++ b/data/themes/gray/checkbox.on.normal.png
Binary files differ
diff --git a/data/themes/gray/config.txt b/data/themes/gray/config.txt
new file mode 100644
index 0000000..0ea2006
--- /dev/null
+++ b/data/themes/gray/config.txt
@@ -0,0 +1,244 @@
+desktop background desktop.png
+
+input font Vera.ttf 16
+input background input.normal.png
+input color #000000
+input:focus background input.focus.png
+input padding_left 6
+input padding_right 6
+input padding_top 3
+input padding_bottom 3
+
+label font Vera.ttf 16
+label color #000000
+
+document font Vera.ttf 16
+document color #000000
+div font Vera.ttf 16
+div color #000000
+
+td font Vera.ttf 16
+td color #000000
+th font Vera.ttf 16
+th color #000000
+
+h1 font Vera.ttf 24
+h1 color #000000
+h2 font Vera.ttf 20
+h2 color #000000
+h3 font Vera.ttf 16
+h3 color #000000
+h4 font Vera.ttf 14
+h4 color #000000
+h5 font Vera.ttf 12
+h5 color #000000
+h6 font Vera.ttf 10
+h6 color #000000
+
+ul font Vera.ttf 16
+ul color #000000
+ol font Vera.ttf 16
+ol color #000000
+li font Vera.ttf 16
+li color #000000
+li padding_left 32
+
+pre font mono 16
+pre color #000000
+code font mono 16
+code color #000000
+
+checkbox off checkbox.off.normal.png
+checkbox on checkbox.on.normal.png
+checkbox:down off checkbox.off.down.png
+checkbox:down on checkbox.on.down.png
+
+switch off checkbox.off.normal.png
+switch on checkbox.on.normal.png
+switch:down off checkbox.off.down.png
+switch:down on checkbox.on.down.png
+
+radio off radio.off.normal.png
+radio on radio.on.normal.png
+radio:down off radio.off.down.png
+radio:down on radio.on.down.png
+
+button background button.normal.png
+button:down background button.down.png
+button padding_left 8
+button padding_right 8
+button padding_top 1
+button padding_bottom 1
+button.label font Vera.ttf 16
+button.label color #000000
+
+slider background slider.png
+slider bar slider.bar.normal.png
+slider:down bar slider.bar.down.png
+slider width 16
+slider height 16
+
+hslider background slider.png
+hslider bar slider.bar.normal.png
+hslider:down bar slider.bar.down.png
+hslider width 16
+hslider height 16
+
+vslider background slider.png
+vslider bar slider.bar.normal.png
+vslider:down bar slider.bar.down.png
+vslider width 16
+vslider height 16
+
+select.selected background select.selected.normal.png
+#select.selected:down background select.selected.down.png
+select.selected padding_left 4
+select.selected padding_right 4
+select.selected padding_top 1
+select.selected padding_bottom 1
+select.arrow background select.arrow.normal.png
+select.arrow:down background select.arrow.down.png
+select.arrow padding_left 1
+select.arrow padding_right 1
+
+select.options background select.options.png
+select.option background select.option.normal.png
+#select.option:hover background select.option.hover.png
+select.option padding_left 4
+select.option padding_right 4
+select.option padding_top 1
+select.option padding_bottom 1
+#select.option border_top 1
+#select.option border_right 1
+#select.option border_bottom 1
+#select.option border_left 1
+
+select.option.label font Vera.ttf 16
+select.option.label color #000000
+select.options padding_left 1
+select.options padding_right 1
+select.options padding_top 1
+select.options padding_bottom 1
+select arrow select.arrow.png
+
+
+dialog.bar background dialog.bar.png
+dialog.bar padding_left 8
+dialog.bar padding_right 8
+dialog.bar padding_top 2
+dialog.bar padding_bottom 1
+dialog.bar.close image dialog.close.normal.png
+dialog.bar.close:down image dialog.close.down.png
+dialog.main background dialog.png
+dialog.main padding_left 8
+dialog.main padding_right 8
+dialog.main padding_top 4
+dialog.main padding_bottom 4
+
+keysym font helvetica 16
+keysym background input.normal.png
+keysym color #000000
+keysym:focus background input.focus.png
+keysym padding_left 6
+keysym padding_right 6
+keysym padding_top 3
+keysym padding_bottom 3
+
+tool background tool.normal.png
+tool:down background tool.down.png
+tool padding_left 4
+tool padding_right 4
+tool padding_top 1
+tool padding_bottom 1
+tool.label font Vera.ttf 16
+tool.label color #000000
+
+menu background menu.normal.png
+menu:hover background menu.hover.png
+menu:down background menu.down.png
+menu padding_left 6
+menu padding_right 6
+menu padding_top 3
+menu padding_bottom 3
+menu.label font Vera.ttf 16
+menu.label color #000000
+
+menu-open background menu.down.png
+menu-open:down background menu.down.png
+menu-open padding_left 6
+menu-open padding_right 6
+menu-open padding_top 3
+menu-open padding_bottom 3
+
+menu.options background select.options.png
+menu.option background menu.option.normal.png
+menu.option:hover background menu.option.hover.png
+#menu.option.label color #FF0000
+menu.option padding_left 6
+menu.option padding_right 6
+menu.option padding_top 1
+menu.option padding_bottom 1
+menu.option.label font Vera.ttf 16
+menu.option.label color #000000
+menu.options padding_left 1
+menu.options padding_right 1
+menu.options padding_top 1
+menu.options padding_bottom 1
+menu arrow select.arrow.png
+
+
+scrollarea.content background #ffffff
+scrollarea.content padding_left 1
+scrollarea.content padding_right 1
+scrollarea.content padding_top 1
+scrollarea.content padding_bottom 1
+hscrollbar height 15
+##hscrollbar background scroller.slide.h.png
+hscrollbar background slider.png
+##hscrollbar bar scroller.slide.bar.normal.png
+hscrollbar bar slider.bar.normal.png
+##hscrollbar:down bar scroller.slide.bar.down.png
+vscrollbar width 15
+##vscrollbar background scroller.slide.v.png
+vscrollbar background slider.png
+##vscrollbar bar scroller.slide.bar.normal.png
+vscrollbar bar slider.bar.normal.png
+##vscrollbar:down bar scroller.slide.bar.down.png
+
+list.item background list.item.normal.png
+#list.item:down background list.item.down.png
+list.item padding_left 4
+list.item padding_right 4
+list.item padding_top 2
+list.item padding_bottom 2
+list.item margin_bottom 1
+list.item align -1
+list.item.label font Vera.ttf 14
+list.item.label color #000000
+
+list background list.png
+list padding_left 1
+list padding_right 1
+list padding_top 1
+list padding_bottom 1
+list.content background #eeeeee
+list.content padding_left 1
+list.content padding_right 1
+list.content padding_top 1
+list.content padding_bottom 1
+
+filedialog.folder image filebrowser.folder.png
+filedialog.label font Vera.ttf 14
+filedialog.label color #000000
+filedialog.title.label font Vera.ttf 16
+filedialog.title.label color #000000
+filedialog.input font Vera.ttf 14
+filedialog.input background input.normal.png
+filedialog.input color #000000
+filedialog.input:focus background input.focus.png
+filedialog.input padding_left 6
+filedialog.input padding_right 6
+filedialog.input padding_top 3
+filedialog.input padding_bottom 3
+
+
diff --git a/data/themes/gray/console.input.focus.png b/data/themes/gray/console.input.focus.png
new file mode 100644
index 0000000..819d835
--- /dev/null
+++ b/data/themes/gray/console.input.focus.png
Binary files differ
diff --git a/data/themes/gray/console.input.normal.png b/data/themes/gray/console.input.normal.png
new file mode 100644
index 0000000..a14e329
--- /dev/null
+++ b/data/themes/gray/console.input.normal.png
Binary files differ
diff --git a/data/themes/gray/console.png b/data/themes/gray/console.png
new file mode 100644
index 0000000..a14e329
--- /dev/null
+++ b/data/themes/gray/console.png
Binary files differ
diff --git a/data/themes/gray/desktop.png b/data/themes/gray/desktop.png
new file mode 100644
index 0000000..73ac803
--- /dev/null
+++ b/data/themes/gray/desktop.png
Binary files differ
diff --git a/data/themes/gray/dialog.bar.png b/data/themes/gray/dialog.bar.png
new file mode 100644
index 0000000..ffac15d
--- /dev/null
+++ b/data/themes/gray/dialog.bar.png
Binary files differ
diff --git a/data/themes/gray/dialog.close.down.png b/data/themes/gray/dialog.close.down.png
new file mode 100644
index 0000000..cde6e96
--- /dev/null
+++ b/data/themes/gray/dialog.close.down.png
Binary files differ
diff --git a/data/themes/gray/dialog.close.normal.png b/data/themes/gray/dialog.close.normal.png
new file mode 100644
index 0000000..73dc9e4
--- /dev/null
+++ b/data/themes/gray/dialog.close.normal.png
Binary files differ
diff --git a/data/themes/gray/dialog.png b/data/themes/gray/dialog.png
new file mode 100644
index 0000000..1308b9c
--- /dev/null
+++ b/data/themes/gray/dialog.png
Binary files differ
diff --git a/data/themes/gray/filebrowser.folder.png b/data/themes/gray/filebrowser.folder.png
new file mode 100644
index 0000000..4a3bd2c
--- /dev/null
+++ b/data/themes/gray/filebrowser.folder.png
Binary files differ
diff --git a/data/themes/gray/input.focus.png b/data/themes/gray/input.focus.png
new file mode 100644
index 0000000..477a826
--- /dev/null
+++ b/data/themes/gray/input.focus.png
Binary files differ
diff --git a/data/themes/gray/input.normal.png b/data/themes/gray/input.normal.png
new file mode 100644
index 0000000..8519a98
--- /dev/null
+++ b/data/themes/gray/input.normal.png
Binary files differ
diff --git a/data/themes/gray/list.item.normal.png b/data/themes/gray/list.item.normal.png
new file mode 100644
index 0000000..627790d
--- /dev/null
+++ b/data/themes/gray/list.item.normal.png
Binary files differ
diff --git a/data/themes/gray/list.png b/data/themes/gray/list.png
new file mode 100644
index 0000000..99ad5bc
--- /dev/null
+++ b/data/themes/gray/list.png
Binary files differ
diff --git a/data/themes/gray/menu.down.png b/data/themes/gray/menu.down.png
new file mode 100644
index 0000000..3555053
--- /dev/null
+++ b/data/themes/gray/menu.down.png
Binary files differ
diff --git a/data/themes/gray/menu.hover.png b/data/themes/gray/menu.hover.png
new file mode 100644
index 0000000..f4b2a6a
--- /dev/null
+++ b/data/themes/gray/menu.hover.png
Binary files differ
diff --git a/data/themes/gray/menu.normal.png b/data/themes/gray/menu.normal.png
new file mode 100644
index 0000000..9a7aca6
--- /dev/null
+++ b/data/themes/gray/menu.normal.png
Binary files differ
diff --git a/data/themes/gray/menu.option.hover.png b/data/themes/gray/menu.option.hover.png
new file mode 100644
index 0000000..8ae05f3
--- /dev/null
+++ b/data/themes/gray/menu.option.hover.png
Binary files differ
diff --git a/data/themes/gray/menu.option.normal.png b/data/themes/gray/menu.option.normal.png
new file mode 100644
index 0000000..394200b
--- /dev/null
+++ b/data/themes/gray/menu.option.normal.png
Binary files differ
diff --git a/data/themes/gray/radio.off.down.png b/data/themes/gray/radio.off.down.png
new file mode 100644
index 0000000..5a6e9a3
--- /dev/null
+++ b/data/themes/gray/radio.off.down.png
Binary files differ
diff --git a/data/themes/gray/radio.off.normal.png b/data/themes/gray/radio.off.normal.png
new file mode 100644
index 0000000..4a57f1f
--- /dev/null
+++ b/data/themes/gray/radio.off.normal.png
Binary files differ
diff --git a/data/themes/gray/radio.on.down.png b/data/themes/gray/radio.on.down.png
new file mode 100644
index 0000000..483bd66
--- /dev/null
+++ b/data/themes/gray/radio.on.down.png
Binary files differ
diff --git a/data/themes/gray/radio.on.normal.png b/data/themes/gray/radio.on.normal.png
new file mode 100644
index 0000000..43b380b
--- /dev/null
+++ b/data/themes/gray/radio.on.normal.png
Binary files differ
diff --git a/data/themes/gray/select.arrow.down.png b/data/themes/gray/select.arrow.down.png
new file mode 100644
index 0000000..9ef850e
--- /dev/null
+++ b/data/themes/gray/select.arrow.down.png
Binary files differ
diff --git a/data/themes/gray/select.arrow.normal.png b/data/themes/gray/select.arrow.normal.png
new file mode 100644
index 0000000..fde6e42
--- /dev/null
+++ b/data/themes/gray/select.arrow.normal.png
Binary files differ
diff --git a/data/themes/gray/select.arrow.png b/data/themes/gray/select.arrow.png
new file mode 100644
index 0000000..19de760
--- /dev/null
+++ b/data/themes/gray/select.arrow.png
Binary files differ
diff --git a/data/themes/gray/select.option.normal.png b/data/themes/gray/select.option.normal.png
new file mode 100644
index 0000000..627790d
--- /dev/null
+++ b/data/themes/gray/select.option.normal.png
Binary files differ
diff --git a/data/themes/gray/select.options.png b/data/themes/gray/select.options.png
new file mode 100644
index 0000000..477a826
--- /dev/null
+++ b/data/themes/gray/select.options.png
Binary files differ
diff --git a/data/themes/gray/select.selected.normal.png b/data/themes/gray/select.selected.normal.png
new file mode 100644
index 0000000..e1463f8
--- /dev/null
+++ b/data/themes/gray/select.selected.normal.png
Binary files differ
diff --git a/data/themes/gray/slider.bar.normal.png b/data/themes/gray/slider.bar.normal.png
new file mode 100644
index 0000000..b335bda
--- /dev/null
+++ b/data/themes/gray/slider.bar.normal.png
Binary files differ
diff --git a/data/themes/gray/slider.png b/data/themes/gray/slider.png
new file mode 100644
index 0000000..0ed9619
--- /dev/null
+++ b/data/themes/gray/slider.png
Binary files differ
diff --git a/data/themes/gray/tool.down.png b/data/themes/gray/tool.down.png
new file mode 100644
index 0000000..760e666
--- /dev/null
+++ b/data/themes/gray/tool.down.png
Binary files differ
diff --git a/data/themes/gray/tool.normal.png b/data/themes/gray/tool.normal.png
new file mode 100644
index 0000000..17b344d
--- /dev/null
+++ b/data/themes/gray/tool.normal.png
Binary files differ
diff --git a/data/themes/tools/config.txt b/data/themes/tools/config.txt
new file mode 100644
index 0000000..b663eb0
--- /dev/null
+++ b/data/themes/tools/config.txt
@@ -0,0 +1,11 @@
+tool.draw image icons48.draw.tga
+tool.pixel image icons48.pixel.tga
+tool.line image icons48.line.tga
+tool.fill image icons48.fill.tga
+
+tool.select image icons48.select.tga
+tool.eraser image icons48.eraser.tga
+
+tool.tile image icons48.tile.tga
+tool.code image icons48.code.tga
+tool.bkgr image icons48.bkgr.tga
diff --git a/data/themes/tools/icons48.bkgr.tga b/data/themes/tools/icons48.bkgr.tga
new file mode 100644
index 0000000..67a614b
--- /dev/null
+++ b/data/themes/tools/icons48.bkgr.tga
Binary files differ
diff --git a/data/themes/tools/icons48.code.tga b/data/themes/tools/icons48.code.tga
new file mode 100644
index 0000000..bfe9615
--- /dev/null
+++ b/data/themes/tools/icons48.code.tga
Binary files differ
diff --git a/data/themes/tools/icons48.draw.tga b/data/themes/tools/icons48.draw.tga
new file mode 100644
index 0000000..0eec5ff
--- /dev/null
+++ b/data/themes/tools/icons48.draw.tga
Binary files differ
diff --git a/data/themes/tools/icons48.eraser.tga b/data/themes/tools/icons48.eraser.tga
new file mode 100644
index 0000000..a7f4d42
--- /dev/null
+++ b/data/themes/tools/icons48.eraser.tga
Binary files differ
diff --git a/data/themes/tools/icons48.fill.tga b/data/themes/tools/icons48.fill.tga
new file mode 100644
index 0000000..cb7be71
--- /dev/null
+++ b/data/themes/tools/icons48.fill.tga
Binary files differ
diff --git a/data/themes/tools/icons48.line.tga b/data/themes/tools/icons48.line.tga
new file mode 100644
index 0000000..19f9f9c
--- /dev/null
+++ b/data/themes/tools/icons48.line.tga
Binary files differ
diff --git a/data/themes/tools/icons48.pixel.tga b/data/themes/tools/icons48.pixel.tga
new file mode 100644
index 0000000..976b66a
--- /dev/null
+++ b/data/themes/tools/icons48.pixel.tga
Binary files differ
diff --git a/data/themes/tools/icons48.select.tga b/data/themes/tools/icons48.select.tga
new file mode 100644
index 0000000..09ee631
--- /dev/null
+++ b/data/themes/tools/icons48.select.tga
Binary files differ
diff --git a/data/themes/tools/icons48.tile.tga b/data/themes/tools/icons48.tile.tga
new file mode 100644
index 0000000..8ca8bae
--- /dev/null
+++ b/data/themes/tools/icons48.tile.tga
Binary files differ
diff --git a/db/Africa.csv b/db/Africa.csv
new file mode 100755
index 0000000..ddcca4c
--- /dev/null
+++ b/db/Africa.csv
@@ -0,0 +1 @@
+africa2.jpg;;Where is Egypt?;;274, 87, 272, 95, 273, 147, 338, 148, 334, 143, 332, 137, 314, 100, 315, 98, 321, 109, 327, 112, 329, 101, 325, 91, 313, 91, 311, 87, 308, 88, 296, 90, 295, 92, 277, 86, ;;13;;Today, Egypt is widely regarded as an important political and cultural centre of the Middle East.;;&lt;a href='http://en.wikipedia.org/wiki/Egypt' target='_blank'&gt;Egypt&lt;/a&gt;;;<br>africa2.jpg;;Where is Ethiopia?;;338, 197, 319, 228, 332, 249, 354, 256, 367, 253, 383, 248, 401, 231, 374, 216, 367, 212, 370, 205, 361, 194, 340, 195, ;;13;;is one of the oldest nations in the world and Africa\'s second-most populous nation.;;&lt;a href='http://en.wikipedia.org/wiki/Ethiopia' target='_blank'&gt;Ethiopia&lt;/a&gt;;;<br>africa2.jpg;;Where is Algeria?;;79, 116, 148, 164, 164, 164, 200, 140, 186, 120, 184, 96, 172, 72, 176, 50, 145, 52, 117, 62, 123, 83, 108, 86, 109, 92, 79, 106, 80, 114, ;;13;;is the second largest country on the African continent;;&lt;a href='http://en.wikipedia.org/wiki/Algeria' target='_blank'&gt;Algeria&lt;/a&gt;;;<br>africa2.jpg;;Where is Italy?;;187, 1, 196, 11, 202, 18, 200, 42, 200, 48, 209, 53, 217, 55, 227, 52, 228, 46, 233, 36, 237, 27, 234, 18, 225, 14, 218, 10, 214, 3, 188, 1, 200, 15, 177, 20, 175, 31, 182, 40, 201, 44, ;;13;;Italy (Italian: Italia), officially the Italian Republic, (Italian: Repubblica Italiana), is located in Southern Europe;;&lt;a href='http://en.wikipedia.org/wiki/Italy' target='_blank'&gt;Italy&lt;/a&gt;;;<br>africa2.jpg;;Where is Madagascar?;;412, 346, 400, 360, 389, 370, 382, 374, 381, 380, 381, 388, 380, 396, 377, 403, 380, 413, 382, 420, 389, 425, 401, 422, 402, 412, 409, 401, 412, 386, 415, 374, 418, 365, 412, 345, ;;13;;is the fourth largest island in the world, and is home to five percent of the world\'s plant and animal species.;;&lt;a href='http://en.wikipedia.org/wiki/Madagascar' target='_blank'&gt;Madagascar&lt;/a&gt;;;<br>africa2.jpg;;Where is Portugal ?;;81, 11, 92, 16, 88, 48, 79, 48, 78, 35, 79, 13, ;;13;;Portugal has about 11.8 millions inhabitants, most people who speak Portuguese live in Brazil.;;&lt;a href='http://en.wikipedia.org/wiki/Portugal' target='_blank'&gt;Portugal&lt;/a&gt;;;<br>africa2.jpg;;Where is Lybia ?;;198, 78, 183, 94, 191, 133, 215, 142, 250, 154, 270, 158, 272, 87, 242, 76, 238, 89, 218, 84, 199, 74, ;;13;;Lybia is a country in Africa.;;&lt;a href='http://en.wikipedia.org/wiki/Lybia' target='_blank'&gt;Lybia&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Animals.csv b/db/Animals.csv
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/db/Animals.csv
diff --git a/db/Arts.csv b/db/Arts.csv
new file mode 100755
index 0000000..d2dae6f
--- /dev/null
+++ b/db/Arts.csv
@@ -0,0 +1 @@
+21arts1.jpg;;Which painting is not from Da Vinci?;;401, 2, 403, 242, 587, 241, 588, 4, 401, 4, ;;5;;(1452 - 1519) was a universal genius, inventor, painter, anatomist, musician, mathematican who lived in Italy.;;&lt;a href='http://en.wikipedia.org/wiki/Da_vinci' target='_blank'&gt;Leonardo da Vinci&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Astronomy.csv b/db/Astronomy.csv
new file mode 100755
index 0000000..4f8d4c6
--- /dev/null
+++ b/db/Astronomy.csv
@@ -0,0 +1 @@
+planets_iau_big.jpg;;Which one is Saturn?;;461, 150, 447, 144, 433, 148, 419, 155, 413, 170, 410, 186, 402, 193, 390, 206, 384, 217, 389, 223, 405, 221, 418, 207, 428, 202, 447, 204, 460, 205, 473, 194, 476, 188, 473, 168, 471, 158, 484, 153, 495, 146, 499, 137, 481, 142, 474, 145, 464, 149, 455, 149, ;;3;;is the sixth planet from the Sun and the second largest planet in the Solar System, after Jupiter.;;&lt;a href='http://en.wikipedia.org/wiki/Saturn' target='_blank'&gt;Saturn&lt;/a&gt;;;<br>planets_iau_big.jpg;;Which one is Neptune?;;583, 170, 578, 166, 570, 166, 562, 173, 563, 182, 567, 191, 580, 188, 588, 183, 588, 173, 580, 166, 577, 166, ;;3;;The planet is named after the Roman god of the sea. Its astronomical symbol is a stylized version of Poseidons trident.;;&lt;a href='http://en.wikipedia.org/wiki/Neptune' target='_blank'&gt;Neptune&lt;/a&gt;;;<br>planets_iau_big.jpg;;Where is the Jupiter?;;315, 139, 328, 139, 342, 144, 353, 154, 359, 164, 363, 177, 363, 188, 361, 196, 353, 203, 349, 211, 344, 215, 337, 218, 330, 221, 320, 222, 312, 222, 299, 216, 289, 207, 283, 200, 280, 190, 279, 178, 281, 170, 284, 159, 290, 154, 296, 149, 304, 146, 311, 142, 320, 142, 321, 139, ;;3;;is two and a half times as massive as all of the other planets in our solar system combined;;&lt;a href='http://en.wikipedia.org/wiki/Jupiter' target='_blank'&gt;Jupiter&lt;/a&gt;;;<br>planets_iau_big.jpg;;Where is the Sun?;;69, 2, 123, 122, 129, 266, 76, 449, 0, 450, 0, 1, 69, 2, ;;3;;Temperature of the corona: ~ 5.000.000 K;;&lt;a href='http://en.wikipedia.org/wiki/Sun' target='_blank'&gt;The Sun&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Bears.csv b/db/Bears.csv
new file mode 100755
index 0000000..5eee134
--- /dev/null
+++ b/db/Bears.csv
@@ -0,0 +1 @@
+bears.jpg;;which is the Panda?;;220, 37, 204, 53, 212, 79, 234, 115, 256, 177, 268, 235, 267, 258, 176, 256, 82, 247, 27, 229, 56, 181, 86, 113, 92, 82, 78, 45, 121, 6, 164, 11, 224, 38, ;;14;;;;&lt;a href='http://en.wikipedia.org/wiki/Panda' target='_blank'&gt;Panda&lt;/a&gt;;;<br>bears.jpg;;And which is the polar bear?;;289, 14, 289, 316, 463, 314, 466, 274, 789, 273, 787, 13, 290, 15, ;;14;;is a bear native to the Arctic. Polar bears and Kodiak bears are the world\'s largest land carnivores ;;&lt;a href='http://en.wikipedia.org/wiki/Polar Bear ' target='_blank'&gt;Polar Bear&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Cars_&_Motorbikes.csv b/db/Cars_&_Motorbikes.csv
new file mode 100755
index 0000000..d44336d
--- /dev/null
+++ b/db/Cars_&_Motorbikes.csv
@@ -0,0 +1 @@
+5104352.JPG;;Can you find the Air-Filter?;;256, 227, 242, 233, 233, 240, 236, 251, 231, 269, 238, 271, 248, 267, 259, 270, 273, 270, 287, 268, 302, 262, 307, 255, 310, 243, 303, 235, 285, 229, 266, 227, 253, 229, ;;1;;;;&lt;a href='http://en.wikipedia.org/wiki/Air_filter' target='_blank'&gt;Air Filter&lt;/a&gt;;;<br>5104352.JPG;;Where is the Battery?;;175, 252, 138, 275, 138, 280, 142, 281, 143, 316, 177, 322, 183, 319, 186, 314, 191, 309, 209, 299, 208, 258, 175, 253, ;;1;;A car battery is a type of rechargeable battery that supplies electric energy to an automobile.;;&lt;a href='http://en.wikipedia.org/wiki/Car_battery' target='_blank'&gt;Car Battery&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Difference_Game.csv b/db/Difference_Game.csv
new file mode 100755
index 0000000..473f215
--- /dev/null
+++ b/db/Difference_Game.csv
@@ -0,0 +1 @@
+different1.png;;Which of these things is not like the others?;;87, 220, 107, 215, 130, 219, 145, 224, 157, 246, 172, 271, 179, 301, 181, 326, 192, 353, 195, 380, 206, 408, 216, 427, 221, 460, 225, 483, 226, 515, 220, 543, 211, 563, 200, 577, 185, 587, 168, 592, 153, 594, 129, 598, 98, 599, 67, 591, 42, 574, 29, 552, 19, 528, 19, 507, 19, 484, 20, 451, 17, 421, 25, 401, 28, 376, 35, 357, 46, 330, 63, 306, 64, 284, 72, 267, 74, 251, 78, 237, 84, 228, 86, 220, ;;20;;The cello is a stringed instrument. The other three are horn instruments. ;;&lt;a href='http://en.wikipedia.org/wiki/Cello' target='_blank'&gt;Cello&lt;/a&gt;;;<br>different2.png;;Which of these things is not like the others?;;243, 17, 236, 32, 231, 56, 223, 71, 222, 90, 224, 104, 224, 125, 224, 146, 229, 164, 234, 179, 242, 196, 251, 205, 267, 217, 279, 227, 294, 228, 322, 234, 344, 236, 373, 237, 388, 230, 409, 217, 424, 200, 440, 178, 448, 154, 451, 125, 451, 96, 443, 63, 431, 44, 420, 27, 398, 11, 370, 3, 327, 1, 281, 3, 263, 8, 249, 12, 244, 16, ;;20;;Eye glasses can help you see better.;;&lt;a href='http://en.wikipedia.org/wiki/Glasses' target='_blank'&gt;The boy with glasses&lt;/a&gt;;;<br>different3.png;;Which of these things is not like the others?;;214, 30, 206, 48, 204, 70, 203, 89, 204, 115, 211, 131, 218, 144, 235, 158, 247, 173, 269, 177, 290, 182, 327, 190, 358, 190, 379, 175, 405, 157, 412, 136, 421, 106, 421, 78, 414, 52, 403, 12, 384, 6, 354, 5, 332, 3, 281, 12, 265, 12, 247, 14, 232, 20, 219, 23, 214, 31, ;;20;;is different from the others. The other girls have blond hair.;;&lt;a href='' target='_blank'&gt;The girl with brown hair.&lt;/a&gt;;;<br>different4.png;;Which of these things is not like the others?;;164, 176, 160, 196, 160, 208, 156, 222, 152, 237, 154, 246, 155, 256, 158, 266, 161, 279, 168, 297, 174, 308, 185, 318, 194, 327, 212, 331, 232, 339, 243, 338, 258, 337, 270, 328, 283, 320, 290, 311, 296, 300, 299, 290, 308, 274, 314, 258, 314, 244, 316, 232, 316, 223, 314, 213, 308, 202, 306, 193, 299, 183, 289, 171, 277, 160, 268, 156, 251, 156, 232, 157, 221, 157, 211, 157, 195, 157, 187, 162, 179, 163, 172, 169, 165, 178, ;;20;;is different. The other men have dark hair.;;&lt;a href='' target='_blank'&gt;The man with blond hair&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Dogs.csv b/db/Dogs.csv
new file mode 100755
index 0000000..1e4b389
--- /dev/null
+++ b/db/Dogs.csv
@@ -0,0 +1 @@
+dogs.jpg;;Which dog is a Labrador?;;395, 54, 396, 38, 396, 222, 538, 223, 537, 37, 397, 37, ;;15;;;;&lt;a href='http://en.wikipedia.org/wiki/Labrador_Retriever' target='_blank'&gt;Labrador Retriever&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Europe.csv b/db/Europe.csv
new file mode 100755
index 0000000..964ced4
--- /dev/null
+++ b/db/Europe.csv
@@ -0,0 +1 @@
+europe.gif;;Which country is the most populated?;;734, 35, 713, 53, 706, 38, 681, 30, 676, 53, 667, 62, 657, 50, 622, 38, 590, 21, 566, 15, 547, 18, 534, 27, 532, 38, 548, 44, 540, 53, 546, 65, 550, 76, 554, 83, 556, 94, 573, 108, 540, 138, 550, 142, 564, 148, 547, 149, 542, 156, 539, 166, 539, 174, 544, 183, 546, 191, 548, 196, 551, 202, 555, 204, 564, 203, 567, 208, 575, 208, 581, 212, 584, 221, 586, 228, 595, 234, 600, 236, 606, 239, 601, 245, 593, 243, 593, 247, 598, 253, 597, 257, 605, 259, 611, 258, 615, 257, 624, 260, 628, 268, 636, 275, 643, 277, 647, 285, 655, 287, 666, 285, 672, 291, 682, 291, 692, 295, 700, 298, 696, 306, 700, 316, 697, 323, 690, 325, 685, 329, 693, 333, 686, 338, 683, 339, 687, 346, 682, 352, 677, 360, 669, 361, 676, 365, 688, 372, 708, 386, 715, 386, 733, 390, 734, 34, ;;11;;The population of Russia is around 143,000,000 people.;;&lt;a href='http://en.wikipedia.org/wiki/Russia' target='_blank'&gt;Russia&lt;/a&gt;;;<br>europe.gif;;Where is Liechtenstein, a really small country in Europe?;;337, 329, 331, 339, 338, 341, 354, 339, 354, 332, 345, 328, 335, 327, ;;11;;has around 32,000 people living on a total 160 square km.;;&lt;a href='http://en.wikipedia.org/wiki/Liechtenstein' target='_blank'&gt;Liechtenstein&lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is Scandinavia ?;;421, 3, 451, 126, 413, 170, 359, 202, 337, 235, 290, 241, 251, 228, 240, 164, 256, 105, 420, 5, ;;11;;Scandinava is the northern region of Europe;;&lt;a href='http://en.wikipedia.org/wiki/Scandinavia' target='_blank'&gt;Scandinavia&lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is Andorra?;;164, 384, 158, 384, 157, 381, 159, 376, 165, 374, 169, 375, 171, 382, 167, 383, 163, 382, ;;11;;is small country in the pyrinees;;&lt;a href='http://en.wikipedia.org/wiki/Andorra ' target='_blank'&gt;Andorra &lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is Moldova?;;415, 336, 427, 359, 435, 382, 451, 374, 457, 352, 449, 331, 432, 329, 423, 334, 420, 342, ;;11;;is one of the youngest countries in europe.;;&lt;a href='http://en.wikipedia.org/wiki/Moldova' target='_blank'&gt;Moldova&lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is the Peloponnes?;;367, 480, 361, 482, 363, 488, 366, 495, 366, 501, 371, 500, 376, 505, 379, 500, 386, 504, 384, 493, 382, 494, 384, 485, 378, 485, 376, 480, 374, 480, ;;11;;...is a mediterrain half-island;;&lt;a href='http://en.wikipedia.org/wiki/Pelopnnes ' target='_blank'&gt;Pelopnnes &lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/Geography.csv b/db/Geography.csv
new file mode 100755
index 0000000..2bb9950
--- /dev/null
+++ b/db/Geography.csv
@@ -0,0 +1 @@
+latinamerica.jpg;;Where is Brazil?;;225, 95, 222, 120, 207, 132, 209, 155, 243, 164, 268, 194, 277, 204, 273, 214, 274, 227, 280, 229, 286, 235, 288, 250, 283, 260, 274, 270, 271, 273, 277, 280, 280, 284, 282, 284, 281, 285, 282, 290, 282, 293, 292, 282, 310, 261, 315, 244, 334, 234, 343, 231, 352, 220, 361, 203, 368, 174, 390, 143, 386, 133, 368, 121, 343, 112, 326, 104, 315, 91, 312, 76, 310, 71, 304, 81, 303, 85, 297, 86, 289, 86, 285, 87, 277, 89, 272, 80, 269, 72, 263, 69, 260, 72, 253, 73, 249, 74, 252, 84, 247, 90, 238, 91, 233, 86, 224, 87, 222, 92, 224, 97, ;;2;;officially the Federative Republic of Brazil is the fifth-largest country in the world.;;&lt;a href='http://en.wikipedia.org/wiki/Brazil' target='_blank'&gt;Brazil&lt;/a&gt;;;<br>latinamerica.jpg;;Where is Paraguay?;;273, 227, 274, 222, 272, 215, 266, 214, 257, 216, 253, 222, 252, 232, 259, 238, 266, 242, 270, 248, 269, 254, 266, 258, 271, 259, 278, 259, 283, 253, 286, 247, 287, 241, 284, 240, 283, 233, 278, 231, 274, 225, ;;2;;Population: 6,158,000 people;;&lt;a href='http://en.wikipedia.org/wiki/Paraguay' target='_blank'&gt;Paraguay&lt;/a&gt;;;<br>latinamerica.jpg;;Can you find Chile?;;214, 208, 212, 254, 205, 271, 202, 298, 193, 317, 193, 329, 190, 338, 176, 385, 180, 394, 183, 401, 187, 404, 194, 404, 192, 394, 187, 391, 187, 385, 187, 378, 191, 368, 194, 362, 196, 352, 196, 343, 198, 337, 202, 322, 204, 314, 205, 309, 211, 296, 211, 283, 216, 263, 223, 257, 224, 242, 229, 236, 226, 232, 221, 208, 216, 208, ;;2;;is a country in South America occupying a long and narrow coastal strip wedged between the Andes and the Pacific;;&lt;a href='http://en.wikipedia.org/wiki/Chile' target='_blank'&gt;Chile&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/United_Mexican_States.csv b/db/United_Mexican_States.csv
new file mode 100755
index 0000000..5edfbbe
--- /dev/null
+++ b/db/United_Mexican_States.csv
@@ -0,0 +1 @@
+Mexico_states.png;;Where is the state of Baja California?;;7, 9, 38, 10, 36, 17, 36, 24, 36, 31, 35, 40, 37, 53, 44, 61, 49, 68, 50, 74, 53, 76, 56, 81, 59, 81, 59, 88, 39, 88, 43, 79, 37, 71, 26, 61, 21, 56, 20, 48, 17, 41, 15, 35, 11, 27, 6, 9, ;;21;;Baja California is the northernmost state in Mexico.;;&lt;a href='http://en.wikipedia.org/wiki/Baja_California' target='_blank'&gt;Baja California&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Baja California Sur?;;39, 87, 58, 89, 71, 110, 84, 142, 85, 153, 90, 155, 91, 150, 101, 166, 102, 171, 94, 173, 91, 168, 85, 161, 78, 153, 69, 147, 64, 140, 65, 125, 59, 116, 48, 108, 35, 101, 24, 88, 37, 88, ;;21;;The name Baja California Sur means \&quot;South Lower California\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Baja_California_Sur' target='_blank'&gt;Baja California Sur&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Sonora?;;37, 14, 84, 38, 119, 42, 120, 72, 120, 90, 113, 90, 119, 109, 111, 119, 104, 112, 100, 112, 100, 108, 91, 104, 90, 94, 81, 90, 73, 79, 67, 66, 62, 58, 61, 46, 61, 38, 54, 36, 50, 29, 48, 30, 36, 24, 36, 15, ;;21;;Sonora borders the U.S. states of Arizona and New Mexico.;;&lt;a href='http://en.wikipedia.org/wiki/Sonora' target='_blank'&gt;Sonora&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Chihuahua?;;118, 40, 126, 42, 126, 35, 152, 35, 156, 42, 165, 49, 172, 57, 176, 66, 181, 73, 189, 77, 195, 81, 184, 96, 188, 116, 180, 114, 175, 121, 165, 118, 155, 114, 150, 119, 147, 127, 144, 130, 139, 131, 135, 125, 129, 123, 127, 114, 124, 109, 119, 109, 118, 102, 114, 90, 119, 89, 120, 71, 119, 40, ;;21;;Although the state is named after a desert, Chihuahua has more forests than any other state in Mexico.;;&lt;a href='http://en.wikipedia.org/wiki/Chihuahau' target='_blank'&gt;Chihuahau&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Coahuila?;;202, 142, 202, 146, 197, 146, 191, 141, 190, 134, 193, 121, 188, 117, 184, 98, 201, 72, 206, 65, 221, 67, 223, 70, 230, 76, 235, 87, 242, 95, 242, 101, 238, 100, 235, 105, 229, 109, 232, 111, 232, 117, 228, 118, 224, 121, 237, 138, 231, 140, 229, 150, 223, 146, 218, 147, 212, 142, 207, 141, 202, 142, ;;21;;About 95% of Mexico\'s coal reserves are found in Coahuila, which is the country\'s top mining state.;;&lt;a href='http://en.wikipedia.org/wiki/Coahuila' target='_blank'&gt;Coahuila&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Neuvo Leon?;;243, 100, 237, 100, 235, 105, 228, 109, 232, 112, 232, 116, 229, 117, 224, 121, 228, 132, 236, 139, 232, 141, 228, 142, 228, 151, 233, 155, 233, 162, 234, 171, 240, 174, 242, 169, 245, 164, 251, 162, 249, 157, 246, 151, 254, 146, 264, 136, 262, 128, 254, 125, 249, 118, 246, 112, 243, 101, ;;21;;The capital of Nuevo Le&Atilde;&sup3;n is Monterrey, the third largest city in Mexico with over three million residents.;;&lt;a href='http://en.wikipedia.org/wiki/Nuevo_Leon' target='_blank'&gt;Nuevo Leon&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Tamaulipas?;;242, 99, 245, 114, 252, 124, 262, 128, 265, 136, 256, 143, 248, 150, 249, 162, 244, 163, 241, 170, 243, 176, 243, 179, 252, 181, 255, 185, 271, 183, 274, 185, 275, 174, 274, 163, 276, 151, 279, 140, 283, 128, 275, 126, 266, 124, 256, 120, 252, 115, 249, 106, 248, 100, 243, 99, ;;21;;No one knows where the name came from, but many people think it comes from a Huastec term meaning \&quot;place of high hills\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Tamaulipas' target='_blank'&gt;Tamaulipas&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Sinaloa?;;105, 128, 111, 118, 121, 109, 126, 109, 130, 122, 135, 124, 139, 130, 137, 139, 143, 146, 145, 153, 152, 153, 158, 169, 162, 172, 160, 183, 153, 177, 143, 168, 133, 156, 125, 148, 116, 138, 107, 132, 105, 129, ;;21;;Sinaloa it is the only place where the ancient ball game of Mesoamerica is still played. ;;&lt;a href='http://en.wikipedia.org/wiki/Sinaloa' target='_blank'&gt;Sinaloa&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Durango;;138, 130, 144, 130, 148, 121, 152, 118, 155, 112, 163, 118, 168, 119, 175, 120, 180, 116, 187, 116, 192, 122, 192, 134, 191, 139, 194, 146, 199, 147, 202, 143, 206, 147, 206, 154, 192, 154, 185, 160, 185, 165, 181, 169, 178, 186, 173, 182, 168, 182, 168, 175, 162, 172, 155, 163, 151, 152, 146, 152, 140, 144, 138, 137, 138, 130, ;;21;;;;;;<br>Mexico_states.png;;Where is the state of Zacatecas?;;183, 186, 178, 187, 179, 171, 183, 166, 184, 161, 190, 152, 205, 152, 204, 142, 211, 141, 219, 148, 223, 146, 228, 153, 223, 159, 216, 168, 208, 170, 209, 177, 214, 182, 221, 180, 222, 186, 220, 193, 215, 192, 209, 186, 204, 188, 199, 193, 201, 199, 204, 202, 199, 204, 195, 208, 186, 205, 188, 199, 191, 195, 196, 191, 195, 187, 192, 184, 191, 190, 185, 186, ;;21;;The state\'s name comes the Nahuatl word for a type of grass common in the region, \&quot;zacatl\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Zacatecas' target='_blank'&gt;Zacatecas&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of San Louis Potosi?;;221, 180, 217, 182, 209, 176, 206, 172, 216, 169, 222, 160, 222, 155, 227, 153, 233, 155, 234, 161, 235, 172, 242, 174, 243, 178, 251, 181, 257, 185, 265, 186, 269, 188, 266, 200, 265, 203, 259, 203, 254, 197, 252, 200, 248, 198, 246, 200, 237, 197, 234, 200, 222, 196, 222, 181, ;;21;;The native peoples of the state include the Huastecs and Pame people.;;&lt;a href='http://en.wikipedia.org/wiki/San_Louis_Potosi' target='_blank'&gt;San Louis Potosi&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Nayarit?;;159, 192, 159, 183, 162, 174, 168, 174, 169, 182, 173, 184, 178, 188, 179, 194, 180, 201, 183, 203, 179, 206, 179, 212, 171, 208, 163, 213, 160, 210, 163, 205, 164, 199, 159, 192, ;;21;;Also belonging to the state are the Islas Marias, Islas Tres Marietas and Isla Isabel, all in the Pacific Ocean.;;&lt;a href='http://en.wikipedia.org/wiki/Nayarit' target='_blank'&gt;Nayarit&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Jalisco?;;159, 217, 164, 214, 170, 208, 178, 211, 180, 206, 183, 202, 180, 200, 180, 195, 177, 189, 181, 186, 183, 180, 189, 185, 188, 190, 193, 189, 192, 183, 197, 188, 195, 193, 192, 194, 190, 199, 187, 204, 193, 207, 197, 204, 202, 202, 201, 198, 205, 196, 210, 196, 215, 192, 221, 195, 220, 202, 212, 210, 213, 216, 211, 218, 198, 221, 198, 225, 203, 225, 201, 231, 206, 233, 189, 242, 189, 234, 183, 234, 178, 234, 176, 237, 168, 234, 162, 228, 158, 219, ;;21;;Jalisco is known for being the birthplace of mariachi music and tequila.;;&lt;a href='http://en.wikipedia.org/wiki/Jalisco' target='_blank'&gt;Jalisco&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Aguascalientes ?;;200, 197, 198, 191, 208, 185, 213, 190, 215, 193, 209, 198, 201, 197, ;;21;;The name means \&quot;hot waters\&quot; in Spanish and originated from the abundance of hot springs in the area.;;&lt;a href='http://en.wikipedia.org/wiki/Aguascalientes' target='_blank'&gt;Aguascalientes&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Guanajuato?;;220, 194, 231, 199, 236, 196, 245, 200, 248, 204, 243, 204, 241, 208, 234, 208, 236, 216, 239, 221, 234, 225, 230, 223, 222, 224, 221, 218, 216, 219, 213, 216, 212, 212, 219, 203, 220, 195, ;;21;;Guanajuato means \&quot;place of frogs\&quot; in the local indigenous language.;;&lt;a href='http://en.wikipedia.org/wiki/Guanajuato' target='_blank'&gt;Guanajuato&lt;/a&gt;;;<br>Mexico_states.png;;Where is teh state of Quer&Atilde;&copy;taro Arteaga?;;242, 221, 236, 218, 234, 211, 241, 208, 243, 204, 246, 204, 246, 198, 252, 200, 254, 196, 258, 203, 253, 204, 250, 213, 247, 214, 246, 219, 244, 222, ;;21;;The name is thought to come from a phrase in the Otomi language meaning \&quot;the great ball game\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Quer&Atilde;&copy;taro' target='_blank'&gt;Quer&Atilde;&copy;taro Arteaga&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Hidalgo?;;251, 222, 246, 219, 246, 215, 250, 213, 253, 205, 258, 202, 266, 199, 271, 204, 271, 210, 267, 212, 267, 215, 272, 214, 274, 215, 271, 218, 273, 221, 272, 228, 266, 229, 261, 226, 259, 224, 254, 226, 252, 222, ;;21;;The state is named after Mexican independence leader Miguel Hidalgo y Costilla.;;&lt;a href='http://en.wikipedia.org/wiki/Hidalgo_(Mexico)' target='_blank'&gt;Hidalgo&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Colima?;;186, 244, 189, 242, 190, 234, 187, 231, 182, 231, 179, 236, 175, 237, 182, 241, 185, 244, ;;21;;A famous volcano in the zone is called \&quot;Volcan de Colima\&quot;, but in fact, half of it is in the nearby state of Jalisco.;;&lt;a href='http://en.wikipedia.org/wiki/Colima' target='_blank'&gt;Colima&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Michoac&Atilde;&iexcl;n?;;188, 243, 197, 239, 205, 232, 201, 229, 201, 225, 196, 222, 211, 217, 216, 220, 221, 218, 223, 223, 230, 222, 234, 225, 239, 222, 242, 224, 241, 228, 240, 237, 239, 242, 232, 243, 234, 249, 228, 248, 220, 248, 215, 247, 215, 251, 212, 254, 211, 257, 201, 255, 192, 251, 187, 245, ;;21;;It is the sixteenth largest state in Mexico, taking up 3% of the national territory.;;&lt;a href='http://en.wikipedia.org/wiki/Michoacan' target='_blank'&gt;Michoac&Atilde;&iexcl;n&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Mexico?;;240, 249, 237, 243, 241, 237, 241, 229, 243, 223, 246, 219, 251, 221, 253, 226, 259, 224, 260, 227, 266, 227, 265, 230, 265, 240, 261, 239, 261, 235, 257, 231, 254, 233, 255, 237, 255, 240, 253, 243, 248, 245, 244, 246, 240, 249, ;;21;;Mexico State is often abbreviated to \&quot;Edomex\&quot;, from the Spanish \&quot;Estado de M&Atilde;&copy;xico\&quot;.;;&lt;a href='' target='_blank'&gt;&lt;/a&gt;;;<br>Mexico_states.png;;Where is Mexico City?;;255, 238, 253, 235, 256, 229, 261, 232, 261, 235, 260, 238, 256, 238, ;;21;;Residents of Mexico City are called \&quot;capitalinos\&quot;, in reference to the city being the capital of the country.;;&lt;a href='http://en.wikipedia.org/wiki/Mexico_City' target='_blank'&gt;Mexico City&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Tlaxcala?;;264, 230, 270, 228, 279, 231, 277, 236, 273, 236, 268, 233, 263, 231, ;;21;;It covers an area of 1,037 square kilometers (400.4 sq mi) and is thus the smallest of Mexico\'s states.;;&lt;a href='http://en.wikipedia.org/wiki/Tlaxcala' target='_blank'&gt;Tlaxcala&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Morelos?;;256, 247, 255, 248, 252, 245, 254, 240, 259, 239, 265, 242, 265, 248, 261, 248, 259, 250, 257, 248, ;;21;;Morelos\' capital is Cuernavaca, also known as \&quot;La Ciudad de la Eterna Primavera\&quot;, \&quot;The City of Eternal Spring\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Morelos' target='_blank'&gt;Morelos&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Puebla?;;266, 231, 271, 235, 277, 233, 274, 229, 272, 225, 271, 218, 277, 209, 282, 213, 279, 219, 282, 221, 285, 218, 287, 222, 285, 231, 289, 235, 285, 241, 286, 243, 288, 247, 292, 248, 284, 255, 279, 250, 276, 251, 276, 255, 269, 256, 259, 249, 264, 248, 265, 229, ;;21;;The state covers an area of about 33,919 square kilometers (13,096.2 sq mi).;;&lt;a href='http://en.wikipedia.org/wiki/Puebla' target='_blank'&gt;Puebla&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Veracruz?;;268, 186, 271, 184, 274, 187, 283, 208, 293, 217, 300, 227, 300, 231, 307, 241, 312, 241, 320, 241, 326, 249, 335, 248, 337, 254, 343, 262, 338, 264, 328, 265, 320, 261, 319, 257, 311, 260, 309, 256, 309, 250, 303, 249, 297, 244, 294, 246, 291, 247, 287, 247, 286, 245, 284, 243, 285, 235, 289, 235, 284, 231, 286, 222, 282, 222, 279, 220, 282, 215, 278, 211, 276, 211, 273, 214, 268, 217, 267, 212, 270, 209, 271, 205, 267, 202, 267, 196, 266, 190, 268, 186, ;;21;;With more than 7 million inhabitants, the state of Veracruz is the third most populous in the nation.;;&lt;a href='http://en.wikipedia.org/wiki/Veracruz' target='_blank'&gt;Veracruz&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Guerrero?;;211, 256, 214, 251, 214, 245, 235, 249, 232, 244, 238, 244, 241, 247, 248, 247, 249, 244, 255, 248, 257, 246, 261, 252, 269, 256, 270, 266, 275, 273, 274, 276, 269, 281, 261, 277, 254, 277, 248, 276, 241, 272, 232, 270, 226, 264, 219, 260, 213, 257, ;;21;;Guerrero is named in honor of the second president of the republic, General Vicente Ram&Atilde;&sup3;n Guerrero Salda&Atilde;&plusmn;a.;;&lt;a href='http://en.wikipedia.org/wiki/Guerrero' target='_blank'&gt;Guerrero&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Oaxaca?;;268, 279, 274, 274, 274, 269, 269, 265, 269, 256, 276, 254, 278, 247, 283, 252, 287, 251, 293, 248, 295, 242, 300, 245, 302, 249, 308, 250, 310, 253, 308, 258, 313, 260, 317, 258, 323, 263, 341, 265, 338, 271, 338, 276, 338, 280, 329, 278, 325, 279, 317, 283, 307, 289, 297, 289, 288, 286, 282, 286, 274, 282, 268, 281, ;;21;;Oaxaca has more speakers of indigenous languages than any other Mexican state.;;&lt;a href='http://en.wikipedia.org/wiki/Oaxaca' target='_blank'&gt;Oaxaca&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Tabasco?;;343, 262, 339, 255, 336, 252, 334, 248, 345, 244, 348, 246, 357, 239, 360, 242, 364, 241, 365, 246, 370, 248, 374, 247, 383, 248, 383, 259, 380, 262, 378, 257, 372, 257, 369, 252, 364, 253, 358, 260, 353, 259, 351, 251, 348, 251, 346, 256, 344, 261, ;;21;;Tabasco is in the northern half of the Isthmus of Tehuantepec.;;&lt;a href='http://en.wikipedia.org/wiki/Tabasco' target='_blank'&gt;Tabasco&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Chiapas?;;340, 283, 338, 277, 339, 269, 341, 265, 345, 259, 348, 254, 349, 251, 352, 255, 357, 259, 367, 250, 375, 256, 378, 259, 385, 266, 392, 271, 396, 273, 396, 276, 376, 280, 370, 292, 369, 296, 369, 304, 362, 300, 356, 295, 352, 290, 348, 287, 344, 285, 341, 283, ;;21;;About one quarter of the population of Chiapas are of full or predominant Maya descent.;;&lt;a href='http://en.wikipedia.org/wiki/Chiapas' target='_blank'&gt;Chiapas&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Campeche?;;362, 240, 363, 245, 367, 246, 372, 250, 376, 246, 383, 249, 384, 252, 411, 251, 409, 226, 395, 209, 389, 209, 388, 202, 386, 217, 386, 226, 378, 232, 373, 237, 366, 239, 363, 239, ;;21;;Campeche derives from the name of the Mayan city of \&quot;Kan pech\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Campeche' target='_blank'&gt;Campeche&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Quintana Roo?;;412, 247, 415, 247, 420, 237, 426, 236, 431, 239, 434, 222, 431, 222, 435, 217, 432, 215, 436, 203, 442, 196, 442, 189, 438, 184, 433, 186, 432, 192, 425, 204, 405, 218, 408, 225, 410, 246, ;;21;;Quintana Roo was granted statehood on October 8, 1974, making it the Mexican Republic\'s youngest state.;;&lt;a href='http://en.wikipedia.org/wiki/Quintana_Roo' target='_blank'&gt;Quintana Roo&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Yucat&Atilde;&iexcl;n?;;405, 218, 425, 204, 431, 194, 431, 190, 425, 188, 417, 187, 410, 188, 397, 194, 389, 198, 389, 203, 389, 208, 395, 208, 404, 217, ;;21;;The source of the name \&quot;Yucatan\&quot; is the Nahuatl (Aztec) word Yokatl&Auml;n, meaning \&quot;place of richness.\&quot;;;&lt;a href='http://en.wikipedia.org/wiki/Yucatan' target='_blank'&gt;Yucat&Atilde;&iexcl;n&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/XO_Laptop.csv b/db/XO_Laptop.csv
new file mode 100755
index 0000000..3db6c89
--- /dev/null
+++ b/db/XO_Laptop.csv
@@ -0,0 +1 @@
+xo.jpg;;Can you find the CPU in this open XO laptop?;;304, 297, 293, 308, 292, 389, 303, 400, 380, 399, 393, 387, 396, 309, 383, 295, 303, 297, ;;4;;\'Central Processing Unit\', or sometimes just \'processor\';;&lt;a href='http://en.wikipedia.org/wiki/CPU' target='_blank'&gt;CPU&lt;/a&gt;;;<br>olpc-xo-2.jpg;;Where is the Power Button?;;437, 300, 430, 303, 428, 310, 430, 313, 437, 316, 444, 312, 446, 306, 441, 303, 436, 302, ;;4;;\'One Laptop Per Child\' is an education project aiming especially at bridging the digital gap.;;&lt;a href='http://en.wikipedia.org/wiki/One_Laptop_per_Child' target='_blank'&gt;XO Laptop&lt;/a&gt;;;<br>olpc-xo-2.jpg;;Where is the left speaker ?;;138, 213, 143, 205, 159, 210, 164, 225, 155, 235, 140, 238, 135, 228, 135, 217, 135, 214, 137, 212, ;;4;;The XO has 2 speakers for stereo audio output.;;&lt;a href='http://en.wikipedia.org/wiki/Loudspeaker' target='_blank'&gt;Audio Speaker&lt;/a&gt;;;<br>olpc-xo-2.jpg;;where is the antenna?;;447, 79, 446, 57, 451, 19, 471, 23, 478, 49, 477, 104, 468, 129, 453, 137, 441, 100, 446, 83, 449, 77, 146, 75, 126, 8, 99, 26, 105, 84, 108, 120, 129, 132, 142, 131, 147, 100, 147, 87, 147, 78, 147, 78, 149, 80, ;;4;;2dBi or 3dBi antennas help your XO to communicate with other XO laptops;;&lt;a href='http://en.wikipedia.org/wiki/antennas' target='_blank'&gt;antennas&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/db/main.db b/db/main.db
new file mode 100755
index 0000000..d572eff
--- /dev/null
+++ b/db/main.db
Binary files differ
diff --git a/frontend.py b/frontend.py
new file mode 100755
index 0000000..7a1a632
--- /dev/null
+++ b/frontend.py
@@ -0,0 +1,880 @@
+import os
+import sys
+import pygame
+from pygame.locals import *
+
+# the following line is not needed if pgu is installed
+import sys; sys.path.insert(0, "..")
+
+from pgu import gui
+
+from layout import *
+from sugar.activity import activity
+
+DATA = os.path.join(activity.get_activity_root(), "data")
+IMAGES = os.path.join(DATA, 'image')
+SOUNDS = os.path.join(DATA, 'sound')
+
+debug_info = True
+
+''' Utilities '''
+def empty(): pass
+def hex2dec(hex): return int(hex, 16);
+def hex2rgb(hex):
+ if hex[0:1] == '#': hex = hex[1:];
+ return (hex2dec(hex[:2]), hex2dec(hex[2:4]), hex2dec(hex[4:6]))
+
+def border2px_rgb(border):
+ px = 1;
+ b = '75b844';
+ if border.find('px') > -1:
+ if border.find('#') > 1:
+ px = border[0:border.find('px')]
+ b = border[border.find('#')+1:]
+ return int(px), hex2rgb(b)
+
+''' Class Display() handles the drawing/blittins things '''
+class Display:
+ Layout = Layout()
+ Colors = Colors()
+
+ menu_entries = [] # Array with MenuEntry Objects
+ text_items = []
+ tooltips = []
+
+ window = '' # pygame window
+ screen = '' # pygame screen
+
+ # Create Surfaces for Layouts
+ bg_layout = pygame.Surface((1200, 825), 16)
+
+ bw, bc = border2px_rgb(Layout.MenuMain.border)
+ bg_menu = pygame.Surface((Layout.MenuMain.width - (2*bw)+1, Layout.MenuMain.height - (2*bw)+1))
+
+ bw, bc = border2px_rgb(Layout.Question.border)
+ bg_question = pygame.Surface((Layout.Question.width - (2*bw)+1, Layout.Question.height - (2*bw)+1))
+
+ bw, bc = border2px_rgb(Layout.Toolbar.border)
+ bg_toolbar = pygame.Surface((Layout.Toolbar.width - (2*bw)+1, Layout.Toolbar.height - (2*bw)+1))
+
+ def __init__(self, s):
+ self.__SERVICES__ = s
+
+ # Fire Up Display
+ self.window = pygame.display.set_mode((1200, 825))
+ pygame.display.set_caption('XO Image-Quiz')
+
+ # Init Surface
+ self.screen = pygame.display.get_surface()
+
+ # init gui
+ self.app = gui.App()
+
+ def set_cursor(self, name):
+ if name == "pointer-big":
+ a, b, c, d = pygame.cursors.load_xbm("images/cursor_big.xbm", "images/cursor_big_mask.xbm")
+ pygame.mouse.set_cursor(a, b, c, d)
+
+ ''' Create the Backgrounds -> self.bg_layout '''
+ def create_bg(self):
+ # Fill all Black
+ self.bg_layout.fill(hex2rgb(self.Layout.background))
+ self.bg_question.fill(hex2rgb(self.Layout.Question.background))
+ self.bg_menu.fill(hex2rgb(self.Layout.MenuMain.background))
+ self.bg_toolbar.fill(hex2rgb(self.Layout.Toolbar.background))
+
+ # Create bg_Layout and Menu
+ self.draw_layout()
+ self.draw_menu("/")
+
+ self.add_emblem(self.bg_question)
+
+ def refresh(self):
+ self.screen.fill(hex2rgb(self.Layout.background))
+
+ self.display_surface(self.bg_layout)
+ self.display_menu()
+ self.display_question()
+ self.display_toolbar()
+ self.display_text_items()
+
+ pygame.display.update()
+
+
+ ''' Display = Blit Surface on Screen '''
+ def display_surface(self, surface, pos=(0,0), to=None):
+ if to == None: self.screen.blit(surface, pos)
+ elif to == "toolbar":
+ self.bg_toolbar.blit(surface, pos)
+ print "added surface to toolbar"
+ elif to == "question":
+ self.bg_question.blit(surface, pos)
+ print "added surface to question frame"
+# self.snapshot = self.screen
+# pygame.display.update()
+
+ def display_menu(self):
+ bw, bc = border2px_rgb(Layout.MenuMain.border)
+ self.display_surface(self.bg_menu, (self.Layout.MenuMain.x+bw, self.Layout.MenuMain.y+bw))
+
+ def display_question(self):
+ bw, bc = border2px_rgb(Layout.Question.border)
+ self.display_surface(self.bg_question, (self.Layout.Question.x+bw, self.Layout.Question.y+bw))
+
+ def display_toolbar(self):
+ bw, bc = border2px_rgb(Layout.Toolbar.border)
+ self.display_surface(self.bg_toolbar, (self.Layout.Toolbar.x+bw, self.Layout.Toolbar.y+bw))
+
+ def display_text_items(self):
+ for i in self.text_items:
+ if i != None:
+ self.display_surface(i.image, i.pos)
+
+ def display_tooltip(self, text, pos, format=None):
+ self.add_text(self.bg_toolbar, text, pos, format)
+ self.display_toolbar()
+
+
+ def display_notfound_line(self, percent):
+ pass
+
+ ''' Adds Text / Images / ... to Surfaces '''
+ def add_emblem(self, surface):
+ # Adding the emblem to any surface
+ self.add_image(surface, 'images/logo.jpg', (30, 100))
+ textformat = TextFormat(None, 46)
+ self.add_text(surface, 'XO ImageQuiz', (30, 20), textformat)
+
+ def add_image(self, surface, imgfn, pos):
+ im, im_rect = self.image_load(imgfn)
+ surface.blit(im, pos)
+
+ def add_text(self, surface, text, pos, format=None):
+ if format == None: format = TextFormat()
+
+ font = pygame.font.Font(None, format.size)
+ text = font.render(text, 1, hex2rgb(format.color))
+ textpos = text.get_rect()
+ x,y,w,h = list(textpos)
+
+ # clear text behind
+ bg = pygame.Surface((w,h))
+ bg.fill(hex2rgb(self.Layout.Toolbar.background))
+
+ surface.blit(bg, pos)
+ surface.blit(text, pos)
+
+ '''
+ Drawing Backgrounds
+ '''
+ def draw_layout(self):
+ q = self.Layout.Question;
+ m1 = self.Layout.MenuTop;
+ m2 = self.Layout.MenuMain;
+ t = self.Layout.Toolbar;
+
+ # Clear
+ self.bg_layout.fill(hex2rgb(self.Layout.background))
+
+ # Question Part
+ bw, bc = border2px_rgb(q.border)
+ pygame.draw.rect(self.bg_layout, bc, (q.x, q.y, q.width, q.height), bw)
+
+ # Menu Caption and Menu Field
+ bw, bc = border2px_rgb(m1.border)
+ pygame.draw.rect(self.bg_layout, bc, (m1.x, m1.y, m1.width, m1.height), bw)
+
+ bw, bc = border2px_rgb(m2.border)
+ pygame.draw.rect(self.bg_layout, bc, (m2.x, m2.y, m2.width, m2.height), bw)
+
+ # Toolbar
+ bw, bc = border2px_rgb(t.border)
+ pygame.draw.rect(self.bg_layout, bc, (t.x, t.y, t.width, t.height), bw)
+
+ # Menu Text
+ font = pygame.font.Font(None, m1.Text.size)
+ text = font.render(m1.Text.text, 1, hex2rgb(m1.Text.color))
+ textpos = text.get_rect()
+ self.bg_layout.blit(text, (m1.x + m1.Text.x, m1.y + m1.Text.y))
+
+ def draw_menu(self, menu_path):
+ self.bg_menu.fill(hex2rgb(self.Layout.MenuMain.background))
+
+ x = self.Layout.MenuMain.Button.x
+ y = self.Layout.MenuMain.Button.y
+
+ for m in self.menu_entries:
+ if m.path == menu_path:
+ m.pos = (x,y)
+ self.bg_menu.blit(m.box.image, (x,y))
+ y += 50;
+
+
+ ''' Display A Question '''
+ def clear_question_frame(self, emblem=True):
+ self.bg_question.fill(hex2rgb(self.Layout.Question.background))
+ if emblem: self.add_emblem(self.bg_question)
+
+ self.display_question()
+ pygame.display.update()
+
+ def question_display(self, q):
+ ''' class q (CurrentQuestion):
+ id = 0
+ imgfn = u''
+ map = u''
+ cat = 0
+ subcat = 0
+ text = u''
+ answer = u''
+ answer_link = ''
+ '''
+ self.bg_question.fill(hex2rgb(self.Layout.Question.background))
+
+ # Load Image & Render on Background
+ print 'IMAGES', IMAGES
+ print 'imgfn', q.imgfn
+ print 'image', os.path.join(IMAGES, q.imgfn)
+ im, im_rect = self.image_load(os.path.join(IMAGES, q.imgfn))
+ self.bg_question.blit(im, (self.Layout.Question.Image.x, self.Layout.Question.Image.y))
+
+ # Render Question Text on Screen
+ font = pygame.font.Font(None, self.Layout.Question.Text.size)
+ text = font.render(q.text, 1, hex2rgb(self.Layout.Question.Text.color))
+ textpos = text.get_rect()
+
+ self.bg_question.blit(text, (self.Layout.Question.Text.x, self.Layout.Question.Text.y))
+
+ self.refresh()
+ print 'in question_display', q.map
+ return q.map
+
+
+ def image_load(self, name):
+ fullname = name
+ try:
+ print 'image fullname', fullname
+ image = pygame.image.load(fullname)
+ except pygame.error, message:
+ print "Cannot load image:", name, 'fullname', fullname
+ raise SystemExit, message
+ image = image.convert()
+ return image, image.get_rect()
+
+
+ ''' Toolbar Tools '''
+ def display_line(self, percent, found=True):
+ print "draw line"
+
+ if found: f = Layout.Toolbar.FoundLine
+ else: f = Layout.Toolbar.NotFoundLine
+
+
+ # Very Background Surface
+ bbg = pygame.Surface((f.width + f.image_width, f.height))
+ self.add_image(bbg, f.image, (f.image_left,0))
+
+ # Draw Outside Rect
+ bg = pygame.Surface((f.width, f.height))
+ bg.fill(hex2rgb(f.Outside.background))
+
+ bw, bc = border2px_rgb(f.Outside.border)
+ if bw > 0: pygame.draw.rect(bg, bc, (0,0, f.width, f.height), bw)
+
+ # Draw Inside Rect - Accorting to %
+ new_height = f.height - (2*f.padding)
+ max_width = f.width - (2*f.padding)
+ new_width = max_width * percent / 100
+
+ bg2 = pygame.Surface((new_width, new_height))
+ bg2.fill(hex2rgb(f.Inside.background))
+
+ bw, bc = border2px_rgb(f.Inside.border)
+ if bw > 0: pygame.draw.rect(bg2, bc, (0,0,new_width, new_height), bw)
+
+ bg.blit(bg2, (f.padding, f.padding))
+ bbg.blit(bg, (f.image_width,0))
+
+ self.bg_toolbar.blit(bbg, (f.x, f.y))
+# self.display_surface(bbg, (Layout.Toolbar.x + f.x, Layout.Toolbar.y + f.y))
+ self.refresh()
+
+'''
+Overall Game Frontend
+ - Needs no services yet
+'''
+class Kernel:
+ Layout = Layout()
+ Colors = Colors()
+
+ lang = "English"
+
+ map_list = ((0,0)) # Current Questions Map as List
+ image_file_name = u"" # Current Questions Filename
+
+ menu_current = '/' # Current Directory
+ menu_entries = [] # Array with MenuEntry Objects
+ menu_raw = [] # [path, caption, onclick, part, is_Directory]
+
+ menu_dirs = [] # Array with menu directories (['/', '/quit', ...])
+
+ question_frame_reacts = [] # Reacts Array [x, y, width, height, onclick, stopsearch]
+ text_items = [] # Array containing text item-objects on the question_frame
+ hooks = [] # Array [event, function]
+
+ def __init__(self):
+ pygame.init()
+
+ if not pygame.font: print 'Warning, fonts disabled'
+ if not pygame.mixer: print 'Warning, sound disabled'
+
+ def load(self, s):
+ # Setup Menu
+ global __SERVICES__
+ __SERVICES__ = s
+
+ self.D = Display(s)
+
+ self.menu_setup()
+
+ self.D.set_cursor("pointer-big")
+ self.D.create_bg()
+ self.D.refresh()
+
+# self.display_tooltip("Language: %s" % self.lang, (10, 10))
+ if debug_info: print "- frontend loaded"
+
+ def start_event_hooks(self, event, params):
+ print "- checking event hooks"
+ for h in self.hooks:
+ # h[0] contains event, h[1] pointer to function
+ print "-", event, h[0]
+ if event == h[0]:
+ try:
+ h[1](params)
+ print "- hook function started"
+ except: print "! couldn't start hook", h
+
+ def check_click(self, pos):
+ # Click happened!
+ # Could mean: In game, on menu, ...
+
+ x, y = pos
+ print 'click', pos
+
+ ''' Start Plugin onclick Events '''
+ self.start_event_hooks('onclick', pos);
+
+ ''' Check In Reacts '''
+ i = 0
+ for m in self.question_frame_reacts:
+ if m != None:
+ bw, bc = border2px_rgb(self.Layout.Question.border)
+ if x >= m[0] and x <= m[0] + m[2] and y >= m[1] and y <= m[1] + m[3]:
+ print "* React #", i, " found"
+ m[4]()
+ if m[5]: return True
+ else:
+ print "* React #", i, ": not found"
+ i += 1
+
+ ''' Check in Text-Items '''
+ for qi in self.text_items:
+ if qi != None:
+ if qi.pos_inside((x,y)):
+ if qi.onclick != None:
+ if qi.retvar != None:
+ qi.onclick(qi.retvar)
+ return True
+ else:
+ qi.onclick()
+
+ ''' Check in Polygon '''
+ if self.map_list != ((0, 0)):
+ if self.point_inside_polygon(x, y, self.map_list):
+ __SERVICES__.start_service('spot_found', pos)
+ print "Click inside Polygon!"
+ return True
+ else:
+ # If click inside game window, trigger not_found
+ q = self.Layout.Question
+ if x > q.x and x < (q.x + q.width) and y > q.y and y < (q.y + q.height):
+ __SERVICES__.start_service('spot_not_found', pos)
+
+ ''' Check ON Menu '''
+ if (x >= self.Layout.MenuTop.x and x <= self.Layout.MenuTop.x + self.Layout.MenuTop.width) and (y >= self.Layout.MenuTop.y and y <= self.Layout.MenuTop.y + self.Layout.MenuTop.height):
+ self.change_dir('/')
+ return True
+
+ ''' Check In Menu '''
+ x, y = pos
+ x -= self.Layout.MenuMain.x
+ y -= self.Layout.MenuMain.y
+ checkpath = self.menu_current
+ for m in self.menu_entries:
+ if m.path == checkpath:
+ print "- Click on Menu",m.caption,":", m.pos_inside((x,y))
+ if m.pos_inside((x,y)):
+ if m.isDirectory == None:
+ # Check for Back Button
+ if m.onclick == None:
+ path = m.path.strip()
+ if len(path) > 1 and path[-1] == '/': path = path[0:-1]
+ path_base = path[:path.rfind('/')]
+ if len(path_base) == 0: path_base = '/'
+ self.change_dir(path_base)
+ return True
+ else:
+ # Action Button
+ self.mycaption = m.caption
+ m.onclick()
+ return True
+ else:
+ # Directory Button
+ self.change_dir(m.isDirectory)
+ return True
+
+ def map_str_to_list(self, map_str):
+ # convert 11, 12, 21, 22, 31, 33 in list [(11, 12), (21, 22), (31, 32)]
+ print 'map_str', map_str
+ map_arr = map_str.split(", ")
+ o = []
+ a = "x"
+ for s in map_arr:
+ if a == "x": a = s
+ else:
+ # Take care of border pixels
+ bw, bc = border2px_rgb(self.Layout.Question.border)
+ o.append((int(a)+self.Layout.Question.x+self.Layout.Question.Image.x+(bw+1), int(s)+self.Layout.Question.y+self.Layout.Question.Image.y+bw))
+ a = "x"
+
+ return o
+
+ def question_set(self, question, image, map):
+ self.question = question
+ self.image = image
+ self.map_list = self.map_str_to_list(map)
+
+ '''
+ Menu Functions
+ ==============
+ '''
+ def menu_setup(self):
+ # Put Menu items in correct order from self.menu_raw into self.menu_entries
+ ms01 = []
+ ms23 = []
+ ms45 = []
+
+ self.menu_entries = []
+
+ for n in self.menu_raw:
+ if n[3] == 0: ms01.insert(0, n)
+ elif n[3] == 1: ms01.append(n)
+ elif n[3] == 2: ms23.insert(0, n)
+ elif n[3] == 3: ms23.append(n)
+ elif n[3] == 4: ms45.insert(0, n)
+ elif n[3] == 5: ms45.append(n)
+
+ for n in ms01:
+ m = MenuEntry(n[0], n[1], n[2], n[3], n[4], n[5])
+ self.menu_entries.append(m)
+
+ for n in ms23:
+ m = MenuEntry(n[0], n[1], n[2], n[3], n[4], n[5])
+ self.menu_entries.append(m)
+
+ for n in ms45:
+ m = MenuEntry(n[0], n[1], n[2], n[3], n[4], n[5])
+ self.menu_entries.append(m)
+
+ self.D.menu_entries = self.menu_entries
+
+
+ def change_dir(self, menu_dir):
+ print "change dir to", menu_dir
+ self.menu_current = menu_dir;
+ self.clear_text_items()
+ self.refresh()
+
+ self.D.draw_menu(menu_dir)
+ self.D.display_menu()
+
+ def add_menu_item(self, path, caption, onclick, part = 3):
+ self.menu_raw.append([path, caption, onclick, part, None, len(self.menu_raw)]);
+ return len(self.menu_raw)-1
+
+ def change_menu_item(self, change_type, menu_item_id, change_text):
+ print change_type, menu_item_id, change_text
+
+ if change_type == "change_caption":
+ i = 0
+ for n in self.menu_entries:
+ if n.id == menu_item_id:
+ self.menu_entries[i] = MenuEntry(n.path, change_text, n.onclick, n.part, n.isDirectory, n.id)
+ self.D.menu_entries = self.menu_entries
+ self.D.draw_menu(self.menu_current)
+ self.D.refresh()
+ return True
+ i += 1
+
+ def add_menu_dir(self, path, caption, part = 4):
+ path = path.strip()
+ if len(path) > 1 and path[-1] == '/': path = path[0:-1]
+
+ # Avoid Duplicates:
+ if self.menu_dirs.count(path) == 0:
+ path_base = path[:path.rfind('/')]
+ if len(path_base) == 0: path_base = '/'
+
+ self.menu_raw.append([path_base, caption, None, part, path, len(self.menu_raw)]);
+ self.menu_dirs.append(path)
+
+ self.add_menu_item(path, "Back", None, 5)
+
+ return True
+
+ '''
+ Services
+ ========
+ '''
+ def refresh(self): self.D.refresh()
+ def display_tooltip(self, text, pos, format=None): self.D.display_tooltip(text, pos, format)
+ def display_line(self, percent, found): self.D.display_line(percent, found)
+ def question_display(self, q): self.map_list = self.map_str_to_list(self.D.question_display(q))
+ def display_surface(self, s, pos, to=None): self.D.display_surface(s, pos, to)
+ def clear_question_frame(self, emblem=False): self.D.clear_question_frame(emblem)
+ def get_screen(self): return self.D.screen
+ def get_app(self): return self.D.app
+
+ #service added for library
+ def current_caption(self):
+ return self.mycaption
+
+ def add_event_hook(self, event, function):
+ self.hooks.append([event, function])
+ return len(self.hooks) - 1
+
+ def del_event_hook(self, id):
+ self.hooks[id:id+1] = [None, None]
+
+ def list_event_hooks(self, event = None):
+ if event == None:
+ print self.hooks
+ return True
+
+ for h in self.hooks:
+ if h[0] == event: print h
+
+ def add_text_item(self, text, pos, onclick=None, retvar=None, border=False, format=None):
+ # adds an clickable text-item to the question frame
+ if format == None: format=TextFormat()
+ qi = TextItem(text, pos, onclick, retvar, border, format)
+ item_count = len(self.text_items)
+ self.text_items.append(qi)
+ self.D.text_items = self.text_items
+ self.D.display_text_items()
+
+# print self.text_items
+ return item_count
+
+ def del_text_item(self, item_id):
+# print self.text_items
+ self.text_items[item_id] = None
+ self.D.text_items = self.text_items
+ self.refresh()
+# print self.text_items
+
+ def clear_text_items(self, refresh=True):
+# print "clearing"
+# print self.text_items
+ self.text_items = []
+ self.D.text_items = []
+# print self.text_items
+ if refresh: self.refresh()
+
+ def edit_text_item(self, text, pos, onclick=None, retvar=None, border=False, format=None):
+ # adds a editable text-item to the question frame
+ if format == None: format = TextFormat()
+ qi = TextItem(text, pos, onclick, retvar, border, format)
+ item_count = len(self.text_items)
+ self.text_items.append(qi)
+ self.D.text_items = self.text_items
+ self.D.display_text_items()
+
+ def add_react(self, x, y, width, height, onclick, stopsearch = False, display = False):
+ print 'react', x, y, stopsearch, display
+ self.question_frame_reacts.append([x, y, width, height, onclick, stopsearch])
+
+ if display:
+ pygame.draw.rect(self.screen, hex2rgb(self.Colors.blue), (x, y, width, height), 3)
+ pygame.display.update()
+
+ print "- react #", len(self.question_frame_reacts)-1, "created"
+ return len(self.question_frame_reacts)-1
+
+ def del_react(self, id):
+ self.question_frame_reacts[id:id+1] = [None, None]
+ print "- react #", id, "cleared"
+ return True
+
+
+
+ ''' Polygon Drawing '''
+ def draw_polygon(self):
+ # Draw The Polygon on Screen
+ pygame.draw.polygon(self.D.screen, hex2rgb(self.Layout.Polygon.color), self.map_list)
+
+ def draw_lines(self):
+ last_pos = ()
+ for i in self.map_list:
+ if len(last_pos) > 0:
+ pygame.draw.line(self.D.screen, hex2rgb(self.Layout.Polygon.color), last_pos, i, 5)
+ last_pos = i
+
+
+ '''
+ Text Input Tools
+ '''
+ def display_box(self, message):
+ x = self.Layout.TextInput;
+
+ area = Rect(x.Box.x, x.Box.y, x.Box.width, x.Box.height)
+
+ bw, bc = border2px_rgb(x.Box.border)
+
+ pygame.draw.rect(self.D.screen, hex2rgb(x.Box.background), area, 0)
+ pygame.draw.rect(self.D.screen, bc, area, bw)
+
+ oldclip = self.D.screen.get_clip()
+ self.D.screen.set_clip(area.inflate(-4, -4))
+
+ if len(message):
+ text = pygame.font.Font(None, x.Text.size)
+ text = text.render(message, 1, hex2rgb(x.Text.color))
+ self.D.screen.blit(text, area.move(5, 7))
+
+ self.D.screen.set_clip(oldclip)
+ pygame.display.update(area)
+
+ def ask(self, question):
+ "ask(screen, question) -> answer"
+ current_string = ''
+ self.display_box(question + ":")
+ while 1:
+ event = pygame.event.wait()
+
+ if event.type == QUIT:
+ sys.exit(0)
+
+ if event.type == MOUSEBUTTONUP:
+ self.check_click(event.pos)
+
+ if event.type != KEYDOWN:
+ continue
+
+ if event.key == K_ESCAPE:
+ current_string = ""
+ break
+
+ elif event.key == 271 or event.key == K_RETURN:
+ break
+
+ elif event.key == K_BACKSPACE:
+ current_string = current_string[:-1]
+ self.display_box(question + ": " + current_string)
+
+ elif event.unicode:
+ current_string += chr(event.key) #should be += event.unicode, workaround for xo bug
+ self.display_box(question + ": " + current_string)
+
+# self.refresh_question_display()
+ return current_string
+
+ '''
+ Other Tools
+ ===========
+ '''
+ def sound_load(self, name, path = 'sounds'):
+ class NoneSound:
+ def play(self): pass
+ if not pygame.mixer:
+ return NoneSound()
+ fullname = os.path.join(path, name)
+ print 'fullname=',fullname
+ try:
+ sound = pygame.mixer.Sound(fullname)
+ except pygame.error, message:
+ print "Cannot load sound:", name
+ sound = NoneSound()
+ return sound
+
+ def image_load(self, name, path=''):
+ fullname = os.path.join(path, name)
+# fullname = name
+ try:
+ image = pygame.image.load(fullname)
+ except pygame.error, message:
+ print "Cannot load image:", name, 'fullname', fullname
+ #raise SystemExit, message
+ return None
+ image = image.convert()
+ return image, image.get_rect()
+
+ def point_inside_polygon(self, x,y,poly):
+ n = len(poly)
+ inside =False
+ p1x,p1y = poly[0]
+ for i in range(n+1):
+ p2x,p2y = poly[i % n]
+ if y > min(p1y,p2y):
+ if y <= max(p1y,p2y):
+ if x <= max(p1x,p2x):
+ if p1y != p2y:
+ xinters = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
+ if p1x == p2x or x <= xinters:
+ inside = not inside
+ p1x,p1y = p2x,p2y
+ return inside
+
+
+'''
+ Classes for the Menu Structure
+ ==============================
+'''
+class MenuBox(pygame.sprite.Sprite):
+ Layout = Layout();
+ def __init__(self, caption, isDirectory):
+ # Init Sprite
+ pygame.sprite.Sprite.__init__(self)
+
+ # Create Button Surface
+ self.image = pygame.Surface((self.Layout.MenuMain.Button.width, self.Layout.MenuMain.Button.height))
+
+ # Apply Styles
+ if isDirectory:
+ bgcolor = self.Layout.MenuMain.Button.Button_Directory.background
+ border = self.Layout.MenuMain.Button.Button_Directory.border
+ textsize = self.Layout.MenuMain.Button.Button_Directory.Text.size
+ textcolor = self.Layout.MenuMain.Button.Button_Directory.Text.color
+ else:
+ bgcolor = self.Layout.MenuMain.Button.background
+ border = self.Layout.MenuMain.Button.border
+ textsize = self.Layout.MenuMain.Button.Text.size
+ textcolor = self.Layout.MenuMain.Button.Text.color
+
+ # Fill Menu BG
+ self.image.fill(hex2rgb(bgcolor))
+
+ # Draw Border of MenuBox
+ bw, bc = border2px_rgb(border)
+ pygame.draw.rect(self.image, bc, (0, 0, self.Layout.MenuMain.Button.width, self.Layout.MenuMain.Button.height), bw)
+
+ # Render Text
+ font = pygame.font.Font(None, textsize)
+ text = font.render(caption, 1, hex2rgb(textcolor))
+
+ # Blit on self.image
+ self.image.blit(text, (self.Layout.MenuMain.Button.Text.x, self.Layout.MenuMain.Button.Text.y))
+ self.rect = self.image.get_rect()
+
+class MenuEntry:
+ # Load Layout
+ Layout = Layout()
+
+ # 'pos' stores the current Position; draw_menu sets it
+ pos = (0, 0)
+
+ def __init__(self, path, caption, onclick, part, isDirectory, id):
+ self.isDirectory = isDirectory;
+ self.id = id
+ self.caption = caption
+ self.onclick = onclick
+ self.path = path
+
+ if onclick == None:
+ self.box = MenuBox(caption, True)
+ else:
+ self.box = MenuBox(caption, isDirectory)
+ self.part = part # part is display-order 0-5
+
+ def pos_inside(self, pos):
+ x, y = list(pos)
+ self_w = self.Layout.MenuMain.Button.width
+ self_h = self.Layout.MenuMain.Button.height
+ self_x, self_y = list(self.pos)
+
+ if (x >= self_x and x <= self_x + self_w) and (y >= self_y and y <= self_y + self_h):
+ return True
+ else:
+ return False
+
+class TextItem:
+ # Represents a Text Field w/wo border in the Question Frame
+ # * Catches Clicks on It
+
+ Layout = Layout()
+ pos = (0, 0)
+ size = (0, 0)
+ text = u''
+ textformat = TextFormat()
+
+ def __init__(self, text, pos, onclick, retvar, border, textformat=None):
+ self.text = text
+ self.pos = pos
+ self.onclick = onclick
+ self.border = border
+ self.retvar = retvar
+
+ if textformat != True and textformat != False and textformat != None:
+ self.textformat = textformat
+
+ self.draw()
+
+ x,y,w,h = list(self.textpos)
+ x,y = list(pos)
+ self.area = ((x,y,w,h))
+
+ def draw(self):
+ font = pygame.font.Font(None, self.textformat.size)
+ text = font.render(self.text, 1, hex2rgb(self.textformat.color))
+ textpos = text.get_rect()
+ self.textpos = textpos
+
+ x,y,w,h = list(textpos)
+ self.size = (w+16,h+8)
+
+ self.image = pygame.Surface(self.size)
+ if self.textformat.background != None:
+ self.image.fill(hex2rgb(self.textformat.background))
+ self.image.blit(text, (8,4))
+
+ if self.border:
+ pygame.draw.rect(self.image, (255, 255, 255), (x,y,w+16,h+8), 2)
+
+ def pos_inside(self, pos):
+ x, y = list(pos)
+ self_x, self_y = list(self.pos)
+ self_w, self_h = list(self.size)
+
+ if (x >= self_x and x <= self_x + self_w) and (y >= self_y and y <= self_y + self_h):
+ return True
+ else:
+ return False
+
+class ToolTip:
+ Layout = Layout()
+
+ def __init__(self, pos, text):
+ self.pos = pos
+ self.display(text)
+
+ def display(self, caption):
+ font = pygame.font.Font(None, self.Layout.Toolbar.Text.size)
+ text = font.render(caption, 1, hex2rgb(self.Layout.Toolbar.Text.color))
+ textpos = text.get_rect()
+
+ x,y,w,h = list(textpos)
+ self.size = (w,h)
+
+ self.image = pygame.Surface(self.size)
+ self.image.blit(text, (0,0))
diff --git a/imagequiz_library/Africa.csv b/imagequiz_library/Africa.csv
new file mode 100755
index 0000000..ddcca4c
--- /dev/null
+++ b/imagequiz_library/Africa.csv
@@ -0,0 +1 @@
+africa2.jpg;;Where is Egypt?;;274, 87, 272, 95, 273, 147, 338, 148, 334, 143, 332, 137, 314, 100, 315, 98, 321, 109, 327, 112, 329, 101, 325, 91, 313, 91, 311, 87, 308, 88, 296, 90, 295, 92, 277, 86, ;;13;;Today, Egypt is widely regarded as an important political and cultural centre of the Middle East.;;&lt;a href='http://en.wikipedia.org/wiki/Egypt' target='_blank'&gt;Egypt&lt;/a&gt;;;<br>africa2.jpg;;Where is Ethiopia?;;338, 197, 319, 228, 332, 249, 354, 256, 367, 253, 383, 248, 401, 231, 374, 216, 367, 212, 370, 205, 361, 194, 340, 195, ;;13;;is one of the oldest nations in the world and Africa\'s second-most populous nation.;;&lt;a href='http://en.wikipedia.org/wiki/Ethiopia' target='_blank'&gt;Ethiopia&lt;/a&gt;;;<br>africa2.jpg;;Where is Algeria?;;79, 116, 148, 164, 164, 164, 200, 140, 186, 120, 184, 96, 172, 72, 176, 50, 145, 52, 117, 62, 123, 83, 108, 86, 109, 92, 79, 106, 80, 114, ;;13;;is the second largest country on the African continent;;&lt;a href='http://en.wikipedia.org/wiki/Algeria' target='_blank'&gt;Algeria&lt;/a&gt;;;<br>africa2.jpg;;Where is Italy?;;187, 1, 196, 11, 202, 18, 200, 42, 200, 48, 209, 53, 217, 55, 227, 52, 228, 46, 233, 36, 237, 27, 234, 18, 225, 14, 218, 10, 214, 3, 188, 1, 200, 15, 177, 20, 175, 31, 182, 40, 201, 44, ;;13;;Italy (Italian: Italia), officially the Italian Republic, (Italian: Repubblica Italiana), is located in Southern Europe;;&lt;a href='http://en.wikipedia.org/wiki/Italy' target='_blank'&gt;Italy&lt;/a&gt;;;<br>africa2.jpg;;Where is Madagascar?;;412, 346, 400, 360, 389, 370, 382, 374, 381, 380, 381, 388, 380, 396, 377, 403, 380, 413, 382, 420, 389, 425, 401, 422, 402, 412, 409, 401, 412, 386, 415, 374, 418, 365, 412, 345, ;;13;;is the fourth largest island in the world, and is home to five percent of the world\'s plant and animal species.;;&lt;a href='http://en.wikipedia.org/wiki/Madagascar' target='_blank'&gt;Madagascar&lt;/a&gt;;;<br>africa2.jpg;;Where is Portugal ?;;81, 11, 92, 16, 88, 48, 79, 48, 78, 35, 79, 13, ;;13;;Portugal has about 11.8 millions inhabitants, most people who speak Portuguese live in Brazil.;;&lt;a href='http://en.wikipedia.org/wiki/Portugal' target='_blank'&gt;Portugal&lt;/a&gt;;;<br>africa2.jpg;;Where is Lybia ?;;198, 78, 183, 94, 191, 133, 215, 142, 250, 154, 270, 158, 272, 87, 242, 76, 238, 89, 218, 84, 199, 74, ;;13;;Lybia is a country in Africa.;;&lt;a href='http://en.wikipedia.org/wiki/Lybia' target='_blank'&gt;Lybia&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/Astronomy.csv b/imagequiz_library/Astronomy.csv
new file mode 100755
index 0000000..4f8d4c6
--- /dev/null
+++ b/imagequiz_library/Astronomy.csv
@@ -0,0 +1 @@
+planets_iau_big.jpg;;Which one is Saturn?;;461, 150, 447, 144, 433, 148, 419, 155, 413, 170, 410, 186, 402, 193, 390, 206, 384, 217, 389, 223, 405, 221, 418, 207, 428, 202, 447, 204, 460, 205, 473, 194, 476, 188, 473, 168, 471, 158, 484, 153, 495, 146, 499, 137, 481, 142, 474, 145, 464, 149, 455, 149, ;;3;;is the sixth planet from the Sun and the second largest planet in the Solar System, after Jupiter.;;&lt;a href='http://en.wikipedia.org/wiki/Saturn' target='_blank'&gt;Saturn&lt;/a&gt;;;<br>planets_iau_big.jpg;;Which one is Neptune?;;583, 170, 578, 166, 570, 166, 562, 173, 563, 182, 567, 191, 580, 188, 588, 183, 588, 173, 580, 166, 577, 166, ;;3;;The planet is named after the Roman god of the sea. Its astronomical symbol is a stylized version of Poseidons trident.;;&lt;a href='http://en.wikipedia.org/wiki/Neptune' target='_blank'&gt;Neptune&lt;/a&gt;;;<br>planets_iau_big.jpg;;Where is the Jupiter?;;315, 139, 328, 139, 342, 144, 353, 154, 359, 164, 363, 177, 363, 188, 361, 196, 353, 203, 349, 211, 344, 215, 337, 218, 330, 221, 320, 222, 312, 222, 299, 216, 289, 207, 283, 200, 280, 190, 279, 178, 281, 170, 284, 159, 290, 154, 296, 149, 304, 146, 311, 142, 320, 142, 321, 139, ;;3;;is two and a half times as massive as all of the other planets in our solar system combined;;&lt;a href='http://en.wikipedia.org/wiki/Jupiter' target='_blank'&gt;Jupiter&lt;/a&gt;;;<br>planets_iau_big.jpg;;Where is the Sun?;;69, 2, 123, 122, 129, 266, 76, 449, 0, 450, 0, 1, 69, 2, ;;3;;Temperature of the corona: ~ 5.000.000 K;;&lt;a href='http://en.wikipedia.org/wiki/Sun' target='_blank'&gt;The Sun&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/Cars_&_Motorbikes.csv b/imagequiz_library/Cars_&_Motorbikes.csv
new file mode 100755
index 0000000..d44336d
--- /dev/null
+++ b/imagequiz_library/Cars_&_Motorbikes.csv
@@ -0,0 +1 @@
+5104352.JPG;;Can you find the Air-Filter?;;256, 227, 242, 233, 233, 240, 236, 251, 231, 269, 238, 271, 248, 267, 259, 270, 273, 270, 287, 268, 302, 262, 307, 255, 310, 243, 303, 235, 285, 229, 266, 227, 253, 229, ;;1;;;;&lt;a href='http://en.wikipedia.org/wiki/Air_filter' target='_blank'&gt;Air Filter&lt;/a&gt;;;<br>5104352.JPG;;Where is the Battery?;;175, 252, 138, 275, 138, 280, 142, 281, 143, 316, 177, 322, 183, 319, 186, 314, 191, 309, 209, 299, 208, 258, 175, 253, ;;1;;A car battery is a type of rechargeable battery that supplies electric energy to an automobile.;;&lt;a href='http://en.wikipedia.org/wiki/Car_battery' target='_blank'&gt;Car Battery&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/Difference_Game.csv b/imagequiz_library/Difference_Game.csv
new file mode 100755
index 0000000..473f215
--- /dev/null
+++ b/imagequiz_library/Difference_Game.csv
@@ -0,0 +1 @@
+different1.png;;Which of these things is not like the others?;;87, 220, 107, 215, 130, 219, 145, 224, 157, 246, 172, 271, 179, 301, 181, 326, 192, 353, 195, 380, 206, 408, 216, 427, 221, 460, 225, 483, 226, 515, 220, 543, 211, 563, 200, 577, 185, 587, 168, 592, 153, 594, 129, 598, 98, 599, 67, 591, 42, 574, 29, 552, 19, 528, 19, 507, 19, 484, 20, 451, 17, 421, 25, 401, 28, 376, 35, 357, 46, 330, 63, 306, 64, 284, 72, 267, 74, 251, 78, 237, 84, 228, 86, 220, ;;20;;The cello is a stringed instrument. The other three are horn instruments. ;;&lt;a href='http://en.wikipedia.org/wiki/Cello' target='_blank'&gt;Cello&lt;/a&gt;;;<br>different2.png;;Which of these things is not like the others?;;243, 17, 236, 32, 231, 56, 223, 71, 222, 90, 224, 104, 224, 125, 224, 146, 229, 164, 234, 179, 242, 196, 251, 205, 267, 217, 279, 227, 294, 228, 322, 234, 344, 236, 373, 237, 388, 230, 409, 217, 424, 200, 440, 178, 448, 154, 451, 125, 451, 96, 443, 63, 431, 44, 420, 27, 398, 11, 370, 3, 327, 1, 281, 3, 263, 8, 249, 12, 244, 16, ;;20;;Eye glasses can help you see better.;;&lt;a href='http://en.wikipedia.org/wiki/Glasses' target='_blank'&gt;The boy with glasses&lt;/a&gt;;;<br>different3.png;;Which of these things is not like the others?;;214, 30, 206, 48, 204, 70, 203, 89, 204, 115, 211, 131, 218, 144, 235, 158, 247, 173, 269, 177, 290, 182, 327, 190, 358, 190, 379, 175, 405, 157, 412, 136, 421, 106, 421, 78, 414, 52, 403, 12, 384, 6, 354, 5, 332, 3, 281, 12, 265, 12, 247, 14, 232, 20, 219, 23, 214, 31, ;;20;;is different from the others. The other girls have blond hair.;;&lt;a href='' target='_blank'&gt;The girl with brown hair.&lt;/a&gt;;;<br>different4.png;;Which of these things is not like the others?;;164, 176, 160, 196, 160, 208, 156, 222, 152, 237, 154, 246, 155, 256, 158, 266, 161, 279, 168, 297, 174, 308, 185, 318, 194, 327, 212, 331, 232, 339, 243, 338, 258, 337, 270, 328, 283, 320, 290, 311, 296, 300, 299, 290, 308, 274, 314, 258, 314, 244, 316, 232, 316, 223, 314, 213, 308, 202, 306, 193, 299, 183, 289, 171, 277, 160, 268, 156, 251, 156, 232, 157, 221, 157, 211, 157, 195, 157, 187, 162, 179, 163, 172, 169, 165, 178, ;;20;;is different. The other men have dark hair.;;&lt;a href='' target='_blank'&gt;The man with blond hair&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/Europe.csv b/imagequiz_library/Europe.csv
new file mode 100755
index 0000000..964ced4
--- /dev/null
+++ b/imagequiz_library/Europe.csv
@@ -0,0 +1 @@
+europe.gif;;Which country is the most populated?;;734, 35, 713, 53, 706, 38, 681, 30, 676, 53, 667, 62, 657, 50, 622, 38, 590, 21, 566, 15, 547, 18, 534, 27, 532, 38, 548, 44, 540, 53, 546, 65, 550, 76, 554, 83, 556, 94, 573, 108, 540, 138, 550, 142, 564, 148, 547, 149, 542, 156, 539, 166, 539, 174, 544, 183, 546, 191, 548, 196, 551, 202, 555, 204, 564, 203, 567, 208, 575, 208, 581, 212, 584, 221, 586, 228, 595, 234, 600, 236, 606, 239, 601, 245, 593, 243, 593, 247, 598, 253, 597, 257, 605, 259, 611, 258, 615, 257, 624, 260, 628, 268, 636, 275, 643, 277, 647, 285, 655, 287, 666, 285, 672, 291, 682, 291, 692, 295, 700, 298, 696, 306, 700, 316, 697, 323, 690, 325, 685, 329, 693, 333, 686, 338, 683, 339, 687, 346, 682, 352, 677, 360, 669, 361, 676, 365, 688, 372, 708, 386, 715, 386, 733, 390, 734, 34, ;;11;;The population of Russia is around 143,000,000 people.;;&lt;a href='http://en.wikipedia.org/wiki/Russia' target='_blank'&gt;Russia&lt;/a&gt;;;<br>europe.gif;;Where is Liechtenstein, a really small country in Europe?;;337, 329, 331, 339, 338, 341, 354, 339, 354, 332, 345, 328, 335, 327, ;;11;;has around 32,000 people living on a total 160 square km.;;&lt;a href='http://en.wikipedia.org/wiki/Liechtenstein' target='_blank'&gt;Liechtenstein&lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is Scandinavia ?;;421, 3, 451, 126, 413, 170, 359, 202, 337, 235, 290, 241, 251, 228, 240, 164, 256, 105, 420, 5, ;;11;;Scandinava is the northern region of Europe;;&lt;a href='http://en.wikipedia.org/wiki/Scandinavia' target='_blank'&gt;Scandinavia&lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is Andorra?;;164, 384, 158, 384, 157, 381, 159, 376, 165, 374, 169, 375, 171, 382, 167, 383, 163, 382, ;;11;;is small country in the pyrinees;;&lt;a href='http://en.wikipedia.org/wiki/Andorra ' target='_blank'&gt;Andorra &lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is Moldova?;;415, 336, 427, 359, 435, 382, 451, 374, 457, 352, 449, 331, 432, 329, 423, 334, 420, 342, ;;11;;is one of the youngest countries in europe.;;&lt;a href='http://en.wikipedia.org/wiki/Moldova' target='_blank'&gt;Moldova&lt;/a&gt;;;<br>800px-Regions_of_Europe.png;;Where is the Peloponnes?;;367, 480, 361, 482, 363, 488, 366, 495, 366, 501, 371, 500, 376, 505, 379, 500, 386, 504, 384, 493, 382, 494, 384, 485, 378, 485, 376, 480, 374, 480, ;;11;;...is a mediterrain half-island;;&lt;a href='http://en.wikipedia.org/wiki/Pelopnnes ' target='_blank'&gt;Pelopnnes &lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/Geography.csv b/imagequiz_library/Geography.csv
new file mode 100755
index 0000000..2bb9950
--- /dev/null
+++ b/imagequiz_library/Geography.csv
@@ -0,0 +1 @@
+latinamerica.jpg;;Where is Brazil?;;225, 95, 222, 120, 207, 132, 209, 155, 243, 164, 268, 194, 277, 204, 273, 214, 274, 227, 280, 229, 286, 235, 288, 250, 283, 260, 274, 270, 271, 273, 277, 280, 280, 284, 282, 284, 281, 285, 282, 290, 282, 293, 292, 282, 310, 261, 315, 244, 334, 234, 343, 231, 352, 220, 361, 203, 368, 174, 390, 143, 386, 133, 368, 121, 343, 112, 326, 104, 315, 91, 312, 76, 310, 71, 304, 81, 303, 85, 297, 86, 289, 86, 285, 87, 277, 89, 272, 80, 269, 72, 263, 69, 260, 72, 253, 73, 249, 74, 252, 84, 247, 90, 238, 91, 233, 86, 224, 87, 222, 92, 224, 97, ;;2;;officially the Federative Republic of Brazil is the fifth-largest country in the world.;;&lt;a href='http://en.wikipedia.org/wiki/Brazil' target='_blank'&gt;Brazil&lt;/a&gt;;;<br>latinamerica.jpg;;Where is Paraguay?;;273, 227, 274, 222, 272, 215, 266, 214, 257, 216, 253, 222, 252, 232, 259, 238, 266, 242, 270, 248, 269, 254, 266, 258, 271, 259, 278, 259, 283, 253, 286, 247, 287, 241, 284, 240, 283, 233, 278, 231, 274, 225, ;;2;;Population: 6,158,000 people;;&lt;a href='http://en.wikipedia.org/wiki/Paraguay' target='_blank'&gt;Paraguay&lt;/a&gt;;;<br>latinamerica.jpg;;Can you find Chile?;;214, 208, 212, 254, 205, 271, 202, 298, 193, 317, 193, 329, 190, 338, 176, 385, 180, 394, 183, 401, 187, 404, 194, 404, 192, 394, 187, 391, 187, 385, 187, 378, 191, 368, 194, 362, 196, 352, 196, 343, 198, 337, 202, 322, 204, 314, 205, 309, 211, 296, 211, 283, 216, 263, 223, 257, 224, 242, 229, 236, 226, 232, 221, 208, 216, 208, ;;2;;is a country in South America occupying a long and narrow coastal strip wedged between the Andes and the Pacific;;&lt;a href='http://en.wikipedia.org/wiki/Chile' target='_blank'&gt;Chile&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/Quiz0001.xml b/imagequiz_library/Quiz0001.xml
new file mode 100755
index 0000000..1d5d5c5
--- /dev/null
+++ b/imagequiz_library/Quiz0001.xml
@@ -0,0 +1,131 @@
+<quiz>
+ <card bin="0" count="0" id="1" shown="" think="">
+ <question>
+ <sound>k2361.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2361.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="2" shown="" think="">
+ <question>
+ <sound>k1809.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k1809.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="3" shown="" think="">
+ <question>
+ <sound>k1960.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k1960.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="4" shown="" think="">
+ <question>
+ <sound>k0993.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k0993.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="5" shown="" think="">
+ <question>
+ <sound>k1030.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k1030.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="6" shown="" think="">
+ <question>
+ <sound>k2579.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2579.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="7" shown="" think="">
+ <question>
+ <sound>k0061.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k0061.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="8" shown="" think="">
+ <question>
+ <sound>k2532.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2532.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="9" shown="" think="">
+ <question>
+ <sound>k2701.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2701.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="10" shown="" think="">
+ <question>
+ <sound>k2364.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2364.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="11" shown="" think="">
+ <question>
+ <sound>k0177.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k0177.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="12" shown="" think="">
+ <question>
+ <sound>k1391.ogg/k1391r.ogg/k1391r1.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k1391.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="13" shown="" think="">
+ <question>
+ <sound>k2849.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2849.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="14" shown="" think="">
+ <question>
+ <sound>k2621.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k2621.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="15" shown="" think="">
+ <question>
+ <sound>k1267.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k1267.png</image>
+ <more />
+ </answer>
+ </card><card bin="0" count="0" id="16" shown="" think="">
+ <question>
+ <sound>k3184.ogg</sound>
+ <hint />
+ </question><answer>
+ <image>k3184.png</image>
+ <more />
+ </answer>
+ </card>
+</quiz>
diff --git a/imagequiz_library/United_Mexican_States.csv b/imagequiz_library/United_Mexican_States.csv
new file mode 100755
index 0000000..bf10baa
--- /dev/null
+++ b/imagequiz_library/United_Mexican_States.csv
@@ -0,0 +1 @@
+Mexico_states.png;;Where is the state of Baja California?;;7, 9, 38, 10, 36, 17, 36, 24, 36, 31, 35, 40, 37, 53, 44, 61, 49, 68, 50, 74, 53, 76, 56, 81, 59, 81, 59, 88, 39, 88, 43, 79, 37, 71, 26, 61, 21, 56, 20, 48, 17, 41, 15, 35, 11, 27, 6, 9, ;;21;;Baja California is the northernmost state in Mexico.;;&lt;a href='http://en.wikipedia.org/wiki/Baja_California' target='_blank'&gt;Baja California&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Baja California Sur?;;39, 87, 58, 89, 71, 110, 84, 142, 85, 153, 90, 155, 91, 150, 101, 166, 102, 171, 94, 173, 91, 168, 85, 161, 78, 153, 69, 147, 64, 140, 65, 125, 59, 116, 48, 108, 35, 101, 24, 88, 37, 88, ;;21;;The name Baja California Sur means \&quot;South Lower California\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Baja_California_Sur' target='_blank'&gt;Baja California Sur&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Sonora?;;37, 14, 84, 38, 119, 42, 120, 72, 120, 90, 113, 90, 119, 109, 111, 119, 104, 112, 100, 112, 100, 108, 91, 104, 90, 94, 81, 90, 73, 79, 67, 66, 62, 58, 61, 46, 61, 38, 54, 36, 50, 29, 48, 30, 36, 24, 36, 15, ;;21;;Sonora borders the U.S. states of Arizona and New Mexico.;;&lt;a href='http://en.wikipedia.org/wiki/Sonora' target='_blank'&gt;Sonora&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Chihuahua?;;118, 40, 126, 42, 126, 35, 152, 35, 156, 42, 165, 49, 172, 57, 176, 66, 181, 73, 189, 77, 195, 81, 184, 96, 188, 116, 180, 114, 175, 121, 165, 118, 155, 114, 150, 119, 147, 127, 144, 130, 139, 131, 135, 125, 129, 123, 127, 114, 124, 109, 119, 109, 118, 102, 114, 90, 119, 89, 120, 71, 119, 40, ;;21;;Although the state is named after a desert, Chihuahua has more forests than any other state in Mexico.;;&lt;a href='http://en.wikipedia.org/wiki/Chihuahau' target='_blank'&gt;Chihuahau&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Coahuila?;;202, 142, 202, 146, 197, 146, 191, 141, 190, 134, 193, 121, 188, 117, 184, 98, 201, 72, 206, 65, 221, 67, 223, 70, 230, 76, 235, 87, 242, 95, 242, 101, 238, 100, 235, 105, 229, 109, 232, 111, 232, 117, 228, 118, 224, 121, 237, 138, 231, 140, 229, 150, 223, 146, 218, 147, 212, 142, 207, 141, 202, 142, ;;21;;About 95% of Mexico\'s coal reserves are found in Coahuila, which is the country\'s top mining state.;;&lt;a href='http://en.wikipedia.org/wiki/Coahuila' target='_blank'&gt;Coahuila&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Neuvo Leon?;;243, 100, 237, 100, 235, 105, 228, 109, 232, 112, 232, 116, 229, 117, 224, 121, 228, 132, 236, 139, 232, 141, 228, 142, 228, 151, 233, 155, 233, 162, 234, 171, 240, 174, 242, 169, 245, 164, 251, 162, 249, 157, 246, 151, 254, 146, 264, 136, 262, 128, 254, 125, 249, 118, 246, 112, 243, 101, ;;21;;The capital of Nuevo Le&Atilde;&sup3;n is Monterrey, the third largest city in Mexico with over three million residents.;;&lt;a href='http://en.wikipedia.org/wiki/Nuevo_Leon' target='_blank'&gt;Nuevo Leon&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Tamaulipas?;;242, 99, 245, 114, 252, 124, 262, 128, 265, 136, 256, 143, 248, 150, 249, 162, 244, 163, 241, 170, 243, 176, 243, 179, 252, 181, 255, 185, 271, 183, 274, 185, 275, 174, 274, 163, 276, 151, 279, 140, 283, 128, 275, 126, 266, 124, 256, 120, 252, 115, 249, 106, 248, 100, 243, 99, ;;21;;No one knows where the name came from, but many people think it comes from a Huastec term meaning \&quot;place of high hills\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Tamaulipas' target='_blank'&gt;Tamaulipas&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Sinaloa?;;105, 128, 111, 118, 121, 109, 126, 109, 130, 122, 135, 124, 139, 130, 137, 139, 143, 146, 145, 153, 152, 153, 158, 169, 162, 172, 160, 183, 153, 177, 143, 168, 133, 156, 125, 148, 116, 138, 107, 132, 105, 129, ;;21;;Sinaloa it is the only place where the ancient ball game of Mesoamerica is still played. ;;&lt;a href='http://en.wikipedia.org/wiki/Sinaloa' target='_blank'&gt;Sinaloa&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Durango;;138, 130, 144, 130, 148, 121, 152, 118, 155, 112, 163, 118, 168, 119, 175, 120, 180, 116, 187, 116, 192, 122, 192, 134, 191, 139, 194, 146, 199, 147, 202, 143, 206, 147, 206, 154, 192, 154, 185, 160, 185, 165, 181, 169, 178, 186, 173, 182, 168, 182, 168, 175, 162, 172, 155, 163, 151, 152, 146, 152, 140, 144, 138, 137, 138, 130, ;;21;;;;;;<br>Mexico_states.png;;Where is the state of Zacatecas?;;183, 186, 178, 187, 179, 171, 183, 166, 184, 161, 190, 152, 205, 152, 204, 142, 211, 141, 219, 148, 223, 146, 228, 153, 223, 159, 216, 168, 208, 170, 209, 177, 214, 182, 221, 180, 222, 186, 220, 193, 215, 192, 209, 186, 204, 188, 199, 193, 201, 199, 204, 202, 199, 204, 195, 208, 186, 205, 188, 199, 191, 195, 196, 191, 195, 187, 192, 184, 191, 190, 185, 186, ;;21;;The state\'s name comes the Nahuatl word for a type of grass common in the region, \&quot;zacatl\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Zacatecas' target='_blank'&gt;Zacatecas&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of San Louis Potosi?;;221, 180, 217, 182, 209, 176, 206, 172, 216, 169, 222, 160, 222, 155, 227, 153, 233, 155, 234, 161, 235, 172, 242, 174, 243, 178, 251, 181, 257, 185, 265, 186, 269, 188, 266, 200, 265, 203, 259, 203, 254, 197, 252, 200, 248, 198, 246, 200, 237, 197, 234, 200, 222, 196, 222, 181, ;;21;;The native peoples of the state include the Huastecs and Pame people.;;&lt;a href='http://en.wikipedia.org/wiki/San_Louis_Potosi' target='_blank'&gt;San Louis Potosi&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Nayarit?;;159, 192, 159, 183, 162, 174, 168, 174, 169, 182, 173, 184, 178, 188, 179, 194, 180, 201, 183, 203, 179, 206, 179, 212, 171, 208, 163, 213, 160, 210, 163, 205, 164, 199, 159, 192, ;;21;;Also belonging to the state are the Islas Marias, Islas Tres Marietas and Isla Isabel, all in the Pacific Ocean.;;&lt;a href='http://en.wikipedia.org/wiki/Nayarit' target='_blank'&gt;Nayarit&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Jalisco?;;159, 217, 164, 214, 170, 208, 178, 211, 180, 206, 183, 202, 180, 200, 180, 195, 177, 189, 181, 186, 183, 180, 189, 185, 188, 190, 193, 189, 192, 183, 197, 188, 195, 193, 192, 194, 190, 199, 187, 204, 193, 207, 197, 204, 202, 202, 201, 198, 205, 196, 210, 196, 215, 192, 221, 195, 220, 202, 212, 210, 213, 216, 211, 218, 198, 221, 198, 225, 203, 225, 201, 231, 206, 233, 189, 242, 189, 234, 183, 234, 178, 234, 176, 237, 168, 234, 162, 228, 158, 219, ;;21;;Jalisco is known for being the birthplace of mariachi music and tequila.;;&lt;a href='http://en.wikipedia.org/wiki/Jalisco' target='_blank'&gt;Jalisco&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Aguascalientes ?;;200, 197, 198, 191, 208, 185, 213, 190, 215, 193, 209, 198, 201, 197, ;;21;;The name means \&quot;hot waters\&quot; in Spanish and originated from the abundance of hot springs in the area.;;&lt;a href='http://en.wikipedia.org/wiki/Aguascalientes' target='_blank'&gt;Aguascalientes&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Guanajuato?;;220, 194, 231, 199, 236, 196, 245, 200, 248, 204, 243, 204, 241, 208, 234, 208, 236, 216, 239, 221, 234, 225, 230, 223, 222, 224, 221, 218, 216, 219, 213, 216, 212, 212, 219, 203, 220, 195, ;;21;;Guanajuato means \&quot;place of frogs\&quot; in the local indigenous language.;;&lt;a href='http://en.wikipedia.org/wiki/Guanajuato' target='_blank'&gt;Guanajuato&lt;/a&gt;;;<br>Mexico_states.png;;Where is teh state of Quer&Atilde;&copy;taro Arteaga?;;242, 221, 236, 218, 234, 211, 241, 208, 243, 204, 246, 204, 246, 198, 252, 200, 254, 196, 258, 203, 253, 204, 250, 213, 247, 214, 246, 219, 244, 222, ;;21;;The name is thought to come from a phrase in the Otomi language meaning \&quot;the great ball game\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Quer&Atilde;&copy;taro' target='_blank'&gt;Quer&Atilde;&copy;taro Arteaga&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Hidalgo?;;251, 222, 246, 219, 246, 215, 250, 213, 253, 205, 258, 202, 266, 199, 271, 204, 271, 210, 267, 212, 267, 215, 272, 214, 274, 215, 271, 218, 273, 221, 272, 228, 266, 229, 261, 226, 259, 224, 254, 226, 252, 222, ;;21;;The state is named after Mexican independence leader Miguel Hidalgo y Costilla.;;&lt;a href='http://en.wikipedia.org/wiki/Hidalgo_(Mexico)' target='_blank'&gt;Hidalgo&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Colima?;;186, 244, 189, 242, 190, 234, 187, 231, 182, 231, 179, 236, 175, 237, 182, 241, 185, 244, ;;21;;A famous volcano in the zone is called \&quot;Volcan de Colima\&quot;, but in fact, half of it is in the nearby state of Jalisco.;;&lt;a href='http://en.wikipedia.org/wiki/Colima' target='_blank'&gt;Colima&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Michoac&Atilde;&iexcl;n?;;188, 243, 197, 239, 205, 232, 201, 229, 201, 225, 196, 222, 211, 217, 216, 220, 221, 218, 223, 223, 230, 222, 234, 225, 239, 222, 242, 224, 241, 228, 240, 237, 239, 242, 232, 243, 234, 249, 228, 248, 220, 248, 215, 247, 215, 251, 212, 254, 211, 257, 201, 255, 192, 251, 187, 245, ;;21;;It is the sixteenth largest state in Mexico, taking up 3% of the national territory.;;&lt;a href='http://en.wikipedia.org/wiki/Michoacan' target='_blank'&gt;Michoac&Atilde;&iexcl;n&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Mexico?;;240, 249, 237, 243, 241, 237, 241, 229, 243, 223, 246, 219, 251, 221, 253, 226, 259, 224, 260, 227, 266, 227, 265, 230, 265, 240, 261, 239, 261, 235, 257, 231, 254, 233, 255, 237, 255, 240, 253, 243, 248, 245, 244, 246, 240, 249, ;;21;;Mexico State is often abbreviated to \&quot;Edomex\&quot;, from the Spanish \&quot;Estado de M&Atilde;&copy;xico\&quot;.;;&lt;a href='' target='_blank'&gt;&lt;/a&gt;;;<br>Mexico_states.png;;Where is Mexico City?;;255, 238, 253, 235, 256, 229, 261, 232, 261, 235, 260, 238, 256, 238, ;;21;;Residents of Mexico City are called \&quot;capitalinos\&quot;, in reference to the city being the capital of the country.;;&lt;a href='http://en.wikipedia.org/wiki/Mexico_City' target='_blank'&gt;Mexico City&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Tlaxcala?;;264, 230, 270, 228, 279, 231, 277, 236, 273, 236, 268, 233, 263, 231, ;;21;;It covers an area of 1,037 square kilometers (400.4 sq mi) and is thus the smallest of Mexico\'s states.;;&lt;a href='http://en.wikipedia.org/wiki/Tlaxcala' target='_blank'&gt;Tlaxcala&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Morelos?;;256, 247, 255, 248, 252, 245, 254, 240, 259, 239, 265, 242, 265, 248, 261, 248, 259, 250, 257, 248, ;;21;;Morelos\' capital is Cuernavaca, also known as \&quot;La Ciudad de la Eterna Primavera\&quot;, \&quot;The City of Eternal Spring\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Morelos' target='_blank'&gt;Morelos&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Puebla?;;266, 231, 271, 235, 277, 233, 274, 229, 272, 225, 271, 218, 277, 209, 282, 213, 279, 219, 282, 221, 285, 218, 287, 222, 285, 231, 289, 235, 285, 241, 286, 243, 288, 247, 292, 248, 284, 255, 279, 250, 276, 251, 276, 255, 269, 256, 259, 249, 264, 248, 265, 229, ;;21;;The state covers an area of about 33,919 square kilometers (13,096.2 sq mi).;;&lt;a href='http://en.wikipedia.org/wiki/Puebla' target='_blank'&gt;Puebla&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Veracruz?;;268, 186, 271, 184, 274, 187, 283, 208, 293, 217, 300, 227, 300, 231, 307, 241, 312, 241, 320, 241, 326, 249, 335, 248, 337, 254, 343, 262, 338, 264, 328, 265, 320, 261, 319, 257, 311, 260, 309, 256, 309, 250, 303, 249, 297, 244, 294, 246, 291, 247, 287, 247, 286, 245, 284, 243, 285, 235, 289, 235, 284, 231, 286, 222, 282, 222, 279, 220, 282, 215, 278, 211, 276, 211, 273, 214, 268, 217, 267, 212, 270, 209, 271, 205, 267, 202, 267, 196, 266, 190, 268, 186, ;;21;;With more than 7 million inhabitants, the state of Veracruz is the third most populous in the nation.;;&lt;a href='http://en.wikipedia.org/wiki/Veracruz' target='_blank'&gt;Veracruz&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Guerrero?;;211, 256, 214, 251, 214, 245, 235, 249, 232, 244, 238, 244, 241, 247, 248, 247, 249, 244, 255, 248, 257, 246, 261, 252, 269, 256, 270, 266, 275, 273, 274, 276, 269, 281, 261, 277, 254, 277, 248, 276, 241, 272, 232, 270, 226, 264, 219, 260, 213, 257, ;;21;;Guerrero is named in honor of the second president of the republic, General Vicente Ram&Atilde;&sup3;n Guerrero Salda&Atilde;&plusmn;a.;;&lt;a href='http://en.wikipedia.org/wiki/Guerrero' target='_blank'&gt;Guerrero&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Oaxaca?;;268, 279, 274, 274, 274, 269, 269, 265, 269, 256, 276, 254, 278, 247, 283, 252, 287, 251, 293, 248, 295, 242, 300, 245, 302, 249, 308, 250, 310, 253, 308, 258, 313, 260, 317, 258, 323, 263, 341, 265, 338, 271, 338, 276, 338, 280, 329, 278, 325, 279, 317, 283, 307, 289, 297, 289, 288, 286, 282, 286, 274, 282, 268, 281, ;;21;;Oaxaca has more speakers of indigenous languages than any other Mexican state.;;&lt;a href='http://en.wikipedia.org/wiki/Oaxaca' target='_blank'&gt;Oaxaca&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Tabasco?;;343, 262, 339, 255, 336, 252, 334, 248, 345, 244, 348, 246, 357, 239, 360, 242, 364, 241, 365, 246, 370, 248, 374, 247, 383, 248, 383, 259, 380, 262, 378, 257, 372, 257, 369, 252, 364, 253, 358, 260, 353, 259, 351, 251, 348, 251, 346, 256, 344, 261, ;;21;;Tabasco is in the northern half of the Isthmus of Tehuantepec.;;&lt;a href='http://en.wikipedia.org/wiki/Tabasco' target='_blank'&gt;Tabasco&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Chiapas?;;340, 283, 338, 277, 339, 269, 341, 265, 345, 259, 348, 254, 349, 251, 352, 255, 357, 259, 367, 250, 375, 256, 378, 259, 385, 266, 392, 271, 396, 273, 396, 276, 376, 280, 370, 292, 369, 296, 369, 304, 362, 300, 356, 295, 352, 290, 348, 287, 344, 285, 341, 283, ;;21;;About one quarter of the population of Chiapas are of full or predominant Maya descent.;;&lt;a href='http://en.wikipedia.org/wiki/Chiapas' target='_blank'&gt;Chiapas&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Campeche?;;362, 240, 363, 245, 367, 246, 372, 250, 376, 246, 383, 249, 384, 252, 411, 251, 409, 226, 395, 209, 389, 209, 388, 202, 386, 217, 386, 226, 378, 232, 373, 237, 366, 239, 363, 239, ;;21;;Campeche derives from the name of the Mayan city of \&quot;Kan pech\&quot;.;;&lt;a href='http://en.wikipedia.org/wiki/Campeche' target='_blank'&gt;Campeche&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Quintana Roo?;;412, 247, 415, 247, 420, 237, 426, 236, 431, 239, 434, 222, 431, 222, 435, 217, 432, 215, 436, 203, 442, 196, 442, 189, 438, 184, 433, 186, 432, 192, 425, 204, 405, 218, 408, 225, 410, 246, ;;21;;Quintana Roo was granted statehood on October 8, 1974, making it the Mexican Republic\'s youngest state.;;&lt;a href='http://en.wikipedia.org/wiki/Quintana_Roo' target='_blank'&gt;Quintana Roo&lt;/a&gt;;;<br>Mexico_states.png;;Where is the state of Yucat&Atilde;&iexcl;n?;;405, 218, 425, 204, 431, 194, 431, 190, 425, 188, 417, 187, 410, 188, 397, 194, 389, 198, 389, 203, 389, 208, 395, 208, 404, 217, ;;21;;The source of the name \&quot;Yucatan\&quot; is the Nahuatl (Aztec) word Yokatl&Auml; meaning \&quot;place of richness.\&quot;;;&lt;a href='http://en.wikipedia.org/wiki/Yucatan' target='_blank'&gt;Yucat&Atilde;&iexcl;n&lt;/a&gt;;;<br>
diff --git a/imagequiz_library/XO_Laptop.csv b/imagequiz_library/XO_Laptop.csv
new file mode 100755
index 0000000..3db6c89
--- /dev/null
+++ b/imagequiz_library/XO_Laptop.csv
@@ -0,0 +1 @@
+xo.jpg;;Can you find the CPU in this open XO laptop?;;304, 297, 293, 308, 292, 389, 303, 400, 380, 399, 393, 387, 396, 309, 383, 295, 303, 297, ;;4;;\'Central Processing Unit\', or sometimes just \'processor\';;&lt;a href='http://en.wikipedia.org/wiki/CPU' target='_blank'&gt;CPU&lt;/a&gt;;;<br>olpc-xo-2.jpg;;Where is the Power Button?;;437, 300, 430, 303, 428, 310, 430, 313, 437, 316, 444, 312, 446, 306, 441, 303, 436, 302, ;;4;;\'One Laptop Per Child\' is an education project aiming especially at bridging the digital gap.;;&lt;a href='http://en.wikipedia.org/wiki/One_Laptop_per_Child' target='_blank'&gt;XO Laptop&lt;/a&gt;;;<br>olpc-xo-2.jpg;;Where is the left speaker ?;;138, 213, 143, 205, 159, 210, 164, 225, 155, 235, 140, 238, 135, 228, 135, 217, 135, 214, 137, 212, ;;4;;The XO has 2 speakers for stereo audio output.;;&lt;a href='http://en.wikipedia.org/wiki/Loudspeaker' target='_blank'&gt;Audio Speaker&lt;/a&gt;;;<br>olpc-xo-2.jpg;;where is the antenna?;;447, 79, 446, 57, 451, 19, 471, 23, 478, 49, 477, 104, 468, 129, 453, 137, 441, 100, 446, 83, 449, 77, 146, 75, 126, 8, 99, 26, 105, 84, 108, 120, 129, 132, 142, 131, 147, 100, 147, 87, 147, 78, 147, 78, 149, 80, ;;4;;2dBi or 3dBi antennas help your XO to communicate with other XO laptops;;&lt;a href='http://en.wikipedia.org/wiki/antennas' target='_blank'&gt;antennas&lt;/a&gt;;;<br> \ No newline at end of file
diff --git a/imagequiz_library/image/21arts1.jpg b/imagequiz_library/image/21arts1.jpg
new file mode 100755
index 0000000..805a27e
--- /dev/null
+++ b/imagequiz_library/image/21arts1.jpg
Binary files differ
diff --git a/imagequiz_library/image/5104352.jpg b/imagequiz_library/image/5104352.jpg
new file mode 100755
index 0000000..6fb1b7b
--- /dev/null
+++ b/imagequiz_library/image/5104352.jpg
Binary files differ
diff --git a/imagequiz_library/image/800px-Regions_of_Europe.png b/imagequiz_library/image/800px-Regions_of_Europe.png
new file mode 100755
index 0000000..15457c5
--- /dev/null
+++ b/imagequiz_library/image/800px-Regions_of_Europe.png
Binary files differ
diff --git a/imagequiz_library/image/Mexico_states.png b/imagequiz_library/image/Mexico_states.png
new file mode 100755
index 0000000..635b4ad
--- /dev/null
+++ b/imagequiz_library/image/Mexico_states.png
Binary files differ
diff --git a/imagequiz_library/image/africa2.jpg b/imagequiz_library/image/africa2.jpg
new file mode 100755
index 0000000..e142a05
--- /dev/null
+++ b/imagequiz_library/image/africa2.jpg
Binary files differ
diff --git a/imagequiz_library/image/bears.jpg b/imagequiz_library/image/bears.jpg
new file mode 100755
index 0000000..2aa9c83
--- /dev/null
+++ b/imagequiz_library/image/bears.jpg
Binary files differ
diff --git a/imagequiz_library/image/different1.png b/imagequiz_library/image/different1.png
new file mode 100755
index 0000000..f2fcef9
--- /dev/null
+++ b/imagequiz_library/image/different1.png
Binary files differ
diff --git a/imagequiz_library/image/different2.png b/imagequiz_library/image/different2.png
new file mode 100755
index 0000000..aa18bf4
--- /dev/null
+++ b/imagequiz_library/image/different2.png
Binary files differ
diff --git a/imagequiz_library/image/different3.png b/imagequiz_library/image/different3.png
new file mode 100755
index 0000000..990173d
--- /dev/null
+++ b/imagequiz_library/image/different3.png
Binary files differ
diff --git a/imagequiz_library/image/different4.png b/imagequiz_library/image/different4.png
new file mode 100755
index 0000000..d5104da
--- /dev/null
+++ b/imagequiz_library/image/different4.png
Binary files differ
diff --git a/imagequiz_library/image/dogs.jpg b/imagequiz_library/image/dogs.jpg
new file mode 100755
index 0000000..b9596cd
--- /dev/null
+++ b/imagequiz_library/image/dogs.jpg
Binary files differ
diff --git a/imagequiz_library/image/europe.gif b/imagequiz_library/image/europe.gif
new file mode 100755
index 0000000..7f2c3f1
--- /dev/null
+++ b/imagequiz_library/image/europe.gif
Binary files differ
diff --git a/imagequiz_library/image/k0061.png b/imagequiz_library/image/k0061.png
new file mode 100755
index 0000000..8501779
--- /dev/null
+++ b/imagequiz_library/image/k0061.png
Binary files differ
diff --git a/imagequiz_library/image/k0168.png b/imagequiz_library/image/k0168.png
new file mode 100755
index 0000000..64c56e8
--- /dev/null
+++ b/imagequiz_library/image/k0168.png
Binary files differ
diff --git a/imagequiz_library/image/k0177.png b/imagequiz_library/image/k0177.png
new file mode 100755
index 0000000..993caab
--- /dev/null
+++ b/imagequiz_library/image/k0177.png
Binary files differ
diff --git a/imagequiz_library/image/k0286.png b/imagequiz_library/image/k0286.png
new file mode 100755
index 0000000..47a4259
--- /dev/null
+++ b/imagequiz_library/image/k0286.png
Binary files differ
diff --git a/imagequiz_library/image/k0670.png b/imagequiz_library/image/k0670.png
new file mode 100755
index 0000000..58c91f6
--- /dev/null
+++ b/imagequiz_library/image/k0670.png
Binary files differ
diff --git a/imagequiz_library/image/k0762.png b/imagequiz_library/image/k0762.png
new file mode 100755
index 0000000..2eb0497
--- /dev/null
+++ b/imagequiz_library/image/k0762.png
Binary files differ
diff --git a/imagequiz_library/image/k0820.png b/imagequiz_library/image/k0820.png
new file mode 100755
index 0000000..a115b9e
--- /dev/null
+++ b/imagequiz_library/image/k0820.png
Binary files differ
diff --git a/imagequiz_library/image/k0963.png b/imagequiz_library/image/k0963.png
new file mode 100755
index 0000000..8004ae9
--- /dev/null
+++ b/imagequiz_library/image/k0963.png
Binary files differ
diff --git a/imagequiz_library/image/k0993.png b/imagequiz_library/image/k0993.png
new file mode 100755
index 0000000..bab83f6
--- /dev/null
+++ b/imagequiz_library/image/k0993.png
Binary files differ
diff --git a/imagequiz_library/image/k1030.png b/imagequiz_library/image/k1030.png
new file mode 100755
index 0000000..818e0c5
--- /dev/null
+++ b/imagequiz_library/image/k1030.png
Binary files differ
diff --git a/imagequiz_library/image/k1094.png b/imagequiz_library/image/k1094.png
new file mode 100755
index 0000000..d5570fe
--- /dev/null
+++ b/imagequiz_library/image/k1094.png
Binary files differ
diff --git a/imagequiz_library/image/k1267.png b/imagequiz_library/image/k1267.png
new file mode 100755
index 0000000..d3008f6
--- /dev/null
+++ b/imagequiz_library/image/k1267.png
Binary files differ
diff --git a/imagequiz_library/image/k1391.png b/imagequiz_library/image/k1391.png
new file mode 100755
index 0000000..fe7363b
--- /dev/null
+++ b/imagequiz_library/image/k1391.png
Binary files differ
diff --git a/imagequiz_library/image/k1524.png b/imagequiz_library/image/k1524.png
new file mode 100755
index 0000000..921a80c
--- /dev/null
+++ b/imagequiz_library/image/k1524.png
Binary files differ
diff --git a/imagequiz_library/image/k1527.png b/imagequiz_library/image/k1527.png
new file mode 100755
index 0000000..661273e
--- /dev/null
+++ b/imagequiz_library/image/k1527.png
Binary files differ
diff --git a/imagequiz_library/image/k1702.png b/imagequiz_library/image/k1702.png
new file mode 100755
index 0000000..3556d39
--- /dev/null
+++ b/imagequiz_library/image/k1702.png
Binary files differ
diff --git a/imagequiz_library/image/k1809.png b/imagequiz_library/image/k1809.png
new file mode 100755
index 0000000..51a446c
--- /dev/null
+++ b/imagequiz_library/image/k1809.png
Binary files differ
diff --git a/imagequiz_library/image/k1873.png b/imagequiz_library/image/k1873.png
new file mode 100755
index 0000000..2db65f4
--- /dev/null
+++ b/imagequiz_library/image/k1873.png
Binary files differ
diff --git a/imagequiz_library/image/k1960.png b/imagequiz_library/image/k1960.png
new file mode 100755
index 0000000..5dbd171
--- /dev/null
+++ b/imagequiz_library/image/k1960.png
Binary files differ
diff --git a/imagequiz_library/image/k2006.png b/imagequiz_library/image/k2006.png
new file mode 100755
index 0000000..c7211c1
--- /dev/null
+++ b/imagequiz_library/image/k2006.png
Binary files differ
diff --git a/imagequiz_library/image/k2361.png b/imagequiz_library/image/k2361.png
new file mode 100755
index 0000000..78ef0c2
--- /dev/null
+++ b/imagequiz_library/image/k2361.png
Binary files differ
diff --git a/imagequiz_library/image/k2364.png b/imagequiz_library/image/k2364.png
new file mode 100755
index 0000000..edd0f93
--- /dev/null
+++ b/imagequiz_library/image/k2364.png
Binary files differ
diff --git a/imagequiz_library/image/k2372.png b/imagequiz_library/image/k2372.png
new file mode 100755
index 0000000..783a687
--- /dev/null
+++ b/imagequiz_library/image/k2372.png
Binary files differ
diff --git a/imagequiz_library/image/k2488.png b/imagequiz_library/image/k2488.png
new file mode 100755
index 0000000..4626577
--- /dev/null
+++ b/imagequiz_library/image/k2488.png
Binary files differ
diff --git a/imagequiz_library/image/k2532.png b/imagequiz_library/image/k2532.png
new file mode 100755
index 0000000..ae81ffd
--- /dev/null
+++ b/imagequiz_library/image/k2532.png
Binary files differ
diff --git a/imagequiz_library/image/k2579.png b/imagequiz_library/image/k2579.png
new file mode 100755
index 0000000..3bbf92e
--- /dev/null
+++ b/imagequiz_library/image/k2579.png
Binary files differ
diff --git a/imagequiz_library/image/k2621.png b/imagequiz_library/image/k2621.png
new file mode 100755
index 0000000..608766b
--- /dev/null
+++ b/imagequiz_library/image/k2621.png
Binary files differ
diff --git a/imagequiz_library/image/k2701.png b/imagequiz_library/image/k2701.png
new file mode 100755
index 0000000..644784d
--- /dev/null
+++ b/imagequiz_library/image/k2701.png
Binary files differ
diff --git a/imagequiz_library/image/k2702.png b/imagequiz_library/image/k2702.png
new file mode 100755
index 0000000..514b065
--- /dev/null
+++ b/imagequiz_library/image/k2702.png
Binary files differ
diff --git a/imagequiz_library/image/k2849.png b/imagequiz_library/image/k2849.png
new file mode 100755
index 0000000..c8f8093
--- /dev/null
+++ b/imagequiz_library/image/k2849.png
Binary files differ
diff --git a/imagequiz_library/image/k2889.png b/imagequiz_library/image/k2889.png
new file mode 100755
index 0000000..68b12be
--- /dev/null
+++ b/imagequiz_library/image/k2889.png
Binary files differ
diff --git a/imagequiz_library/image/k3184.png b/imagequiz_library/image/k3184.png
new file mode 100755
index 0000000..d893565
--- /dev/null
+++ b/imagequiz_library/image/k3184.png
Binary files differ
diff --git a/imagequiz_library/image/latinamerica.jpg b/imagequiz_library/image/latinamerica.jpg
new file mode 100755
index 0000000..9806453
--- /dev/null
+++ b/imagequiz_library/image/latinamerica.jpg
Binary files differ
diff --git a/imagequiz_library/image/olpc-xo-2.jpg b/imagequiz_library/image/olpc-xo-2.jpg
new file mode 100755
index 0000000..e353023
--- /dev/null
+++ b/imagequiz_library/image/olpc-xo-2.jpg
Binary files differ
diff --git a/imagequiz_library/image/planets_iau_big.jpg b/imagequiz_library/image/planets_iau_big.jpg
new file mode 100755
index 0000000..cce5dbe
--- /dev/null
+++ b/imagequiz_library/image/planets_iau_big.jpg
Binary files differ
diff --git a/imagequiz_library/image/xo.jpg b/imagequiz_library/image/xo.jpg
new file mode 100755
index 0000000..555e271
--- /dev/null
+++ b/imagequiz_library/image/xo.jpg
Binary files differ
diff --git a/imagequiz_library/rld001.xml b/imagequiz_library/rld001.xml
new file mode 100755
index 0000000..c15cf24
--- /dev/null
+++ b/imagequiz_library/rld001.xml
@@ -0,0 +1,103 @@
+<quiz last="25">
+ <card id="0">
+ <question>и<hint>МоÑква и Петербург</hint>
+ </question><answer>and<more />
+ </answer>
+ </card><card id="1">
+ <question>в/во<hint>в МоÑкве/в МоÑкву</hint>
+ </question><answer>in/(+pr)/into/to/(+a)<more />
+ </answer>
+ </card><card id="2">
+ <question>не<hint>Он не в МоÑкве</hint>
+ </question><answer>not<more />
+ </answer>
+ </card><card id="3">
+ <question>на<hint>на работе/на Ñтол</hint>
+ </question><answer>on/at/(+pr)/onto/to/(+a)<more />
+ </answer>
+ </card><card id="4">
+ <question>Ñ/Я<hint>Ñ Ð³Ð¾Ð²Ð¾Ñ€ÑŽ</hint>
+ </question><answer>I<more />
+ </answer>
+ </card><card id="5">
+ <question>он<hint>Он гоборнт</hint>
+ </question><answer>he<more />
+ </answer>
+ </card><card id="6">
+ <question>что<hint>Что ето? Я гоборю, что он на работе</hint>
+ </question><answer>what/that<more />
+ </answer>
+ </card><card id="7">
+ <question>Ñ/Ñо<hint>Чай Ñ Ð»Ð¸Ð¼Ð¾Ð½Ð¾Ð¼/Ñо Ñтола</hint>
+ </question><answer>with/(+inst)/from/off(+g)<more />
+ </answer>
+ </card><card id="8">
+ <question>Ñто<hint>Это наш клуб; Это верно</hint>
+ </question><answer>this/that/it<more />
+ </answer>
+ </card><card id="9">
+ <question>быть/еÑÑ‚ÑŒ<hint>Быть нлн не блыт? / ЕÑÑ‚ кофе?</hint>
+ </question><answer>to be/there is/there are<more />
+ </answer>
+ </card><card id="10">
+ <question>а<hint>Она в МоÑкве, а он в Петербурге</hint>
+ </question><answer>and/but/(slight contrast)<more />
+ </answer>
+ </card><card id="11">
+ <question>веÑÑŒ/вÑÑ/вÑÑ‘/вÑе<hint>веÑÑŒ Ñтол / вÑÑ ÐœÐ¾Ñква</hint>
+ </question><answer>all<more />
+ </answer>
+ </card><card id="12">
+ <question>они<hint>Они в МоÑкве</hint>
+ </question><answer>they<more />
+ </answer>
+ </card><card id="13">
+ <question>она<hint>Она Ñо мной</hint>
+ </question><answer>she<more />
+ </answer>
+ </card><card id="14">
+ <question>как<hint>Как он говорйт? как Ñ, как Ñ‚Ñ‹</hint>
+ </question><answer>how/as/like<more />
+ </answer>
+ </card><card id="15">
+ <question>мы<hint>Мы выли в Петербурге</hint>
+ </question><answer>we<more />
+ </answer>
+ </card><card id="16">
+ <question>к/ко<hint>к дому; ко мне</hint>
+ </question><answer>towards/to/(+d)<more />
+ </answer>
+ </card><card id="17">
+ <question>у<hint>у окна; у Ðвана; у Ðвана еÑÑ‚ÑŒ дом</hint>
+ </question><answer>by/at/(has)<more />
+ </answer>
+ </card><card id="18">
+ <question>вы<hint>Вы говорнте</hint>
+ </question><answer>you/(polite/pl)<more />
+ </answer>
+ </card><card id="19">
+ <question>Ñтот/Ñта/Ñто/Ñти<hint>Ñтот Ñтол, Ñта книга, Ñти люди</hint>
+ </question><answer>this<more />
+ </answer>
+ </card><card id="20">
+ <question>за<hint>платить за водку / за домом</hint>
+ </question><answer>beyond/for/(+a)/behind/ (+inst)<more />
+ </answer>
+ </card><card id="21">
+ <question>тот/та/то/те<hint>тот дом; в то времÑ; ...то, что ...</hint>
+ </question><answer>that<more />
+ </answer>
+ </card><card id="22">
+ <question>но<hint>Ðо Ñто не правда</hint>
+ </question><answer>but<more />
+ </answer>
+ </card><card id="23">
+ <question>ты<hint>Ты говоришь</hint>
+ </question><answer>you/(familiar)<more />
+ </answer>
+ </card><card id="24">
+ <question>по<hint>по уличе; по городу; по плану</hint>
+ </question><answer>along/around/according to/(+d)<more />
+ </answer>
+ </card>
+</quiz> \ No newline at end of file
diff --git a/imagequiz_library/sound/k0061.ogg b/imagequiz_library/sound/k0061.ogg
new file mode 100755
index 0000000..bddfc30
--- /dev/null
+++ b/imagequiz_library/sound/k0061.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0168.ogg b/imagequiz_library/sound/k0168.ogg
new file mode 100755
index 0000000..0d58774
--- /dev/null
+++ b/imagequiz_library/sound/k0168.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0177.ogg b/imagequiz_library/sound/k0177.ogg
new file mode 100755
index 0000000..b92031b
--- /dev/null
+++ b/imagequiz_library/sound/k0177.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0286.ogg b/imagequiz_library/sound/k0286.ogg
new file mode 100755
index 0000000..7e5bc36
--- /dev/null
+++ b/imagequiz_library/sound/k0286.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0670.ogg b/imagequiz_library/sound/k0670.ogg
new file mode 100755
index 0000000..b2942fe
--- /dev/null
+++ b/imagequiz_library/sound/k0670.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0762.ogg b/imagequiz_library/sound/k0762.ogg
new file mode 100755
index 0000000..179cb73
--- /dev/null
+++ b/imagequiz_library/sound/k0762.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0820.ogg b/imagequiz_library/sound/k0820.ogg
new file mode 100755
index 0000000..25bd363
--- /dev/null
+++ b/imagequiz_library/sound/k0820.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0963.ogg b/imagequiz_library/sound/k0963.ogg
new file mode 100755
index 0000000..772b0d7
--- /dev/null
+++ b/imagequiz_library/sound/k0963.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k0993.ogg b/imagequiz_library/sound/k0993.ogg
new file mode 100755
index 0000000..498c1fc
--- /dev/null
+++ b/imagequiz_library/sound/k0993.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1030.ogg b/imagequiz_library/sound/k1030.ogg
new file mode 100755
index 0000000..2d0bc0c
--- /dev/null
+++ b/imagequiz_library/sound/k1030.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1094.ogg b/imagequiz_library/sound/k1094.ogg
new file mode 100755
index 0000000..2473cde
--- /dev/null
+++ b/imagequiz_library/sound/k1094.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1267.ogg b/imagequiz_library/sound/k1267.ogg
new file mode 100755
index 0000000..d737df9
--- /dev/null
+++ b/imagequiz_library/sound/k1267.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1391.ogg b/imagequiz_library/sound/k1391.ogg
new file mode 100755
index 0000000..3762fae
--- /dev/null
+++ b/imagequiz_library/sound/k1391.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1391r.ogg b/imagequiz_library/sound/k1391r.ogg
new file mode 100755
index 0000000..5170002
--- /dev/null
+++ b/imagequiz_library/sound/k1391r.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1391r1.ogg b/imagequiz_library/sound/k1391r1.ogg
new file mode 100755
index 0000000..0f0f99a
--- /dev/null
+++ b/imagequiz_library/sound/k1391r1.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1524.ogg b/imagequiz_library/sound/k1524.ogg
new file mode 100755
index 0000000..957551e
--- /dev/null
+++ b/imagequiz_library/sound/k1524.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1527.ogg b/imagequiz_library/sound/k1527.ogg
new file mode 100755
index 0000000..c1dc049
--- /dev/null
+++ b/imagequiz_library/sound/k1527.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1702.ogg b/imagequiz_library/sound/k1702.ogg
new file mode 100755
index 0000000..70ae65f
--- /dev/null
+++ b/imagequiz_library/sound/k1702.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1809.ogg b/imagequiz_library/sound/k1809.ogg
new file mode 100755
index 0000000..fda4fd9
--- /dev/null
+++ b/imagequiz_library/sound/k1809.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1873.ogg b/imagequiz_library/sound/k1873.ogg
new file mode 100755
index 0000000..5d0966d
--- /dev/null
+++ b/imagequiz_library/sound/k1873.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k1960.ogg b/imagequiz_library/sound/k1960.ogg
new file mode 100755
index 0000000..15a5cd2
--- /dev/null
+++ b/imagequiz_library/sound/k1960.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2006.ogg b/imagequiz_library/sound/k2006.ogg
new file mode 100755
index 0000000..4f46579
--- /dev/null
+++ b/imagequiz_library/sound/k2006.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2361.ogg b/imagequiz_library/sound/k2361.ogg
new file mode 100755
index 0000000..a6e2a62
--- /dev/null
+++ b/imagequiz_library/sound/k2361.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2364.ogg b/imagequiz_library/sound/k2364.ogg
new file mode 100755
index 0000000..202d7a4
--- /dev/null
+++ b/imagequiz_library/sound/k2364.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2372.ogg b/imagequiz_library/sound/k2372.ogg
new file mode 100755
index 0000000..43b3917
--- /dev/null
+++ b/imagequiz_library/sound/k2372.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2488.ogg b/imagequiz_library/sound/k2488.ogg
new file mode 100755
index 0000000..9cd1bb7
--- /dev/null
+++ b/imagequiz_library/sound/k2488.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2532.ogg b/imagequiz_library/sound/k2532.ogg
new file mode 100755
index 0000000..d3241a3
--- /dev/null
+++ b/imagequiz_library/sound/k2532.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2579.ogg b/imagequiz_library/sound/k2579.ogg
new file mode 100755
index 0000000..a92dc16
--- /dev/null
+++ b/imagequiz_library/sound/k2579.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2621.ogg b/imagequiz_library/sound/k2621.ogg
new file mode 100755
index 0000000..03a6065
--- /dev/null
+++ b/imagequiz_library/sound/k2621.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2701.ogg b/imagequiz_library/sound/k2701.ogg
new file mode 100755
index 0000000..f647672
--- /dev/null
+++ b/imagequiz_library/sound/k2701.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2702.ogg b/imagequiz_library/sound/k2702.ogg
new file mode 100755
index 0000000..04df342
--- /dev/null
+++ b/imagequiz_library/sound/k2702.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2849.ogg b/imagequiz_library/sound/k2849.ogg
new file mode 100755
index 0000000..5ddad74
--- /dev/null
+++ b/imagequiz_library/sound/k2849.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k2889.ogg b/imagequiz_library/sound/k2889.ogg
new file mode 100755
index 0000000..ae28c65
--- /dev/null
+++ b/imagequiz_library/sound/k2889.ogg
Binary files differ
diff --git a/imagequiz_library/sound/k3184.ogg b/imagequiz_library/sound/k3184.ogg
new file mode 100755
index 0000000..8426894
--- /dev/null
+++ b/imagequiz_library/sound/k3184.ogg
Binary files differ
diff --git a/images/Africa/africa2.jpg b/images/Africa/africa2.jpg
new file mode 100755
index 0000000..e142a05
--- /dev/null
+++ b/images/Africa/africa2.jpg
Binary files differ
diff --git a/images/Astronomy/planets_iau_big.jpg b/images/Astronomy/planets_iau_big.jpg
new file mode 100755
index 0000000..cce5dbe
--- /dev/null
+++ b/images/Astronomy/planets_iau_big.jpg
Binary files differ
diff --git a/images/Bears/bears.jpg b/images/Bears/bears.jpg
new file mode 100755
index 0000000..2aa9c83
--- /dev/null
+++ b/images/Bears/bears.jpg
Binary files differ
diff --git a/images/Cars_&_Motorbikes/5104352.jpg b/images/Cars_&_Motorbikes/5104352.jpg
new file mode 100755
index 0000000..6fb1b7b
--- /dev/null
+++ b/images/Cars_&_Motorbikes/5104352.jpg
Binary files differ
diff --git a/images/Difference_Game/different1.png b/images/Difference_Game/different1.png
new file mode 100755
index 0000000..f2fcef9
--- /dev/null
+++ b/images/Difference_Game/different1.png
Binary files differ
diff --git a/images/Difference_Game/different2.png b/images/Difference_Game/different2.png
new file mode 100755
index 0000000..aa18bf4
--- /dev/null
+++ b/images/Difference_Game/different2.png
Binary files differ
diff --git a/images/Difference_Game/different3.png b/images/Difference_Game/different3.png
new file mode 100755
index 0000000..990173d
--- /dev/null
+++ b/images/Difference_Game/different3.png
Binary files differ
diff --git a/images/Difference_Game/different4.png b/images/Difference_Game/different4.png
new file mode 100755
index 0000000..d5104da
--- /dev/null
+++ b/images/Difference_Game/different4.png
Binary files differ
diff --git a/images/Dogs/dogs.jpg b/images/Dogs/dogs.jpg
new file mode 100755
index 0000000..b9596cd
--- /dev/null
+++ b/images/Dogs/dogs.jpg
Binary files differ
diff --git a/images/Emotes/face-crying.png b/images/Emotes/face-crying.png
new file mode 100755
index 0000000..eabc23c
--- /dev/null
+++ b/images/Emotes/face-crying.png
Binary files differ
diff --git a/images/Emotes/face-devil-grin.png b/images/Emotes/face-devil-grin.png
new file mode 100755
index 0000000..ef63685
--- /dev/null
+++ b/images/Emotes/face-devil-grin.png
Binary files differ
diff --git a/images/Emotes/face-devil-grin_24px.png b/images/Emotes/face-devil-grin_24px.png
new file mode 100755
index 0000000..bd38dfc
--- /dev/null
+++ b/images/Emotes/face-devil-grin_24px.png
Binary files differ
diff --git a/images/Emotes/face-glasses.png b/images/Emotes/face-glasses.png
new file mode 100755
index 0000000..1e865c5
--- /dev/null
+++ b/images/Emotes/face-glasses.png
Binary files differ
diff --git a/images/Emotes/face-grin.png b/images/Emotes/face-grin.png
new file mode 100755
index 0000000..b7f49b9
--- /dev/null
+++ b/images/Emotes/face-grin.png
Binary files differ
diff --git a/images/Emotes/face-grin_24px.png b/images/Emotes/face-grin_24px.png
new file mode 100755
index 0000000..4335cfc
--- /dev/null
+++ b/images/Emotes/face-grin_24px.png
Binary files differ
diff --git a/images/Emotes/face-monkey.png b/images/Emotes/face-monkey.png
new file mode 100755
index 0000000..fb0204a
--- /dev/null
+++ b/images/Emotes/face-monkey.png
Binary files differ
diff --git a/images/Europe/800px-Regions_of_Europe.png b/images/Europe/800px-Regions_of_Europe.png
new file mode 100755
index 0000000..15457c5
--- /dev/null
+++ b/images/Europe/800px-Regions_of_Europe.png
Binary files differ
diff --git a/images/Europe/europe.gif b/images/Europe/europe.gif
new file mode 100755
index 0000000..7f2c3f1
--- /dev/null
+++ b/images/Europe/europe.gif
Binary files differ
diff --git a/images/Geography/latinamerica.jpg b/images/Geography/latinamerica.jpg
new file mode 100755
index 0000000..9806453
--- /dev/null
+++ b/images/Geography/latinamerica.jpg
Binary files differ
diff --git a/images/Icons/bulb.png b/images/Icons/bulb.png
new file mode 100755
index 0000000..2ac5747
--- /dev/null
+++ b/images/Icons/bulb.png
Binary files differ
diff --git a/images/Icons/no1.png b/images/Icons/no1.png
new file mode 100755
index 0000000..f63ce5d
--- /dev/null
+++ b/images/Icons/no1.png
Binary files differ
diff --git a/images/Icons/no2.png b/images/Icons/no2.png
new file mode 100755
index 0000000..c668a3a
--- /dev/null
+++ b/images/Icons/no2.png
Binary files differ
diff --git a/images/Icons/ok1.png b/images/Icons/ok1.png
new file mode 100755
index 0000000..b4e118f
--- /dev/null
+++ b/images/Icons/ok1.png
Binary files differ
diff --git a/images/Icons/ok2.png b/images/Icons/ok2.png
new file mode 100755
index 0000000..d1a052b
--- /dev/null
+++ b/images/Icons/ok2.png
Binary files differ
diff --git a/images/United_Mexican_States/Mexico_states.png b/images/United_Mexican_States/Mexico_states.png
new file mode 100755
index 0000000..635b4ad
--- /dev/null
+++ b/images/United_Mexican_States/Mexico_states.png
Binary files differ
diff --git a/images/XO_Laptop/olpc-xo-2.jpg b/images/XO_Laptop/olpc-xo-2.jpg
new file mode 100755
index 0000000..e353023
--- /dev/null
+++ b/images/XO_Laptop/olpc-xo-2.jpg
Binary files differ
diff --git a/images/XO_Laptop/xo.jpg b/images/XO_Laptop/xo.jpg
new file mode 100755
index 0000000..555e271
--- /dev/null
+++ b/images/XO_Laptop/xo.jpg
Binary files differ
diff --git a/images/bubble.gif b/images/bubble.gif
new file mode 100755
index 0000000..435c46b
--- /dev/null
+++ b/images/bubble.gif
Binary files differ
diff --git a/images/cursor_big.png b/images/cursor_big.png
new file mode 100755
index 0000000..94e8638
--- /dev/null
+++ b/images/cursor_big.png
Binary files differ
diff --git a/images/cursor_big.xbm b/images/cursor_big.xbm
new file mode 100755
index 0000000..5c9e229
--- /dev/null
+++ b/images/cursor_big.xbm
@@ -0,0 +1,20 @@
+#define cursor_big_width 40
+#define cursor_big_height 40
+static unsigned char cursor_big_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0x7f, 0x00, 0xf8, 0xff, 0xff, 0xff,
+ 0x00, 0xf8, 0xff, 0xff, 0xff, 0x01, 0xf8, 0xff, 0xff, 0xff, 0x01, 0xf8,
+ 0xff, 0xff, 0xff, 0x01, 0xf8, 0xff, 0xff, 0xff, 0x01, 0xf8, 0xff, 0xff,
+ 0xff, 0x00, 0xf8, 0xff, 0xff, 0x7f, 0x00, 0xf8, 0xff, 0x01, 0x00, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0x00, 0xf8, 0xff, 0x07, 0x00, 0x00, 0xf8, 0xff,
+ 0x0f, 0x00, 0x00, 0xf8, 0xff, 0x1f, 0x00, 0x00, 0xf8, 0xff, 0x3f, 0x00,
+ 0x00, 0xf8, 0xf7, 0x7f, 0x00, 0x00, 0xf8, 0xe7, 0xff, 0x00, 0x00, 0xf8,
+ 0xc7, 0xff, 0x01, 0x00, 0xf8, 0x87, 0xff, 0x03, 0x00, 0xf8, 0x07, 0xff,
+ 0x07, 0x00, 0xf8, 0x07, 0xfe, 0x0f, 0x00, 0xf8, 0x07, 0xfc, 0x1f, 0x00,
+ 0xf8, 0x07, 0xf8, 0x3f, 0x00, 0xf8, 0x07, 0xf0, 0x7f, 0x00, 0xf8, 0x07,
+ 0xe0, 0xff, 0x00, 0xf8, 0x07, 0xc0, 0xff, 0x01, 0xf8, 0x07, 0x80, 0xff,
+ 0x03, 0xf8, 0x07, 0x00, 0xff, 0x03, 0xf8, 0x07, 0x00, 0xfe, 0x07, 0xf0,
+ 0x03, 0x00, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x00,
+ 0xf0, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
diff --git a/images/cursor_big_mask.xbm b/images/cursor_big_mask.xbm
new file mode 100755
index 0000000..cc9d996
--- /dev/null
+++ b/images/cursor_big_mask.xbm
@@ -0,0 +1,17 @@
+#define cursor_big_width 40
+#define cursor_big_height 40
+static unsigned char cursor_big_bits[] = {
+ 0xff,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0x01,0xff,0xff,0xff,0xff,0x03,
+ 0xff,0xff,0xff,0xff,0x07,0xff,0xff,0xff,0xff,0x0f,0xff,0xff,0xff,0xff,0x0f,
+ 0xff,0xff,0xff,0xff,0x0f,0xff,0xff,0xff,0xff,0x0f,0xff,0xff,0xff,0xff,0x0f,
+ 0xff,0xff,0xff,0xff,0x0f,0xff,0xff,0xff,0xff,0x07,0xff,0xff,0xff,0xff,0x03,
+ 0xff,0xff,0xff,0xff,0x01,0xff,0xff,0xff,0xff,0x01,0xff,0xff,0xff,0x00,0x00,
+ 0xff,0xff,0xff,0x01,0x00,0xff,0xff,0xff,0x03,0x00,0xff,0xff,0xff,0x07,0x00,
+ 0xff,0xff,0xff,0x0f,0x00,0xff,0xff,0xff,0x1f,0x00,0xff,0xff,0xff,0x3f,0x00,
+ 0xff,0xff,0xff,0x7f,0x00,0xff,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0x01,
+ 0xff,0xbf,0xff,0xff,0x03,0xff,0x3f,0xff,0xff,0x07,0xff,0x3f,0xfe,0xff,0x0f,
+ 0xff,0x3f,0xfc,0xff,0x1f,0xff,0x3f,0xf8,0xff,0x1f,0xff,0x3f,0xf0,0xff,0x1f,
+ 0xff,0x3f,0xe0,0xff,0x1f,0xff,0x3f,0xc0,0xff,0x1f,0xff,0x3f,0x80,0xff,0x1f,
+ 0xfe,0x1f,0x00,0xff,0x1f,0xfc,0x0f,0x00,0xfe,0x1f,0xf8,0x07,0x00,0xfc,0x0f,
+ 0x00,0x00,0x00,0xf8,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00};
diff --git a/images/logo.jpg b/images/logo.jpg
new file mode 100755
index 0000000..3eea6e8
--- /dev/null
+++ b/images/logo.jpg
Binary files differ
diff --git a/images/ooops.png b/images/ooops.png
new file mode 100755
index 0000000..c03e392
--- /dev/null
+++ b/images/ooops.png
Binary files differ
diff --git a/images/play.png b/images/play.png
new file mode 100755
index 0000000..4f9799a
--- /dev/null
+++ b/images/play.png
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="60.0000000"
+ id="svg1"
+ inkscape:version="0.46"
+ sodipodi:docbase="/home/danny/flat/scalable/actions"
+ sodipodi:docname="play.png"
+ sodipodi:version="0.32"
+ version="1.0"
+ width="60.0000000"
+ x="0"
+ y="0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata3">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:title>Part of the Flat Icon Collection (Wed Aug 25 23:29:46 2004)</dc:title>
+ <dc:description />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>hash</rdf:li>
+ <rdf:li />
+ <rdf:li>action</rdf:li>
+ <rdf:li>computer</rdf:li>
+ <rdf:li>icons</rdf:li>
+ <rdf:li>theme</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:publisher>
+ <cc:Agent
+ rdf:about="http://www.openclipart.org">
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <dc:date />
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://web.resource.org/cc/PublicDomain" />
+ <dc:language>en</dc:language>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://web.resource.org/cc/PublicDomain">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ bordercolor="#666666"
+ borderopacity="1.0"
+ id="base"
+ inkscape:cx="46.730748"
+ inkscape:cy="29.671475"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:window-height="722"
+ inkscape:window-width="1016"
+ inkscape:window-x="5"
+ inkscape:window-y="25"
+ inkscape:zoom="9.8238822"
+ pagecolor="#ffffff"
+ showgrid="false"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 30 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="60 : 30 : 1"
+ inkscape:persp3d-origin="30 : 20 : 1"
+ id="perspective8" />
+ </defs>
+ <path
+ d="M 4.6891408,30.424115 C 4.8581808,44.527979 16.451924,55.816216 30.555786,55.647175 C 44.659651,55.478133 55.948263,43.915641 55.779221,29.811776 C 55.610179,15.707912 44.047312,4.388052 29.943448,4.5570947 C 15.839584,4.7261363 4.5200988,16.320252 4.6891408,30.424115 z M 25.317138,48.803216 L 25.153473,35.147947 L 25.14551,24.053496 L 24.990085,11.085677 L 44.026319,29.640118 L 25.317138,48.803216 z"
+ id="path612"
+ sodipodi:stroke-cmyk="(0 0 0 0.8)"
+ style="font-size:12px;fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" />
+</svg>
diff --git a/images/play.svg b/images/play.svg
new file mode 100755
index 0000000..28c8388
--- /dev/null
+++ b/images/play.svg
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="60.0000000"
+ id="svg1"
+ inkscape:version="0.46"
+ sodipodi:docbase="/home/danny/flat/scalable/actions"
+ sodipodi:docname="play.svg"
+ sodipodi:version="0.32"
+ version="1.0"
+ width="60.0000000"
+ x="0"
+ y="0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <metadata
+ id="metadata3">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:title>Part of the Flat Icon Collection (Wed Aug 25 23:29:46 2004)</dc:title>
+ <dc:description />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>hash</rdf:li>
+ <rdf:li />
+ <rdf:li>action</rdf:li>
+ <rdf:li>computer</rdf:li>
+ <rdf:li>icons</rdf:li>
+ <rdf:li>theme</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:publisher>
+ <cc:Agent
+ rdf:about="http://www.openclipart.org">
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Danny Allen</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <dc:date />
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://web.resource.org/cc/PublicDomain" />
+ <dc:language>en</dc:language>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://web.resource.org/cc/PublicDomain">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ bordercolor="#666666"
+ borderopacity="1.0"
+ id="base"
+ inkscape:cx="46.730748"
+ inkscape:cy="29.671475"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:window-height="722"
+ inkscape:window-width="1016"
+ inkscape:window-x="5"
+ inkscape:window-y="25"
+ inkscape:zoom="9.8238822"
+ pagecolor="#ffffff"
+ showgrid="false"
+ inkscape:current-layer="svg1" />
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 30 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="60 : 30 : 1"
+ inkscape:persp3d-origin="30 : 20 : 1"
+ id="perspective8" />
+ </defs>
+ <path
+ d="M 4.6891408,30.424115 C 4.8581808,44.527979 16.451924,55.816216 30.555786,55.647175 C 44.659651,55.478133 55.948263,43.915641 55.779221,29.811776 C 55.610179,15.707912 44.047312,4.388052 29.943448,4.5570947 C 15.839584,4.7261363 4.5200988,16.320252 4.6891408,30.424115 z M 25.317138,48.803216 L 25.153473,35.147947 L 25.14551,24.053496 L 24.990085,11.085677 L 44.026319,29.640118 L 25.317138,48.803216 z"
+ id="path612"
+ sodipodi:stroke-cmyk="(0 0 0 0.8)"
+ style="font-size:12px;fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:#333333;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" />
+</svg>
diff --git a/images/play_button.png b/images/play_button.png
new file mode 100755
index 0000000..1ffcdf3
--- /dev/null
+++ b/images/play_button.png
Binary files differ
diff --git a/ink.py b/ink.py
new file mode 100755
index 0000000..544bc78
--- /dev/null
+++ b/ink.py
@@ -0,0 +1,61 @@
+# ink.py
+#
+# B. Mayton <bmayton@cs.washington.edu>
+#
+# 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.
+#
+# -*- mode:python; tab-width:4; indent-tabs-mode:t; -*-
+
+# 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import random
+import logging
+
+class Path:
+
+ def __init__(self, inkstr=None):
+ self.__logger = logging.getLogger('Path')
+ self.points=[]
+ self.color = (0,0,1.0)
+ self.pen = 4
+ self.uid = random.randint(0, 2147483647)
+ if inkstr:
+ try:
+ i=0
+ parts = inkstr.split('#')
+ if len(parts) > 1:
+ params = parts[i].split(';')
+ self.uid = int(params[0])
+ colorparts = params[1].split(',')
+ self.color = (float(colorparts[0]),float(colorparts[1]),float(colorparts[2]))
+ self.pen = float(params[2])
+ i = i + 1
+ pathstr = parts[i]
+ pointstrs = pathstr.split(';')
+ for pointstr in pointstrs:
+ pparts = pointstr.split(',')
+ if len(pparts) == 2:
+ self.add((int(pparts[0]), int(pparts[1])))
+ except Exception, e:
+ self.__logger.debug('Could not unserialize ink string (old ink?)')
+
+ def add(self, point):
+ self.points.append(point)
+
+ def __str__(self):
+ s = str(self.uid) + ";"
+ s = s + str(self.color[0]) + "," + str(self.color[1]) + "," + str(self.color[2]) + ";"
+ s = s + str(self.pen) + "#"
+ for p in self.points:
+ s = s + str(int(p[0])) + "," + str(int(p[1])) + ";"
+ return s
diff --git a/layout.py b/layout.py
new file mode 100755
index 0000000..acf52bb
--- /dev/null
+++ b/layout.py
@@ -0,0 +1,146 @@
+''' Overall Frontend Layout'''
+class Colors:
+ grey = '#efefef'
+ green = '#75b844'
+ lime = '#cef474'
+ yellow = '#ffff33'
+ blue = '#94b0b4'
+ black = '#000000'
+ white = '#ffffff'
+
+class TextFormat:
+ color = Colors().white
+ size = 36
+ background = None
+ def __init__(self, color=None, size=None, background=None):
+ if color != None: self.color = color
+ if size != None: self.size = size
+ if background != None: self.background = background
+
+
+class Layout:
+ user_name = "chris"
+ port = 50123
+
+ background = Colors().black;
+
+ class Question:
+ x = 20
+ y = 20
+ width = 860
+ height = 650
+ border = '3px ' + Colors().green;
+ background = Colors().black;
+ class Text:
+ x = 20
+ y = 12
+ size = 46
+ color = Colors().green;
+ class Image:
+ x = 10
+ y = 60
+
+ class MenuTop:
+ x = 900
+ y = 20
+ width = 280
+ height = 50
+ border = '2px ' + Colors().blue
+ class Text:
+ x = 20
+ y = 10
+ size = 46
+ color = Colors().green
+ text = 'Menu'
+
+ class MenuMain:
+ x = 900
+ y = 80
+ width = 280
+ height = 590
+ border = '2px ' + Colors().blue
+ background = Colors().black
+ class Button:
+ x = 9
+ y = 10
+ width = 260
+ height = 40
+ border = '2px ' + Colors().green
+ background = Colors().black
+ class Text:
+ x = 20
+ y = 12
+ size = 30
+ color = Colors().green;
+ class Button_Directory:
+ border = '2px ' + Colors().blue
+ background = Colors().black
+ class Text:
+ size = 30
+ color = Colors().green
+
+ class Toolbar:
+ x = 20
+ y = 690
+ width = 1160
+ height = 100
+ border = '2px ' + Colors().blue;
+ background = Colors().black;
+ class Text:
+ size = 26
+ color = Colors().white;
+
+ class FoundLine:
+ x = 30
+ y = 18
+ width = 400
+ height = 24
+ padding = 4
+ image = "images/Emotes/face-grin_24px.png"
+ image_width = 34
+ image_left = 0
+ class Outside:
+ border = "1px " + Colors().blue
+ background = Colors().black
+ class Inside:
+ border = "0px " + Colors().blue
+ background = Colors().green
+ class NotFoundLine:
+ x = 30
+ y = 52
+ width = 400
+ height = 24
+ padding = 4
+ image = "images/Emotes/face-devil-grin_24px.png"
+ image_width = 34
+ image_left = 2
+ class Outside:
+ border = "1px " + Colors().blue
+ background = Colors().black
+ class Inside:
+ border = "0px " + Colors().blue
+ background = Colors().blue
+
+ class Polygon:
+ color = Colors().yellow;
+
+ class TextInput:
+ class Box:
+ x = 350
+ y = 140
+ width = 340
+ height = 36
+ background = Colors().black
+ border = '2px ' + Colors().white
+ class Text:
+ size = 36
+ color = Colors().white
+
+
+ class QuestionEntry:
+ class Box:
+ background = Colors().black
+ border = '2px ' + Colors().white
+ class Text:
+ size = 30
+ color = Colors().white \ No newline at end of file
diff --git a/locale/de/activity.linfo b/locale/de/activity.linfo
new file mode 100755
index 0000000..860d9ab
--- /dev/null
+++ b/locale/de/activity.linfo
@@ -0,0 +1,2 @@
+[Activity]
+name = ImageQuiz
diff --git a/manifest b/manifest
new file mode 100755
index 0000000..efb34b6
--- /dev/null
+++ b/manifest
@@ -0,0 +1,159 @@
+ImageQuizPlus.activity/
+ImageQuizPlus.activity/plugins
+ImageQuizPlus.activity/plugins/questions.py
+ImageQuizPlus.activity/plugins/single_player.py
+ImageQuizPlus.activity/plugins/tools.py
+ImageQuizPlus.activity/plugins/demoplugin.py
+ImageQuizPlus.activity/plugins/multi_player.py
+ImageQuizPlus.activity/plugins/README
+ImageQuizPlus.activity/plugins/_quizsocket.py
+ImageQuizPlus.activity/plugins/flashcard.py
+ImageQuizPlus.activity/db
+ImageQuizPlus.activity/db/main.db
+ImageQuizPlus.activity/db/Geography.csv
+ImageQuizPlus.activity/db/Animals.csv
+ImageQuizPlus.activity/db/Arts.csv
+ImageQuizPlus.activity/db/Cars_&_Motorbikes.csv
+ImageQuizPlus.activity/db/Africa.csv
+ImageQuizPlus.activity/db/Europe.csv
+ImageQuizPlus.activity/db/XO_Laptop.csv
+ImageQuizPlus.activity/db/Astronomy.csv
+ImageQuizPlus.activity/db/Dogs.csv
+ImageQuizPlus.activity/db/Bears.csv
+ImageQuizPlus.activity/activity
+ImageQuizPlus.activity/activity/activity-ImageQuizPlus.svg
+ImageQuizPlus.activity/activity/activity.info
+ImageQuizPlus.activity/locale
+ImageQuizPlus.activity/locale/de
+ImageQuizPlus.activity/locale/de/activity.linfo
+ImageQuizPlus.activity/images
+ImageQuizPlus.activity/images/Animals
+ImageQuizPlus.activity/images/Bears
+ImageQuizPlus.activity/images/Bears/bears.jpg
+ImageQuizPlus.activity/images/Dogs
+ImageQuizPlus.activity/images/Dogs/dogs.jpg
+ImageQuizPlus.activity/images/Africa
+ImageQuizPlus.activity/images/Africa/africa2.jpg
+ImageQuizPlus.activity/images/bubble.gif
+ImageQuizPlus.activity/images/logo.jpg
+ImageQuizPlus.activity/images/cursor_big.png
+ImageQuizPlus.activity/images/cursor_big.xbm
+ImageQuizPlus.activity/images/cursor_big_mask.xbm
+ImageQuizPlus.activity/images/Icons
+ImageQuizPlus.activity/images/Icons/no1.png
+ImageQuizPlus.activity/images/Icons/no2.png
+ImageQuizPlus.activity/images/Icons/ok1.png
+ImageQuizPlus.activity/images/Icons/ok2.png
+ImageQuizPlus.activity/images/Icons/bulb.png
+ImageQuizPlus.activity/images/Emotes
+ImageQuizPlus.activity/images/Emotes/face-crying.png
+ImageQuizPlus.activity/images/Emotes/face-devil-grin.png
+ImageQuizPlus.activity/images/Emotes/face-glasses.png
+ImageQuizPlus.activity/images/Emotes/face-grin.png
+ImageQuizPlus.activity/images/Emotes/face-monkey.png
+ImageQuizPlus.activity/images/Emotes/face-devil-grin_24px.png
+ImageQuizPlus.activity/images/Emotes/face-grin_24px.png
+ImageQuizPlus.activity/images/Cars_&_Motorbikes
+ImageQuizPlus.activity/images/Cars_&_Motorbikes/5104352.JPG
+ImageQuizPlus.activity/images/xclock.svg
+ImageQuizPlus.activity/images/xclock.png
+ImageQuizPlus.activity/images/Astronomy
+ImageQuizPlus.activity/images/Astronomy/planets_iau_big.jpg
+ImageQuizPlus.activity/images/Geography
+ImageQuizPlus.activity/images/Geography/latinamerica.jpg
+ImageQuizPlus.activity/images/Europe
+ImageQuizPlus.activity/images/Europe/europe.gif
+ImageQuizPlus.activity/images/Europe/800px-Regions_of_Europe.png
+ImageQuizPlus.activity/images/XO_Laptop
+ImageQuizPlus.activity/images/XO_Laptop/xo.jpg
+ImageQuizPlus.activity/images/XO_Laptop/olpc-xo-2.jpg
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.5.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.7.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.3.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.4.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.3.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.5.2.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.4.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.4.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.5.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.7.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.2.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.5.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.2.6.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.4.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.3.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.5.2.1.xml
+ImageQuizPlus.activity/flashcards/french/BFV1.1.1.6.xml
+ImageQuizPlus.activity/flashcards/russian/RLD524.xml
+ImageQuizPlus.activity/flashcards/russian/RLD053.xml
+ImageQuizPlus.activity/flashcards/russian/RLD697.xml
+ImageQuizPlus.activity/flashcards/russian/RLD261.xml
+ImageQuizPlus.activity/flashcards/russian/RLD551.xml
+ImageQuizPlus.activity/flashcards/russian/RLD180.xml
+ImageQuizPlus.activity/flashcards/russian/RLD601.xml
+ImageQuizPlus.activity/flashcards/russian/RLD577.xml
+ImageQuizPlus.activity/flashcards/russian/RLD449.xml
+ImageQuizPlus.activity/flashcards/russian/RLD206.xml
+ImageQuizPlus.activity/flashcards/russian/RLD235.xml
+ImageQuizPlus.activity/flashcards/russian/RLD317.xml
+ImageQuizPlus.activity/flashcards/russian/RLD770.xml
+ImageQuizPlus.activity/flashcards/russian/RLD661.xml
+ImageQuizPlus.activity/flashcards/russian/RLD474.xml
+ImageQuizPlus.activity/flashcards/russian/RLD001.xml
+ImageQuizPlus.activity/flashcards/russian/RLD374.xml
+ImageQuizPlus.activity/flashcards/russian/RLD080.xml
+ImageQuizPlus.activity/flashcards/russian/RLD026.xml
+ImageQuizPlus.activity/flashcards/russian/RLD425.xml
+ImageQuizPlus.activity/flashcards/russian/RLD290.xml
+ImageQuizPlus.activity/flashcards/russian/RLD156.xml
+ImageQuizPlus.activity/flashcards/russian/RLD398.xml
+ImageQuizPlus.activity/flashcards/russian/RLD634.xml
+ImageQuizPlus.activity/flashcards/russian/RLD346.xml
+ImageQuizPlus.activity/flashcards/russian/RLD500.xml
+ImageQuizPlus.activity/flashcards/russian/RLD105.xml
+ImageQuizPlus.activity/flashcards/russian/RLD131.xml
+ImageQuizPlus.activity/flashcards/russian/RLD735.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/street traffic.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/travel.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/vehicles.xml
+ImageQuizPlus.activity/flashcards/german/Travel and Traffic/rail, plane, ship.xml
+ImageQuizPlus.activity/flashcards/german/colors.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/house and apartment.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/clothing and jewelry.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/furnishings.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/doctor and hospital.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/basic commodities.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/groceries, food.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/fruits and vegetables.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/drinking and smoking.xml
+ImageQuizPlus.activity/flashcards/german/Every Day Life/meals, restaurant.xml
+ImageQuizPlus.activity/flashcards/german/Countries and People/geographical names.xml
+ImageQuizPlus.activity/flashcards/german/Countries and People/nationalities, inhabitants, languages.xml
+ImageQuizPlus.activity/olpcgames
+ImageQuizPlus.activity/olpcgames/camera.py
+ImageQuizPlus.activity/olpcgames/canvas.py
+ImageQuizPlus.activity/olpcgames/eventwrap.py
+ImageQuizPlus.activity/olpcgames/pangofont.py
+ImageQuizPlus.activity/olpcgames/activity.py
+ImageQuizPlus.activity/olpcgames/mesh.py
+ImageQuizPlus.activity/olpcgames/tubeconn.py
+ImageQuizPlus.activity/olpcgames/util.py
+ImageQuizPlus.activity/olpcgames/__init__.py
+ImageQuizPlus.activity/olpcgames/video.py
+ImageQuizPlus.activity/olpcgames/gtkEvent.py
+ImageQuizPlus.activity/sounds
+ImageQuizPlus.activity/sounds/accessed.wav
+ImageQuizPlus.activity/sounds/transfer_data.wav
+ImageQuizPlus.activity/sounds/sorry.wav
+ImageQuizPlus.activity/po
+ImageQuizPlus.activity/quiz.py
+ImageQuizPlus.activity/activity.py
+ImageQuizPlus.activity/setup.py
+ImageQuizPlus.activity/LICENSE.txt
+ImageQuizPlus.activity/layout.py
+ImageQuizPlus.activity/frontend.py
+ImageQuizPlus.activity/backend.py
+ImageQuizPlus.activity/MANIFEST
diff --git a/olpcgames/__init__.py b/olpcgames/__init__.py
new file mode 100755
index 0000000..e27aed4
--- /dev/null
+++ b/olpcgames/__init__.py
@@ -0,0 +1,34 @@
+"""Wrapper/adaptation system for writing/porting PyGame games to OLPC/Sugar
+
+The wrapper system attempts to substitute various pieces of the PyGame
+implementation in order to make code written without knowledge of the
+OLPC/Sugar environment run "naturally" under the GTK environment of
+Sugar. It also provides some convenience mechanisms for dealing with
+e.g. the Camera and Mesh Network system.
+
+Considerations for Developers:
+
+PyGame programs running under OLPCGames will generally not have
+"hardware" surfaces, and will not be able to have a reduced-resolution
+full-screen view to optimise rendering. The PyGame code will run in
+a secondary thread, with the main GTK UI running in the primary thread.
+A third "mainloop" thread will occasionally be created to handle the
+GStreamer interface to the camera.
+"""
+# XXX handle configurations that are not running under Sugar and
+# report proper errors to describe the problem, rather than letting the
+# particular errors propagate outward.
+# XXX allow use of a particular feature within the library without needing
+# to be running under sugar. e.g. allow importing mesh or camera without
+# needing to import the activity machinery.
+from olpcgames.canvas import *
+try:
+ from olpcgames.activity import *
+except ImportError, err:
+ PyGameActivity = None
+from olpcgames import camera
+from olpcgames import pangofont
+from olpcgames import mesh
+
+ACTIVITY = None
+widget = None
diff --git a/olpcgames/__init__.pyc b/olpcgames/__init__.pyc
new file mode 100755
index 0000000..5e7904d
--- /dev/null
+++ b/olpcgames/__init__.pyc
Binary files differ
diff --git a/olpcgames/activity.py b/olpcgames/activity.py
new file mode 100755
index 0000000..f159b50
--- /dev/null
+++ b/olpcgames/activity.py
@@ -0,0 +1,162 @@
+"""Embeds the Canvas widget into a Sugar-specific Activity environment"""
+import logging
+logging.root.setLevel( logging.WARN )
+log = logging.getLogger( 'activity' )
+log.setLevel( logging.INFO )
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gtk.gdk
+
+from sugar.activity import activity
+from sugar.graphics import style
+from olpcgames.canvas import PyGameCanvas
+from olpcgames import mesh, util
+
+__all__ = ['PyGameActivity']
+
+class PyGameActivity(activity.Activity):
+ """PyGame-specific activity type, provides boilerplate toolbar, creates canvas
+
+ Subclass Overrides:
+
+ game_name -- specifies a fully-qualified name for the game's main-loop
+ format like so:
+ 'package.module:main'
+ if not function name is provided, "main" is assumed.
+ game_handler -- alternate specification via direct reference to a main-loop
+ function
+
+ game_size -- two-value tuple specifying the size of the display in pixels,
+ this is currently static, so once the window is created it cannot be
+ changed.
+
+ If None, use the bulk of the screen for the PyGame surface based on
+ the values reported by the gtk.gdk functions. Note that None is
+ *not* the default value.
+
+ game_title -- title to be displayed in the Sugar Shell UI
+
+ pygame_mode -- chooses the rendering engine used for handling the
+ PyGame drawing mode, 'SDL' chooses the standard PyGame renderer,
+ 'Cairo' chooses the experimental pygamecairo renderer.
+
+ PYGAME_CANVAS_CLASS -- normally PyGameCanvas, but can be overridden
+ if you want to provide a different canvas class, e.g. to provide a different
+ internal layout. Note: only used where pygame_mode == 'SDL'
+
+ The Activity, once created, will be made available as olpcgames.ACTIVITY,
+ and that access mechanism should allow code to test for the presence of the
+ activity before accessing Sugar-specific functionality.
+
+ XXX Note that currently the toolbar and window layout are hard-coded into
+ this super-class, with no easy way of overriding without completely rewriting
+ the __init__ method. We should allow for customising both the UI layout and
+ the toolbar contents/layout/connection.
+
+ XXX Note that if you change the title of your activity in the toolbar you may
+ see the same focus issues as we have patched around in the build_toolbar
+ method. If so, please report them to Mike Fletcher.
+ """
+ game_name = None
+ game_title = 'PyGame Game'
+ game_handler = None
+ game_size = (16 * style.GRID_CELL_SIZE,
+ 11 * style.GRID_CELL_SIZE)
+ pygame_mode = 'SDL'
+
+ def __init__(self, handle):
+ """Initialise the Activity with the activity-description handle"""
+ super(PyGameActivity, self).__init__(handle)
+ self.make_global()
+ if self.game_size is None:
+ width,height = gtk.gdk.screen_width(), gtk.gdk.screen_height()
+ log.info( 'Total screen size: %s %s', width,height)
+ # for now just fudge the toolbar size...
+ self.game_size = width, height - (1*style.GRID_CELL_SIZE)
+ self.set_title(self.game_title)
+ toolbar = self.build_toolbar()
+ log.debug( 'Toolbar size: %s', toolbar.get_size_request())
+ canvas = self.build_canvas()
+
+ def make_global( self ):
+ """Hack to make olpcgames.ACTIVITY point to us
+ """
+ import weakref, olpcgames
+ assert not olpcgames.ACTIVITY, """Activity.make_global called twice, have you created two Activity instances in a single process?"""
+ olpcgames.ACTIVITY = weakref.proxy( self )
+
+ def build_toolbar( self ):
+ """Build our Activity toolbar for the Sugar system
+
+ This is a customisation point for those games which want to
+ provide custom toolbars when running under Sugar.
+ """
+ toolbar = activity.ActivityToolbar(self)
+ toolbar.show()
+ self.set_toolbox(toolbar)
+ def shared_cb(*args, **kwargs):
+ log.info( 'shared: %s, %s', args, kwargs )
+ try:
+ mesh.activity_shared(self)
+ except Exception, err:
+ log.error( """Failure signaling activity sharing to mesh module: %s""", util.get_traceback(err) )
+ else:
+ log.info( 'mesh activity shared message sent, trying to grab focus' )
+ try:
+ self._pgc.grab_focus()
+ except Exception, err:
+ log.warn( 'Focus failed: %s', err )
+ else:
+ log.info( 'asserting focus' )
+ assert self._pgc.is_focus(), """Did not successfully set pygame canvas focus"""
+ log.info( 'callback finished' )
+
+ def joined_cb(*args, **kwargs):
+ log.info( 'joined: %s, %s', args, kwargs )
+ mesh.activity_joined(self)
+ self._pgc.grab_focus()
+ self.connect("shared", shared_cb)
+ self.connect("joined", joined_cb)
+
+ if self.get_shared():
+ # if set at this point, it means we've already joined (i.e.,
+ # launched from Neighborhood)
+ joined_cb()
+
+ toolbar.title.unset_flags(gtk.CAN_FOCUS)
+ return toolbar
+
+ PYGAME_CANVAS_CLASS = PyGameCanvas
+ def build_canvas( self ):
+ """Construct the PyGame or PyGameCairo canvas for drawing"""
+ assert self.game_handler or self.game_name, 'You must specify a game_handler or game_name on your Activity (%r)'%(
+ self.game_handler or self.game_name
+ )
+ if self.pygame_mode != 'Cairo':
+ self._pgc = self.PYGAME_CANVAS_CLASS(*self.game_size)
+ self.set_canvas(self._pgc)
+ self._pgc.grab_focus()
+ self._pgc.connect_game(self.game_handler or self.game_name)
+ gtk.gdk.threads_init()
+ return self._pgc
+ else:
+ import hippo
+ self._drawarea = gtk.DrawingArea()
+ canvas = hippo.Canvas()
+ canvas.grab_focus()
+ self.set_canvas(canvas)
+ self.show_all()
+
+ import pygamecairo
+ pygamecairo.install()
+
+ pygamecairo.display.init(canvas)
+ app = self.game_handler or self.game_name
+ if ':' not in app:
+ app += ':main'
+ mod_name, fn_name = app.split(':')
+ mod = __import__(mod_name, globals(), locals(), [])
+ fn = getattr(mod, fn_name)
+ fn()
diff --git a/olpcgames/activity.pyc b/olpcgames/activity.pyc
new file mode 100755
index 0000000..c01a38d
--- /dev/null
+++ b/olpcgames/activity.pyc
Binary files differ
diff --git a/olpcgames/camera.py b/olpcgames/camera.py
new file mode 100755
index 0000000..dfc969a
--- /dev/null
+++ b/olpcgames/camera.py
@@ -0,0 +1,235 @@
+"""Accesses OLPC Camera functionality via gstreamer
+
+Depends upon:
+ pygame
+ python-gstreamer
+"""
+import threading
+import logging
+import time
+import os
+import pygame
+import gst
+from olpcgames.util import get_activity_root
+
+log = logging.getLogger( 'camera' )
+#log.setLevel( logging.DEBUG )
+
+CAMERA_LOAD = 9917
+CAMERA_LOAD_FAIL = 9918
+
+class CameraSprite(object):
+ """Create gstreamer surface for the camera."""
+ def __init__(self, x, y):
+ import olpcgames
+ if olpcgames.widget:
+ self._init_video(olpcgames.widget, x, y)
+
+ def _init_video(self, widget, x, y):
+ from olpcgames import video
+ self._vid = video.VideoWidget()
+ widget._fixed.put(self._vid, x, y)
+ self._vid.show()
+
+ self.player = video.Player(self._vid)
+ self.player.play()
+
+class Camera(object):
+ """A class representing a still-picture camera
+
+ Produces a simple gstreamer bus that terminates in a filesink, that is,
+ it stores the results in a file. When a picture is "snapped" the gstreamer
+ stream is iterated until it finishes processing and then the file can be
+ read.
+
+ There are two APIs available, a synchronous API which can potentially
+ stall your activity's GUI (and is NOT recommended) and an
+ asynchronous API which returns immediately and delivers the captured
+ camera image via a Pygame event. To be clear, it is recommended
+ that you use the snap_async method, *not* the snap method.
+
+ Note:
+
+ The Camera class is simply a convenience wrapper around a fairly
+ straightforward gstreamer bus. If you have more involved
+ requirements for your camera manipulations you will probably
+ find it easier to write your own camera implementation than to
+ use this one. Basically we provide here the "normal" use case of
+ snapping a picture into a pygame image.
+ """
+ _aliases = {
+ 'camera': 'v4l2src',
+ 'test': 'videotestsrc',
+ 'testing': 'videotestsrc',
+ 'png': 'pngenc',
+ 'jpeg': 'jpegenc',
+ 'jpg': 'jpegenc',
+ }
+ def __init__(self, source='camera', format='png', filename='snap.png', directory = None):
+ """Initialises the Camera's internal description
+
+ source -- the gstreamer source for the video to capture, useful values:
+ 'v4l2src','camera' -- the camera
+ 'videotestsrc','test' -- test pattern generator source
+ format -- the gstreamer encoder to use for the capture, useful values:
+ 'pngenc','png' -- PNG format graphic
+ 'jpegenc','jpg','jpeg' -- JPEG format graphic
+ filename -- the filename to use for the capture
+ directory -- the directory in which to create the temporary file, defaults
+ to get_activity_root() + 'tmp'
+ """
+ self.source = self._aliases.get( source, source )
+ self.format = self._aliases.get( format, format )
+ self.filename = filename
+ self.directory = directory
+ SNAP_PIPELINE = '%(source)s ! ffmpegcolorspace ! %(format)s ! filesink location="%(filename)s"'
+ def _create_pipe( self ):
+ """Method to create the cstreamer pipe from our settings"""
+ if not self.directory:
+ path = os.path.join( get_activity_root(), 'tmp' )
+ try:
+ os.makedirs( path )
+ log.info( 'Created temporary directory: %s', path )
+ except (OSError,IOError), err:
+ pass
+ else:
+ path = self.directory
+ filename = os.path.join( path, self.filename )
+ format = self.format
+ source = self.source
+ pipeline = self.SNAP_PIPELINE % locals()
+ log.debug( 'Background thread processing: %s', pipeline )
+ return filename, gst.parse_launch(pipeline)
+
+ def snap(self):
+ """Snap a picture via the camera by iterating gstreamer until finished
+
+ Note: this is an unsafe implementation, it will cause the whole
+ activity to hang if the operation happens to fail! It is strongly
+ recommended that you use snap_async instead of snap!
+ """
+ log.debug( 'Starting snap' )
+ filename, pipe = self._create_pipe()
+ pipe.set_state(gst.STATE_PLAYING)
+ bus = pipe.get_bus()
+ tmp = False
+ while True:
+ event = self.bus.poll(gst.MESSAGE_STATE_CHANGED, 5)
+ if event:
+ old, new, pending = event.parse_state_changed()
+ if pending == gst.STATE_VOID_PENDING:
+ if tmp:
+ break
+ else:
+ tmp = True
+ else:
+ break
+ log.log( 'Ending snap, loading: %s', filename )
+ return self._load_and_clean( filename )
+ def _load_and_clean( self, filename ):
+ """Use pygame to load given filename, delete after loading/attempt"""
+ try:
+ log.info( 'Loading snapshot file: %s', filename )
+ return pygame.image.load(filename)
+ finally:
+ try:
+ os.remove( filename )
+ except (IOError,OSError), err:
+ pass
+ def snap_async( self, token=None ):
+ """Snap a picture asynchronously generating event on success/failure
+
+ token -- passed back as attribute of the event which signals that capture
+ is finished
+
+ We return two types of events CAMERA_LOAD and CAMERA_LOAD_FAIL,
+ depending on whether we succeed or not. Attributes of the events which
+ are returned:
+
+ token -- as passed to this method
+ filename -- the filename in our temporary directory we used to store
+ the file temporarily
+ image -- pygame image.load result if successful, None otherwise
+ err -- Exception instance if failed, None otherwise
+
+ Basically identical to the snap method, save that it posts a message
+ to the event bus in eventwrap instead of blocking and returning...
+ """
+ log.debug( 'beginning async snap')
+ t = threading.Thread(target=self._background_snap, args=(token,))
+ t.start()
+ log.debug( 'background thread started for gstreamer' )
+ return token
+
+ def _background_snap(
+ self,
+ token = None,
+ ):
+ """Process gst messages until pipe is finished
+
+ pipe -- gstreamer pipe definition for parse_launch, normally it will
+ produce a file into which the camera should store an image
+
+ We consider pipe to be finished when we have had two "state changed"
+ gstreamer events where the pending state is VOID, the first for when
+ we begin playing, the second for when we finish.
+ """
+ log.debug( 'Background thread kicking off gstreamer capture begun' )
+ from olpcgames import eventwrap
+ from pygame.event import Event
+ filename, pipe = self._create_pipe()
+ bus = pipe.get_bus()
+ bus.add_signal_watch()
+ def _background_snap_onmessage( bus, message ):
+ """Handle messages from the picture-snapping bus"""
+ log.debug( 'Message handler for gst messages: %s', message )
+ t = message.type
+ if t == gst.MESSAGE_EOS:
+ pipe.set_state(gst.STATE_NULL)
+ try:
+ image = self._load_and_clean( filename )
+ success = True
+ except Exception, err:
+ success = False
+ image = None
+ else:
+ err = None
+ log.debug( 'Success loading file %r', token )
+ eventwrap.post(Event(
+ CAMERA_LOAD,
+ filename=filename,
+ success = success,
+ token = token,
+ image=image,
+ err=err
+ ))
+
+ elif t == gst.MESSAGE_ERROR:
+ log.warn( 'Failure loading file %r: %s', token, message )
+ pipe.set_state(gst.STATE_NULL)
+ err, debug = message.parse_error()
+ eventwrap.post(Event(
+ CAMERA_LOAD_FAIL,
+ filename=filename,
+ success = False,
+ token = token,
+ image=None,
+ err=err
+ ))
+ return False
+ bus.connect('message', _background_snap_onmessage)
+ pipe.set_state(gst.STATE_PLAYING)
+
+def snap():
+ """Dump a snapshot from the camera to a pygame surface in background thread
+
+ See Camera.snap
+ """
+ return Camera().snap()
+
+def snap_async( token=None, **named ):
+ """Dump snapshot from camera return asynchronously as event in Pygame
+
+ See Camera.snap_async
+ """
+ return Camera(**named).snap_async( token )
diff --git a/olpcgames/camera.pyc b/olpcgames/camera.pyc
new file mode 100755
index 0000000..d4c22c6
--- /dev/null
+++ b/olpcgames/camera.pyc
Binary files differ
diff --git a/olpcgames/canvas.py b/olpcgames/canvas.py
new file mode 100755
index 0000000..eda64a2
--- /dev/null
+++ b/olpcgames/canvas.py
@@ -0,0 +1,111 @@
+"""Implements bridge connection between Sugar/GTK and PyGame"""
+import os
+import sys
+import threading
+from pprint import pprint
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pygame
+
+from olpcgames import gtkEvent, video
+
+__all__ = ['PyGameCanvas']
+
+class PyGameCanvas(gtk.EventBox):
+ """Canvas providing bridge methods to run PyGame in GTK
+
+ The PyGameCanvas creates a secondary thread in which the Pygame instance will
+ live, providing synthetic PyGame events to that thread via a Queue. The GUI
+ connection is done by having the PyGame canvas use a GTK Port object as it's
+ window pointer, it draws to that X-level window in order to produce output.
+ """
+ def __init__(self, width, height):
+ """Initializes the Canvas Object
+
+ width,height -- passed to the inner EventBox in order to request a given size,
+ the Socket is the only child of this EventBox, and the PyGame commands
+ will be writing to the Window ID of the socket. The internal EventBox is
+ centered via an Alignment instance within the PyGameCanvas instance.
+
+ XXX Should refactor so that the internal setup can be controlled by the
+ sub-class, e.g. to get size from the host window, or something similar.
+ """
+ # Build the main widget
+ super(PyGameCanvas,self).__init__()
+ self.set_flags(gtk.CAN_FOCUS)
+
+ # Build the sub-widgets
+ self._align = gtk.Alignment(0.5, 0.5)
+ self._inner_evb = gtk.EventBox()
+ self._socket = gtk.Socket()
+
+
+ # Add internal widgets
+ self._inner_evb.set_size_request(width, height)
+ self._inner_evb.add(self._socket)
+
+ self._socket.show()
+
+ self._align.add(self._inner_evb)
+ self._inner_evb.show()
+
+ self._align.show()
+
+ self.add(self._align)
+
+ # Construct a gtkEvent.Translator
+ self._translator = gtkEvent.Translator(self, self._inner_evb)
+ # <Cue Thus Spract Zarathustra>
+ self.show()
+ def connect_game(self, app):
+ """Imports the given main-loop and starts processing in secondary thread
+
+ app -- fully-qualified Python path-name for the game's main-loop, with
+ name within module as :functionname, if no : character is present then
+ :main will be assumed.
+
+ Side effects:
+
+ Sets the SDL_WINDOWID variable to our socket's window ID
+ Calls PyGame init
+ Causes the gtkEvent.Translator to "hook" PyGame
+ Creates and starts secondary thread for Game/PyGame event processing.
+ """
+ # Setup the embedding
+ os.environ['SDL_WINDOWID'] = str(self._socket.get_id())
+ #print 'Socket ID=%s'%os.environ['SDL_WINDOWID']
+ pygame.init()
+
+ self._translator.hook_pygame()
+
+ # Load the modules
+ # NOTE: This is delayed because pygame.init() must come after the embedding is up
+ if ':' not in app:
+ app += ':main'
+ mod_name, fn_name = app.split(':')
+ mod = __import__(mod_name, globals(), locals(), [])
+ fn = getattr(mod, fn_name)
+
+ # Start Pygame
+ self.__thread = threading.Thread(target=self._start, args=[fn])
+ self.__thread.start()
+
+ def _start(self, fn):
+ """The method that actually runs in the background thread"""
+ import olpcgames
+ olpcgames.widget = self
+ try:
+ import sugar.activity.activity,os
+ except ImportError, err:
+ log.info( """Running outside Sugar""" )
+ else:
+ os.chdir(sugar.activity.activity.get_bundle_path())
+
+ try:
+ fn()
+ finally:
+ gtk.main_quit()
+
diff --git a/olpcgames/canvas.pyc b/olpcgames/canvas.pyc
new file mode 100755
index 0000000..51658ae
--- /dev/null
+++ b/olpcgames/canvas.pyc
Binary files differ
diff --git a/olpcgames/eventwrap.py b/olpcgames/eventwrap.py
new file mode 100755
index 0000000..000511c
--- /dev/null
+++ b/olpcgames/eventwrap.py
@@ -0,0 +1,160 @@
+"""Converts from GTK to Pygame events
+
+Provides methods which will be substituted into Pygame in order to
+provide the synthetic events that we will feed into the Pygame queue.
+These methods are registered by the "install" method.
+"""
+import pygame
+import gtk
+import Queue
+import thread
+import logging
+
+log = logging.getLogger( 'eventwrap' )
+
+# This module reuses Pygame's Event, but
+# reimplements the event queue.
+from pygame.event import Event, event_name, pump as pygame_pump, get as pygame_get
+
+#print "Initializing own event.py"
+
+# Install myself on top of pygame.event
+def install():
+ """Installs this module (eventwrap) as an in-place replacement for the pygame.event module.
+
+ Use install() when you need to interact with Pygame code written
+ without reference to the olpcgames wrapper mechanisms to have the
+ code use this module's event queue.
+
+ XXX Really, use it everywhere you want to use olpcgames, as olpcgames
+ registers the handler itself, so you will always wind up with it registered when
+ you use olpcgames (the gtkEvent.Translator.hook_pygame method calls it).
+ """
+ import eventwrap,pygame
+ pygame.event = eventwrap
+ import sys
+ sys.modules["pygame.event"] = eventwrap
+
+
+# Event queue:
+g_events = Queue.Queue()
+
+# Set of blocked events as set by set
+g_blocked = set()
+g_blockedlock = thread.allocate_lock()
+g_blockAll = False
+
+def pump():
+ """Handle any window manager and other external events that aren't passed to the user. Call this periodically (once a frame) if you don't call get(), poll() or wait()."""
+ pygame_pump()
+
+def get():
+ """Get a list of all pending events. (Unlike pygame, there's no option to filter by event type; you should use set_blocked() if you don't want to see certain events.)"""
+ pump()
+ eventlist = []
+ try:
+ while True:
+ eventlist.append(g_events.get(block=False))
+ except Queue.Empty:
+ pass
+
+ pygameEvents = pygame_get()
+ if pygameEvents:
+ log.info( 'Raw Pygame events: %s', pygameEvents)
+ eventlist.extend( pygameEvents )
+
+ return eventlist
+
+def poll():
+ """Get the next pending event if exists. Otherwise, return pygame.NOEVENT."""
+ pump()
+ try:
+ return g_events.get(block=False)
+ except Queue.Empty:
+ return Event(pygame.NOEVENT)
+
+
+def wait():
+ """Get the next pending event, waiting if none."""
+ pump()
+ return g_events.get(block=True)
+
+def peek(types=None):
+ """True if there is any pending event. (Unlike pygame, there's no option to
+ filter by event type)"""
+ return not g_events.empty()
+
+def clear():
+ """Dunno why you would do this, but throws every event out of the queue"""
+ try:
+ while True:
+ g_events.get(block=False)
+ except Queue.Empty:
+ pass
+
+def set_blocked(item):
+ g_blockedlock.acquire()
+ try:
+ # FIXME: we do not currently know how to block all event types when
+ # you set_blocked(none).
+ [g_blocked.add(x) for x in makeseq(item)]
+ finally:
+ g_blockedlock.release()
+
+def set_allowed(item):
+ g_blockedlock.acquire()
+ try:
+ if item is None:
+ # Allow all events when you set_allowed(none). Strange, eh?
+ # Pygame is a wonderful API.
+ g_blocked.clear()
+ else:
+ [g_blocked.remove(x) for x in makeseq(item)]
+ finally:
+ g_blockedlock.release()
+
+def get_blocked(*args, **kwargs):
+ g_blockedlock.acquire()
+ try:
+ blocked = frozenset(g_blocked)
+ return blocked
+ finally:
+ g_blockedlock.release()
+
+def set_grab(grabbing):
+ # We don't do this.
+ pass
+
+def get_grab():
+ # We don't do this.
+ return False
+
+def post(event):
+ #print "posting on own"
+ g_blockedlock.acquire()
+ try:
+ if event.type not in g_blocked:
+ g_events.put(event, block=False)
+ finally:
+ g_blockedlock.release()
+
+def makeseq(obj):
+ """Accept either a scalar object or a sequence, and return a sequence
+ over which we can iterate. If we were passed a sequence, return it
+ unchanged. If we were passed a scalar, return a tuple containing only
+ that scalar. This allows the caller to easily support one-or-many.
+ """
+ # Strings are the exception because you can iterate over their chars
+ # -- yet, for all the purposes I've ever cared about, I want to treat
+ # a string as a scalar.
+ if isinstance(obj, basestring):
+ return (obj,)
+ try:
+ # Except as noted above, if you can get an iter() from an object,
+ # it's a collection.
+ iter(obj)
+ return obj
+ except TypeError:
+ # obj is a scalar. Wrap it in a tuple so we can iterate over the
+ # one item.
+ return (obj,)
diff --git a/olpcgames/eventwrap.pyc b/olpcgames/eventwrap.pyc
new file mode 100755
index 0000000..3511089
--- /dev/null
+++ b/olpcgames/eventwrap.pyc
Binary files differ
diff --git a/olpcgames/gtkEvent.py b/olpcgames/gtkEvent.py
new file mode 100755
index 0000000..2dbe652
--- /dev/null
+++ b/olpcgames/gtkEvent.py
@@ -0,0 +1,264 @@
+"""gtkEvent.py: translate GTK events into Pygame events."""
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pygame
+import pygame.event as Pevent
+#PCevent = Pevent
+from olpcgames import eventwrap as PCevent
+import logging
+log = logging.getLogger( 'gtkevent' )
+log.setLevel( logging.DEBUG )
+
+class MockEvent(object):
+ """Used to inject key-repeat events."""
+
+ def __init__(self, keyval):
+ self.keyval = keyval
+
+class Translator(object):
+ """Utility class to translate GTK events into Pygame events
+
+ The Translator object interprets incoming GTK events and generates
+ Pygame events in the eventwrap module's queue as a result.
+ It also handles generating Pygame style key-repeat events
+ by synthesizing them via a GTK timer.
+ """
+ key_trans = {
+ 'Alt_L': pygame.K_LALT,
+ 'Alt_R': pygame.K_RALT,
+ 'Control_L': pygame.K_LCTRL,
+ 'Control_R': pygame.K_RCTRL,
+ 'Shift_L': pygame.K_LSHIFT,
+ 'Shift_R': pygame.K_RSHIFT,
+ 'Super_L': pygame.K_LSUPER,
+ 'Super_R': pygame.K_RSUPER,
+ 'KP_Page_Up' : pygame.K_KP9,
+ 'KP_Page_Down' : pygame.K_KP3,
+ 'KP_End' : pygame.K_KP1,
+ 'KP_Home' : pygame.K_KP7,
+ 'KP_Up' : pygame.K_KP8,
+ 'KP_Down' : pygame.K_KP2,
+ 'KP_Left' : pygame.K_KP4,
+ 'KP_Right' : pygame.K_KP6,
+
+ }
+
+ mod_map = {
+ pygame.K_LALT: pygame.KMOD_LALT,
+ pygame.K_RALT: pygame.KMOD_RALT,
+ pygame.K_LCTRL: pygame.KMOD_LCTRL,
+ pygame.K_RCTRL: pygame.KMOD_RCTRL,
+ pygame.K_LSHIFT: pygame.KMOD_LSHIFT,
+ pygame.K_RSHIFT: pygame.KMOD_RSHIFT,
+ }
+
+ def __init__(self, mainwindow, mouselistener=None):
+ """Initialise the Translator with the windows to which to listen"""
+ # _inner_evb is Mouselistener
+ self._mainwindow = mainwindow
+ if mouselistener is None:
+ mouselistener = mainwindow
+
+ self._inner_evb = mouselistener
+
+ # Need to set our X event masks so we see mouse motion and stuff --
+ mainwindow.set_events(
+ gtk.gdk.KEY_PRESS_MASK | \
+ gtk.gdk.KEY_RELEASE_MASK \
+ )
+
+ self._inner_evb.set_events(
+ gtk.gdk.POINTER_MOTION_MASK | \
+ gtk.gdk.POINTER_MOTION_HINT_MASK | \
+ gtk.gdk.BUTTON_MOTION_MASK | \
+ gtk.gdk.BUTTON_PRESS_MASK | \
+ gtk.gdk.BUTTON_RELEASE_MASK
+ )
+
+ # Callback functions to link the event systems
+ mainwindow.connect('unrealize', self._quit)
+ mainwindow.connect('key_press_event', self._keydown)
+ mainwindow.connect('key_release_event', self._keyup)
+ self._inner_evb.connect('button_press_event', self._mousedown)
+ self._inner_evb.connect('button_release_event', self._mouseup)
+ self._inner_evb.connect('motion-notify-event', self._mousemove)
+
+ # You might need to do this
+ mainwindow.set_flags(gtk.CAN_FOCUS)
+ self._inner_evb.set_flags(gtk.CAN_FOCUS)
+
+ # Internal data
+ self.__stopped = False
+ self.__keystate = [0] * 323
+ self.__button_state = [0,0,0]
+ self.__mouse_pos = (0,0)
+ self.__repeat = (None, None)
+ self.__held = set()
+ self.__held_time_left = {}
+ self.__held_last_time = {}
+ self.__tick_id = None
+
+ #print "translator initialized"
+
+ def hook_pygame(self):
+ """Hook the various Pygame features so that we implement the event APIs"""
+ # Pygame should be initialized. Hijack their key and mouse methods
+ pygame.key.get_pressed = self._get_pressed
+ pygame.key.set_repeat = self._set_repeat
+ pygame.mouse.get_pressed = self._get_mouse_pressed
+ pygame.mouse.get_pos = self._get_mouse_pos
+ import eventwrap
+ eventwrap.install()
+
+ def _quit(self, data=None):
+ self.__stopped = True
+ PCevent.post(Pevent.Event(pygame.QUIT))
+
+ def _keydown(self, widget, event):
+ key = event.keyval
+ log.debug( 'key down: %s', key )
+ if key in self.__held:
+ return True
+ else:
+ if self.__repeat[0] is not None:
+ self.__held_last_time[key] = pygame.time.get_ticks()
+ self.__held_time_left[key] = self.__repeat[0]
+ self.__held.add(key)
+
+ return self._keyevent(widget, event, pygame.KEYDOWN)
+
+ def _keyup(self, widget, event):
+ key = event.keyval
+ if self.__repeat[0] is not None:
+ if key in self.__held:
+ # This is possibly false if set_repeat() is called with a key held
+ del self.__held_time_left[key]
+ del self.__held_last_time[key]
+ self.__held.discard(key)
+
+ return self._keyevent(widget, event, pygame.KEYUP)
+
+ def _keymods(self):
+ """Extract the keymods as they stand currently."""
+ mod = 0
+ for key_val, mod_val in self.mod_map.iteritems():
+ mod |= self.__keystate[key_val] and mod_val
+ return mod
+
+
+ def _keyevent(self, widget, event, type):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if key is None:
+ # No idea what this key is.
+ return False
+
+ keycode = None
+ if key in self.key_trans:
+ keycode = self.key_trans[key]
+ elif hasattr(pygame, 'K_'+key.upper()):
+ keycode = getattr(pygame, 'K_'+key.upper())
+ elif hasattr(pygame, 'K_'+key.lower()):
+ keycode = getattr(pygame, 'K_'+key.lower())
+ else:
+ print 'Key %s unrecognized'%key
+
+ if keycode is not None:
+ if type == pygame.KEYDOWN:
+ mod = self._keymods()
+ self.__keystate[keycode] = type == pygame.KEYDOWN
+ if type == pygame.KEYUP:
+ mod = self._keymods()
+ ukey = gtk.gdk.keyval_to_unicode(event.keyval)
+ evt = Pevent.Event(type, key=keycode, unicode=ukey, mod=mod)
+ self._post(evt)
+ return True
+
+ def _get_pressed(self):
+ """Retrieve map/array of which keys are currently depressed (held down)"""
+ return self.__keystate
+
+ def _get_mouse_pressed(self):
+ """Return three-element array of which mouse-buttons are currently depressed (held down)"""
+ return self.__button_state
+
+ def _mousedown(self, widget, event):
+ self.__button_state[event.button-1] = 1
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONDOWN)
+
+ def _mouseup(self, widget, event):
+ self.__button_state[event.button-1] = 0
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONUP)
+
+ def _mouseevent(self, widget, event, type):
+
+ evt = Pevent.Event(type,
+ button=event.button,
+ pos=(event.x, event.y))
+ self._post(evt)
+ return True
+
+ def _mousemove(self, widget, event):
+ # From http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
+ # if this is a hint, then let's get all the necessary
+ # information, if not it's all we need.
+ if event.is_hint:
+ x, y, state = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+ state = event.state
+
+ rel = (x - self.__mouse_pos[0],
+ y - self.__mouse_pos[1])
+ self.__mouse_pos = (x, y)
+
+ self.__button_state = [
+ state & gtk.gdk.BUTTON1_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON2_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON3_MASK and 1 or 0,
+ ]
+
+ evt = Pevent.Event(pygame.MOUSEMOTION,
+ pos=self.__mouse_pos,
+ rel=rel,
+ buttons=self.__button_state)
+ self._post(evt)
+ return True
+
+ def _tick(self):
+ """Generate synthetic events for held-down keys"""
+ cur_time = pygame.time.get_ticks()
+ for key in self.__held:
+ delta = cur_time - self.__held_last_time[key]
+ self.__held_last_time[key] = cur_time
+
+ self.__held_time_left[key] -= delta
+ if self.__held_time_left[key] <= 0:
+ self.__held_time_left[key] = self.__repeat[1]
+ self._keyevent(None, MockEvent(key), pygame.KEYDOWN)
+
+ return True
+
+ def _set_repeat(self, delay=None, interval=None):
+ """Set the key-repetition frequency for held-down keys"""
+ if delay is not None and self.__repeat[0] is None:
+ self.__tick_id = gobject.timeout_add(10, self._tick)
+ elif delay is None and self.__repeat[0] is not None:
+ gobject.source_remove(self.__tick_id)
+ self.__repeat = (delay, interval)
+
+ def _get_mouse_pos(self):
+ """Retrieve the current mouse position as a two-tuple of integers"""
+ return self.__mouse_pos
+
+ def _post(self, evt):
+ try:
+ PCevent.post(evt)
+ except pygame.error, e:
+ if str(e) == 'Event queue full':
+ print "Event queue full!"
+ pass
+ else:
+ raise e
diff --git a/olpcgames/gtkEvent.pyc b/olpcgames/gtkEvent.pyc
new file mode 100755
index 0000000..ccdab8e
--- /dev/null
+++ b/olpcgames/gtkEvent.pyc
Binary files differ
diff --git a/olpcgames/mesh.py b/olpcgames/mesh.py
new file mode 100755
index 0000000..254089f
--- /dev/null
+++ b/olpcgames/mesh.py
@@ -0,0 +1,398 @@
+'''mesh.py: utilities for wrapping the mesh and making it accessible to Pygame'''
+import logging
+log = logging.getLogger( 'olpcgames.mesh' )
+#log.setLevel( logging.DEBUG )
+try:
+ from sugar.presence.tubeconn import TubeConnection
+except ImportError, err:
+ TubeConnection = object
+try:
+ from dbus.gobject_service import ExportedGObject
+except ImportError, err:
+ ExportedGObject = object
+from dbus.service import method, signal
+
+try:
+ import telepathy
+except ImportError, err:
+ telepathy = None
+
+class OfflineError( Exception ):
+ """Raised when we cannot complete an operation due to being offline"""
+
+DBUS_IFACE="org.laptop.games.pygame"
+DBUS_PATH="/org/laptop/games/pygame"
+DBUS_SERVICE = None
+
+
+### NEW PYGAME EVENTS ###
+
+'''The tube connection was started. (i.e., the user clicked Share or started
+the activity from the Neighborhood screen).
+Event properties:
+ id: a unique identifier for this connection. (shouldn't be needed for anything)'''
+CONNECT = 9912
+
+'''A participant joined the activity. This will trigger for the local user
+as well as any arriving remote users.
+Event properties:
+ handle: the arriving user's handle.'''
+PARTICIPANT_ADD = 9913
+
+'''A participant quit the activity.
+Event properties:
+ handle: the departing user's handle.'''
+PARTICIPANT_REMOVE = 9914
+
+'''A message was sent to you.
+Event properties:
+ content: the content of the message (a string)
+ handle: the handle of the sending user.'''
+MESSAGE_UNI = 9915
+
+'''A message was sent to everyone.
+Event properties:
+ content: the content of the message (a string)
+ handle: the handle of the sending user.'''
+MESSAGE_MULTI = 9916
+
+
+# Private objects for useful purposes!
+pygametubes = []
+text_chan, tubes_chan = (None, None)
+conn = None
+initiating = False
+joining = False
+
+connect_callback = None
+
+def is_initiating():
+ '''A version of is_initiator that's a bit less goofy, and can be used
+ before the Tube comes up.'''
+ global initiating
+ return initiating
+
+def is_joining():
+ '''Returns True if the activity was started up by means of the
+ Neighbourhood mesh view.'''
+ global joining
+ return joining
+
+def set_connect_callback(cb):
+ '''Just the same as the Pygame event loop can listen for CONNECT,
+ this is just an ugly callback that the glib side can use to be aware
+ of when the Tube is ready.'''
+ global connect_callback
+ connect_callback = cb
+
+def activity_shared(activity):
+ '''Called when the user clicks Share.'''
+
+ global initiating
+ initiating = True
+
+ _setup(activity)
+
+
+ log.debug('This is my activity: making a tube...')
+ channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ if hasattr( channel, 'OfferDBusTube' ):
+ id = channel.OfferDBusTube(
+ DBUS_SERVICE, {})
+ else:
+ id = channel.OfferTube(
+ telepathy.TUBE_TYPE_DBUS, DBUS_SERVICE, {})
+
+ global connect_callback
+ if connect_callback is not None:
+ connect_callback()
+
+def activity_joined(activity):
+ '''Called at the startup of our Activity, when the user started it via Neighborhood intending to join an existing activity.'''
+
+ # Find out who's already in the shared activity:
+ log.debug('Joined an existing shared activity')
+
+ for buddy in activity._shared_activity.get_joined_buddies():
+ log.debug('Buddy %s is already in the activity' % buddy.props.nick)
+
+
+ global initiating
+ global joining
+ initiating = False
+ joining = True
+
+
+ _setup(activity)
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=_list_tubes_reply_cb,
+ error_handler=_list_tubes_error_cb)
+
+ global connect_callback
+ if connect_callback is not None:
+ connect_callback()
+
+def _getConn():
+ log.info( '_getConn' )
+ pservice = _get_presence_service()
+ name, path = pservice.get_preferred_connection()
+ global conn
+ conn = telepathy.client.Connection(name, path)
+ return conn
+
+
+
+def _setup(activity):
+ '''Determines text and tube channels for the current Activity. If no tube
+channel present, creates one. Updates text_chan and tubes_chan.
+
+setup(sugar.activity.Activity, telepathy.client.Connection)'''
+ global text_chan, tubes_chan, DBUS_SERVICE
+ if not DBUS_SERVICE:
+ DBUS_SERVICE = activity.get_bundle_id()
+ if not activity.get_shared():
+ log.error('Failed to share or join activity')
+ raise "Failure"
+
+ bus_name, conn_path, channel_paths = activity._shared_activity.get_channels()
+ _getConn()
+
+ # Work out what our room is called and whether we have Tubes already
+ room = None
+ tubes_chan = None
+ text_chan = None
+ for channel_path in channel_paths:
+ channel = telepathy.client.Channel(bus_name, channel_path)
+ htype, handle = channel.GetHandle()
+ if htype == telepathy.HANDLE_TYPE_ROOM:
+ log.debug('Found our room: it has handle#%d "%s"',
+ handle, conn.InspectHandles(htype, [handle])[0])
+ room = handle
+ ctype = channel.GetChannelType()
+ if ctype == telepathy.CHANNEL_TYPE_TUBES:
+ log.debug('Found our Tubes channel at %s', channel_path)
+ tubes_chan = channel
+ elif ctype == telepathy.CHANNEL_TYPE_TEXT:
+ log.debug('Found our Text channel at %s', channel_path)
+ text_chan = channel
+
+ if room is None:
+ log.error("Presence service didn't create a room")
+ raise "Failure"
+ if text_chan is None:
+ log.error("Presence service didn't create a text channel")
+ raise "Failure"
+
+ # Make sure we have a Tubes channel - PS doesn't yet provide one
+ if tubes_chan is None:
+ log.debug("Didn't find our Tubes channel, requesting one...")
+ tubes_chan = conn.request_channel(telepathy.CHANNEL_TYPE_TUBES,
+ telepathy.HANDLE_TYPE_ROOM, room, True)
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ new_tube_cb)
+
+ return (text_chan, tubes_chan)
+
+def new_tube_cb(id, initiator, type, service, params, state):
+ log.debug("New_tube_cb called: %s %s %s" % (id, initiator, type))
+ if (type == telepathy.TUBE_TYPE_DBUS and service == DBUS_SERVICE):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ if hasattr( channel, 'AcceptDBusTube' ):
+ channel.AcceptDBusTube( id )
+ else:
+ channel.AcceptTube(id)
+
+ tube_conn = TubeConnection(conn,
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+ id, group_iface=text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+
+ global pygametubes, initiating
+ pygametubes.append(PygameTube(tube_conn, initiating, len(pygametubes)))
+
+
+def _list_tubes_reply_cb(tubes):
+ for tube_info in tubes:
+ new_tube_cb(*tube_info)
+
+def _list_tubes_error_cb(e):
+ log.error('ListTubes() failed: %s', e)
+
+
+
+def get_buddy(dbus_handle):
+ """Get a Buddy from a handle."""
+ log.debug('Trying to find owner of handle %s...', dbus_handle)
+ cs_handle = instance().tube.bus_name_to_handle[dbus_handle]
+ log.debug('Trying to find my handle in %s...', cs_handle)
+ group = text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
+ log.debug( 'Calling GetSelfHandle' )
+ my_csh = group.GetSelfHandle()
+ log.debug('My handle in that group is %s', my_csh)
+ if my_csh == cs_handle:
+ handle = conn.GetSelfHandle()
+ log.debug('CS handle %s belongs to me, %s', cs_handle, handle)
+ elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ log.debug('CS handle %s belongs to %s', cs_handle, handle)
+ else:
+ handle = cs_handle
+ log.debug('non-CS handle %s belongs to itself', handle)
+
+ # XXX: we're assuming that we have Buddy objects for all contacts -
+ # this might break when the server becomes scalable.
+ pservice = _get_presence_service()
+ name, path = pservice.get_preferred_connection()
+ return pservice.get_buddy_by_telepathy_handle(name, path, handle)
+
+def _get_presence_service( ):
+ """Attempt to retrieve the presence service (check for offline condition)
+
+ The presence service, when offline, has no preferred connection type,
+ so we check that before returning the object...
+ """
+ import sugar.presence.presenceservice
+ pservice = sugar.presence.presenceservice.get_instance()
+ try:
+ name, path = pservice.get_preferred_connection()
+ except (TypeError,ValueError), err:
+ log.warn('Working in offline mode, cannot retrieve buddy information for %s: %s', handle, err )
+ raise OfflineError( """Unable to retrieve buddy information, currently offline""" )
+ else:
+ return pservice
+
+def instance(idx=0):
+ return pygametubes[idx]
+
+import eventwrap,pygame.event as PEvent
+
+class PygameTube(ExportedGObject):
+ '''The object whose instance is shared across D-bus
+
+ Call instance() to get the instance of this object for your activity service.
+ Its 'tube' property contains the underlying D-bus Connection.
+ '''
+ def __init__(self, tube, is_initiator, tube_id):
+ super(PygameTube, self).__init__(tube, DBUS_PATH)
+ log.info( 'PygameTube init' )
+ self.tube = tube
+ self.is_initiator = is_initiator
+ self.entered = False
+ self.ordered_bus_names = []
+ eventwrap.post(PEvent.Event(CONNECT, id=tube_id))
+
+ if not self.is_initiator:
+ self.tube.add_signal_receiver(self.new_participant_cb, 'NewParticipants', DBUS_IFACE, path=DBUS_PATH)
+ self.tube.watch_participants(self.participant_change_cb)
+ self.tube.add_signal_receiver(self.broadcast_cb, 'Broadcast', DBUS_IFACE, path=DBUS_PATH, sender_keyword='sender')
+
+
+ def participant_change_cb(self, added, removed):
+ log.debug( 'participant_change_cb: %s %s', added, removed )
+ def nick(buddy):
+ if buddy is not None:
+ return buddy.props.nick
+ else:
+ return 'Unknown'
+
+ for handle, bus_name in added:
+ dbus_handle = self.tube.participants[handle]
+ self.ordered_bus_names.append(dbus_handle)
+ eventwrap.post(PEvent.Event(PARTICIPANT_ADD, handle=dbus_handle))
+
+ for handle in removed:
+ dbus_handle = self.tube.participants[handle]
+ self.ordered_bus_names.remove(dbus_handle)
+ eventwrap.post(PEvent.Event(PARTICIPANT_REMOVE, handle=dbus_handle))
+
+ if self.is_initiator:
+ if not self.entered:
+ # Initiator will broadcast a new ordered_bus_names each time
+ # a participant joins.
+ self.ordered_bus_names = [self.tube.get_unique_name()]
+ self.NewParticipants(self.ordered_bus_names)
+
+ self.entered = True
+
+ @signal(dbus_interface=DBUS_IFACE, signature='as')
+ def NewParticipants(self, ordered_bus_names):
+ '''This is the NewParticipants signal, sent when the authoritative list of ordered_bus_names changes.'''
+ log.debug("sending NewParticipants: %s" % ordered_bus_names)
+ pass
+
+ @signal(dbus_interface=DBUS_IFACE, signature='s')
+ def Broadcast(self, content):
+ '''This is the Broadcast signal; it sends a message to all other activity participants.'''
+ pass
+
+ @method(dbus_interface=DBUS_IFACE, in_signature='s', out_signature='', sender_keyword='sender')
+ def Tell(self, content, sender=None):
+ '''This is the targeted-message interface; called when a message is received that was sent directly to me.'''
+ eventwrap.post(PEvent.Event(MESSAGE_UNI, handle=sender, content=content))
+
+ def broadcast_cb(self, content, sender=None):
+ '''This is the Broadcast callback, fired when someone sends a Broadcast signal along the bus.'''
+ eventwrap.post(PEvent.Event(MESSAGE_MULTI, handle=sender, content=content))
+
+ def new_participant_cb(self, new_bus_names):
+ '''This is the NewParticipants callback, fired when someone joins or leaves.'''
+ log.debug("new participant. new bus names %s, old %s" % (new_bus_names, self.ordered_bus_names))
+ if self.ordered_bus_names != new_bus_names:
+ log.warn("ordered bus names out of sync with server, resyncing")
+ self.ordered_bus_names = new_bus_names
+
+def send_to(handle, content=""):
+ '''Sends the given message to the given buddy identified by handle.'''
+ log.debug( 'send_to: %s %s', handle, content )
+ remote_proxy = dbus_get_object(handle, DBUS_PATH)
+ remote_proxy.Tell(content, reply_handler=dbus_msg, error_handler=dbus_err)
+
+def dbus_msg():
+ log.debug("async reply to send_to")
+def dbus_err(e):
+ log.error("async error: %s" % e)
+
+def broadcast(content=""):
+ '''Sends the given message to all participants.'''
+ log.debug( 'Broadcast: %s', content )
+ instance().Broadcast(content)
+
+def my_handle():
+ '''Returns the handle of this user
+
+ Note, you can get a DBusException from this if you have
+ not yet got a unique ID assigned by the bus. You may need
+ to delay calling until you are sure you are connected.
+ '''
+ log.debug( 'my handle' )
+ return instance().tube.get_unique_name()
+
+def is_initiator():
+ '''Returns the handle of this user.'''
+ log.debug( 'is initiator' )
+ return instance().is_initiator
+
+def get_participants():
+ '''Returns the list of active participants, in order of arrival.
+ List is maintained by the activity creator; if that person leaves it may not stay in sync.'''
+ log.debug( 'get_participants' )
+ try:
+ return instance().ordered_bus_names[:]
+ except IndexError, err:
+ return [] # no participants yet, as we don't yet have a connection
+
+def dbus_get_object(handle, path):
+ '''Get a D-bus object from another participant.
+
+ This is how you can communicate with other participants using
+ arbitrary D-bus objects without having to manage the participants
+ yourself.
+
+ Simply define a D-bus class with an interface and path that you
+ choose; when you want a reference to the corresponding remote
+ object on a participant, call this method.
+ '''
+ log.debug( 'dbus_get_object: %s %s', handle, path )
+ return instance().tube.get_object(handle, path)
diff --git a/olpcgames/mesh.pyc b/olpcgames/mesh.pyc
new file mode 100755
index 0000000..4ef256a
--- /dev/null
+++ b/olpcgames/mesh.pyc
Binary files differ
diff --git a/olpcgames/pangofont.py b/olpcgames/pangofont.py
new file mode 100755
index 0000000..ef2640a
--- /dev/null
+++ b/olpcgames/pangofont.py
@@ -0,0 +1,293 @@
+"""Implement Pygame's font interface using Pango for international support
+
+Depends on:
+
+ pygtk (to get the pango context)
+ pycairo (for the pango rendering context)
+ python-pango (obviously)
+ pygame (obviously)
+"""
+import pango
+import logging
+import cairo
+import pangocairo
+import pygame.rect, pygame.image
+import gtk
+import struct
+from pygame import surface
+
+log = logging.getLogger( 'pangofont' )
+#log.setLevel( logging.DEBUG )
+
+# Install myself on top of pygame.font
+def install():
+ """Replace Pygame's font module with this module"""
+ log.info( 'installing' )
+ from olpcgames import pangofont
+ import pygame
+ pygame.font = pangofont
+ import sys
+ sys.modules["pygame.font"] = pangofont
+
+class PangoFont(object):
+ """Base class for a pygame.font.Font-like object drawn by Pango."""
+ def __init__(self, family=None, size=None, bold=False, italic=False, fd=None):
+ """If you know what pango.FontDescription (fd) you want, pass it in as
+ 'fd'. Otherwise, specify any number of family, size, bold, or italic,
+ and we will try to match something up for you."""
+
+ # Always set the FontDescription (FIXME - only set it if the user wants
+ # to change something?)
+ if fd is None:
+ fd = pango.FontDescription()
+ if family is not None:
+ fd.set_family(family)
+ if size is not None:
+ fd.set_size(size*1000)
+
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+
+ self.fd = fd
+
+ def render(self, text, antialias=True, color=(255,255,255), background=None ):
+ """Render the font onto a new Surface and return it.
+ We ignore 'antialias' and use system settings.
+
+ text -- (unicode) string with the text to render
+ antialias -- attempt to antialias the text or not
+ color -- three or four-tuple of 0-255 values specifying rendering
+ colour for the text
+ background -- three or four-tuple of 0-255 values specifying rendering
+ colour for the background, or None for trasparent background
+
+ returns a pygame image instance
+ """
+ log.info( 'render: %r, antialias = %s, color=%s, background=%s', text, antialias, color, background )
+
+ # create layout
+ layout = pango.Layout(gtk.gdk.pango_context_get())
+ layout.set_font_description(self.fd)
+ layout.set_text(text)
+
+ # determine pixel size
+ (logical, ink) = layout.get_pixel_extents()
+ ink = pygame.rect.Rect(ink)
+
+ # Create a new Cairo ImageSurface
+ csrf = cairo.ImageSurface(cairo.FORMAT_ARGB32, ink.w, ink.h)
+ cctx = pangocairo.CairoContext(cairo.Context(csrf))
+
+ # Mangle the colors on little-endian machines. The reason for this
+ # is that Cairo writes native-endian 32-bit ARGB values whereas
+ # Pygame expects endian-independent values in whatever format. So we
+ # tell our users not to expect transparency here (avoiding the A issue)
+ # and we swizzle all the colors around.
+ big_endian = struct.pack( '=i', 1 ) == struct.pack( '>i', 1 )
+ if hasattr(csrf,'get_data'):
+ swap = True
+ else:
+ swap = False
+ log.debug( 'big_endian: %s swap: %s', big_endian, swap )
+ def mangle_color(color):
+ """Mange a colour depending on endian-ness, and swap-necessity
+
+ This implementation has only been tested on an AMD64
+ machine with a get_data implementation (rather than
+ a get_data_as_rgba implementation).
+ """
+ r,g,b = color[:3]
+ if len(color) > 3:
+ a = color[3]
+ else:
+ a = 255.0
+ if swap and not big_endian:
+ return map(_fixColorBase, (b,g,r,a) )
+ return map(_fixColorBase, (r,g,b,a) )
+
+ # render onto it
+ if background is not None:
+ background = mangle_color( background )
+ cctx.set_source_rgba(*background)
+ cctx.paint()
+
+ log.debug( 'incoming color: %s', color )
+ color = mangle_color( color )
+ log.debug( ' translated color: %s', color )
+
+ cctx.new_path()
+ cctx.layout_path(layout)
+ cctx.set_source_rgba(*color)
+ cctx.fill()
+
+ # Create and return a new Pygame Image derived from the Cairo Surface
+ if big_endian:
+ # You see, in big-endian-world, we can just use the RGB values
+ format = "ARGB"
+ else:
+ # But with little endian, we've already swapped R and B in
+ # mangle_color, so now just move the A
+ format = "RGBA"
+ if hasattr(csrf,'get_data'):
+ data = csrf.get_data()
+ else:
+ # XXX little-endian here, check on a big-endian machine
+ data = csrf.get_data_as_rgba()
+ format = 'RGBA' # XXX wrong, what's with all the silly swapping!
+ try:
+ data = str(data)
+ return pygame.image.fromstring(data, (ink.w,ink.h), format)
+ except ValueError, err:
+ err.args += (len(data), ink.w*ink.h*4,format )
+ raise
+
+class SysFont(PangoFont):
+ """Construct a PangoFont from a font description (name), size in pixels,
+ bold, and italic designation. Similar to SysFont from Pygame."""
+ def __init__(self, name, size, bold=False, italic=False):
+ fd = pango.FontDescription(name)
+ fd.set_absolute_size(size*pango.SCALE)
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+ super(SysFont, self).__init__(fd=fd)
+
+# originally defined a new class, no reason for that...
+NotImplemented = NotImplementedError
+
+class Font(PangoFont):
+ """Abstract class, do not use"""
+ def __init__(self, *args, **kwargs):
+ raise NotImplementedError("PangoFont doesn't support Font directly, use SysFont or .fontByDesc")
+
+def match_font(name,bold=False,italic=False):
+ """Stub, does not work, use fontByDesc instead"""
+ raise NotImplementedError("PangoFont doesn't support match_font directly, use SysFont or .fontByDesc")
+
+def fontByDesc(desc="",bold=False,italic=False):
+ """Constructs a FontDescription from the given string representation.
+The format of the string representation is:
+
+ "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]"
+
+where FAMILY-LIST is a comma separated list of families optionally terminated by a comma, STYLE_OPTIONS is a whitespace separated list of words where each WORD describes one of style, variant, weight, or stretch, and SIZE is an decimal number (size in points). For example the following are all valid string representations:
+
+ "sans bold 12"
+ "serif,monospace bold italic condensed 16"
+ "normal 10"
+
+The commonly available font families are: Normal, Sans, Serif and Monospace. The available styles are:
+Normal the font is upright.
+Oblique the font is slanted, but in a roman style.
+Italic the font is slanted in an italic style.
+
+The available weights are:
+Ultra-Light the ultralight weight (= 200)
+Light the light weight (=300)
+Normal the default weight (= 400)
+Bold the bold weight (= 700)
+Ultra-Bold the ultra-bold weight (= 800)
+Heavy the heavy weight (= 900)
+
+The available variants are:
+Normal
+Small-Caps
+
+The available stretch styles are:
+Ultra-Condensed the smallest width
+Extra-Condensed
+Condensed
+Semi-Condensed
+Normal the normal width
+Semi-Expanded
+Expanded
+Extra-Expanded
+Ultra-Expanded the widest width
+ """
+ fd = pango.FontDescription(name)
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+ return PangoFont(fd=fd)
+
+def get_init():
+ """Return boolean indicating whether we are initialised
+
+ Always returns True
+ """
+ return True
+
+def init():
+ """Initialise the module (null operation)"""
+ pass
+
+def quit():
+ """De-initialise the module (null operation)"""
+ pass
+
+def get_default_font():
+ """Return default-font specification to be passed to e.g. fontByDesc"""
+ return "sans"
+
+def get_fonts():
+ """Return the set of all fonts available (currently just 3 generic types)"""
+ return ["sans","serif","monospace"]
+
+
+def stdcolor(color):
+ """Produce a 4-element 0.0-1.0 color value from input"""
+ def fixlen(color):
+ if len(color) == 3:
+ return tuple(color) + (255,)
+ elif len(color) == 4:
+ return color
+ else:
+ raise TypeError("What sort of color is this: %s" % (color,))
+ return [_fixColorBase(x) for x in fixlen(color)]
+def _fixColorBase( v ):
+ """Return a properly clamped colour in floating-point space"""
+ return max((0,min((v,255.0))))/255.0
+
+if __name__ == "__main__":
+ # Simple testing code...
+ logging.basicConfig()
+ from pygame import image,display, event, sprite
+ import pygame
+ import pygame.event
+ def main():
+ display.init()
+ maxX,maxY = display.list_modes()[0]
+ screen = display.set_mode( (maxX/2, maxY/2 ) )
+ background = pygame.Surface(screen.get_size())
+ background = background.convert()
+ background.fill((255, 255,255))
+
+ screen.blit(background, (0, 0))
+ display.flip()
+
+ clock = pygame.time.Clock()
+
+ font = PangoFont( size=30, family='monospace' )
+ text1 = font.render( 'red', color=(255,0,0) , background=(255,255,255,0) )
+ text2 = font.render( 'green', color=(0,255,0) )
+ text3 = font.render( 'blue', color=(0,0,255) )
+ text4 = font.render( 'blue-trans', color=(0,0,255,128) )
+ text5 = font.render( 'cyan-trans', color=(0,255,255,128) )
+ while 1:
+ clock.tick( 60 )
+ for event in pygame.event.get():
+ log.debug( 'event: %s', event )
+ if event.type == pygame.QUIT:
+ return True
+ screen.blit( text1, (20,20 ))
+ screen.blit( text2, (20,80 ))
+ screen.blit( text3, (20,140 ))
+ screen.blit( text4, (200,20 ))
+ screen.blit( text5, (200,80 ))
+ display.flip()
+ main()
+
diff --git a/olpcgames/pangofont.pyc b/olpcgames/pangofont.pyc
new file mode 100755
index 0000000..6c9f08c
--- /dev/null
+++ b/olpcgames/pangofont.pyc
Binary files differ
diff --git a/olpcgames/tubeconn.py b/olpcgames/tubeconn.py
new file mode 100755
index 0000000..d1c1403
--- /dev/null
+++ b/olpcgames/tubeconn.py
@@ -0,0 +1,107 @@
+# This should eventually land in telepathy-python, so has the same license:
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+__all__ = ('TubeConnection',)
+__docformat__ = 'reStructuredText'
+
+
+import logging
+
+from dbus.connection import Connection
+
+
+logger = logging.getLogger('telepathy.tubeconn')
+
+
+class TubeConnection(Connection):
+
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ if address is None:
+ address = tubes_iface.GetDBusServerAddress(tube_id)
+ self = super(TubeConnection, cls).__new__(cls, address,
+ mainloop=mainloop)
+
+ self._tubes_iface = tubes_iface
+ self.tube_id = tube_id
+ self.participants = {}
+ self.bus_name_to_handle = {}
+ self._mapping_watches = []
+
+ if group_iface is None:
+ method = conn.GetSelfHandle
+ else:
+ method = group_iface.GetSelfHandle
+ method(reply_handler=self._on_get_self_handle_reply,
+ error_handler=self._on_get_self_handle_error)
+
+ return self
+
+ def _on_get_self_handle_reply(self, handle):
+ self.self_handle = handle
+ match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
+ self._on_dbus_names_changed)
+ self._tubes_iface.GetDBusNames(self.tube_id,
+ reply_handler=self._on_get_dbus_names_reply,
+ error_handler=self._on_get_dbus_names_error)
+ self._dbus_names_changed_match = match
+
+ def _on_get_self_handle_error(self, e):
+ logging.basicConfig()
+ logger.error('GetSelfHandle failed: %s', e)
+
+ def close(self):
+ self._dbus_names_changed_match.remove()
+ self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
+ super(TubeConnection, self).close()
+
+ def _on_get_dbus_names_reply(self, names):
+ self._on_dbus_names_changed(self.tube_id, names, ())
+
+ def _on_get_dbus_names_error(self, e):
+ logging.basicConfig()
+ logger.error('GetDBusNames failed: %s', e)
+
+ def _on_dbus_names_changed(self, tube_id, added, removed):
+ if tube_id == self.tube_id:
+ for handle, bus_name in added:
+ if handle == self.self_handle:
+ # I've just joined - set my unique name
+ self.set_unique_name(bus_name)
+ self.participants[handle] = bus_name
+ self.bus_name_to_handle[bus_name] = handle
+
+ # call the callback while the removed people are still in
+ # participants, so their bus names are available
+ for callback in self._mapping_watches:
+ callback(added, removed)
+
+ for handle in removed:
+ bus_name = self.participants.pop(handle, None)
+ self.bus_name_to_handle.pop(bus_name, None)
+
+ def watch_participants(self, callback):
+ self._mapping_watches.append(callback)
+ if self.participants:
+ # GetDBusNames already returned: fake a participant add event
+ # immediately
+ added = []
+ for k, v in self.participants.iteritems():
+ added.append((k, v))
+ callback(added, [])
diff --git a/olpcgames/util.py b/olpcgames/util.py
new file mode 100755
index 0000000..44d42b5
--- /dev/null
+++ b/olpcgames/util.py
@@ -0,0 +1,67 @@
+"""Abstraction layer for working outside the Sugar environment"""
+import traceback, cStringIO
+import logging
+log = logging.getLogger( 'olpcgames.util' )
+import os
+import os.path
+
+
+try:
+ from sugar.activity.activity import get_bundle_path as _get_bundle_path
+ def get_bundle_path( ):
+ """Retrieve bundle path from activity with fix for silly registration bug"""
+ path = _get_bundle_path()
+ if path.endswith( '.activity.activity' ):
+ log.warn( '''Found double .activity suffix in bundle path, truncating: %s''', path )
+ path = path[:-9]
+ return path
+except ImportError:
+ log.warn( '''Do not appear to be running under Sugar, stubbing-in get_bundle_path''' )
+ def get_bundle_path():
+ """Retrieve a substitute data-path for non OLPC systems"""
+ return os.getcwd()
+
+
+def get_activity_root( ):
+ """Return the activity root for data storage operations
+
+ If the activity is present, returns the activity's root,
+ otherwise returns NON_SUGAR_ROOT as the directory.
+ """
+ import olpcgames
+ if olpcgames.ACTIVITY:
+ return olpcgames.ACTIVITY.get_activity_root()
+ else:
+ return os.path.expanduser( NON_SUGAR_ROOT )
+
+def data_path(file_name):
+ """Return the full path to a file in the data sub-directory of the bundle"""
+ return os.path.join(get_bundle_path(), 'data', file_name)
+def tmp_path(file_name):
+ """Return the full path to a file in the temporary directory"""
+ return os.path.join(get_activity_root(), 'tmp', file_name)
+
+def get_traceback(error):
+ """Get formatted traceback from current exception
+
+ error -- Exception instance raised
+
+ Attempts to produce a 10-level traceback as a string
+ that you can log off. Use like so:
+
+ try:
+ doSomething()
+ except Exception, err:
+ log.error(
+ '''Failure during doSomething with X,Y,Z parameters: %s''',
+ util.get_traceback( err ),
+ )
+ """
+ exception = str(error)
+ file = cStringIO.StringIO()
+ try:
+ traceback.print_exc( limit=10, file = file )
+ exception = file.getvalue()
+ finally:
+ file.close()
+ return exception
diff --git a/olpcgames/util.pyc b/olpcgames/util.pyc
new file mode 100755
index 0000000..28a4636
--- /dev/null
+++ b/olpcgames/util.pyc
Binary files differ
diff --git a/olpcgames/video.py b/olpcgames/video.py
new file mode 100755
index 0000000..2c3a842
--- /dev/null
+++ b/olpcgames/video.py
@@ -0,0 +1,71 @@
+"""Video widget for displaying a gstreamer pipe"""
+import logging
+log = logging.getLogger( 'olpcgames.video' )
+import os
+import signal
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gst
+
+class VideoWidget(gtk.DrawingArea):
+ """A custom widget to render GStreamer video."""
+
+ def __init__(self, x=160, y=120):
+ super(VideoWidget, self).__init__()
+ self._imagesink = None
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ self.set_size_request(x,y)
+
+ def do_expose_event(self, event):
+ if self._imagesink:
+ self._imagesink.expose()
+ return False
+ else:
+ return True
+
+ def set_sink(self, sink):
+ assert self.window.xid
+ self._imagesink = sink
+ self._imagesink.set_xwindow_id(self.window.xid)
+
+#pipe_desc = 'v4l2src ! video/x-raw-yuv,width=160,height=120 ! ffmpegcolorspace ! xvimagesink'
+pipe_desc = 'v4l2src ! ffmpegcolorspace ! video/x-raw-yuv ! xvimagesink'
+class Player(object):
+ def __init__(self, videowidget):
+ self._playing = False
+ self._videowidget = videowidget
+
+ self._pipeline = gst.parse_launch(pipe_desc)
+
+ bus = self._pipeline.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ bus.connect('sync-message::element', self.on_sync_message)
+ bus.connect('message', self.on_message)
+
+ def play(self):
+ if self._playing == False:
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._playing = True
+
+ def pause(self):
+ if self._playing == True:
+ self._pipeline.set_state(gst.STATE_PAUSED)
+ self._playing = False
+
+ def on_sync_message(self, bus, message):
+ if message.structure is None:
+ return
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ self._videowidget.set_sink(message.src)
+
+ def on_message(self, bus, message):
+ t = message.type
+ if t == gst.MESSAGE_ERROR:
+ err, debug = message.parse_error()
+ log.debug("Video error: (%s) %s" ,err, debug)
+ self._playing = False
+ gtk.main_quit()
+
diff --git a/olpcgames/video.pyc b/olpcgames/video.pyc
new file mode 100755
index 0000000..f0f8668
--- /dev/null
+++ b/olpcgames/video.pyc
Binary files differ
diff --git a/path.py b/path.py
new file mode 100755
index 0000000..01c2c04
--- /dev/null
+++ b/path.py
@@ -0,0 +1,971 @@
+""" path.py - An object representing a path to a file or directory.
+
+Example:
+
+from path import path
+d = path('/home/guido/bin')
+for f in d.files('*.py'):
+ f.chmod(0755)
+
+This module requires Python 2.2 or later.
+
+
+URL: http://www.jorendorff.com/articles/python/path
+Author: Jason Orendorff <jason.orendorff\x40gmail\x2ecom> (and others - see the url!)
+Date: 9 Mar 2007
+"""
+
+
+# TODO
+# - Tree-walking functions don't avoid symlink loops. Matt Harrison
+# sent me a patch for this.
+# - Bug in write_text(). It doesn't support Universal newline mode.
+# - Better error message in listdir() when self isn't a
+# directory. (On Windows, the error message really sucks.)
+# - Make sure everything has a good docstring.
+# - Add methods for regex find and replace.
+# - guess_content_type() method?
+# - Perhaps support arguments to touch().
+
+from __future__ import generators
+
+import sys, warnings, os, fnmatch, glob, shutil, codecs, md5
+
+__version__ = '2.2'
+__all__ = ['path']
+
+# Platform-specific support for path.owner
+if os.name == 'nt':
+ try:
+ import win32security
+ except ImportError:
+ win32security = None
+else:
+ try:
+ import pwd
+ except ImportError:
+ pwd = None
+
+# Pre-2.3 support. Are unicode filenames supported?
+_base = str
+_getcwd = os.getcwd
+try:
+ if os.path.supports_unicode_filenames:
+ _base = unicode
+ _getcwd = os.getcwdu
+except AttributeError:
+ pass
+
+# Pre-2.3 workaround for booleans
+try:
+ True, False
+except NameError:
+ True, False = 1, 0
+
+# Pre-2.3 workaround for basestring.
+try:
+ basestring
+except NameError:
+ basestring = (str, unicode)
+
+# Universal newline support
+_textmode = 'r'
+if hasattr(file, 'newlines'):
+ _textmode = 'U'
+
+
+class TreeWalkWarning(Warning):
+ pass
+
+class path(_base):
+ """ Represents a filesystem path.
+
+ For documentation on individual methods, consult their
+ counterparts in os.path.
+ """
+
+ # --- Special Python methods.
+
+ def __repr__(self):
+ return 'path(%s)' % _base.__repr__(self)
+
+ # Adding a path and a string yields a path.
+ def __add__(self, more):
+ try:
+ resultStr = _base.__add__(self, more)
+ except TypeError: #Python bug
+ resultStr = NotImplemented
+ if resultStr is NotImplemented:
+ return resultStr
+ return self.__class__(resultStr)
+
+ def __radd__(self, other):
+ if isinstance(other, basestring):
+ return self.__class__(other.__add__(self))
+ else:
+ return NotImplemented
+
+ # The / operator joins paths.
+ def __div__(self, rel):
+ """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+
+ Join two path components, adding a separator character if
+ needed.
+ """
+ return self.__class__(os.path.join(self, rel))
+
+ # Make the / operator work even when true division is enabled.
+ __truediv__ = __div__
+
+ def getcwd(cls):
+ """ Return the current working directory as a path object. """
+ return cls(_getcwd())
+ getcwd = classmethod(getcwd)
+
+
+ # --- Operations on path strings.
+
+ isabs = os.path.isabs
+ def abspath(self): return self.__class__(os.path.abspath(self))
+ def normcase(self): return self.__class__(os.path.normcase(self))
+ def normpath(self): return self.__class__(os.path.normpath(self))
+ def realpath(self): return self.__class__(os.path.realpath(self))
+ def expanduser(self): return self.__class__(os.path.expanduser(self))
+ def expandvars(self): return self.__class__(os.path.expandvars(self))
+ def dirname(self): return self.__class__(os.path.dirname(self))
+ basename = os.path.basename
+
+ def expand(self):
+ """ Clean up a filename by calling expandvars(),
+ expanduser(), and normpath() on it.
+
+ This is commonly everything needed to clean up a filename
+ read from a configuration file, for example.
+ """
+ return self.expandvars().expanduser().normpath()
+
+ def _get_namebase(self):
+ base, ext = os.path.splitext(self.name)
+ return base
+
+ def _get_ext(self):
+ f, ext = os.path.splitext(_base(self))
+ return ext
+
+ def _get_drive(self):
+ drive, r = os.path.splitdrive(self)
+ return self.__class__(drive)
+
+ parent = property(
+ dirname, None, None,
+ """ This path's parent directory, as a new path object.
+
+ For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
+ """)
+
+ name = property(
+ basename, None, None,
+ """ The name of this file or directory without the full path.
+
+ For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
+ """)
+
+ namebase = property(
+ _get_namebase, None, None,
+ """ The same as path.name, but with one file extension stripped off.
+
+ For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
+ but path('/home/guido/python.tar.gz').namebase == 'python.tar'
+ """)
+
+ ext = property(
+ _get_ext, None, None,
+ """ The file extension, for example '.py'. """)
+
+ drive = property(
+ _get_drive, None, None,
+ """ The drive specifier, for example 'C:'.
+ This is always empty on systems that don't use drive specifiers.
+ """)
+
+ def splitpath(self):
+ """ p.splitpath() -> Return (p.parent, p.name). """
+ parent, child = os.path.split(self)
+ return self.__class__(parent), child
+
+ def splitdrive(self):
+ """ p.splitdrive() -> Return (p.drive, <the rest of p>).
+
+ Split the drive specifier from this path. If there is
+ no drive specifier, p.drive is empty, so the return value
+ is simply (path(''), p). This is always the case on Unix.
+ """
+ drive, rel = os.path.splitdrive(self)
+ return self.__class__(drive), rel
+
+ def splitext(self):
+ """ p.splitext() -> Return (p.stripext(), p.ext).
+
+ Split the filename extension from this path and return
+ the two parts. Either part may be empty.
+
+ The extension is everything from '.' to the end of the
+ last path segment. This has the property that if
+ (a, b) == p.splitext(), then a + b == p.
+ """
+ filename, ext = os.path.splitext(self)
+ return self.__class__(filename), ext
+
+ def stripext(self):
+ """ p.stripext() -> Remove one file extension from the path.
+
+ For example, path('/home/guido/python.tar.gz').stripext()
+ returns path('/home/guido/python.tar').
+ """
+ return self.splitext()[0]
+
+ if hasattr(os.path, 'splitunc'):
+ def splitunc(self):
+ unc, rest = os.path.splitunc(self)
+ return self.__class__(unc), rest
+
+ def _get_uncshare(self):
+ unc, r = os.path.splitunc(self)
+ return self.__class__(unc)
+
+ uncshare = property(
+ _get_uncshare, None, None,
+ """ The UNC mount point for this path.
+ This is empty for paths on local drives. """)
+
+ def joinpath(self, *args):
+ """ Join two or more path components, adding a separator
+ character (os.sep) if needed. Returns a new path
+ object.
+ """
+ return self.__class__(os.path.join(self, *args))
+
+ def splitall(self):
+ r""" Return a list of the path components in this path.
+
+ The first item in the list will be a path. Its value will be
+ either os.curdir, os.pardir, empty, or the root directory of
+ this path (for example, '/' or 'C:\\'). The other items in
+ the list will be strings.
+
+ path.path.joinpath(*result) will yield the original path.
+ """
+ parts = []
+ loc = self
+ while loc != os.curdir and loc != os.pardir:
+ prev = loc
+ loc, child = prev.splitpath()
+ if loc == prev:
+ break
+ parts.append(child)
+ parts.append(loc)
+ parts.reverse()
+ return parts
+
+ def relpath(self):
+ """ Return this path as a relative path,
+ based from the current working directory.
+ """
+ cwd = self.__class__(os.getcwd())
+ return cwd.relpathto(self)
+
+ def relpathto(self, dest):
+ """ Return a relative path from self to dest.
+
+ If there is no relative path from self to dest, for example if
+ they reside on different drives in Windows, then this returns
+ dest.abspath().
+ """
+ origin = self.abspath()
+ dest = self.__class__(dest).abspath()
+
+ orig_list = origin.normcase().splitall()
+ # Don't normcase dest! We want to preserve the case.
+ dest_list = dest.splitall()
+
+ if orig_list[0] != os.path.normcase(dest_list[0]):
+ # Can't get here from there.
+ return dest
+
+ # Find the location where the two paths start to differ.
+ i = 0
+ for start_seg, dest_seg in zip(orig_list, dest_list):
+ if start_seg != os.path.normcase(dest_seg):
+ break
+ i += 1
+
+ # Now i is the point where the two paths diverge.
+ # Need a certain number of "os.pardir"s to work up
+ # from the origin to the point of divergence.
+ segments = [os.pardir] * (len(orig_list) - i)
+ # Need to add the diverging part of dest_list.
+ segments += dest_list[i:]
+ if len(segments) == 0:
+ # If they happen to be identical, use os.curdir.
+ relpath = os.curdir
+ else:
+ relpath = os.path.join(*segments)
+ return self.__class__(relpath)
+
+ # --- Listing, searching, walking, and matching
+
+ def listdir(self, pattern=None):
+ """ D.listdir() -> List of items in this directory.
+
+ Use D.files() or D.dirs() instead if you want a listing
+ of just files or just subdirectories.
+
+ The elements of the list are path objects.
+
+ With the optional 'pattern' argument, this only lists
+ items whose names match the given pattern.
+ """
+ names = os.listdir(self)
+ if pattern is not None:
+ names = fnmatch.filter(names, pattern)
+ return [self / child for child in names]
+
+ def dirs(self, pattern=None):
+ """ D.dirs() -> List of this directory's subdirectories.
+
+ The elements of the list are path objects.
+ This does not walk recursively into subdirectories
+ (but see path.walkdirs).
+
+ With the optional 'pattern' argument, this only lists
+ directories whose names match the given pattern. For
+ example, d.dirs('build-*').
+ """
+ return [p for p in self.listdir(pattern) if p.isdir()]
+
+ def files(self, pattern=None):
+ """ D.files() -> List of the files in this directory.
+
+ The elements of the list are path objects.
+ This does not walk into subdirectories (see path.walkfiles).
+
+ With the optional 'pattern' argument, this only lists files
+ whose names match the given pattern. For example,
+ d.files('*.pyc').
+ """
+
+ return [p for p in self.listdir(pattern) if p.isfile()]
+
+ def walk(self, pattern=None, errors='strict'):
+ """ D.walk() -> iterator over files and subdirs, recursively.
+
+ The iterator yields path objects naming each child item of
+ this directory and its descendants. This requires that
+ D.isdir().
+
+ This performs a depth-first traversal of the directory tree.
+ Each directory is returned just before all its children.
+
+ The errors= keyword argument controls behavior when an
+ error occurs. The default is 'strict', which causes an
+ exception. The other allowed values are 'warn', which
+ reports the error via warnings.warn(), and 'ignore'.
+ """
+ if errors not in ('strict', 'warn', 'ignore'):
+ raise ValueError("invalid errors parameter")
+
+ try:
+ childList = self.listdir()
+ except Exception:
+ if errors == 'ignore':
+ return
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to list directory '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ return
+ else:
+ raise
+
+ for child in childList:
+ if pattern is None or child.fnmatch(pattern):
+ yield child
+ try:
+ isdir = child.isdir()
+ except Exception:
+ if errors == 'ignore':
+ isdir = False
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to access '%s': %s"
+ % (child, sys.exc_info()[1]),
+ TreeWalkWarning)
+ isdir = False
+ else:
+ raise
+
+ if isdir:
+ for item in child.walk(pattern, errors):
+ yield item
+
+ def walkdirs(self, pattern=None, errors='strict'):
+ """ D.walkdirs() -> iterator over subdirs, recursively.
+
+ With the optional 'pattern' argument, this yields only
+ directories whose names match the given pattern. For
+ example, mydir.walkdirs('*test') yields only directories
+ with names ending in 'test'.
+
+ The errors= keyword argument controls behavior when an
+ error occurs. The default is 'strict', which causes an
+ exception. The other allowed values are 'warn', which
+ reports the error via warnings.warn(), and 'ignore'.
+ """
+ if errors not in ('strict', 'warn', 'ignore'):
+ raise ValueError("invalid errors parameter")
+
+ try:
+ dirs = self.dirs()
+ except Exception:
+ if errors == 'ignore':
+ return
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to list directory '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ return
+ else:
+ raise
+
+ for child in dirs:
+ if pattern is None or child.fnmatch(pattern):
+ yield child
+ for subsubdir in child.walkdirs(pattern, errors):
+ yield subsubdir
+
+ def walkfiles(self, pattern=None, errors='strict'):
+ """ D.walkfiles() -> iterator over files in D, recursively.
+
+ The optional argument, pattern, limits the results to files
+ with names that match the pattern. For example,
+ mydir.walkfiles('*.tmp') yields only files with the .tmp
+ extension.
+ """
+ if errors not in ('strict', 'warn', 'ignore'):
+ raise ValueError("invalid errors parameter")
+
+ try:
+ childList = self.listdir()
+ except Exception:
+ if errors == 'ignore':
+ return
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to list directory '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ return
+ else:
+ raise
+
+ for child in childList:
+ try:
+ isfile = child.isfile()
+ isdir = not isfile and child.isdir()
+ except:
+ if errors == 'ignore':
+ continue
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to access '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ continue
+ else:
+ raise
+
+ if isfile:
+ if pattern is None or child.fnmatch(pattern):
+ yield child
+ elif isdir:
+ for f in child.walkfiles(pattern, errors):
+ yield f
+
+ def fnmatch(self, pattern):
+ """ Return True if self.name matches the given pattern.
+
+ pattern - A filename pattern with wildcards,
+ for example '*.py'.
+ """
+ return fnmatch.fnmatch(self.name, pattern)
+
+ def glob(self, pattern):
+ """ Return a list of path objects that match the pattern.
+
+ pattern - a path relative to this directory, with wildcards.
+
+ For example, path('/users').glob('*/bin/*') returns a list
+ of all the files users have in their bin directories.
+ """
+ cls = self.__class__
+ return [cls(s) for s in glob.glob(_base(self / pattern))]
+
+
+ # --- Reading or writing an entire file at once.
+
+ def open(self, mode='r'):
+ """ Open this file. Return a file object. """
+ return file(self, mode)
+
+ def bytes(self):
+ """ Open this file, read all bytes, return them as a string. """
+ f = self.open('rb')
+ try:
+ return f.read()
+ finally:
+ f.close()
+
+ def write_bytes(self, bytes, append=False):
+ """ Open this file and write the given bytes to it.
+
+ Default behavior is to overwrite any existing file.
+ Call p.write_bytes(bytes, append=True) to append instead.
+ """
+ if append:
+ mode = 'ab'
+ else:
+ mode = 'wb'
+ f = self.open(mode)
+ try:
+ f.write(bytes)
+ finally:
+ f.close()
+
+ def text(self, encoding=None, errors='strict'):
+ r""" Open this file, read it in, return the content as a string.
+
+ This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r'
+ are automatically translated to '\n'.
+
+ Optional arguments:
+
+ encoding - The Unicode encoding (or character set) of
+ the file. If present, the content of the file is
+ decoded and returned as a unicode object; otherwise
+ it is returned as an 8-bit str.
+ errors - How to handle Unicode errors; see help(str.decode)
+ for the options. Default is 'strict'.
+ """
+ if encoding is None:
+ # 8-bit
+ f = self.open(_textmode)
+ try:
+ return f.read()
+ finally:
+ f.close()
+ else:
+ # Unicode
+ f = codecs.open(self, 'r', encoding, errors)
+ # (Note - Can't use 'U' mode here, since codecs.open
+ # doesn't support 'U' mode, even in Python 2.3.)
+ try:
+ t = f.read()
+ finally:
+ f.close()
+ return (t.replace(u'\r\n', u'\n')
+ .replace(u'\r\x85', u'\n')
+ .replace(u'\r', u'\n')
+ .replace(u'\x85', u'\n')
+ .replace(u'\u2028', u'\n'))
+
+ def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False):
+ r""" Write the given text to this file.
+
+ The default behavior is to overwrite any existing file;
+ to append instead, use the 'append=True' keyword argument.
+
+ There are two differences between path.write_text() and
+ path.write_bytes(): newline handling and Unicode handling.
+ See below.
+
+ Parameters:
+
+ - text - str/unicode - The text to be written.
+
+ - encoding - str - The Unicode encoding that will be used.
+ This is ignored if 'text' isn't a Unicode string.
+
+ - errors - str - How to handle Unicode encoding errors.
+ Default is 'strict'. See help(unicode.encode) for the
+ options. This is ignored if 'text' isn't a Unicode
+ string.
+
+ - linesep - keyword argument - str/unicode - The sequence of
+ characters to be used to mark end-of-line. The default is
+ os.linesep. You can also specify None; this means to
+ leave all newlines as they are in 'text'.
+
+ - append - keyword argument - bool - Specifies what to do if
+ the file already exists (True: append to the end of it;
+ False: overwrite it.) The default is False.
+
+
+ --- Newline handling.
+
+ write_text() converts all standard end-of-line sequences
+ ('\n', '\r', and '\r\n') to your platform's default end-of-line
+ sequence (see os.linesep; on Windows, for example, the
+ end-of-line marker is '\r\n').
+
+ If you don't like your platform's default, you can override it
+ using the 'linesep=' keyword argument. If you specifically want
+ write_text() to preserve the newlines as-is, use 'linesep=None'.
+
+ This applies to Unicode text the same as to 8-bit text, except
+ there are three additional standard Unicode end-of-line sequences:
+ u'\x85', u'\r\x85', and u'\u2028'.
+
+ (This is slightly different from when you open a file for
+ writing with fopen(filename, "w") in C or file(filename, 'w')
+ in Python.)
+
+
+ --- Unicode
+
+ If 'text' isn't Unicode, then apart from newline handling, the
+ bytes are written verbatim to the file. The 'encoding' and
+ 'errors' arguments are not used and must be omitted.
+
+ If 'text' is Unicode, it is first converted to bytes using the
+ specified 'encoding' (or the default encoding if 'encoding'
+ isn't specified). The 'errors' argument applies only to this
+ conversion.
+
+ """
+ if isinstance(text, unicode):
+ if linesep is not None:
+ # Convert all standard end-of-line sequences to
+ # ordinary newline characters.
+ text = (text.replace(u'\r\n', u'\n')
+ .replace(u'\r\x85', u'\n')
+ .replace(u'\r', u'\n')
+ .replace(u'\x85', u'\n')
+ .replace(u'\u2028', u'\n'))
+ text = text.replace(u'\n', linesep)
+ if encoding is None:
+ encoding = sys.getdefaultencoding()
+ bytes = text.encode(encoding, errors)
+ else:
+ # It is an error to specify an encoding if 'text' is
+ # an 8-bit string.
+ assert encoding is None
+
+ if linesep is not None:
+ text = (text.replace('\r\n', '\n')
+ .replace('\r', '\n'))
+ bytes = text.replace('\n', linesep)
+
+ self.write_bytes(bytes, append)
+
+ def lines(self, encoding=None, errors='strict', retain=True):
+ r""" Open this file, read all lines, return them in a list.
+
+ Optional arguments:
+ encoding - The Unicode encoding (or character set) of
+ the file. The default is None, meaning the content
+ of the file is read as 8-bit characters and returned
+ as a list of (non-Unicode) str objects.
+ errors - How to handle Unicode errors; see help(str.decode)
+ for the options. Default is 'strict'
+ retain - If true, retain newline characters; but all newline
+ character combinations ('\r', '\n', '\r\n') are
+ translated to '\n'. If false, newline characters are
+ stripped off. Default is True.
+
+ This uses 'U' mode in Python 2.3 and later.
+ """
+ if encoding is None and retain:
+ f = self.open(_textmode)
+ try:
+ return f.readlines()
+ finally:
+ f.close()
+ else:
+ return self.text(encoding, errors).splitlines(retain)
+
+ def write_lines(self, lines, encoding=None, errors='strict',
+ linesep=os.linesep, append=False):
+ r""" Write the given lines of text to this file.
+
+ By default this overwrites any existing file at this path.
+
+ This puts a platform-specific newline sequence on every line.
+ See 'linesep' below.
+
+ lines - A list of strings.
+
+ encoding - A Unicode encoding to use. This applies only if
+ 'lines' contains any Unicode strings.
+
+ errors - How to handle errors in Unicode encoding. This
+ also applies only to Unicode strings.
+
+ linesep - The desired line-ending. This line-ending is
+ applied to every line. If a line already has any
+ standard line ending ('\r', '\n', '\r\n', u'\x85',
+ u'\r\x85', u'\u2028'), that will be stripped off and
+ this will be used instead. The default is os.linesep,
+ which is platform-dependent ('\r\n' on Windows, '\n' on
+ Unix, etc.) Specify None to write the lines as-is,
+ like file.writelines().
+
+ Use the keyword argument append=True to append lines to the
+ file. The default is to overwrite the file. Warning:
+ When you use this with Unicode data, if the encoding of the
+ existing data in the file is different from the encoding
+ you specify with the encoding= parameter, the result is
+ mixed-encoding data, which can really confuse someone trying
+ to read the file later.
+ """
+ if append:
+ mode = 'ab'
+ else:
+ mode = 'wb'
+ f = self.open(mode)
+ try:
+ for line in lines:
+ isUnicode = isinstance(line, unicode)
+ if linesep is not None:
+ # Strip off any existing line-end and add the
+ # specified linesep string.
+ if isUnicode:
+ if line[-2:] in (u'\r\n', u'\x0d\x85'):
+ line = line[:-2]
+ elif line[-1:] in (u'\r', u'\n',
+ u'\x85', u'\u2028'):
+ line = line[:-1]
+ else:
+ if line[-2:] == '\r\n':
+ line = line[:-2]
+ elif line[-1:] in ('\r', '\n'):
+ line = line[:-1]
+ line += linesep
+ if isUnicode:
+ if encoding is None:
+ encoding = sys.getdefaultencoding()
+ line = line.encode(encoding, errors)
+ f.write(line)
+ finally:
+ f.close()
+
+ def read_md5(self):
+ """ Calculate the md5 hash for this file.
+
+ This reads through the entire file.
+ """
+ f = self.open('rb')
+ try:
+ m = md5.new()
+ while True:
+ d = f.read(8192)
+ if not d:
+ break
+ m.update(d)
+ finally:
+ f.close()
+ return m.digest()
+
+ # --- Methods for querying the filesystem.
+
+ exists = os.path.exists
+ isdir = os.path.isdir
+ isfile = os.path.isfile
+ islink = os.path.islink
+ ismount = os.path.ismount
+
+ if hasattr(os.path, 'samefile'):
+ samefile = os.path.samefile
+
+ getatime = os.path.getatime
+ atime = property(
+ getatime, None, None,
+ """ Last access time of the file. """)
+
+ getmtime = os.path.getmtime
+ mtime = property(
+ getmtime, None, None,
+ """ Last-modified time of the file. """)
+
+ if hasattr(os.path, 'getctime'):
+ getctime = os.path.getctime
+ ctime = property(
+ getctime, None, None,
+ """ Creation time of the file. """)
+
+ getsize = os.path.getsize
+ size = property(
+ getsize, None, None,
+ """ Size of the file, in bytes. """)
+
+ if hasattr(os, 'access'):
+ def access(self, mode):
+ """ Return true if current user has access to this path.
+
+ mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
+ """
+ return os.access(self, mode)
+
+ def stat(self):
+ """ Perform a stat() system call on this path. """
+ return os.stat(self)
+
+ def lstat(self):
+ """ Like path.stat(), but do not follow symbolic links. """
+ return os.lstat(self)
+
+ def get_owner(self):
+ r""" Return the name of the owner of this file or directory.
+
+ This follows symbolic links.
+
+ On Windows, this returns a name of the form ur'DOMAIN\User Name'.
+ On Windows, a group can own a file or directory.
+ """
+ if os.name == 'nt':
+ if win32security is None:
+ raise Exception("path.owner requires win32all to be installed")
+ desc = win32security.GetFileSecurity(
+ self, win32security.OWNER_SECURITY_INFORMATION)
+ sid = desc.GetSecurityDescriptorOwner()
+ account, domain, typecode = win32security.LookupAccountSid(None, sid)
+ return domain + u'\\' + account
+ else:
+ if pwd is None:
+ raise NotImplementedError("path.owner is not implemented on this platform.")
+ st = self.stat()
+ return pwd.getpwuid(st.st_uid).pw_name
+
+ owner = property(
+ get_owner, None, None,
+ """ Name of the owner of this file or directory. """)
+
+ if hasattr(os, 'statvfs'):
+ def statvfs(self):
+ """ Perform a statvfs() system call on this path. """
+ return os.statvfs(self)
+
+ if hasattr(os, 'pathconf'):
+ def pathconf(self, name):
+ return os.pathconf(self, name)
+
+
+ # --- Modifying operations on files and directories
+
+ def utime(self, times):
+ """ Set the access and modified times of this file. """
+ os.utime(self, times)
+
+ def chmod(self, mode):
+ os.chmod(self, mode)
+
+ if hasattr(os, 'chown'):
+ def chown(self, uid, gid):
+ os.chown(self, uid, gid)
+
+ def rename(self, new):
+ os.rename(self, new)
+
+ def renames(self, new):
+ os.renames(self, new)
+
+
+ # --- Create/delete operations on directories
+
+ def mkdir(self, mode=0777):
+ os.mkdir(self, mode)
+
+ def makedirs(self, mode=0777):
+ os.makedirs(self, mode)
+
+ def rmdir(self):
+ os.rmdir(self)
+
+ def removedirs(self):
+ os.removedirs(self)
+
+
+ # --- Modifying operations on files
+
+ def touch(self):
+ """ Set the access/modified times of this file to the current time.
+ Create the file if it does not exist.
+ """
+ fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666)
+ os.close(fd)
+ os.utime(self, None)
+
+ def remove(self):
+ os.remove(self)
+
+ def unlink(self):
+ os.unlink(self)
+
+
+ # --- Links
+
+ if hasattr(os, 'link'):
+ def link(self, newpath):
+ """ Create a hard link at 'newpath', pointing to this file. """
+ os.link(self, newpath)
+
+ if hasattr(os, 'symlink'):
+ def symlink(self, newlink):
+ """ Create a symbolic link at 'newlink', pointing here. """
+ os.symlink(self, newlink)
+
+ if hasattr(os, 'readlink'):
+ def readlink(self):
+ """ Return the path to which this symbolic link points.
+
+ The result may be an absolute or a relative path.
+ """
+ return self.__class__(os.readlink(self))
+
+ def readlinkabs(self):
+ """ Return the path to which this symbolic link points.
+
+ The result is always an absolute path.
+ """
+ p = self.readlink()
+ if p.isabs():
+ return p
+ else:
+ return (self.parent / p).abspath()
+
+
+ # --- High-level functions from shutil
+
+ copyfile = shutil.copyfile
+ copymode = shutil.copymode
+ copystat = shutil.copystat
+ copy = shutil.copy
+ copy2 = shutil.copy2
+ copytree = shutil.copytree
+ if hasattr(shutil, 'move'):
+ move = shutil.move
+ rmtree = shutil.rmtree
+
+
+ # --- Special stuff from os
+
+ if hasattr(os, 'chroot'):
+ def chroot(self):
+ os.chroot(self)
+
+ if hasattr(os, 'startfile'):
+ def startfile(self):
+ os.startfile(self)
+
+
diff --git a/pgu/__init__.py b/pgu/__init__.py
new file mode 100644
index 0000000..d90edbf
--- /dev/null
+++ b/pgu/__init__.py
@@ -0,0 +1,7 @@
+"""Phil's pyGame Utilities
+
+
+"""
+__version__ = '0.12.2'
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/__init__.pyc b/pgu/__init__.pyc
new file mode 100644
index 0000000..a696dce
--- /dev/null
+++ b/pgu/__init__.pyc
Binary files differ
diff --git a/pgu/algo.py b/pgu/algo.py
new file mode 100644
index 0000000..dedb020
--- /dev/null
+++ b/pgu/algo.py
@@ -0,0 +1,135 @@
+"""Some handy algorithms for use in games, etc.
+
+<p>please note that this file is alpha, and is subject to modification in
+future versions of pgu!</p>
+"""
+print 'pgu.algo','This module is alpha, and is subject to change.'
+
+#def dist(a,b):
+# return abs(a[0]-b[0]) + abs(a[1]-b[1])
+
+class node:
+ def __init__(self,prev,pos,dest):
+ self.prev,self.pos,self.dest = prev,pos,dest
+ if self.prev == None: self.g = 0
+ else: self.g = self.prev.g + 1
+ self.h = dist(pos,dest)
+ self.f = self.g+self.h
+
+
+def astar(start,end,layer,_dist):
+ """uses the a* algorithm to find a path
+
+ <pre>astar(start,end,layer,dist): return [list of positions]</pre>
+
+ <dl>
+ <dt>start<dd>start position
+ <dt>end<dd>end position
+ <dt>layer<dd>a grid where zero cells are open and non-zero cells are walls
+ <dt>dist<dd>a distance function dist(a,b)
+ </dl>
+
+ <p>returns a list of positions from start to end</p>
+ """
+ global dist
+ dist = _dist
+ if layer[start[1]][start[0]]: return [] #start is blocked
+ if layer[end[1]][end[0]]: return [] #end is blocked
+ w,h = len(layer[0]),len(layer)
+ if start[0] < 0 or start[1] < 0 or start[0] >= w or start[1] >= h: return [] #start outside of layer
+ if end[0] < 0 or end[1] < 0 or end[0] >= w or end[1] >= h: return [] #end outside of layer
+
+ opens = []
+ open = {}
+ closed = {}
+ cur = node(None,start,end)
+ open[cur.pos] = cur
+ opens.append(cur)
+ while len(open):
+ cur = opens.pop(0)
+ if cur.pos not in open: continue
+ del open[cur.pos]
+ closed[cur.pos] = cur
+ if cur.pos == end: break
+ for dx,dy in [(0,-1),(1,0),(0,1),(-1,0)]:#(-1,-1),(1,-1),(-1,1),(1,1)]:
+ x,y = pos = cur.pos[0]+dx,cur.pos[1]+dy
+ if layer[y][x]: continue
+ #check for blocks of diagonals
+ if layer[cur.pos[1]+dy][cur.pos[0]]: continue
+ if layer[cur.pos[1]][cur.pos[0]+dx]: continue
+ new = node(cur,pos,end)
+ if pos in open and new.f >= open[pos].f: continue
+ if pos in closed and new.f >= closed[pos].f: continue
+ if pos in open: del open[pos]
+ if pos in closed: del closed[pos]
+ open[pos] = new
+ lo = 0
+ hi = len(opens)
+ while lo < hi:
+ mid = (lo+hi)/2
+ if new.f < opens[mid].f: hi = mid
+ else: lo = mid + 1
+ opens.insert(lo,new)
+
+ if cur.pos != end:
+ return []
+
+ path = []
+ while cur.prev != None:
+ path.append(cur.pos)
+ cur = cur.prev
+ path.reverse()
+ return path
+
+
+def getline(a,b):
+ """returns a path of points from a to b
+
+ <pre>getline(a,b): return [list of points]</pre>
+
+ <dl>
+ <dt>a<dd>starting point
+ <dt>b<dd>ending point
+ </dl>
+
+ <p>returns a list of points from a to b</p>
+ """
+
+ path = []
+
+ x1,y1 = a
+ x2,y2 = b
+ dx,dy = abs(x2-x1),abs(y2-y1)
+
+ if x2 >= x1: xi1,xi2 = 1,1
+ else: xi1,xi2 = -1,-1
+
+ if y2 >= y1: yi1,yi2 = 1,1
+ else: yi1,yi2 = -1,-1
+
+ if dx >= dy:
+ xi1,yi2 = 0,0
+ d = dx
+ n = dx/2
+ a = dy
+ p = dx
+ else:
+ xi2,yi1 = 0,0
+ d = dy
+ n = dy/2
+ a = dx
+ p = dy
+
+ x,y = x1,y1
+ c = 0
+ while c <= p:
+ path.append((x,y))
+ n += a
+ if n > d:
+ n -= d
+ x += xi1
+ y += yi1
+ x += xi2
+ y += yi2
+ c += 1
+ return path
diff --git a/pgu/ani.py b/pgu/ani.py
new file mode 100644
index 0000000..c33d380
--- /dev/null
+++ b/pgu/ani.py
@@ -0,0 +1,90 @@
+"""animation loading and manipulating functions.
+
+<p>please note that this file is alpha, and is subject to modification in
+future versions of pgu!</p>
+"""
+
+print 'pgu.ani','This module is alpha, and is subject to change.'
+
+import math
+import pygame
+
+def _ani_load(tv,name,parts,frames,shape):
+ l = len(frames)
+ #print name,parts,l
+ n = parts.pop()
+ if len(parts):
+ s = l/n
+ for i in xrange(0,n):
+ _ani_load(tv,name + ".%d"%i,parts[:],frames[s*i:s*(i+1)],shape)
+ return
+
+ for i in xrange(0,n):
+ tv.images[name+".%d"%i] = frames[i],shape
+
+def ani_load(tv,name,img,size,shape,parts):
+ """load an animation from an image
+
+ <pre>ani_load(tv,name,image,size,shape,parts)</pre>
+
+ <dl>
+ <dt>tv<dd>vid to load into
+ <dt>name <dd>prefix name to give the images
+ <dt>image <dd>image to load anis from
+ <dt>size <dd>w,h size of image
+ <dt>shape <dd>shape of image (usually a subset of 0,0,w,h) used for collision detection
+ <dt>parts <dd>list of parts to divide the animation into
+ <br>for example parts = [4,5] would yield 4 animations 5 frames long, 20 total
+ <br>for example parts = [a,b,c] would yield ... images['name.a.b.c'] ..., a*b*c total
+ </dl>
+
+ """
+ parts = parts[:]
+ parts.reverse()
+ w,h = size
+ frames = []
+ for y in xrange(0,img.get_height(),h):
+ for x in xrange(0,img.get_width(),w):
+ frames.append(img.subsurface(x,y,w,h))
+ _ani_load(tv,name,parts,frames,shape)
+
+
+def image_rotate(tv,name,img,shape,angles,diff=0):
+ """rotate an image and put it into tv.images
+
+ <pre>image_rotate(tv,name,image,shape,angles,diff=0)</pre>
+
+ <dl>
+ <dt>tv <dd>vid to load into
+ <dt>name <dd>prefix name to give the images
+ <dt>image <dd>image to load anis from
+ <dt>shape <dd>shape fimage (usually a subset of 0,0,w,h) used for collision detection
+ <dt>angles <dd>a list of angles to render in degrees
+ <dt>diff <dd>a number to add to the angles, to correct for source image not actually being at 0 degrees
+ </dl>
+ """
+ w1,h1 = img.get_width(),img.get_height()
+ shape = pygame.Rect(shape)
+ ps = shape.topleft,shape.topright,shape.bottomleft,shape.bottomright
+ for a in angles:
+ img2 = pygame.transform.rotate(img,a+diff)
+ w2,h2 = img2.get_width(),img2.get_height()
+ minx,miny,maxx,maxy = 1024,1024,0,0
+ for x,y in ps:
+ x,y = x-w1/2,y-h1/2
+ a2 = math.radians(a+diff)
+ #NOTE: the + and - are switched from the normal formula because of
+ #the weird way that pygame does the angle...
+ x2 = x*math.cos(a2) + y*math.sin(a2)
+ y2 = y*math.cos(a2) - x*math.sin(a2)
+ x2,y2 = x2+w2/2,y2+h2/2
+ minx = min(minx,x2)
+ miny = min(miny,y2)
+ maxx = max(maxx,x2)
+ maxy = max(maxy,y2)
+ r = pygame.Rect(minx,miny,maxx-minx,maxy-miny)
+ #print r
+ #((ww-w)/2,(hh-h)/2,w,h)
+ tv.images["%s.%d"%(name,a)] = img2,r
+
+
diff --git a/pgu/engine.py b/pgu/engine.py
new file mode 100644
index 0000000..76be583
--- /dev/null
+++ b/pgu/engine.py
@@ -0,0 +1,154 @@
+"""a state engine.
+"""
+import pygame
+from pygame.locals import *
+
+class State:
+ """Template Class -- for a state.
+
+ <pre>State(game,value...)</pre>
+
+ <dl>
+ <dt>game<dd>The state engine.
+ <dt>value<dd>I usually pass in a custom value to a state
+ </dl>
+
+ <p>For all of the template methods, they should return None unless they return
+ a new State to switch the engine to.</p>
+ """
+ def __init__(self,game,value=None):
+ self.game,self.value = game,value
+ def init(self):
+ """Template Method - Initialize the state, called once the first time a state is selected.
+
+ <pre>State.init()</pre>
+ """
+ return
+ def paint(self,screen):
+ """Template Method - Paint the screen. Called once after the state is selected.
+
+ <p>State is responsible for calling <tt>pygame.display.flip()</tt> or whatever.</p>
+
+ <pre>State.paint(screen)</pre>
+ """
+ return
+
+ def repaint(self):
+ """Template Method - Request a repaint of this state.
+
+ <pre>State.repaint()</pre>
+ """
+ self._paint = 1
+ def update(self,screen):
+ """Template Method - Update the screen.
+
+ <p>State is responsible for calling <tt>pygame.display.update(updates)</tt> or whatever.</p>
+
+ <pre>State.update(screen)</pre>
+ """
+ return
+ def loop(self):
+ """Template Method - Run a logic loop, called once per frame.
+
+ <pre>State.loop()</pre>
+ """
+ return
+ def event(self,e):
+ """Template Method - Recieve an event.
+
+ <pre>State.event(e)</pre>
+ """
+ return
+
+class Quit(State):
+ """A state to quit the state engine.
+
+ <pre>Quit(game,value)</pre>
+ """
+
+ def init(self):
+ self.game.quit = 1
+
+class Game:
+ """Template Class - The state engine.
+ """
+ def fnc(self,f,v=None):
+ s = self.state
+ if not hasattr(s,f): return 0
+ f = getattr(s,f)
+ if v != None: r = f(v)
+ else: r = f()
+ if r != None:
+ self.state = r
+ self.state._paint = 1
+ return 1
+ return 0
+
+ def run(self,state,screen=None):
+ """Run the state engine, this is a infinite loop (until a quit occurs).
+
+ <pre>Game.run(state,screen=None)</pre>
+
+ <dl>
+ <dt>game<dd>a state engine
+ <dt>screen<dd>the screen
+ </dl>
+ """
+ self.quit = 0
+ self.state = state
+ if screen != None: self.screen = screen
+
+ self.init()
+
+ while not self.quit:
+ self.loop()
+
+ def loop(self):
+ s = self.state
+ if not hasattr(s,'_init') or s._init:
+ s._init = 0
+ if self.fnc('init'): return
+ else:
+ if self.fnc('loop'): return
+ if not hasattr(s,'_paint') or s._paint:
+ s._paint = 0
+ if self.fnc('paint',self.screen): return
+ else:
+ if self.fnc('update',self.screen): return
+
+ for e in pygame.event.get():
+ #NOTE: this might break API?
+ #if self.event(e): return
+ if not self.event(e):
+ if self.fnc('event',e): return
+
+ self.tick()
+ return
+
+ def init(self):
+ """Template Method - called at the beginning of State.run() to initialize things.
+
+ <pre>Game.init()</pre>
+ """
+ return
+
+ def tick(self):
+ """Template Method - called once per frame, usually for timer purposes.
+
+ <pre>Game.tick()</pre>
+ """
+ pygame.time.wait(10)
+
+ def event(self,e):
+ """Template Method - called with each event, so the engine can capture special events.
+
+ <pre>Game.event(e): return captured</pre>
+
+ <p>return a True value if the event is captured and does not need to be passed onto the current
+ state</p>
+ """
+ if e.type is QUIT:
+ self.state = Quit(self)
+ return 1
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/engine.pyc b/pgu/engine.pyc
new file mode 100644
index 0000000..36c5d0b
--- /dev/null
+++ b/pgu/engine.pyc
Binary files differ
diff --git a/pgu/fonts.py b/pgu/fonts.py
new file mode 100644
index 0000000..ab6f73d
--- /dev/null
+++ b/pgu/fonts.py
@@ -0,0 +1,130 @@
+"""Some handy font-like objects.
+
+<p>please note that this file is alpha, and is subject to modification in
+future versions of pgu!</p>
+"""
+
+print 'pgu.fonts','This module is alpha, and is subject to change.'
+
+import pygame
+from pygame.locals import *
+
+class TileFont:
+ """Creates an instance of the TileFont class. Interface compatible with pygame.Font
+
+ <p>TileFonts are fonts that are stored in a tiled image. Where the image opaque, it assumed that the font is visible. Font color is changed automatically, so it does not work with
+ fonts with stylized coloring.</p>
+
+ <pre>TileFont(fname,size,hints,scale=None,sensitive=False)</pre>
+
+ <dl>
+ <dt>size <dd>the dimensions of the characters
+ <dt>hints <dd>a string of hints "abcdefg..."
+ <dt>scale <dd>size to scale font to
+ <dt>sensitive <dd>case sensitivity
+ </dl>
+ """
+
+ def __init__(self,fname,size,hints,scale=None,sensitive=False):
+
+ self.image = pygame.image.load(fname)
+
+ w,h = self.image.get_width(),self.image.get_height()
+ tw,th = size
+ if not scale: scale = size
+ self._size = size
+ self.scale = scale
+
+ self.chars = {}
+ x,y = 0,0
+ self.sensitive = sensitive
+ if not self.sensitive: hints = hints.lower()
+ for c in hints:
+ if c not in ('\r','\n','\t'):
+ img = self.image.subsurface(x,y,tw,th)
+ self.chars[c] = img
+ x += tw
+ if x >= w: x,y = 0,y+th
+
+ self.colors = {}
+
+ def size(self,text):
+ tw,th = self.scale
+ return len(text)*tw,th
+
+ def render(self,text,antialias=0,color=(255,255,255),background=None):
+ size = self.size(text)
+ scale = self.scale
+ tw,th = self._size
+ if background == None:
+ s = pygame.Surface(size).convert_alpha()
+ s.fill((0,0,0,0))
+ else:
+ s = pygame.Surface(size).convert()
+ s.fill(background)
+
+ if not self.sensitive: text = text.lower()
+
+ if color not in self.colors: self.colors[color] = {}
+ colored = self.colors[color]
+
+ x,y = 0,0
+ for c in text:
+ if c in self.chars:
+ if c not in colored:
+ img = self.chars[c].convert_alpha()
+ for yy in xrange(0,th):
+ for xx in xrange(0,tw):
+ r,g,b,a = img.get_at((xx,yy))
+ if a > 128:
+ img.set_at((xx,yy),color)
+ colored[c] = img
+ img = colored[c]
+ if scale != (tw,th): img = pygame.transform.scale(img,scale)
+ s.blit(img,(x,y))
+ x += scale[0]
+ return s
+
+
+class BorderFont:
+ """a decorator for normal fonts, adds a border. Interface compatible with pygame.Font.
+
+ <pre>BorderFont(font,size=1,color=(0,0,0))</pre>
+
+ <dl>
+ <dt>size <dd>width of border; defaults 0
+ <dt>color <dd>color of border; default (0,0,0)
+ </dl>
+ """
+ def __init__(self,font,size=1,color=(0,0,0)):
+
+ self.font = font
+ self._size = size
+ self.color = color
+
+ def size(self,text):
+ w,h = self.font.size(text)
+ s = self._size
+ return w+s*2,h+s*2
+
+ def render(self,text,antialias=0,color=(255,255,255),background=None):
+ size = self.size(text)
+
+ if background == None:
+ s = pygame.Surface(size).convert_alpha()
+ s.fill((0,0,0,0))
+ else:
+ s = pygame.Surface(size).convert()
+ s.fill(background)
+
+ bg = self.font.render(text,antialias,self.color)
+ fg = self.font.render(text,antialias,color)
+
+ si = self._size
+ dirs = [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]
+ for dx,dy in dirs: s.blit(bg,(si+dx*si,si+dy*si))
+ s.blit(fg,(si,si))
+
+ return s
+
+
diff --git a/pgu/gui/__init__.py b/pgu/gui/__init__.py
new file mode 100644
index 0000000..256fb63
--- /dev/null
+++ b/pgu/gui/__init__.py
@@ -0,0 +1,32 @@
+import pygame
+from pygame.locals import *
+
+from theme import Theme
+from style import Style
+from widget import Widget
+from surface import subsurface, ProxySurface
+from const import *
+
+from container import Container
+from app import App, Desktop
+from table import Table
+from document import Document
+#html
+from area import SlideBox, ScrollArea, List
+
+from form import Form
+from group import Group
+
+from basic import Spacer, Color, Label, Image, parse_color
+from button import Icon, Button, Switch, Checkbox, Radio, Tool, Link
+from input import Input, Password
+from keysym import Keysym
+from slider import VSlider, HSlider, VScrollBar, HScrollBar
+from select import Select
+from misc import ProgressBar
+
+from menus import Menus
+from dialog import Dialog, FileDialog
+from textarea import TextArea
+
+from deprecated import Toolbox, action_open, action_setvalue, action_quit, action_exec
diff --git a/pgu/gui/__init__.pyc b/pgu/gui/__init__.pyc
new file mode 100644
index 0000000..2da4bda
--- /dev/null
+++ b/pgu/gui/__init__.pyc
Binary files differ
diff --git a/pgu/gui/app.py b/pgu/gui/app.py
new file mode 100644
index 0000000..89ce66c
--- /dev/null
+++ b/pgu/gui/app.py
@@ -0,0 +1,237 @@
+"""
+"""
+import pygame
+from pygame.locals import *
+
+import pguglobals
+import container
+from const import *
+
+class App(container.Container):
+ """The top-level widget for an application.
+
+ <pre>App(theme=None)</pre>
+
+ <dl>
+ <dt>theme<dd>an instance of a Theme, optional as it will use the default Theme class.
+ </dl>
+
+ <strong>Basic Example</strong>
+ <code>
+ app = gui.App()
+ app.run(widget=widget,screen=screen)
+ </code>
+
+ <strong>Integrated Example</strong>
+ <code>
+ app = gui.App()
+ gui.init(widget=widget)
+ while 1:
+ for e in pygame.event.get():
+ app.event(e)
+ app.update(screen)
+ </code>
+
+
+
+ """
+ def __init__(self,theme=None,**params):
+ self.set_global_app()
+
+ if theme == None:
+ from theme import Theme
+ theme = Theme()
+ self.theme = theme
+
+ params['decorate'] = 'app'
+ container.Container.__init__(self,**params)
+ self._quit = False
+ self.widget = None
+ self._chsize = False
+ self._repaint = False
+
+ self.screen = None
+ self.container = None
+ self.events = []
+
+ def set_global_app(self):
+ # Keep a global reference to this application instance so that PGU
+ # components can easily find it.
+ pguglobals.app = self
+ # For backwards compatibility we keep a reference in the class
+ # itself too.
+ App.app = self
+
+ def resize(self):
+
+ screen = self.screen
+ w = self.widget
+ wsize = 0
+
+ #5 cases
+
+ #input screen is already set use its size
+ if screen:
+ self.screen = screen
+ width,height = screen.get_width(),screen.get_height()
+
+ #display.screen
+ elif pygame.display.get_surface():
+ screen = pygame.display.get_surface()
+ self.screen = screen
+ width,height = screen.get_width(),screen.get_height()
+
+ #app has width,height
+ elif self.style.width != 0 and self.style.height != 0:
+ screen = pygame.display.set_mode((self.style.width,self.style.height),SWSURFACE)
+ self.screen = screen
+ width,height = screen.get_width(),screen.get_height()
+
+ #widget has width,height, or its own size..
+ else:
+ wsize = 1
+ width,height = w.rect.w,w.rect.h = w.resize()
+ #w._resize()
+ screen = pygame.display.set_mode((width,height),SWSURFACE)
+ self.screen = screen
+
+ #use screen to set up size of this widget
+ self.style.width,self.style.height = width,height
+ self.rect.w,self.rect.h = width,height
+ self.rect.x,self.rect.y = 0,0
+
+ w.rect.x,w.rect.y = 0,0
+ w.rect.w,w.rect.h = w.resize(width,height)
+
+ for w in self.windows:
+ w.rect.w,w.rect.h = w.resize()
+
+ self._chsize = False
+
+
+ def init(self,widget=None,screen=None): #TODO widget= could conflict with module widget
+ """Initialize the application.
+
+ <pre>App.init(widget=None,screen=None)</pre>
+
+ <dl>
+ <dt>widget<dd>main widget
+ <dt>screen<dd>pygame.Surface to render to
+ </dl>
+ """
+
+ self.set_global_app()
+
+ if widget: self.widget = widget
+ if screen: self.screen = screen
+
+ self.resize()
+
+ w = self.widget
+
+ self.widgets = []
+ self.widgets.append(w)
+ w.container = self
+ self.focus(w)
+
+ pygame.key.set_repeat(500,30)
+
+ self._repaint = True
+ self._quit = False
+
+ self.send(INIT)
+
+ def event(self,e):
+ """Pass an event to the main widget.
+
+ <pre>App.event(e)</pre>
+
+ <dl>
+ <dt>e<dd>event
+ </dl>
+ """
+ self.set_global_app()
+
+ #NOTE: might want to deal with ACTIVEEVENT in the future.
+ self.send(e.type,e)
+ container.Container.event(self,e)
+ if e.type == MOUSEBUTTONUP:
+ if e.button not in (4,5): #ignore mouse wheel
+ sub = pygame.event.Event(CLICK,{
+ 'button':e.button,
+ 'pos':e.pos})
+ self.send(sub.type,sub)
+ container.Container.event(self,sub)
+
+
+ def loop(self):
+ self.set_global_app()
+
+ s = self.screen
+ for e in pygame.event.get():
+ if not (e.type == QUIT and self.mywindow):
+ self.event(e)
+ us = self.update(s)
+ pygame.display.update(us)
+
+
+ def paint(self,screen):
+ self.screen = screen
+ if self._chsize:
+ self.resize()
+ self._chsize = False
+ if hasattr(self,'background'):
+ self.background.paint(screen)
+ container.Container.paint(self,screen)
+
+ def update(self,screen):
+ """Update the screen.
+
+ <dl>
+ <dt>screen<dd>pygame surface
+ </dl>
+ """
+ self.screen = screen
+ if self._chsize:
+ self.resize()
+ self._chsize = False
+ if self._repaint:
+ self.paint(screen)
+ self._repaint = False
+ return [pygame.Rect(0,0,screen.get_width(),screen.get_height())]
+ else:
+ us = container.Container.update(self,screen)
+ return us
+
+ def run(self,widget=None,screen=None):
+ """Run an application.
+
+ <p>Automatically calls <tt>App.init</tt> and then forever loops <tt>App.event</tt> and <tt>App.update</tt></p>
+
+ <dl>
+ <dt>widget<dd>main widget
+ <dt>screen<dd>pygame.Surface to render to
+ </dl>
+ """
+ self.init(widget,screen)
+ while not self._quit:
+ self.loop()
+ pygame.time.wait(10)
+
+ def reupdate(self,w=None): pass
+ def repaint(self,w=None): self._repaint = True
+ def repaintall(self): self._repaint = True
+ def chsize(self):
+ self._chsize = True
+ self._repaint = True
+
+ def quit(self,value=None): self._quit = True
+
+class Desktop(App):
+ """Create an App using the <tt>desktop</tt> theme class.
+
+ <pre>Desktop()</pre>
+ """
+ def __init__(self,**params):
+ params.setdefault('cls','desktop')
+ App.__init__(self,**params)
diff --git a/pgu/gui/app.pyc b/pgu/gui/app.pyc
new file mode 100644
index 0000000..3199e66
--- /dev/null
+++ b/pgu/gui/app.pyc
Binary files differ
diff --git a/pgu/gui/area.py b/pgu/gui/area.py
new file mode 100644
index 0000000..39b8cbd
--- /dev/null
+++ b/pgu/gui/area.py
@@ -0,0 +1,434 @@
+"""
+"""
+import os
+
+import pguglobals
+from const import *
+import surface
+import container, table
+import group
+import basic, button, slider
+
+class SlideBox(container.Container):
+ """A scrollable area with no scrollbars.
+
+ <pre>SlideBox(widget,width,height)</pre>
+
+ <dl>
+ <dt>widget<dd>widget to be able to scroll around
+ <dt>width, height<dd>size of scrollable area
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ c = SlideBox(w,100,100)
+ c.offset = (10,10)
+ c.repaint()
+ </code>
+
+ """
+
+ def __init__(self, widget, width, height, **params):
+ params.setdefault('width', width)
+ params.setdefault('height', height)
+ container.Container.__init__(self, **params)
+ self.offset = [0, 0]
+ self.widget = widget
+
+ def __setattr__(self,k,v):
+ if k == 'widget':
+ if hasattr(self,'widget'):
+ self.remove(self.widget)
+ self.add(v,0,0)
+ self.__dict__[k] = v
+
+
+ def paint(self, s):
+ #if not hasattr(self,'surface'):
+ self.surface = pygame.Surface((self.max_rect.w,self.max_rect.h),0,s)
+ #self.surface.fill((0,0,0,0))
+ pguglobals.app.theme.render(self.surface,self.style.background,pygame.Rect(0,0,self.max_rect.w,self.max_rect.h))
+ self.bkgr = pygame.Surface((s.get_width(),s.get_height()),0,s)
+ self.bkgr.blit(s,(0,0))
+ container.Container.paint(self,self.surface)
+ s.blit(self.surface,(-self.offset[0],-self.offset[1]))
+ self._offset = self.offset[:]
+ return
+
+ def paint_for_when_pygame_supports_other_tricks(self,s):
+ #this would be ideal if pygame had support for it!
+ #and if pgu also had a paint(self,s,rect) method to paint small parts
+ sr = (self.offset[0],self.offset[1],self.max_rect.w,self.max_rect.h)
+ cr = (-self.offset[0],-self.offset[1],s.get_width(),s.get_height())
+ s2 = s.subsurface(sr)
+ s2.set_clip(cr)
+ container.Container.paint(self,s2)
+
+ def proxy_paint(self, s):
+ container.Container.paint(self, surface.ProxySurface(parent=None,
+ rect=self.max_rect,
+ real_surface=s,
+ offset=self.offset))
+ def update(self, s):
+ rects = container.Container.update(self,self.surface)
+
+ rets = []
+ s_rect = pygame.Rect(0,0,s.get_width(),s.get_height())
+
+ if self.offset == self._offset:
+ for r in rects:
+ r2 = r.move((-self.offset[0],-self.offset[1]))
+ if r2.colliderect(s_rect):
+ s.blit(self.surface.subsurface(r),r2)
+ rets.append(r2)
+ else:
+ s.blit(self.bkgr,(0,0))
+ sub = pygame.Rect(self.offset[0],self.offset[1],min(s.get_width(),self.max_rect.w-self.offset[0]),min(s.get_height(),self.max_rect.h-self.offset[1]))
+# print sub
+# print self.surface.get_width(),self.surface.get_height()
+# print s.get_width(),s.get_height()
+# print self.offset
+# print self.style.width,self.style.height
+ s.blit(self.surface.subsurface(sub),(0,0))
+ rets.append(s_rect)
+ self._offset = self.offset[:]
+ return rets
+
+ def proxy_update(self, s):
+ rects = container.Container.update(self, surface.ProxySurface(parent=None,
+ rect=self.max_rect,
+ real_surface=s,
+ offset=self.offset))
+ result = []
+ for r in rects: result.append(pygame.Rect(r).move(self.offset))
+ return result
+
+ def resize(self, width=None, height=None):
+ container.Container.resize(self)
+ self.max_rect = pygame.Rect(self.widget.rect)
+ #self.max_rect.w = max(self.max_rect.w,self.style.width)
+ #self.max_rect.h = max(self.max_rect.h,self.style.height)
+ return self.style.width,self.style.height
+ #self.rect = pygame.Rect(self.rect[0], self.rect[1], self.style.width, self.style.height)
+
+ def event(self, e):
+ if e.type in [MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION]:
+ pos = (e.pos[0] + self.offset[0], e.pos[1] + self.offset[1])
+ if self.max_rect.collidepoint(pos):
+ e_params = {'pos': pos }
+ if e.type == MOUSEMOTION:
+ e_params['buttons'] = e.buttons
+ e_params['rel'] = e.rel
+ else:
+ e_params['button'] = e.button
+ e = pygame.event.Event(e.type, e_params)
+ container.Container.event(self, e)
+
+#class SlideBox(Area):
+# def __init__(self,*args,**params):
+# print 'gui.SlideBox','Scheduled to be renamed to Area.'
+# Area.__init__(self,*args,**params)
+
+class ScrollArea(table.Table):
+ """A scrollable area with scrollbars.
+
+ <pre>ScrollArea(widget,width,height,hscrollbar=True)</pre>
+
+ <dl>
+ <dt>widget<dd>widget to be able to scroll around
+ <dt>width, height<dd>size of scrollable area. Set either to 0 to default to size of widget.
+ <dt>hscrollbar<dd>set to False if you do not wish to have a horizontal scrollbar
+ <dt>vscrollbar<dd>set to False if you do not wish to have a vertical scrollbar
+ <dt>step<dd>set to how far clicks on the icons will step
+ </dl>
+ """
+ def __init__(self, widget, width=0, height=0, hscrollbar=True, vscrollbar=True,step=24, **params):
+ w= widget
+ params.setdefault('cls', 'scrollarea')
+ table.Table.__init__(self, width=width,height=height,**params)
+
+ self.sbox = SlideBox(w, width=width, height=height, cls=self.cls+".content")
+ self.widget = w
+ self.vscrollbar = vscrollbar
+ self.hscrollbar = hscrollbar
+
+ self.step = step
+
+ def __setattr__(self,k,v):
+ if k == 'widget':
+ self.sbox.widget = v
+ self.__dict__[k] = v
+
+ def resize(self,width=None,height=None):
+ widget = self.widget
+ box = self.sbox
+
+ #self.clear()
+ table.Table.clear(self)
+ #print 'resize',self,self._rows
+
+ self.tr()
+ self.td(box)
+
+ widget.rect.w, widget.rect.h = widget.resize()
+ my_width,my_height = self.style.width,self.style.height
+ if not my_width:
+ my_width = widget.rect.w
+ self.hscrollbar = False
+ if not my_height:
+ my_height = widget.rect.h
+ self.vscrollbar = False
+
+ box.style.width,box.style.height = my_width,my_height #self.style.width,self.style.height
+
+ box.rect.w,box.rect.h = box.resize()
+
+ #print widget.rect
+ #print box.rect
+ #r = table.Table.resize(self,width,height)
+ #print r
+ #return r
+
+ #print box.offset
+
+# #this old code automatically adds in a scrollbar if needed
+# #but it doesn't always work
+# self.vscrollbar = None
+# if widget.rect.h > box.rect.h:
+# self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step)
+# self.td(self.vscrollbar)
+# self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None)
+#
+# vs = self.vscrollbar
+# vs.rect.w,vs.rect.h = vs.resize()
+# box.style.width = self.style.width - vs.rect.w
+#
+#
+# self.hscrollbar = None
+# if widget.rect.w > box.rect.w:
+# self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step)
+# self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None)
+# self.tr()
+# self.td(self.hscrollbar)
+#
+# hs = self.hscrollbar
+# hs.rect.w,hs.rect.h = hs.resize()
+# box.style.height = self.style.height - hs.rect.h
+
+ xt,xr,xb,xl = pguglobals.app.theme.getspacing(box)
+
+
+ if self.vscrollbar:
+ self.vscrollbar = slider.VScrollBar(box.offset[1],0, 65535, 0,step=self.step)
+ self.td(self.vscrollbar)
+ self.vscrollbar.connect(CHANGE, self._vscrollbar_changed, None)
+
+ vs = self.vscrollbar
+ vs.rect.w,vs.rect.h = vs.resize()
+ if self.style.width:
+ box.style.width = self.style.width - (vs.rect.w + xl+xr)
+
+ if self.hscrollbar:
+ self.hscrollbar = slider.HScrollBar(box.offset[0], 0,65535, 0,step=self.step)
+ self.hscrollbar.connect(CHANGE, self._hscrollbar_changed, None)
+ self.tr()
+ self.td(self.hscrollbar)
+
+ hs = self.hscrollbar
+ hs.rect.w,hs.rect.h = hs.resize()
+ if self.style.height:
+ box.style.height = self.style.height - (hs.rect.h + xt + xb)
+
+ if self.hscrollbar:
+ hs = self.hscrollbar
+ hs.min = 0
+ hs.max = widget.rect.w - box.style.width
+ hs.style.width = box.style.width
+ hs.size = hs.style.width * box.style.width / max(1,widget.rect.w)
+ else:
+ box.offset[0] = 0
+
+ if self.vscrollbar:
+ vs = self.vscrollbar
+ vs.min = 0
+ vs.max = widget.rect.h - box.style.height
+ vs.style.height = box.style.height
+ vs.size = vs.style.height * box.style.height / max(1,widget.rect.h)
+ else:
+ box.offset[1] = 0
+
+ #print self.style.width,box.style.width, hs.style.width
+
+ r = table.Table.resize(self,width,height)
+ return r
+
+ def x_resize(self, width=None, height=None):
+ w,h = table.Table.resize(self, width, height)
+ if self.hscrollbar:
+ if self.widget.rect.w <= self.sbox.rect.w:
+ self.hscrollbar.size = self.hscrollbar.style.width
+ else:
+ self.hscrollbar.size = max(20,self.hscrollbar.style.width * self.sbox.rect.w / self.widget.rect.w)
+ self._hscrollbar_changed(None)
+ if self.widget.rect.h <= self.sbox.rect.h:
+ self.vscrollbar.size = self.vscrollbar.style.height
+ else:
+ self.vscrollbar.size = max(20,self.vscrollbar.style.height * self.sbox.rect.h / self.widget.rect.h)
+ self._vscrollbar_changed(None)
+ return w,h
+
+ def _vscrollbar_changed(self, xxx):
+ #y = (self.widget.rect.h - self.sbox.rect.h) * self.vscrollbar.value / 1000
+ #if y >= 0: self.sbox.offset[1] = -y
+ self.sbox.offset[1] = self.vscrollbar.value
+ self.sbox.reupdate()
+
+ def _hscrollbar_changed(self, xxx):
+ #x = (self.widget.rect.w - self.sbox.rect.w) * self.hscrollbar.value / 1000
+ #if x >= 0: self.sbox.offset[0] = -x
+ self.sbox.offset[0] = self.hscrollbar.value
+ self.sbox.reupdate()
+
+
+ def set_vertical_scroll(self, percents):
+ #if not self.vscrollbar: return
+ if not hasattr(self.vscrollbar,'value'): return
+ self.vscrollbar.value = percents #min(max(percents*10, 0), 1000)
+ self._vscrollbar_changed(None)
+
+ def set_horizontal_scroll(self, percents):
+ #if not self.hscrollbar: return
+ if not hasattr(self.hscrollbar,'value'): return
+ self.hscrollbar.value = percents #min(max(percents*10, 0), 1000)
+ self._hscrollbar_changed(None)
+
+
+
+
+class _List_Item(button._button):
+ def __init__(self,label=None,image=None,value=None,**params): #TODO label= could conflict with the module label
+ #param image: an imagez.Image object (optional)
+ #param text: a string object
+ params.setdefault('cls','list.item')
+ button._button.__init__(self,**params)
+ self.group = None
+ self.value = value #(self, value)
+ self.widget = None
+
+ if type(label) == str:
+ label = basic.Label(label, cls=self.cls+".label")
+
+ if image and label:
+ self.widget = container.Container()
+ self.widget.add(image, 0, 0)
+ #HACK: improper use of .resize()
+ image.rect.w,image.rect.h = image.resize()
+ self.widget.add(label, image.rect.w, 0)
+ elif image: self.widget = image
+ elif label: self.widget = label
+
+ self.pcls = ""
+
+ def resize(self,width=None,height=None):
+ self.widget.rect.w,self.widget.rect.h = self.widget.resize()
+ return self.widget.rect.w,self.widget.rect.h
+# self.widget._resize()
+# self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h
+
+ def event(self,e):
+ button._button.event(self,e)
+ if self.group.value == self.value: self.pcls = "down"
+
+ def paint(self,s):
+ if self.group.value == self.value: self.pcls = "down"
+ self.widget.paint(surface.subsurface(s,self.widget.rect))
+
+ def click(self):
+ self.group.value = self.value
+ for w in self.group.widgets:
+ if w != self: w.pcls = ""
+
+
+
+class List(ScrollArea):
+ """A list of items in an area.
+
+ <p>This widget can be a form element, it has a value set to whatever item is selected.</p>
+
+ <pre>List(width,height)</pre>
+ """
+ def _change(self, value):
+ self.value = self.group.value
+ self.send(CHANGE)
+
+ def __init__(self, width, height, **params):
+ params.setdefault('cls', 'list')
+ self.table = table.Table(width=width)
+ ScrollArea.__init__(self, self.table, width, height,hscrollbar=False ,**params)
+
+ self.items = []
+
+ g = group.Group()
+ self.group = g
+ g.connect(CHANGE,self._change,None)
+ self.value = self.group.value = None
+
+ self.add = self._add
+ self.remove = self._remove
+
+ def clear(self):
+ """Clear the list.
+
+ <pre>List.clear()</pre>
+ """
+ self.items = []
+ self.group = group.Group()
+ self.group.connect(CHANGE,self._change,None)
+ self.table.clear()
+ self.set_vertical_scroll(0)
+ self.blur(self.myfocus)
+
+ def _docs(self): #HACK: nasty hack to get the docs in "my way"
+ def add(self, label, image=None, value=None):
+ """Add an item to the list.
+
+ <pre>List.add(label,image=None,value=None)</pre>
+
+ <dl>
+ <dt>label<dd>a label for the item
+ <dt>image<dd>an image for the item
+ <dt>value<dd>a value for the item
+ </dl>
+ """
+
+ def remove(self,value):
+ """Remove an item from the list.
+
+ <pre>List.remove(value)</pre>
+
+ <dl>
+ <dt>value<dd>a value of an item to remove from the list
+ </dl>
+ """
+
+ def _add(self, label, image = None, value=None):
+ item = _List_Item(label,image=image,value=value)
+ self.table.tr()
+ self.table.add(item)
+ self.items.append(item)
+ item.group = self.group
+ item.group.add(item)
+
+ def _remove(self, item):
+ for i in self.items:
+ if i.value == item: item = i
+ if item not in self.items: return
+ item.blur()
+ self.items.remove(item)
+ self.group.widgets.remove(item)
+ self.table.remove_row(item.style.row)
+
+#class List(ListArea):
+# def __init__(self,*args,**params):
+# print 'gui.List','Scheduled to be renamed to ListArea. API may also be changed in the future.'
+# ListArea.__init__(self,*args,**params)
diff --git a/pgu/gui/area.pyc b/pgu/gui/area.pyc
new file mode 100644
index 0000000..f2aa1d1
--- /dev/null
+++ b/pgu/gui/area.pyc
Binary files differ
diff --git a/pgu/gui/basic.py b/pgu/gui/basic.py
new file mode 100644
index 0000000..093c6ee
--- /dev/null
+++ b/pgu/gui/basic.py
@@ -0,0 +1,124 @@
+"""These widgets are all grouped together because they are non-interactive widgets.
+"""
+
+import pygame
+
+from const import *
+import widget
+
+# Turns a descriptive string or a tuple into a pygame color
+def parse_color(desc):
+ if (isinstance(desc, pygame.Color)):
+ # Already a color
+ return desc
+ elif (desc and desc[0] == "#"):
+ # Because of a bug in pygame 1.8.1 we need to explicitly define the
+ # alpha value otherwise it will default to transparent.
+ if (len(desc) == 7):
+ desc += "FF"
+ return pygame.Color(desc)
+
+
+class Spacer(widget.Widget):
+ """A invisible space.
+
+ <pre>Spacer(width,height)</pre>
+
+ """
+ def __init__(self,width,height,**params):
+ params.setdefault('focusable',False)
+ widget.Widget.__init__(self,width=width,height=height,**params)
+
+
+class Color(widget.Widget):
+ """A block of color.
+
+ <p>The color can be changed at run-time.</p>
+
+ <pre>Color(value=None)</pre>
+
+ <strong>Example</strong>
+ <code>
+ c = Color()
+ c.value = (255,0,0)
+ c.value = (0,255,0)
+ </code>
+ """
+
+
+ def __init__(self,value=None,**params):
+ params.setdefault('focusable',False)
+ if value != None: params['value']=value
+ widget.Widget.__init__(self,**params)
+
+ def paint(self,s):
+ if hasattr(self,'value'): s.fill(self.value)
+
+ def __setattr__(self,k,v):
+ if k == 'value' and type(v) == str:
+ v = parse_color(v)
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
+
+class Label(widget.Widget):
+ """A text label.
+
+ <pre>Label(value)</pre>
+
+ <dl>
+ <dt>value<dd>text to be displayed
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = Label(value="I own a rubber chicken!")
+
+ w = Label("3 rubber chickens")
+ </code>
+ """
+ def __init__(self,value,**params):
+ params.setdefault('focusable',False)
+ params.setdefault('cls','label')
+ widget.Widget.__init__(self,**params)
+ self.value = value
+ self.font = self.style.font
+ self.style.width, self.style.height = self.font.size(self.value)
+
+ def paint(self,s):
+ s.blit(self.font.render(self.value, 1, self.style.color),(0,0))
+
+class Image(widget.Widget):
+ """An image.
+
+ <pre>Image(value)</pre>
+
+ <dl>
+ <dt>value<dd>a file name or a pygame.Surface
+ </dl>
+
+ """
+ def __init__(self,value,**params):
+ params.setdefault('focusable',False)
+ widget.Widget.__init__(self,**params)
+ if type(value) == str: value = pygame.image.load(value)
+
+ ow,oh = iw,ih = value.get_width(),value.get_height()
+ sw,sh = self.style.width,self.style.height
+
+ if sw and not sh:
+ iw,ih = sw,ih*sw/iw
+ elif sh and not sw:
+ iw,ih = iw*sh/ih,sh
+ elif sw and sh:
+ iw,ih = sw,sh
+
+ if (ow,oh) != (iw,ih):
+ value = pygame.transform.scale(value,(iw,ih))
+ self.style.width,self.style.height = iw,ih
+ self.value = value
+
+ def paint(self,s):
+ s.blit(self.value,(0,0))
diff --git a/pgu/gui/basic.pyc b/pgu/gui/basic.pyc
new file mode 100644
index 0000000..80e1e93
--- /dev/null
+++ b/pgu/gui/basic.pyc
Binary files differ
diff --git a/pgu/gui/button.py b/pgu/gui/button.py
new file mode 100644
index 0000000..90cdd1e
--- /dev/null
+++ b/pgu/gui/button.py
@@ -0,0 +1,351 @@
+"""
+"""
+
+from pygame.locals import *
+
+from const import *
+import widget, surface
+import basic
+
+class _button(widget.Widget):
+ def __init__(self,**params):
+ widget.Widget.__init__(self,**params)
+ self.state = 0
+
+ def event(self,e):
+ if e.type == ENTER: self.repaint()
+ elif e.type == EXIT: self.repaint()
+ elif e.type == FOCUS: self.repaint()
+ elif e.type == BLUR: self.repaint()
+ elif e.type == KEYDOWN:
+ if e.key == K_SPACE or e.key == K_RETURN:
+ self.state = 1
+ self.repaint()
+ elif e.type == MOUSEBUTTONDOWN:
+ self.state = 1
+ self.repaint()
+ elif e.type == KEYUP:
+ if self.state == 1:
+ sub = pygame.event.Event(CLICK,{'pos':(0,0),'button':1})
+ #self.send(sub.type,sub)
+ self._event(sub)
+
+ self.state = 0
+ self.repaint()
+ elif e.type == MOUSEBUTTONUP:
+ self.state = 0
+ self.repaint()
+ elif e.type == CLICK:
+ self.click()
+
+ self.pcls = ""
+ if self.state == 0 and self.container.myhover is self:
+ self.pcls = "hover"
+ if self.state == 1 and self.container.myhover is self:
+ self.pcls = "down"
+
+ def click(self):
+ pass
+
+
+class Button(_button):
+ """A button, buttons can be clicked, they are usually used to set up callbacks.
+
+ <pre>Button(value=None)</pre>
+
+ <dl>
+ <dt>value<dd>either a widget or a string
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = gui.Button("Click Me")
+ w.connect(gui.CLICK,fnc,value)
+ </code>
+ """
+ def __init__(self,value=None,**params):
+ params.setdefault('cls','button')
+ _button.__init__(self,**params)
+ self.value = value
+
+ def __setattr__(self,k,v):
+ if k == 'value' and type(v) == str: v = basic.Label(v,cls=self.cls+".label")
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and v != None:
+ pass
+
+ if k == 'value' and _v != NOATTR and _v != None and _v != v:
+ self.send(CHANGE)
+ self.chsize()
+
+ def resize(self,width=None,height=None):
+ self.value.rect.x,self.value.rect.y = 0,0
+ self.value.rect.w,self.value.rect.h = self.value.resize(width,height)
+ return self.value.rect.w,self.value.rect.h
+#
+# self.value._resize()
+# self.rect.w,self.rect.h = self.value.rect_margin.w,self.value.rect_margin.h
+#
+# if self.style.width: self.rect.w = max(self.rect.w,self.style.width)
+# if self.style.height: self.rect.w = max(self.rect.w,self.style.height)
+#
+# xt,xr,xb,xl = self.value.getspacing()
+#
+# self.value._resize(self.rect.w-(xl+xr),self.rect.h-(xt+xb))
+#
+ def paint(self,s):
+ self.value.pcls = self.pcls
+ self.value.paint(surface.subsurface(s,self.value.rect))
+
+class Switch(_button):
+ """A switch can have two states, True or False.
+
+ <pre>Switch(value=False)</pre>
+
+ <dl>
+ <dt>value<dd>initial value, (True, False)
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = gui.Switch(True)
+ w.connect(gui.CHANGE,fnc,value)
+ </code>
+ """
+ def __init__(self,value=False,**params):
+ params.setdefault('cls','switch')
+ _button.__init__(self,**params)
+ self.value = value
+
+ img = self.style.off
+ self.style.width = img.get_width()
+ self.style.height = img.get_height()
+
+ def paint(self,s):
+ #self.pcls = ""
+ #if self.container.myhover is self: self.pcls = "hover"
+ if self.value: img = self.style.on
+ else: img = self.style.off
+ s.blit(img,(0,0))
+
+ def __setattr__(self,k,v):
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
+
+ def click(self):
+ self.value = not self.value
+
+class Checkbox(_button):
+ """Within a Group of Checkbox widgets several may be selected at a time.
+
+ <pre>Checkbox(group,value=None)</pre>
+
+ <dl>
+ <dt>group<dd>a gui.Group for the Checkbox to belong to
+ <dt>value<dd>the value
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ g = gui.Group(name='colors',value=['r','b'])
+
+ t = gui.Table()
+ t.tr()
+ t.td(gui.Label('Red'))
+ t.td(gui.Checkbox(g,'r'))
+ t.tr()
+ t.td(gui.Label('Green'))
+ t.td(gui.Checkbox(g,'g'))
+ t.tr()
+ t.td(gui.Label('Blue'))
+ t.td(gui.Checkbox(g,'b'))
+ </code>
+ """
+
+ def __init__(self,group,value=None,**params):
+ params.setdefault('cls','checkbox')
+ _button.__init__(self,**params)
+ self.group = group
+ self.group.add(self)
+ if self.group.value == None:
+ self.group.value = []
+ self.value = value
+
+ img = self.style.off
+ self.style.width = img.get_width()
+ self.style.height = img.get_height()
+
+ def paint(self,s):
+ #self.pcls = ""
+ #if self.container.myhover is self: self.pcls = "hover"
+ if self.value in self.group.value: img = self.style.on
+ else: img = self.style.off
+
+ s.blit(img,(0,0))
+
+ def click(self):
+ if self.value in self.group.value:
+ self.group.value.remove(self.value)
+ else:
+ self.group.value.append(self.value)
+ self.group._change()
+
+class Radio(_button):
+ """Within a Group of Radio widgets only one may be selected at a time.
+
+ <pre>Radio(group,value=None)</pre>
+
+ <dl>
+ <dt>group<dd>a gui.Group for the Radio to belong to
+ <dt>value<dd>the value
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ g = gui.Group(name='colors',value='g')
+
+ t = gui.Table()
+ t.tr()
+ t.td(gui.Label('Red'))
+ t.td(gui.Radio(g,'r'))
+ t.tr()
+ t.td(gui.Label('Green'))
+ t.td(gui.Radio(g,'g'))
+ t.tr()
+ t.td(gui.Label('Blue'))
+ t.td(gui.Radio(g,'b'))
+ </code>
+ """
+
+
+ def __init__(self,group=None,value=None,**params):
+ params.setdefault('cls','radio')
+ _button.__init__(self,**params)
+ self.group = group
+ self.group.add(self)
+ self.value = value
+
+ img = self.style.off
+ self.style.width = img.get_width()
+ self.style.height = img.get_height()
+
+ def paint(self,s):
+ #self.pcls = ""
+ #if self.container.myhover is self: self.pcls = "hover"
+ if self.group.value == self.value: img = self.style.on
+ else: img = self.style.off
+ s.blit(img,(0,0))
+
+ def click(self):
+ self.group.value = self.value
+
+class Tool(_button):
+ """Within a Group of Tool widgets only one may be selected at a time.
+
+ <pre>Tool(group,widget=None,value=None)</pre>
+
+ <dl>
+ <dt>group<dd>a gui.Group for the Tool to belong to
+ <dt>widget<dd>a widget to appear on the Tool (similar to a Button)
+ <dt>value<dd>the value
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ g = gui.Group(name='colors',value='g')
+
+ t = gui.Table()
+ t.tr()
+ t.td(gui.Tool(g,'Red','r'))
+ t.tr()
+ t.td(gui.Tool(g,'Green','g'))
+ t.tr()
+ t.td(gui.Tool(g,'Blue','b'))
+ </code>
+ """
+
+ def __init__(self,group,widget=None,value=None,**params): #TODO widget= could conflict with module widget
+ params.setdefault('cls','tool')
+ _button.__init__(self,**params)
+ self.group = group
+ self.group.add(self)
+ self.value = value
+
+ if widget:
+ self.setwidget(widget)
+
+ if self.group.value == self.value: self.pcls = "down"
+
+ def setwidget(self,w):
+ self.widget = w
+
+ def resize(self,width=None,height=None):
+ self.widget.rect.w,self.widget.rect.h = self.widget.resize()
+ #self.widget._resize()
+ #self.rect.w,self.rect.h = self.widget.rect_margin.w,self.widget.rect_margin.h
+
+ return self.widget.rect.w,self.widget.rect.h
+
+ def event(self,e):
+ _button.event(self,e)
+ if self.group.value == self.value: self.pcls = "down"
+
+ def paint(self,s):
+ if self.group.value == self.value: self.pcls = "down"
+ self.widget.paint(surface.subsurface(s,self.widget.rect))
+
+ def click(self):
+ self.group.value = self.value
+ for w in self.group.widgets:
+ if w != self: w.pcls = ""
+
+
+class Icon(_button):
+ """TODO - might be deprecated
+ """
+ def __init__(self,cls,**params):
+ params['cls'] = cls
+ _button.__init__(self,**params)
+ s = self.style.image
+ self.style.width = s.get_width()
+ self.style.height = s.get_height()
+ self.state = 0
+
+ def paint(self,s):
+ #self.pcls = ""
+ #if self.state == 0 and hasattr(self.container,'myhover') and self.container.myhover is self: self.pcls = "hover"
+ #if self.state == 1 and hasattr(self.container,'myhover') and self.container.myhover is self: self.pcls = "down"
+ s.blit(self.style.image,(0,0))
+
+class Link(_button):
+ """A link, links can be clicked, they are usually used to set up callbacks.
+ Basically the same as the button widget, just text only with a different cls. Made for
+ convenience.
+
+ <pre>Link(value=None)</pre>
+
+ <dl>
+ <dt>value<dd>a string
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = gui.Link("Click Me")
+ w.connect(gui.CLICK,fnc,value)
+ </code>
+ """
+ def __init__(self,value,**params):
+ params.setdefault('focusable',True)
+ params.setdefault('cls','link')
+ _button.__init__(self,**params)
+ self.value = value
+ self.font = self.style.font
+ self.style.width, self.style.height = self.font.size(self.value)
+
+ def paint(self,s):
+ s.blit(self.font.render(self.value, 1, self.style.color),(0,0))
+
diff --git a/pgu/gui/button.pyc b/pgu/gui/button.pyc
new file mode 100644
index 0000000..050236a
--- /dev/null
+++ b/pgu/gui/button.pyc
Binary files differ
diff --git a/pgu/gui/const.py b/pgu/gui/const.py
new file mode 100644
index 0000000..c865cd8
--- /dev/null
+++ b/pgu/gui/const.py
@@ -0,0 +1,45 @@
+"""Constants.
+<br><br>
+<strong>Event Types</strong>
+
+<p>from pygame</p>
+<dl>
+<dt>QUIT
+<dt>MOUSEBUTTONDOWN
+<dt>MOUSEBUTTONUP
+<dt>MOUSEMOTION
+<dt>KEYDOWN
+</dl>
+
+<p>gui specific</p>
+<dl>
+<dt>ENTER
+<dt>EXIT
+<dt>BLUR
+<dt>FOCUS
+<dt>CLICK
+<dt>CHANGE
+<dt>OPEN
+<dt>CLOSE
+<dt>INIT
+</dl>
+
+<strong>Other</strong>
+<dl>
+<dt>NOATTR
+</dl>
+"""
+import pygame
+
+from pygame.locals import QUIT, MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION, KEYDOWN, USEREVENT
+ENTER = pygame.locals.USEREVENT + 0
+EXIT = pygame.locals.USEREVENT + 1
+BLUR = pygame.locals.USEREVENT + 2
+FOCUS = pygame.locals.USEREVENT + 3
+CLICK = pygame.locals.USEREVENT + 4
+CHANGE = pygame.locals.USEREVENT + 5
+OPEN = pygame.locals.USEREVENT + 6
+CLOSE = pygame.locals.USEREVENT + 7
+INIT = 'init'
+
+class NOATTR: pass \ No newline at end of file
diff --git a/pgu/gui/const.pyc b/pgu/gui/const.pyc
new file mode 100644
index 0000000..6694c12
--- /dev/null
+++ b/pgu/gui/const.pyc
Binary files differ
diff --git a/pgu/gui/container.py b/pgu/gui/container.py
new file mode 100644
index 0000000..b3dd810
--- /dev/null
+++ b/pgu/gui/container.py
@@ -0,0 +1,455 @@
+"""
+"""
+import pygame
+from pygame.locals import *
+
+from const import *
+import widget, surface
+import pguglobals
+
+class Container(widget.Widget):
+ """The base container widget, can be used as a template as well as stand alone.
+
+ <pre>Container()</pre>
+ """
+ def __init__(self,**params):
+ widget.Widget.__init__(self,**params)
+ self.myfocus = None
+ self.mywindow = None
+ self.myhover = None
+ #self.background = 0
+ self.widgets = []
+ self.windows = []
+ self.toupdate = {}
+ self.topaint = {}
+
+ def update(self,s):
+ updates = []
+
+ if self.myfocus: self.toupdate[self.myfocus] = self.myfocus
+
+ for w in self.topaint:
+ if w is self.mywindow:
+ continue
+ else:
+ sub = surface.subsurface(s,w.rect)
+ sub.blit(w._container_bkgr,(0,0))
+ w.paint(sub)
+ updates.append(pygame.rect.Rect(w.rect))
+
+ for w in self.toupdate:
+ if w is self.mywindow:
+ continue
+ else:
+ us = w.update(surface.subsurface(s,w.rect))
+ if us:
+ for u in us:
+ updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h))
+
+ for w in self.topaint:
+ if w is self.mywindow:
+ w.paint(self.top_surface(s,w))
+ updates.append(pygame.rect.Rect(w.rect))
+ else:
+ continue
+
+ for w in self.toupdate:
+ if w is self.mywindow:
+ us = w.update(self.top_surface(s,w))
+ else:
+ continue
+ if us:
+ for u in us:
+ updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h))
+
+ self.topaint = {}
+ self.toupdate = {}
+
+ return updates
+
+ def repaint(self,w=None):
+ if not w:
+ return widget.Widget.repaint(self)
+ self.topaint[w] = w
+ self.reupdate()
+
+ def reupdate(self,w=None):
+ if not w:
+ return widget.Widget.reupdate(self)
+ self.toupdate[w] = w
+ self.reupdate()
+
+ def paint(self,s):
+ self.toupdate = {}
+ self.topaint = {}
+ for w in self.widgets:
+ try:
+ sub = surface.subsurface(s, w.rect)
+ except:
+ print 'container.paint(): %s not inside %s' % (
+ w.__class__.__name__,self.__class__.__name__)
+ print s.get_width(), s.get_height(), w.rect
+ print ""
+ else:
+ if (not (hasattr(w,'_container_bkgr') and
+ w._container_bkgr.get_width() == sub.get_width() and
+ w._container_bkgr.get_height() == sub.get_height())):
+ w._container_bkgr = sub.copy()
+ w._container_bkgr.fill((0,0,0,0))
+ w._container_bkgr.blit(sub,(0,0))
+
+ w.paint(sub)
+
+ for w in self.windows:
+ print 'container: windows', len(self.windows), s, w.rect
+ w.paint(self.top_surface(s,w))
+
+ def top_surface(self,s,w):
+ x,y = s.get_abs_offset()
+ s = s.get_abs_parent()
+ return surface.subsurface(s,(x+w.rect.x,y+w.rect.y,w.rect.w,w.rect.h))
+
+ def event(self,e):
+ used = False
+
+ if self.mywindow and e.type == MOUSEBUTTONDOWN:
+ w = self.mywindow
+ if self.myfocus is w:
+ if not w.rect.collidepoint(e.pos): self.blur(w)
+ if not self.myfocus:
+ if w.rect.collidepoint(e.pos): self.focus(w)
+
+ if not self.mywindow:
+ #### by Gal Koren
+ ##
+ ## if e.type == FOCUS:
+ if e.type == FOCUS and not self.myfocus:
+ #self.first()
+ pass
+ elif e.type == EXIT:
+ if self.myhover: self.exit(self.myhover)
+ elif e.type == BLUR:
+ if self.myfocus: self.blur(self.myfocus)
+ elif e.type == MOUSEBUTTONDOWN:
+ h = None
+ for w in self.widgets:
+ if not w.disabled: #focusable not considered, since that is only for tabs
+ if w.rect.collidepoint(e.pos):
+ h = w
+ if self.myfocus is not w: self.focus(w)
+ if not h and self.myfocus:
+ self.blur(self.myfocus)
+ elif e.type == MOUSEMOTION:
+ if 1 in e.buttons:
+ if self.myfocus: ws = [self.myfocus]
+ else: ws = []
+ else: ws = self.widgets
+
+ h = None
+ for w in ws:
+ if w.rect.collidepoint(e.pos):
+ h = w
+ if self.myhover is not w: self.enter(w)
+ if not h and self.myhover:
+ self.exit(self.myhover)
+ w = self.myhover
+
+ if w and w is not self.myfocus:
+ sub = pygame.event.Event(e.type,{
+ 'buttons':e.buttons,
+ 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y),
+ 'rel':e.rel})
+ used = w._event(sub)
+
+ w = self.myfocus
+ if w:
+ sub = e
+
+ if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN:
+ sub = pygame.event.Event(e.type,{
+ 'button':e.button,
+ 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)})
+ used = w._event(sub)
+ elif e.type == CLICK and self.myhover is w:
+ sub = pygame.event.Event(e.type,{
+ 'button':e.button,
+ 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)})
+ used = w._event(sub)
+ elif e.type == CLICK: #a dead click
+ pass
+ elif e.type == MOUSEMOTION:
+ sub = pygame.event.Event(e.type,{
+ 'buttons':e.buttons,
+ 'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y),
+ 'rel':e.rel})
+ used = w._event(sub)
+ else:
+ used = w._event(sub)
+
+ if not used:
+ if e.type is KEYDOWN:
+ if e.key is K_TAB and self.myfocus:
+ if (e.mod&KMOD_SHIFT) == 0:
+ self.myfocus.next()
+ else:
+ self.myfocus.previous()
+ return True
+ elif e.key == K_UP:
+ self._move_focus(0,-1)
+ return True
+ elif e.key == K_RIGHT:
+ self._move_focus(1,0)
+ return True
+ elif e.key == K_DOWN:
+ self._move_focus(0,1)
+ return True
+ elif e.key == K_LEFT:
+ self._move_focus(-1,0)
+ return True
+ return used
+
+ def _move_focus(self,dx_,dy_):
+ myfocus = self.myfocus
+ if not self.myfocus: return
+
+ from pgu.gui import App
+ widgets = self._get_widgets(pguglobals.app)
+ #if myfocus not in widgets: return
+ #widgets.remove(myfocus)
+ if myfocus in widgets:
+ widgets.remove(myfocus)
+ rect = myfocus.get_abs_rect()
+ fx,fy = rect.centerx,rect.centery
+
+ def sign(v):
+ if v < 0: return -1
+ if v > 0: return 1
+ return 0
+
+ dist = []
+ for w in widgets:
+ wrect = w.get_abs_rect()
+ wx,wy = wrect.centerx,wrect.centery
+ dx,dy = wx-fx,wy-fy
+ if dx_ > 0 and wrect.left < rect.right: continue
+ if dx_ < 0 and wrect.right > rect.left: continue
+ if dy_ > 0 and wrect.top < rect.bottom: continue
+ if dy_ < 0 and wrect.bottom > rect.top: continue
+ dist.append((dx*dx+dy*dy,w))
+ if not len(dist): return
+ dist.sort()
+ d,w = dist.pop(0)
+ w.focus()
+
+ def _get_widgets(self,c):
+ widgets = []
+ if c.mywindow:
+ widgets.extend(self._get_widgets(c.mywindow))
+ else:
+ for w in c.widgets:
+ if isinstance(w,Container):
+ widgets.extend(self._get_widgets(w))
+ elif not w.disabled and w.focusable:
+ widgets.append(w)
+ return widgets
+
+ def remove(self,w):
+ """Remove a widget from the container.
+
+ <pre>Container.remove(w)</pre>
+ """
+ self.blur(w)
+ self.widgets.remove(w)
+ #self.repaint()
+ self.chsize()
+
+ def add(self,w,x,y):
+ """Add a widget to the container.
+
+ <pre>Container.add(w,x,y)</pre>
+
+ <dl>
+ <dt>x, y<dd>position of the widget
+ </dl>
+ """
+ w.style.x = x
+ w.style.y = y
+ w.container = self
+ #NOTE: this might fix it, sort of...
+ #but the thing is, we don't really want to resize
+ #something if it is going to get resized again later
+ #for no reason...
+ #w.rect.x,w.rect.y = w.style.x,w.style.y
+ #w.rect.w, w.rect.h = w.resize()
+ self.widgets.append(w)
+ self.chsize()
+
+ def open(self,w=None,x=None,y=None):
+ from app import App #HACK: I import it here to prevent circular importing
+ if not w:
+ if (not hasattr(self,'container') or
+ not self.container) and self is not pguglobals.app:
+ self.container = pguglobals.app
+ #print 'top level open'
+ return widget.Widget.open(self)
+
+ if self.container:
+ if x != None: return self.container.open(w,self.rect.x+x,self.rect.y+y)
+ return self.container.open(w)
+
+ w.container = self
+
+ if w.rect.w == 0 or w.rect.h == 0: #this might be okay, not sure if needed.
+ #_chsize = App.app._chsize #HACK: we don't want this resize to trigger a chsize.
+ w.rect.w,w.rect.h = w.resize()
+ #App.app._chsize = _chsize
+
+ if x == None or y == None: #auto center the window
+ #w.style.x,w.style.y = 0,0
+ w.rect.x = (self.rect.w-w.rect.w)/2
+ w.rect.y = (self.rect.h-w.rect.h)/2
+ #w.resize()
+ #w._resize(self.rect.w,self.rect.h)
+ else: #show it where we want it
+ w.rect.x = x
+ w.rect.y = y
+ #w._resize()
+
+
+ self.windows.append(w)
+ self.mywindow = w
+ self.focus(w)
+ self.repaint(w)
+ w.send(OPEN)
+
+ def close(self,w=None):
+ if not w:
+ return widget.Widget.close(self)
+
+ if self.container: #make sure we're in the App
+ return self.container.close(w)
+
+ if self.myfocus is w: self.blur(w)
+
+ if w not in self.windows: return #no need to remove it twice! happens.
+
+ self.windows.remove(w)
+
+ self.mywindow = None
+ if self.windows:
+ self.mywindow = self.windows[-1]
+ self.focus(self.mywindow)
+
+ if not self.mywindow:
+ self.myfocus = self.widget #HACK: should be done fancier, i think..
+ if not self.myhover:
+ self.enter(self.widget)
+
+ self.repaintall()
+ w.send(CLOSE)
+
+ def focus(self,w=None):
+ widget.Widget.focus(self) ### by Gal koren
+# if not w:
+# return widget.Widget.focus(self)
+ if not w: return
+ if self.myfocus: self.blur(self.myfocus)
+ if self.myhover is not w: self.enter(w)
+ self.myfocus = w
+ w._event(pygame.event.Event(FOCUS))
+
+ #print self.myfocus,self.myfocus.__class__.__name__
+
+ def blur(self,w=None):
+ if not w:
+ return widget.Widget.blur(self)
+ if self.myfocus is w:
+ if self.myhover is w: self.exit(w)
+ self.myfocus = None
+ w._event(pygame.event.Event(BLUR))
+
+ def enter(self,w):
+ if self.myhover: self.exit(self.myhover)
+ self.myhover = w
+ w._event(pygame.event.Event(ENTER))
+
+ def exit(self,w):
+ if self.myhover and self.myhover is w:
+ self.myhover = None
+ w._event(pygame.event.Event(EXIT))
+
+
+# def first(self):
+# for w in self.widgets:
+# if w.focusable:
+# self.focus(w)
+# return
+# if self.container: self.container.next(self)
+
+# def next(self,w):
+# if w not in self.widgets: return #HACK: maybe. this happens in windows for some reason...
+#
+# for w in self.widgets[self.widgets.index(w)+1:]:
+# if w.focusable:
+# self.focus(w)
+# return
+# if self.container: return self.container.next(self)
+
+
+ def _next(self,orig=None):
+ start = 0
+ if orig in self.widgets: start = self.widgets.index(orig)+1
+ for w in self.widgets[start:]:
+ if not w.disabled and w.focusable:
+ if isinstance(w,Container):
+ if w._next():
+ return True
+ else:
+ self.focus(w)
+ return True
+ return False
+
+ def _previous(self,orig=None):
+ end = len(self.widgets)
+ if orig in self.widgets: end = self.widgets.index(orig)
+ ws = self.widgets[:end]
+ ws.reverse()
+ for w in ws:
+ if not w.disabled and w.focusable:
+ if isinstance(w,Container):
+ if w._previous():
+ return True
+ else:
+ self.focus(w)
+ return True
+ return False
+
+ def next(self,w=None):
+ if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason...
+
+ if self._next(w): return True
+ if self.container: return self.container.next(self)
+
+
+ def previous(self,w=None):
+ if w != None and w not in self.widgets: return #HACK: maybe. this happens in windows for some reason...
+
+ if self._previous(w): return True
+ if self.container: return self.container.previous(self)
+
+ def resize(self,width=None,height=None):
+ #r = self.rect
+ #r.w,r.h = 0,0
+ ww,hh = 0,0
+ if self.style.width: ww = self.style.width
+ if self.style.height: hh = self.style.height
+
+ for w in self.widgets:
+ #w.rect.w,w.rect.h = 0,0
+ w.rect.x,w.rect.y = w.style.x,w.style.y
+ w.rect.w, w.rect.h = w.resize()
+ #w._resize()
+
+ ww = max(ww,w.rect.right)
+ hh = max(hh,w.rect.bottom)
+ return ww,hh
diff --git a/pgu/gui/container.pyc b/pgu/gui/container.pyc
new file mode 100644
index 0000000..6379fe2
--- /dev/null
+++ b/pgu/gui/container.pyc
Binary files differ
diff --git a/pgu/gui/deprecated.py b/pgu/gui/deprecated.py
new file mode 100644
index 0000000..8d53515
--- /dev/null
+++ b/pgu/gui/deprecated.py
@@ -0,0 +1,76 @@
+import pygame
+
+from const import *
+import table
+import group
+import button, basic
+import pguglobals
+
+def action_open(value):
+ print 'gui.action_open',"Scheduled to be deprecated."
+ value.setdefault('x',None)
+ value.setdefault('y',None)
+ value['container'].open(value['window'],value['x'],value['y'])
+
+def action_setvalue(value):
+ print 'gui.action_setvalue',"Scheduled to be deprecated."
+ a,b = value
+ b.value = a.value
+
+def action_quit(value):
+ print 'gui.action_quit',"Scheduled to be deprecated."
+ value.quit()
+
+def action_exec(value):
+ print 'gui.action_exec',"Scheduled to be deprecated."
+ exec(value['script'],globals(),value['dict'])
+
+class Toolbox(table.Table):
+ def __setattr__(self,k,v):
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.group.value = v
+ for w in self.group.widgets:
+ if w.value != v: w.pcls = ""
+ else: w.pcls = "down"
+ self.repaint()
+
+ def _change(self,value):
+ self.value = self.group.value
+ self.send(CHANGE)
+
+ def __init__(self,data,cols=0,rows=0,tool_cls='tool',value=None,**params):
+ print 'gui.Toolbox','Scheduled to be deprecated.'
+ params.setdefault('cls','toolbox')
+ table.Table.__init__(self,**params)
+
+ if cols == 0 and rows == 0: cols = len(data)
+ if cols != 0 and rows != 0: rows = 0
+
+ self.tools = {}
+
+ _value = value
+
+ g = group.Group()
+ self.group = g
+ g.connect(CHANGE,self._change,None)
+ self.group.value = _value
+
+ x,y,p,s = 0,0,None,1
+ for ico,value in data:
+ #from __init__ import theme
+ img = pguglobals.app.theme.get(tool_cls+"."+ico,"","image")
+ if img:
+ i = basic.Image(img)
+ else: i = basic.Label(ico,cls=tool_cls+".label")
+ p = button.Tool(g,i,value,cls=tool_cls)
+ self.tools[ico] = p
+ #p.style.hexpand = 1
+ #p.style.vexpand = 1
+ self.add(p,x,y)
+ s = 0
+ if cols != 0: x += 1
+ if cols != 0 and x == cols: x,y = 0,y+1
+ if rows != 0: y += 1
+ if rows != 0 and y == rows: x,y = x+1,0
diff --git a/pgu/gui/deprecated.pyc b/pgu/gui/deprecated.pyc
new file mode 100644
index 0000000..4e5c7bc
--- /dev/null
+++ b/pgu/gui/deprecated.pyc
Binary files differ
diff --git a/pgu/gui/dialog.py b/pgu/gui/dialog.py
new file mode 100644
index 0000000..03b48f9
--- /dev/null
+++ b/pgu/gui/dialog.py
@@ -0,0 +1,157 @@
+"""
+"""
+import os
+
+from const import *
+import table, area
+import basic, input, button
+import pguglobals
+
+class Dialog(table.Table):
+ """A dialog window with a title bar and an "close" button on the bar.
+
+ <pre>Dialog(title,main)</pre>
+
+ <dl>
+ <dt>title<dd>title widget, usually a label
+ <dt>main<dd>main widget, usually a container
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ title = gui.Label("My Title")
+ main = gui.Container()
+ #add stuff to the container...
+
+ d = gui.Dialog(title,main)
+ d.open()
+ </code>
+ """
+ def __init__(self,title,main,**params):
+ params.setdefault('cls','dialog')
+ table.Table.__init__(self,**params)
+
+
+ self.tr()
+ self.td(title,align=-1,cls=self.cls+'.bar')
+ clos = button.Icon(self.cls+".bar.close")
+ clos.connect(CLICK,self.close,None)
+ self.td(clos,align=1,cls=self.cls+'.bar')
+
+ self.tr()
+ self.td(main,colspan=2,cls=self.cls+".main")
+
+
+# self.tr()
+#
+#
+# t = table.Table(cls=self.cls+".bar")
+# t.tr()
+# t.td(title)
+# clos = button.Icon(self.cls+".bar.close")
+# t.td(clos,align=1)
+# clos.connect(CLICK,self.close,None)
+# self.add(t,0,0)
+#
+# main.rect.w,main.rect.h = main.resize()
+# clos.rect.w,clos.rect.h = clos.resize()
+# title.container.style.width = main.rect.w - clos.rect.w
+#
+# self.tr()
+# self.td(main,cls=self.cls+".main")
+#
+
+
+class FileDialog(Dialog):
+ """A file picker dialog window.
+
+ <pre>FileDialog()</pre>
+ <p>Some optional parameters:</p>
+ <dl>
+ <dt>title_txt<dd>title text
+ <dt>button_txt<dd>button text
+ <dt>path<dd>initial path
+ </dl>
+ """
+
+ def __init__(self, title_txt="File Browser", button_txt="Okay", cls="dialog", path=None):
+
+ cls1 = 'filedialog'
+ if not path: self.curdir = os.getcwd()
+ else: self.curdir = path
+ self.dir_img = basic.Image(
+ pguglobals.app.theme.get(cls1+'.folder', '', 'image'))
+ td_style = {'padding_left': 4,
+ 'padding_right': 4,
+ 'padding_top': 2,
+ 'padding_bottom': 2}
+ self.title = basic.Label(title_txt, cls=cls+".title.label")
+ self.body = table.Table()
+ self.list = area.List(width=350, height=150)
+ self.input_dir = input.Input()
+ self.input_file = input.Input()
+ self._list_dir_()
+ self.button_ok = button.Button(button_txt)
+ self.body.tr()
+ self.body.td(basic.Label("Folder"), style=td_style, align=-1)
+ self.body.td(self.input_dir, style=td_style)
+ self.body.tr()
+ self.body.td(self.list, colspan=3, style=td_style)
+ self.list.connect(CHANGE, self._item_select_changed_, None)
+ self.button_ok.connect(CLICK, self._button_okay_clicked_, None)
+ self.body.tr()
+ self.body.td(basic.Label("File"), style=td_style, align=-1)
+ self.body.td(self.input_file, style=td_style)
+ self.body.td(self.button_ok, style=td_style)
+ self.value = None
+ Dialog.__init__(self, self.title, self.body)
+
+ def _list_dir_(self):
+ self.input_dir.value = self.curdir
+ self.input_dir.pos = len(self.curdir)
+ self.input_dir.vpos = 0
+ dirs = []
+ files = []
+ try:
+ for i in os.listdir(self.curdir):
+ if os.path.isdir(os.path.join(self.curdir, i)): dirs.append(i)
+ else: files.append(i)
+ except:
+ self.input_file.value = "Opps! no access"
+ #if '..' not in dirs: dirs.append('..')
+ dirs.sort()
+ dirs = ['..'] + dirs
+
+ files.sort()
+ for i in dirs:
+ #item = ListItem(image=self.dir_img, text=i, value=i)
+ self.list.add(i,image=self.dir_img,value=i)
+ for i in files:
+ #item = ListItem(image=None, text=i, value=i)
+ self.list.add(i,value=i)
+ #self.list.resize()
+ self.list.set_vertical_scroll(0)
+ #self.list.repaintall()
+
+
+ def _item_select_changed_(self, arg):
+ self.input_file.value = self.list.value
+ fname = os.path.abspath(os.path.join(self.curdir, self.input_file.value))
+ if os.path.isdir(fname):
+ self.input_file.value = ""
+ self.curdir = fname
+ self.list.clear()
+ self._list_dir_()
+
+
+ def _button_okay_clicked_(self, arg):
+ if self.input_dir.value != self.curdir:
+ if os.path.isdir(self.input_dir.value):
+ self.input_file.value = ""
+ self.curdir = os.path.abspath(self.input_dir.value)
+ self.list.clear()
+ self._list_dir_()
+ else:
+ self.value = os.path.join(self.curdir, self.input_file.value)
+ self.send(CHANGE)
+ self.close()
diff --git a/pgu/gui/dialog.pyc b/pgu/gui/dialog.pyc
new file mode 100644
index 0000000..8837016
--- /dev/null
+++ b/pgu/gui/dialog.pyc
Binary files differ
diff --git a/pgu/gui/document.py b/pgu/gui/document.py
new file mode 100644
index 0000000..1bd96df
--- /dev/null
+++ b/pgu/gui/document.py
@@ -0,0 +1,112 @@
+"""
+"""
+import pygame
+
+import container
+import layout
+
+class _document_widget:
+ def __init__(self,w,align=None):
+ #w.rect.w,w.rect.h = w.resize()
+ #self.rect = w.rect
+ self.widget = w
+ if align != None: self.align = align
+
+class Document(container.Container):
+ """A document container contains many widgets strung together in a document format. (How informative!)
+
+ <pre>Document()</pre>
+
+ """
+ def __init__(self,**params):
+ params.setdefault('cls','document')
+ container.Container.__init__(self,**params)
+ self.layout = layout.Layout(pygame.Rect(0,0,self.rect.w,self.rect.h))
+
+ def add(self,e,align=None):
+ """Add a widget.
+
+ <pre>Document.add(e,align=None)</pre>
+
+ <dl>
+ <dt>e<dd>widget
+ <dt>align<dd>alignment (None,-1,0,1)
+ </dl>
+ """
+ dw = _document_widget(e,align)
+ self.layout.add(dw)
+ e.container = self
+ e._c_dw = dw
+ self.widgets.append(e)
+ self.chsize()
+
+ def remove(self,e):
+ self.layout._widgets.remove(e._c_dw)
+ self.widgets.remove(e)
+ self.chsize()
+
+
+ def block(self,align):
+ """Start a new block.
+
+ <pre>Document.block(align)</pre>
+
+ <dl>
+ <dt>align<dd>alignment of block (-1,0,1)
+ </dl>
+ """
+ self.layout.add(align)
+
+ def space(self,e):
+ """Add a spacer.
+
+ <pre>Document.space(e)</pre>
+
+ <dl>
+ <dt>e<dd>a (w,h) size for the spacer
+ </dl>
+ """
+ self.layout.add(e)
+
+ def br(self,height):
+ """Add a line break.
+
+ <pre>Document.br(height)</pre>
+
+ <dl>
+ <dt>height<dd>height of line break
+ </dl>
+ """
+ self.layout.add((0,height))
+
+ def resize(self,width=None,height=None):
+ if self.style.width: width = self.style.width
+ if self.style.height: height = self.style.height
+
+ for w in self.widgets:
+ w.rect.w,w.rect.h = w.resize()
+
+ if (width != None and w.rect.w > width) or (height != None and w.rect.h > height):
+ w.rect.w,w.rect.h = w.resize(width,height)
+
+ dw = w._c_dw
+ dw.rect = pygame.Rect(0,0,w.rect.w,w.rect.h)
+
+ if width == None: width = 65535
+ self.layout.rect = pygame.Rect(0,0,width,0)
+ self.layout.resize()
+
+ _max_w = 0
+
+ for w in self.widgets:
+ #xt,xl,xb,xr = w.getspacing()
+ dw = w._c_dw
+ w.style.x,w.style.y,w.rect.w,w.rect.h = dw.rect.x,dw.rect.y,dw.rect.w,dw.rect.h
+ #w.resize()
+ w.rect.x,w.rect.y = w.style.x,w.style.y
+ _max_w = max(_max_w,w.rect.right)
+
+ #self.rect.w = _max_w #self.layout.rect.w
+ #self.rect.h = self.layout.rect.h
+ #print 'document',_max_w,self.layout.rect.h
+ return _max_w,self.layout.rect.h
diff --git a/pgu/gui/document.pyc b/pgu/gui/document.pyc
new file mode 100644
index 0000000..1cbdfed
--- /dev/null
+++ b/pgu/gui/document.pyc
Binary files differ
diff --git a/pgu/gui/form.py b/pgu/gui/form.py
new file mode 100644
index 0000000..9a874f9
--- /dev/null
+++ b/pgu/gui/form.py
@@ -0,0 +1,79 @@
+"""
+"""
+import widget
+
+class Form(widget.Widget):
+ """A form that automatically will contain all named widgets.
+
+ <p>After a form is created, all named widget that are subsequently created are added
+ to that form. You may use dict style access to access named widgets.</p>
+
+ <pre>Form()</pre>
+
+ <strong>Example</strong>
+ <code>
+ f = gui.Form()
+
+ w = gui.Input("Phil",name="firstname")
+ w = gui.Input("Hassey",name="lastname")
+
+ print f.results()
+ print ''
+ print f.items()
+ print ''
+ print f['firstname'].value
+ print f['lastname'].value
+ </code>
+ """
+
+ def __init__(self):
+ widget.Widget.__init__(self,decorate=False)
+ self._elist = []
+ self._emap = {}
+ self._dirty = 0
+ Form.form = self
+
+ def add(self,e,name=None,value=None):
+ if name != None: e.name = name
+ if value != None: e.value = value
+ self._elist.append(e)
+ self._dirty = 1
+
+ def _clean(self):
+ for e in self._elist[:]:
+ if not hasattr(e,'name') or e.name == None:
+ self._elist.remove(e)
+ self._emap = {}
+ for e in self._elist:
+ self._emap[e.name] = e
+ self._dirty = 0
+
+ def __getitem__(self,k):
+ if self._dirty: self._clean()
+ return self._emap[k]
+
+ def __contains__(self,k):
+ if self._dirty: self._clean()
+ if k in self._emap: return True
+ return False
+
+ def results(self):
+ """Return a dict of name => values.
+
+ <pre>Form.results(): return dict</pre>
+ """
+ if self._dirty: self._clean()
+ r = {}
+ for e in self._elist:
+ r[e.name] = e.value
+ return r
+
+ def items(self):
+ """Return a list of name, value keys.
+
+ <pre>Form.items(): return list</pre>
+ """
+ return self.results().items()
+
+ #def start(self):
+ # Object.start(self,-1)
diff --git a/pgu/gui/form.pyc b/pgu/gui/form.pyc
new file mode 100644
index 0000000..f9e0e44
--- /dev/null
+++ b/pgu/gui/form.pyc
Binary files differ
diff --git a/pgu/gui/group.py b/pgu/gui/group.py
new file mode 100644
index 0000000..bcb231a
--- /dev/null
+++ b/pgu/gui/group.py
@@ -0,0 +1,43 @@
+"""
+"""
+from const import *
+import widget
+
+class Group(widget.Widget):
+ """An object for grouping together Form elements.
+
+ <pre>Group(name=None,value=None)</pre>
+
+ <dl>
+ <dt>name<dd>name as used in the Form
+ <dt>value<dd>values that are currently selected in the group
+ </dl>
+
+ <p>See [[gui-button]] for several examples.</p>
+
+ <p>When the value changes, an <tt>gui.CHANGE</tt> event is sent.
+ Although note, that when the value is a list, it may have to be sent by hand via
+ <tt>g.send(gui.CHANGE)</tt></p>
+ """
+
+ def __init__(self,name=None,value=None):
+ widget.Widget.__init__(self,name=name,value=value)
+ self.widgets = []
+
+ def add(self,w):
+ """Add a widget to this group.
+
+ <pre>Group.add(w)</pre>
+ """
+ self.widgets.append(w)
+
+ def __setattr__(self,k,v):
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k] = v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self._change()
+
+ def _change(self):
+ self.send(CHANGE)
+ for w in self.widgets:
+ w.repaint()
diff --git a/pgu/gui/group.pyc b/pgu/gui/group.pyc
new file mode 100644
index 0000000..1766fc6
--- /dev/null
+++ b/pgu/gui/group.pyc
Binary files differ
diff --git a/pgu/gui/input.py b/pgu/gui/input.py
new file mode 100644
index 0000000..74c3c9e
--- /dev/null
+++ b/pgu/gui/input.py
@@ -0,0 +1,166 @@
+"""
+"""
+import pygame
+from pygame.locals import *
+
+from const import *
+import widget
+
+class Input(widget.Widget):
+ """A single line text input.
+
+ <pre>Input(value="",size=20)</pre>
+
+ <dl>
+ <dt>value<dd>initial text
+ <dt>size<dd>size for the text box, in characters
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = Input(value="Cuzco the Goat",size=20)
+
+ w = Input("Marbles")
+ </code>
+
+ """
+ def __init__(self,value="",size=20,**params):
+ params.setdefault('cls','input')
+ widget.Widget.__init__(self,**params)
+ self.value = value
+ self.pos = len(str(value))
+ self.vpos = 0
+ self.font = self.style.font
+ w,h = self.font.size("e"*size)
+ if not self.style.height: self.style.height = h
+ if not self.style.width: self.style.width = w
+ #self.style.height = max(self.style.height,h)
+ #self.style.width = max(self.style.width,w)
+ #self.rect.w=w+self.style.padding_left+self.style.padding_right;
+ #self.rect.h=h+self.style.padding_top+self.style.padding_bottom;
+
+ def paint(self,s):
+
+ r = pygame.Rect(0,0,self.rect.w,self.rect.h)
+
+ cs = 2 #NOTE: should be in a style
+
+ w,h = self.font.size(self.value[0:self.pos])
+ x = w-self.vpos
+ if x < 0: self.vpos -= -x
+ if x+cs > s.get_width(): self.vpos += x+cs-s.get_width()
+
+ s.blit(self.font.render(self.value, 1, self.style.color),(-self.vpos,0))
+
+ if self.container.myfocus is self:
+ w,h = self.font.size(self.value[0:self.pos])
+ r.x = w-self.vpos
+ r.w = cs
+ r.h = h
+ s.fill(self.style.color,r)
+
+ def _setvalue(self,v):
+ self.__dict__['value'] = v
+ self.send(CHANGE)
+
+ def event(self,e):
+ used = None
+ if e.type == KEYDOWN:
+ if e.key == K_BACKSPACE:
+ if self.pos:
+ self._setvalue(self.value[:self.pos-1] + self.value[self.pos:])
+ self.pos -= 1
+ elif e.key == K_DELETE:
+ if len(self.value) > self.pos:
+ self._setvalue(self.value[:self.pos] + self.value[self.pos+1:])
+ elif e.key == K_HOME:
+ self.pos = 0
+ elif e.key == K_END:
+ self.pos = len(self.value)
+ elif e.key == K_LEFT:
+ if self.pos > 0: self.pos -= 1
+ used = True
+ elif e.key == K_RIGHT:
+ if self.pos < len(self.value): self.pos += 1
+ used = True
+ elif e.key == K_RETURN:
+ self.next()
+ elif e.key == K_TAB:
+ pass
+ else:
+ #c = str(e.unicode)
+ try:
+ c = (e.unicode).encode('latin-1')
+ if c:
+ self._setvalue(self.value[:self.pos] + c + self.value[self.pos:])
+ self.pos += 1
+ except: #ignore weird characters
+ pass
+ self.repaint()
+ elif e.type == FOCUS:
+ self.repaint()
+ elif e.type == BLUR:
+ self.repaint()
+
+ self.pcls = ""
+ if self.container.myfocus is self: self.pcls = "focus"
+
+ return used
+
+ def __setattr__(self,k,v):
+ if k == 'value':
+ if v == None: v = ''
+ v = str(v)
+ self.pos = len(v)
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
+
+class Password(Input):
+ """A password input, text is *-ed out.
+
+ <pre>Password(value="",size=20)</pre>
+
+ <dl>
+ <dt>value<dd>initial text
+ <dt>size<dd>size for the text box, in characters
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = Password(value="password",size=20)
+
+ w = Password("53[r3+")
+ </code>
+
+ """
+
+ def paint(self,s):
+ hidden="*"
+ show=len(self.value)*hidden
+
+ #print "self.value:",self.value
+
+ if self.pos == None: self.pos = len(self.value)
+
+ r = pygame.Rect(0,0,self.rect.w,self.rect.h)
+
+ cs = 2 #NOTE: should be in a style
+
+ w,h = self.font.size(show)
+ x = w-self.vpos
+ if x < 0: self.vpos -= -x
+ if x+cs > s.get_width(): self.vpos += x+cs-s.get_width()
+
+ s.blit(self.font.render(show, 1, self.style.color),(-self.vpos,0))
+
+ if self.container.myfocus is self:
+ #w,h = self.font.size(self.value[0:self.pos])
+ w,h = self.font.size(show[0:self.pos])
+ r.x = w-self.vpos
+ r.w = cs
+ r.h = h
+ s.fill(self.style.color,r)
+
diff --git a/pgu/gui/input.pyc b/pgu/gui/input.pyc
new file mode 100644
index 0000000..f6ba8c1
--- /dev/null
+++ b/pgu/gui/input.pyc
Binary files differ
diff --git a/pgu/gui/keysym.py b/pgu/gui/keysym.py
new file mode 100644
index 0000000..cc24089
--- /dev/null
+++ b/pgu/gui/keysym.py
@@ -0,0 +1,72 @@
+"""
+"""
+import pygame
+from pygame.locals import *
+
+from const import *
+import widget
+
+class Keysym(widget.Widget):
+ """A keysym input.
+
+ <p>This widget records the keysym of the key pressed while this widget is in focus.</p>
+
+ <pre>Keysym(value=None)</pre>
+
+ <dl>
+ <dt>value<dd>initial keysym, see <a href="http://www.pygame.org/docs/ref/key.html">pygame keysyms</a> </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = Input(value=pygame.locals.K_g)
+
+ w = Input(pygame.locals.K_g)
+
+ w = Input()
+ </code>
+
+ """
+
+ def __init__(self,value=None,**params):
+ params.setdefault('cls','keysym')
+ widget.Widget.__init__(self,**params)
+ self.value = value
+
+ self.font = self.style.font
+ w,h = self.font.size("Right Super") #"Right Shift")
+ self.style.width,self.style.height = w,h
+ #self.rect.w=w+self.style.padding_left+self.style.padding_right
+ #self.rect.h=h+self.style.padding_top+self.style.padding_bottom
+
+ def event(self,e):
+ used = None
+ if e.type == FOCUS or e.type == BLUR: self.repaint()
+ elif e.type == KEYDOWN:
+ if e.key != K_TAB:
+ self.value = e.key
+ self.repaint()
+ self.send(CHANGE)
+ used = True
+ self.next()
+ self.pcls = ""
+ if self.container.myfocus is self: self.pcls = "focus"
+ return used
+
+ def paint(self,s):
+ r = pygame.rect.Rect(0,0,self.rect.w,self.rect.h)
+ #render_box(s,self.style.background,r)
+ if self.value == None: return
+ name = ""
+ for p in pygame.key.name(self.value).split(): name += p.capitalize()+" "
+ #r.x = self.style.padding_left;
+ #r.y = self.style.padding_bottom;
+ s.blit(self.style.font.render(name, 1, self.style.color), r)
+
+ def __setattr__(self,k,v):
+ if k == 'value' and v != None:
+ v = int(v)
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
diff --git a/pgu/gui/keysym.pyc b/pgu/gui/keysym.pyc
new file mode 100644
index 0000000..cc4bf2a
--- /dev/null
+++ b/pgu/gui/keysym.pyc
Binary files differ
diff --git a/pgu/gui/layout.py b/pgu/gui/layout.py
new file mode 100644
index 0000000..01fe0cb
--- /dev/null
+++ b/pgu/gui/layout.py
@@ -0,0 +1,172 @@
+"""document layout engine."""
+class Layout:
+ """the document layout engine
+
+ .widgets -- elements are kept in this list. read-only, use add to add items to it.
+ """
+
+ def __init__(self,rect=None):
+ """initialize the object with the size of the box."""
+ self._widgets = []
+ self.rect = rect
+
+ def add(self,e):
+ """add a document element to the layout.
+
+ a document element may be
+ - a tuple (w,h) if it is a whitespace element
+ - a tuple (0,h) if it is a linebreak element
+ - an integer -1,0,1 if it is a command to start a new block of elements that are aligned either left,center, or right.
+ - an object with a .rect (for size) -- such as a word element
+ - an object with a .rect (for size) and .align -- such as an image element
+ """
+
+ self._widgets.append(e)
+
+
+ def resize(self):
+ """resize the layout
+ this method recalculates the position of all document elements
+ after they have been added to the document. .rect.x,y will be updated for all
+ objects.
+ """
+ self.init()
+ self.widgets = []
+ for e in self._widgets:
+ if type(e) is tuple and e[0] != 0:
+ self.do_space(e)
+ elif type(e) is tuple and e[0] == 0:
+ self.do_br(e[1])
+ elif type(e) is int:
+ self.do_block(align=e)
+ elif hasattr(e,'align'):
+ self.do_align(e)
+ else:
+ self.do_item(e)
+ self.line()
+ self.rect.h = max(self.y,self.left_bottom,self.right_bottom)
+
+ def init(self):
+ self.x,self.y = self.rect.x,self.rect.y
+ self.left = self.rect.left
+ self.right = self.rect.right
+ self.left_bottom = 0
+ self.right_bottom = 0
+ self.y = self.rect.y
+ self.x = self.rect.x
+ self.h = 0
+
+ self.items = []
+ self.align = -1
+
+ def getleft(self):
+ if self.y > self.left_bottom:
+ self.left = self.rect.left
+ return self.left
+
+ def getright(self):
+ if self.y > self.right_bottom:
+ self.right = self.rect.right
+ return self.right
+
+ def do_br(self,h):
+ self.line()
+ self.h = h
+
+ def do_block(self,align=-1):
+ self.line()
+ self.align = align
+
+ def do_align(self,e):
+ align = e.align
+ ox,oy,oh = self.x,self.y,self.h
+ w,h = e.rect.w,e.rect.h
+
+ if align == 0:
+ self.line()
+ self.x = self.rect.left + (self.rect.width-w)/2
+ self.fit = 0
+ elif align == -1:
+ self.line()
+ self.y = max(self.left_bottom,self.y + self.h)
+ self.h = 0
+ self.x = self.rect.left
+ elif align == 1:
+ self.line()
+ self.y = max(self.right_bottom,self.y + self.h)
+ self.h = 0
+ self.x = self.rect.left + (self.rect.width-w)
+
+ e.rect.x,e.rect.y = self.x,self.y
+
+ self.x = self.x + w
+ self.y = self.y
+
+ if align == 0:
+ self.h = max(self.h,h)
+ self.y = self.y + self.h
+ self.x = self.getleft()
+ self.h = 0
+ elif align == -1:
+ self.left = self.x
+ self.left_bottom = self.y + h
+ self.x,self.y,self.h = ox + w,oy,oh
+ elif align == 1:
+ self.right = self.x - w
+ self.right_bottom = self.y + h
+ self.x,self.y,self.h = ox,oy,oh
+
+ self.widgets.append(e)
+
+ def do_space(self,e):
+ w,h = e
+ if self.x+w >= self.getright():
+ self.line()
+ else:
+ self.items.append(e)
+ self.h = max(self.h,h)
+ self.x += w
+
+ def do_item(self,e):
+ w,h = e.rect.w,e.rect.h
+ if self.x+w >= self.getright():
+ self.line()
+ self.items.append(e)
+ self.h = max(self.h,h)
+ self.x += w
+
+ def line(self):
+ x1 = self.getleft()
+ x2 = self.getright()
+ align = self.align
+ y = self.y
+
+ if len(self.items) != 0 and type(self.items[-1]) == tuple:
+ del self.items[-1]
+
+ w = 0
+ for e in self.items:
+ if type(e) == tuple: w += e[0]
+ else: w += e.rect.w
+
+ if align == -1: x = x1
+ elif align == 0:
+ x = x1 + ((x2-x1)-w)/2
+ self.fit = 0
+ elif align == 1: x = x2 - w
+
+ for e in self.items:
+ if type(e) == tuple: x += e[0]
+ else:
+ e.rect.x,e.rect.y = x,y
+ self.widgets.append(e)
+ x += e.rect.w
+
+ self.items = []
+ self.y = self.y + self.h
+ self.x = self.getleft()
+ self.h = 0
+
+
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/gui/layout.pyc b/pgu/gui/layout.pyc
new file mode 100644
index 0000000..4e6c10f
--- /dev/null
+++ b/pgu/gui/layout.pyc
Binary files differ
diff --git a/pgu/gui/menus.py b/pgu/gui/menus.py
new file mode 100644
index 0000000..3f8a08f
--- /dev/null
+++ b/pgu/gui/menus.py
@@ -0,0 +1,121 @@
+"""
+"""
+from const import *
+import table
+import basic, button
+
+class _Menu_Options(table.Table):
+ def __init__(self,menu,**params):
+ table.Table.__init__(self,**params)
+
+ self.menu = menu
+
+ def event(self,e):
+ handled = False
+ arect = self.get_abs_rect()
+
+ if e.type == MOUSEMOTION:
+ abspos = e.pos[0]+arect.x,e.pos[1]+arect.y
+ for w in self.menu.container.widgets:
+ if not w is self.menu:
+ mrect = w.get_abs_rect()
+ if mrect.collidepoint(abspos):
+ self.menu._close(None)
+ w._open(None)
+ handled = True
+
+ if not handled: table.Table.event(self,e)
+
+class _Menu(button.Button):
+ def __init__(self,parent,widget=None,**params): #TODO widget= could conflict with module widget
+ params.setdefault('cls','menu')
+ button.Button.__init__(self,widget,**params)
+
+ self.parent = parent
+
+ self._cls = self.cls
+ self.options = _Menu_Options(self, cls=self.cls+".options")
+
+ self.connect(CLICK,self._open,None)
+
+ self.pos = 0
+
+ def _open(self,value):
+ self.parent.value = self
+ self.pcls = 'down'
+
+ self.repaint()
+ self.container.open(self.options,self.rect.x,self.rect.bottom)
+ self.options.connect(BLUR,self._pass,None)
+ self.options.blur(self.options.myfocus)
+ self.options.connect(BLUR,self._close,None)
+ self.options.focus()
+ self.repaint()
+
+ def _pass(self,value):
+ pass
+
+ def _close(self,value):
+ self.pcls = ''
+ self.parent.value = None
+ self.repaint()
+ self.options.close()
+
+ def _value(self,value):
+ self._close(None)
+ if value['fnc'] != None:
+ value['fnc'](value['value'])
+
+ def event(self,e):
+ button.Button.event(self,e)
+
+ if self.parent.value == self:
+ self.pcls = 'down'
+
+ def add(self,w,fnc=None,value=None):
+ w.style.align = -1
+ b = button.Button(w,cls=self.cls+".option")
+ b.connect(CLICK,self._value,{'fnc':fnc,'value':value})
+
+ self.options.tr()
+ self.options.add(b)
+
+ return b
+
+class Menus(table.Table):
+ """A drop down menu bar.
+
+ <pre>Menus(data)</pre>
+
+ <dl>
+ <dt>data<dd>Menu data, a list of (path,fnc,value), see example below
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ data = [
+ ('File/Save',fnc_save,None),
+ ('File/New',fnc_new,None),
+ ('Edit/Copy',fnc_copy,None),
+ ('Edit/Cut',fnc_cut,None),
+ ('Help/About',fnc_help,help_about_content),
+ ('Help/Reference',fnc_help,help_reference_content),
+ ]
+ w = Menus(data)
+ """
+
+ def __init__(self,data,menu_cls='menu',**params):
+ params.setdefault('cls','menus')
+ table.Table.__init__(self,**params)
+
+ self.value = None
+
+ n,m,mt = 0,None,None
+ for path,cmd,value in data:
+ parts = path.split("/")
+ if parts[0] != mt:
+ mt = parts[0]
+ m = _Menu(self,basic.Label(mt,cls=menu_cls+".label"),cls=menu_cls)
+ self.add(m,n,0)
+ n += 1
+ m.add(basic.Label(parts[1],cls=m.cls+".option.label"),cmd,value)
diff --git a/pgu/gui/menus.pyc b/pgu/gui/menus.pyc
new file mode 100644
index 0000000..911de1b
--- /dev/null
+++ b/pgu/gui/menus.pyc
Binary files differ
diff --git a/pgu/gui/misc.py b/pgu/gui/misc.py
new file mode 100644
index 0000000..afb10c5
--- /dev/null
+++ b/pgu/gui/misc.py
@@ -0,0 +1,43 @@
+from const import *
+import widget
+import pguglobals
+
+class ProgressBar(widget.Widget):
+ """A progress bar.
+
+ <pre>ProgressBar(value,min,max)</pre>
+
+ <dl>
+ <dt>value<dd>starting value
+ <dt>min<dd>minimum value rendered on the screen (usually 0)
+ <dt>max<dd>maximum value
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = gui.ProgressBar(0,0,100)
+ w.value = 25
+ </code>
+ """
+
+ def __init__(self,value,min,max,**params):
+ params.setdefault('cls','progressbar')
+ widget.Widget.__init__(self,**params)
+ self.min,self.max,self.value = min,max,value
+
+ def paint(self,s):
+ r = pygame.rect.Rect(0,0,self.rect.w,self.rect.h)
+ r.w = r.w*(self.value-self.min)/(self.max-self.min)
+ self.bar = r
+ pguglobals.app.theme.render(s,self.style.bar,r)
+
+ def __setattr__(self,k,v):
+ if k == 'value':
+ v = int(v)
+ v = max(v,self.min)
+ v = min(v,self.max)
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
diff --git a/pgu/gui/misc.pyc b/pgu/gui/misc.pyc
new file mode 100644
index 0000000..fcc796f
--- /dev/null
+++ b/pgu/gui/misc.pyc
Binary files differ
diff --git a/pgu/gui/pguglobals.py b/pgu/gui/pguglobals.py
new file mode 100644
index 0000000..dc0e673
--- /dev/null
+++ b/pgu/gui/pguglobals.py
@@ -0,0 +1,7 @@
+# pguglobals.py - A place to stick global variables that need to be accessed
+# from other modules. To avoid problems with circular imports
+# this module should not import any other PGU module.
+
+# A global reference to the application instance (App class)
+app = None
+
diff --git a/pgu/gui/pguglobals.pyc b/pgu/gui/pguglobals.pyc
new file mode 100644
index 0000000..cd5a648
--- /dev/null
+++ b/pgu/gui/pguglobals.pyc
Binary files differ
diff --git a/pgu/gui/select.py b/pgu/gui/select.py
new file mode 100644
index 0000000..90fcfe4
--- /dev/null
+++ b/pgu/gui/select.py
@@ -0,0 +1,191 @@
+"""
+"""
+from const import *
+import table
+import basic, button
+
+class Select(table.Table):
+ """A select input.
+
+ <pre>Select(value=None)</pre>
+
+ <dl>
+ <dt>value<dd>initial value
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = Select(value="goats")
+ w.add("Cats","cats")
+ w.add("Goats","goats")
+ w.add("Dogs","Dogs")
+
+ w.value = 'dogs' #changes the value from goats to dogs
+ </code>
+
+ """
+
+ def __init__(self,value=None,**params):
+ params.setdefault('cls','select')
+ table.Table.__init__(self,**params)
+
+ self.top_selected = button.Button(cls=self.cls+".selected")
+ table.Table.add(self,self.top_selected) #,hexpand=1,vexpand=1)#,0,0)
+ self.top_selected.value = basic.Label(" ",cls=self.cls+".option.label")
+
+ self.top_arrow = button.Button(basic.Image(self.style.arrow),cls=self.cls+".arrow")
+ table.Table.add(self,self.top_arrow) #,hexpand=1,vexpand=1) #,1,0)
+
+ self.options = table.Table() #style={'border':3})
+ self.options_first = None
+
+ self.options.tr()
+ self.spacer_top = basic.Spacer(0,0)
+ self.options.add(self.spacer_top)
+
+ self.options.tr()
+ self._options = table.Table(cls=self.cls+".options")
+ self.options.add(self._options)
+
+ self.options.tr()
+ self.spacer_bottom = basic.Spacer(0,0)
+ self.options.add(self.spacer_bottom)
+
+
+ self.options.connect(BLUR,self._close,None)
+ self.spacer_top.connect(CLICK,self._close,None)
+ self.spacer_bottom.connect(CLICK,self._close,None)
+
+ self.values = []
+ self.value = value
+
+ def resize(self,width=None,height=None):
+ max_w,max_h = 0,0
+ for w in self._options.widgets:
+ w.rect.w,w.rect.h = w.resize()
+ max_w,max_h = max(max_w,w.rect.w),max(max_h,w.rect.h)
+
+ #xt,xr,xb,xl = self.top_selected.getspacing()
+ self.top_selected.style.width = max_w #+ xl + xr
+ self.top_selected.style.height = max_h #+ xt + xb
+
+ self.top_arrow.connect(CLICK,self._open,None)
+ self.top_selected.connect(CLICK,self._open,None)
+
+ w,h = table.Table.resize(self,width,height)
+
+ self.spacer_top.style.width, self.spacer_top.style.height = w,h
+ self.spacer_bottom.style.width, self.spacer_bottom.style.height = w,h
+ self._options.style.width = w
+ #HACK: sort of, but not a big one..
+ self._options.resize()
+
+ return w,h
+
+ def _open(self,value):
+ sh = self.rect.h #spacer height
+ opts = self.options
+
+ self.spacer_top.style.height = 0
+ self.spacer_bottom.style.height = 0
+ opts.rect.w, opts.rect.h = opts.resize()
+ h = opts.rect.h
+
+ y = self.rect.y
+ c = self.container
+ while hasattr(c,'container'):
+ y += c.rect.y
+ if c.container == None: break
+ c = c.container
+
+ if y + sh + h <= c.rect.h: #down
+ self.spacer_top.style.height = sh
+ dy = self.rect.y
+ else: #up
+ self.spacer_bottom.style.height = sh
+ dy = self.rect.y - h
+
+ opts.rect.w, opts.rect.h = opts.resize()
+
+ self.container.open(opts,self.rect.x,dy)
+ self.options_first.focus()
+
+ def _close(self,value):
+# print 'my close!'
+ self.options.close()
+ self.top_selected.focus()
+# self.blur()
+# self.focus()
+# print self.container.myfocus == self
+
+ def _setvalue(self,value):
+ self.value = value._value
+ if hasattr(self,'container'):
+ #self.chsize()
+ #HACK: improper use of resize()
+ #self.resize() #to recenter the new value, etc.
+ pass
+ # #self._resize()
+
+ self._close(None)
+ #self.repaint() #this will happen anyways
+
+
+
+ def __setattr__(self,k,v):
+ mywidget = None
+ if k == 'value':
+ for w in self.values:
+ if w._value == v:
+ mywidget = w
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
+ if k == 'value':
+ if not mywidget:
+ mywidget = basic.Label(" ",cls=self.cls+".option.label")
+ self.top_selected.value = mywidget
+
+ def add(self,w,value=None):
+ """Add a widget, value item to the Select.
+
+ <pre>Select.add(widget,value=None)</pre>
+
+ <dl>
+ <dt>widget<dd>Widget or string to represent the item
+ <dt>value<dd>value for this item
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = Select()
+ w.add("Goat") #adds a Label
+ w.add("Goat","goat") #adds a Label with the value goat
+ w.add(gui.Label("Cuzco"),"goat") #adds a Label with value goat
+ </code>
+ """
+
+ if type(w) == str: w = basic.Label(w,cls=self.cls+".option.label")
+
+ w.style.align = -1
+ b = button.Button(w,cls=self.cls+".option")
+ b.connect(CLICK,self._setvalue,w)
+ #b = w
+ #w.cls = self.cls+".option"
+ #w.cls = self.cls+".option"
+
+ self._options.tr()
+ self._options.add(b) #,align=-1)
+
+ if self.options_first == None:
+ self.options_first = b
+ #self._options.td(b, align=-1, cls=self.cls+".option")
+ #self._options.td(_List_Item(w,value=value),align=-1)
+
+ if value != None: w._value = value
+ else: w._value = w
+ if self.value == w._value:
+ self.top_selected.value = w
+ self.values.append(w)
diff --git a/pgu/gui/select.pyc b/pgu/gui/select.pyc
new file mode 100644
index 0000000..152ad9c
--- /dev/null
+++ b/pgu/gui/select.pyc
Binary files differ
diff --git a/pgu/gui/slider.py b/pgu/gui/slider.py
new file mode 100644
index 0000000..f4fa623
--- /dev/null
+++ b/pgu/gui/slider.py
@@ -0,0 +1,279 @@
+"""All sliders and scroll bar widgets have the same parameters.
+
+<pre>Slider(value,min,max,size)</pre>
+<dl>
+<dt>value<dd>initial value
+<dt>min<dd>minimum value
+<dt>max<dd>maximum value
+<dt>size<dd>size of bar in pixels
+</dl>
+"""
+import pygame
+from pygame.locals import *
+
+from const import *
+import widget
+import table
+import basic
+import pguglobals
+
+_SLIDER_HORIZONTAL = 0
+_SLIDER_VERTICAL = 1
+
+class _slider(widget.Widget):
+ def __init__(self,value,orient,min,max,size,step=1,**params):
+ params.setdefault('cls','slider')
+ widget.Widget.__init__(self,**params)
+ self.min,self.max,self.value,self.orient,self.size,self.step = min,max,value,orient,size,step
+
+
+ def paint(self,s):
+
+ self.value = self.value
+ r = pygame.rect.Rect(0,0,self.style.width,self.style.height)
+ if self.orient == _SLIDER_HORIZONTAL:
+ r.x = (self.value-self.min) * (r.w-self.size) / max(1,self.max-self.min);
+ r.w = self.size;
+ else:
+ r.y = (self.value-self.min) * (r.h-self.size) / max(1,self.max-self.min);
+ r.h = self.size;
+
+ self.bar = r
+
+ pguglobals.app.theme.render(s,self.style.bar,r)
+
+ def event(self,e):
+ used = None
+ r = pygame.rect.Rect(0,0,self.style.width,self.style.height)
+ adj = 0
+ if e.type == ENTER: self.repaint()
+ elif e.type == EXIT: self.repaint()
+ elif e.type == MOUSEBUTTONDOWN:
+ if self.bar.collidepoint(e.pos):
+ self.grab = e.pos[0],e.pos[1]
+ self.grab_value = self.value
+ else:
+ x,y,adj = e.pos[0],e.pos[1],1
+ self.grab = None
+ self.repaint()
+ elif e.type == MOUSEBUTTONUP:
+ #x,y,adj = e.pos[0],e.pos[1],1
+ self.repaint()
+ elif e.type == MOUSEMOTION:
+ if 1 in e.buttons and self.container.myfocus is self:
+ if self.grab != None:
+ rel = e.pos[0]-self.grab[0],e.pos[1]-self.grab[1]
+ if self.orient == _SLIDER_HORIZONTAL:
+ d = (r.w - self.size)
+ if d != 0: self.value = self.grab_value + ((self.max-self.min) * rel[0] / d)
+ else:
+ d = (r.h - self.size)
+ if d != 0: self.value = self.grab_value + ((self.max-self.min) * rel[1] / d)
+ else:
+ x,y,adj = e.pos[0],e.pos[1],1
+
+ elif e.type is KEYDOWN:
+ if self.orient == _SLIDER_HORIZONTAL and e.key == K_LEFT:
+ self.value -= self.step
+ used = True
+ elif self.orient == _SLIDER_HORIZONTAL and e.key == K_RIGHT:
+ self.value += self.step
+ used = True
+ elif self.orient == _SLIDER_VERTICAL and e.key == K_UP:
+ self.value -= self.step
+ used = True
+ elif self.orient == _SLIDER_VERTICAL and e.key == K_DOWN:
+ self.value += self.step
+ used = True
+
+ if adj:
+ if self.orient == _SLIDER_HORIZONTAL:
+ d = self.size/2 - (r.w/(self.max-self.min+1))/2
+ self.value = (x-d) * (self.max-self.min) / (r.w-self.size+1) + self.min
+ else:
+ d = self.size/2 - (r.h/(self.max-self.min+1))/2
+ self.value = (y-d) * (self.max-self.min) / (r.h-self.size+1) + self.min
+
+ self.pcls = ""
+ if self.container.myhover is self: self.pcls = "hover"
+ if (self.container.myfocus is self and 1 in pygame.mouse.get_pressed()): self.pcls = "down"
+
+ return used
+
+
+ def __setattr__(self,k,v):
+ if k == 'value':
+ v = int(v)
+ v = max(v,self.min)
+ v = min(v,self.max)
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
+
+ if hasattr(self,'size'):
+ sz = min(self.size,max(self.style.width,self.style.height))
+ sz = max(sz,min(self.style.width,self.style.height))
+ self.__dict__['size'] = sz
+
+ if hasattr(self,'max') and hasattr(self,'min'):
+ if self.max < self.min: self.max = self.min
+
+class VSlider(_slider):
+ """A verticle slider.
+
+ <pre>VSlider(value,min,max,size)</pre>
+ """
+ def __init__(self,value,min,max,size,step=1,**params):
+ params.setdefault('cls','vslider')
+ _slider.__init__(self,value,_SLIDER_VERTICAL,min,max,size,step,**params)
+
+class HSlider(_slider):
+ """A horizontal slider.
+
+ <pre>HSlider(value,min,max,size)</pre>
+ """
+ def __init__(self,value,min,max,size,step=1,**params):
+ params.setdefault('cls','hslider')
+ _slider.__init__(self,value,_SLIDER_HORIZONTAL,min,max,size,step,**params)
+
+class HScrollBar(table.Table):
+ """A horizontal scroll bar.
+
+ <pre>HScrollBar(value,min,max,size,step=1)</pre>
+ """
+ def __init__(self,value,min,max,size,step=1,**params):
+ params.setdefault('cls','hscrollbar')
+
+ table.Table.__init__(self,**params)
+
+ self.slider = _slider(value,_SLIDER_HORIZONTAL,min,max,size,step=step,cls=self.cls+'.slider')
+
+ self.minus = basic.Image(self.style.minus)
+ self.minus.connect(MOUSEBUTTONDOWN,self._click,-1)
+ self.slider.connect(CHANGE,self.send,CHANGE)
+
+ self.minus2 = basic.Image(self.style.minus)
+ self.minus2.connect(MOUSEBUTTONDOWN,self._click,-1)
+
+ self.plus = basic.Image(self.style.plus)
+ self.plus.connect(MOUSEBUTTONDOWN,self._click,1)
+
+ self.size = size
+
+ def _click(self,value):
+ self.slider.value += self.slider.step*value
+
+ def resize(self,width=None,height=None):
+ self.clear()
+ self.tr()
+
+ w = self.style.width
+ h = self.slider.style.height
+ ww = 0
+
+ if w > (h*2 + self.minus.style.width+self.plus.style.width):
+ self.td(self.minus)
+ ww += self.minus.style.width
+
+ self.td(self.slider)
+
+ if w > (h*2 + self.minus.style.width+self.minus2.style.width+self.plus.style.width):
+ self.td(self.minus2)
+ ww += self.minus2.style.width
+
+ if w > (h*2 + self.minus.style.width+self.plus.style.width):
+ self.td(self.plus)
+ ww += self.plus.style.width
+
+
+ #HACK: handle theme sizing properly
+ xt,xr,xb,xl = pguglobals.app.theme.getspacing(self.slider)
+ ww += xr+xl
+
+ self.slider.style.width = self.style.width - ww
+ setattr(self.slider,'size',self.size * self.slider.style.width / max(1,self.style.width))
+ return table.Table.resize(self,width,height)
+
+
+ def __setattr__(self,k,v):
+ if k in ('min','max','value','step'):
+ return setattr(self.slider,k,v)
+ self.__dict__[k]=v
+
+ def __getattr__(self,k):
+ if k in ('min','max','value','step'):
+ return getattr(self.slider,k)
+ return table.Table.__getattr__(self,k) #self.__dict__[k]
+
+class VScrollBar(table.Table):
+ """A vertical scroll bar.
+
+ <pre>VScrollBar(value,min,max,size,step=1)</pre>
+ """
+ def __init__(self,value,min,max,size,step=1,**params):
+ params.setdefault('cls','vscrollbar')
+
+ table.Table.__init__(self,**params)
+
+ self.minus = basic.Image(self.style.minus)
+ self.minus.connect(MOUSEBUTTONDOWN,self._click,-1)
+
+ self.minus2 = basic.Image(self.style.minus)
+ self.minus2.connect(MOUSEBUTTONDOWN,self._click,-1)
+
+ self.plus = basic.Image(self.style.plus)
+ self.plus.connect(MOUSEBUTTONDOWN,self._click,1)
+
+ self.slider = _slider(value,_SLIDER_VERTICAL,min,max,size,step=step,cls=self.cls+'.slider')
+ self.slider.connect(CHANGE,self.send,CHANGE)
+
+ self.size = size
+
+ def _click(self,value):
+ self.slider.value += self.slider.step*value
+
+ def resize(self,width=None,height=None):
+ self.clear()
+
+ h = self.style.height
+ w = self.slider.style.width
+ hh = 0
+
+ if h > (w*2 + self.minus.style.height+self.plus.style.height):
+ self.tr()
+ self.td(self.minus)
+ hh += self.minus.style.height
+
+ self.tr()
+ self.td(self.slider)
+
+ if h > (w*2 + self.minus.style.height+self.minus2.style.height+self.plus.style.height):
+ self.tr()
+ self.td(self.minus2)
+ hh += self.minus2.style.height
+
+ if h > (w*2 + self.minus.style.height+self.plus.style.height):
+ self.tr()
+ self.td(self.plus)
+ hh += self.plus.style.height
+
+
+ #HACK: handle theme sizing properly
+ xt,xr,xb,xl = pguglobals.app.theme.getspacing(self.slider)
+ hh += xt+xb
+
+ self.slider.style.height = self.style.height - hh
+ setattr(self.slider,'size',self.size * self.slider.style.height / max(1,self.style.height))
+ return table.Table.resize(self,width,height)
+
+ def __setattr__(self,k,v):
+ if k in ('min','max','value','step'):
+ return setattr(self.slider,k,v)
+ self.__dict__[k]=v
+
+ def __getattr__(self,k):
+ if k in ('min','max','value','step'):
+ return getattr(self.slider,k)
+ return table.Table.__getattr__(self,k)
diff --git a/pgu/gui/slider.pyc b/pgu/gui/slider.pyc
new file mode 100644
index 0000000..9ccc719
--- /dev/null
+++ b/pgu/gui/slider.pyc
Binary files differ
diff --git a/pgu/gui/style.py b/pgu/gui/style.py
new file mode 100644
index 0000000..3060928
--- /dev/null
+++ b/pgu/gui/style.py
@@ -0,0 +1,41 @@
+"""
+"""
+
+import pguglobals
+
+class Style:
+ """The class used by widget for the widget.style
+
+ <p>This object is used mainly as a dictionary, accessed via <tt>widget.style.attr</tt>, as opposed to
+ <tt>widget.style['attr']</tt>. It automatically grabs information from the theme via <tt>value = theme.get(widget.cls,widget.pcls,attr)</tt>.</p>
+
+ """
+ def __init__(self,o,dict):
+ self.obj = o
+ for k,v in dict.items(): self.__dict__[k]=v
+ self._cache = {}
+
+ def __getattr__(self,k):
+ key = self.obj.cls,self.obj.pcls,k
+ if key not in self._cache:
+ self._cache[key] = Style_get(self.obj.cls,self.obj.pcls,k)
+ v = self._cache[key]
+ if k in (
+ 'border_top','border_right','border_bottom','border_left',
+ 'padding_top','padding_right','padding_bottom','padding_left',
+ 'margin_top','margin_right','margin_bottom','margin_left',
+ 'align','valign','width','height',
+ ): self.__dict__[k] = v
+ return v
+
+ def __setattr__(self,k,v):
+ self.__dict__[k] = v
+
+
+Style_cache = {}
+def Style_get(cls,pcls,k):
+ key = cls,pcls,k
+ if key not in Style_cache:
+ Style_cache[key] = pguglobals.app.theme.get(cls,pcls,k)
+ return Style_cache[key]
+
diff --git a/pgu/gui/style.pyc b/pgu/gui/style.pyc
new file mode 100644
index 0000000..ae59cf4
--- /dev/null
+++ b/pgu/gui/style.pyc
Binary files differ
diff --git a/pgu/gui/surface.py b/pgu/gui/surface.py
new file mode 100644
index 0000000..82d92d1
--- /dev/null
+++ b/pgu/gui/surface.py
@@ -0,0 +1,142 @@
+"""
+"""
+import pygame
+
+def subsurface(s,r):
+ """Return the subsurface of a surface, with some help, checks.
+
+ <pre>subsurface(s,r): return surface</pre>
+ """
+ r = pygame.Rect(r)
+ if r.x < 0 or r.y < 0:
+ raise "gui.subsurface: %d %d %s"%(s.get_width(),s.get_height(),r)
+ w,h = s.get_width(),s.get_height()
+ if r.right > w:
+ r.w -= r.right-w
+ if r.bottom > h:
+ r.h -= r.bottom-h
+ assert(r.w >= 0 and r.h >= 0)
+ return s.subsurface(r)
+
+class ProxySurface:
+ """
+ A surface-like object which smartly handle out-of-area blitting.
+
+ <pre>ProxySurface(parent, rect, real_surface=None, offset=(0, 0))</pre>
+
+ <p>only one of parent and real_surface should be supplied (non None)</p>
+ <dl>
+ <dt>parent<dd>a ProxySurface object
+ <dt>real_surface<dd>a pygame Surface object
+ </dl>
+
+ <strong>Variables</strong>
+
+ <dl>
+ <dt>mysubsurface<dd>a real and valid pygame.Surface object to be used
+ for blitting.
+ <dt>x, y<dd>if the proxy surface is lefter or higher than the parent,
+ x, y hold the diffs.
+ <dt>offset<dd>an optional feature which let you scroll the whole blitted
+ content.
+ </dl>
+ """
+ def __init__(self, parent, rect, real_surface, offset=(0, 0)):
+ self.offset = offset
+ self.x = self.y = 0
+ if rect.x < 0: self.x = rect.x
+ if rect.y < 0: self.y = rect.y
+ self.real_surface = real_surface
+ if real_surface == None:
+ self.mysubsurface = parent.mysubsurface.subsurface(
+ parent.mysubsurface.get_rect().clip(rect))
+ else:
+ self.mysubsurface = real_surface.subsurface(
+ real_surface.get_rect().clip(rect))
+ rect.topleft = (0, 0)
+ self.rect = rect
+
+ def blit(self, s, pos, rect=None):
+ if rect == None: rect = s.get_rect()
+ pos = (pos[0] + self.offset[0] + self.x, pos[1] + self.offset[1] + self.y)
+ self.mysubsurface.blit(s, pos, rect)
+
+ def subsurface(self, rect):
+ r = pygame.Rect(rect).move(self.offset[0] + self.x,
+ self.offset[1] + self.y)
+ return ProxySurface(self, r, self.real_surface)
+
+ def fill(self, color, rect=None):
+ if rect != None: self.mysubsurface.fill(color, rect)
+ else: self.mysubsurface.fill(color)
+ def get_rect(self): return self.rect
+ def get_width(self): return self.rect[2]
+ def get_height(self): return self.rect[3]
+ def get_abs_offset(): return self.rect[:2]
+ def get_abs_parent(): return self.mysubsurface.get_abs_parent()
+ def set_clip(self, rect=None):
+ if rect == None: self.mysubsurface.set_clip()
+ else:
+ rect = [rect[0] + self.offset[0] + self.x, rect[1] + self.offset[0] + self.y, rect[2], rect[3]]
+ self.mysubsurface.set_clip(rect)
+
+
+
+
+
+
+class xProxySurface:
+ """
+ A surface-like object which smartly handle out-of-area blitting.
+
+ <pre>ProxySurface(parent, rect, real_surface=None, offset=(0, 0))</pre>
+
+ <p>only one of parent and real_surface should be supplied (non None)</p>
+ <dl>
+ <dt>parent<dd>a ProxySurface object
+ <dt>real_surface<dd>a pygame Surface object
+ </dl>
+
+ <strong>Variables</strong>
+
+ <dl>
+ <dt>mysubsurface<dd>a real and valid pygame.Surface object to be used
+ for blitting.
+ <dt>x, y<dd>if the proxy surface is lefter or higher than the parent,
+ x, y hold the diffs.
+ <dt>offset<dd>an optional feature which let you scroll the whole blitted
+ content.
+ </dl>
+ """
+ def __init__(self, parent, rect, real_surface, offset=(0, 0)):
+ self.offset = offset
+ self.x = self.y = 0
+ if rect[0] < 0: self.x = rect[0]
+ if rect[1] < 0: self.y = rect[1]
+ self.real_surface = real_surface
+ if real_surface == None:
+ self.mysubsurface = parent.mysubsurface.subsurface(parent.mysubsurface.get_rect().clip(rect))
+ else:
+ self.mysubsurface = real_surface.subsurface(real_surface.get_rect().clip(rect))
+ rect[0], rect[1] = 0, 0
+ self.rect = rect
+
+ def blit(self, s, pos, rect=None):
+ if rect == None: rect = s.get_rect()
+ pos = (pos[0] + self.offset[0] + self.x, pos[1] + self.offset[1] + self.y)
+ self.mysubsurface.blit(s, pos, rect)
+
+ def subsurface(self, rect): return ProxySurface(self, pygame.Rect(rect).move(self.offset[0] + self.x, self.offset[1] + self.y),self.real_surface)
+ def fill(self, color, rect=None):
+ if rect != None: self.mysubsurface.fill(color, rect)
+ else: self.mysubsurface.fill(color)
+ def get_rect(self): return self.rect
+ def get_width(self): return self.rect[2]
+ def get_height(self): return self.rect[3]
+ def get_abs_offset(): return self.rect[:2]
+ def get_abs_parent(): return self.mysubsurface.get_abs_parent()
+ def set_clip(self, rect=None):
+ if rect == None: self.mysubsurface.set_clip()
+ else:
+ rect = [rect[0] + self.offset[0] + self.x, rect[1] + self.offset[0] + self.y, rect[2], rect[3]]
+ self.mysubsurface.set_clip(rect)
diff --git a/pgu/gui/surface.pyc b/pgu/gui/surface.pyc
new file mode 100644
index 0000000..13f9f89
--- /dev/null
+++ b/pgu/gui/surface.pyc
Binary files differ
diff --git a/pgu/gui/table.py b/pgu/gui/table.py
new file mode 100644
index 0000000..1a92593
--- /dev/null
+++ b/pgu/gui/table.py
@@ -0,0 +1,331 @@
+"""
+"""
+from const import *
+import container
+
+class Table(container.Container):
+ """A table style container.
+
+ <p>If you know HTML, this should all work roughly how you would expect. If you are not
+ familiar with HTML, please read <a href="http://www.w3.org/TR/REC-html40/struct/tables.html">Tables in HTML Documents</a>. Pay attention to TABLE, TR, TD related parts of the document.</p>
+
+ <pre>Table()</pre>
+
+ <strong>Example</strong>
+ <code>
+ t = gui.Table()
+
+ t.tr()
+ t.td(gui.Label("First Name"), align=-1)
+ t.td(gui.Input())
+
+ t.tr()
+ t.td(gui.Label("Last Name"), align=-1)
+ t.td(gui.Input())
+ </code>
+
+ """
+
+
+ def __init__(self, **params):
+ params.setdefault('cls','table')
+ container.Container.__init__(self, **params)
+ self._rows = []
+ self._curRow = 0
+ self._trok = False
+
+ def getRows(self):
+ return len(self._rows)
+
+ def getColumns(self):
+ if self._rows:
+ return len(self._rows[0])
+ else:
+ return 0
+
+ def remove_row(self, n): #NOTE: won't work in all cases.
+ if n >= self.getRows():
+ print "Trying to remove a nonexistant row:", n, "there are only", self.getRows(), "rows"
+ return
+
+ for cell in self._rows[n]:
+ if isinstance(cell, dict) and cell["widget"] in self.widgets:
+ #print 'removing widget'
+ self.widgets.remove(cell["widget"])
+ del self._rows[n]
+ #print "got here"
+
+ for w in self.widgets:
+ if w.style.row > n: w.style.row -= 1
+
+ if self._curRow >= n:
+ self._curRow -= 1
+
+ #self.rect.w, self.rect.h = self.resize()
+ #self.repaint()
+
+ self.chsize()
+
+ def clear(self):
+ self._rows = []
+ self._curRow = 0
+ self._trok = False
+
+ self.widgets = []
+
+ self.chsize()
+
+ #print 'clear',self,self._rows
+
+ def _addRow(self):
+ self._rows.append([None for x in xrange(self.getColumns())])
+
+ def tr(self):
+ """Start on the next row."""
+ if not self._trok:
+ self._trok = True
+ return
+ self._curRow += 1
+ if self.getRows() <= self._curRow:
+ self._addRow()
+
+ def _addColumn(self):
+ if not self._rows:
+ self._addRow()
+ for row in self._rows:
+ row.append(None)
+
+ def _setCell(self, w, col, row, colspan=1, rowspan=1):
+ #make room for the widget by adding columns and rows
+ while self.getColumns() < col + colspan:
+ self._addColumn()
+ while self.getRows() < row + rowspan:
+ self._addRow()
+
+ #print w.__class__.__name__,col,row,colspan,rowspan
+
+ #actual widget setting and modification stuff
+ w.container = self
+ w.style.row = row #HACK - to work with gal's list
+ w.style.col = col #HACK - to work with gal's list
+ self._rows[row][col] = {"widget":w, "colspan":colspan, "rowspan":rowspan}
+ self.widgets.append(self._rows[row][col]["widget"])
+
+ #set the spanned columns
+ #for acell in xrange(col + 1, col + colspan):
+ # self._rows[row][acell] = True
+
+ #set the spanned rows and the columns on them
+ #for arow in xrange(row + 1, row + rowspan):
+ # for acell in xrange(col, col + colspan): #incorrect?
+ # self._rows[arow][acell] = True
+
+ for arow in xrange(row, row + rowspan):
+ for acell in xrange(col, col + colspan): #incorrect?
+ if row != arow or col != acell:
+ self._rows[arow][acell] = True
+
+
+ def td(self, w, col=None, row=None, colspan=1, rowspan=1, **params):
+ """Add a widget to a table after wrapping it in a TD container.
+
+ <pre>Table.td(w,col=None,row=None,colspan=1,rowspan=1,**params)</pre>
+
+ <dl>
+ <dt>w<dd>widget
+ <dt>col<dd>column
+ <dt>row<dd>row
+ <dt>colspan<dd>colspan
+ <dt>rowspan<dd>rowspan
+ <dt>align<dd>horizontal alignment (-1,0,1)
+ <dt>valign<dd>vertical alignment (-1,0,1)
+ <dt>params<dd>other params for the TD container, style information, etc
+ </dl>
+ """
+
+ Table.add(self,_Table_td(w, **params), col=col, row=row, colspan=colspan, rowspan=rowspan)
+
+ def add(self, w, col=None, row=None, colspan=1, rowspan=1):
+ """Add a widget directly into the table, without wrapping it in a TD container.
+
+ <pre>Table.add(w,col=None,row=None,colspan=1,rowspan=1)</pre>
+
+ <p>See Table.td for an explanation of the parameters.</p>
+ """
+ self._trok = True
+ #if no row was specifically specified, set it to the current row
+ if row is None:
+ row = self._curRow
+ #print row
+
+ #if its going to be a new row, have it be on the first column
+ if row >= self.getRows():
+ col = 0
+
+ #try to find an open cell for the widget
+ if col is None:
+ for cell in xrange(self.getColumns()):
+ if col is None and not self._rows[row][cell]:
+ col = cell
+ break
+
+ #otherwise put the widget in a new column
+ if col is None:
+ col = self.getColumns()
+
+ self._setCell(w, col, row, colspan=colspan, rowspan=rowspan)
+
+ self.chsize()
+ return
+
+ def remove(self,w):
+ if hasattr(w,'_table_td'): w = w._table_td
+ row,col = w.style.row,w.style.col
+ cell = self._rows[row][col]
+ colspan,rowspan = cell['colspan'],cell['rowspan']
+
+ for arow in xrange(row , row + rowspan):
+ for acell in xrange(col, col + colspan): #incorrect?
+ self._rows[arow][acell] = False
+ self.widgets.remove(w)
+ self.chsize()
+
+
+
+ def resize(self, width=None, height=None):
+ #if 1 or self.getRows() == 82:
+ #print ''
+ #print 'resize',self.getRows(),self.getColumns(),width,height
+ #import inspect
+ #for obj,fname,line,fnc,code,n in inspect.stack()[9:20]:
+ # print fname,line,':',fnc,code[0].strip()
+
+
+ #resize the widgets to their smallest size
+ for w in self.widgets:
+ w.rect.w, w.rect.h = w.resize()
+
+ #calculate row heights and column widths
+ rowsizes = [0 for y in xrange(self.getRows())]
+ columnsizes = [0 for x in xrange(self.getColumns())]
+ for row in xrange(self.getRows()):
+ for cell in xrange(self.getColumns()):
+ if self._rows[row][cell] and self._rows[row][cell] is not True:
+ if not self._rows[row][cell]["colspan"] > 1:
+ columnsizes[cell] = max(columnsizes[cell], self._rows[row][cell]["widget"].rect.w)
+ if not self._rows[row][cell]["rowspan"] > 1:
+ rowsizes[row] = max(rowsizes[row], self._rows[row][cell]["widget"].rect.h)
+
+ #distribute extra space if necessary for wide colspanning/rowspanning
+ for row in xrange(self.getRows()):
+ for cell in xrange(self.getColumns()):
+ if self._rows[row][cell] and self._rows[row][cell] is not True:
+ if self._rows[row][cell]["colspan"] > 1:
+ columns = xrange(cell, cell + self._rows[row][cell]["colspan"])
+ totalwidth = 0
+ for acol in columns:
+ totalwidth += columnsizes[acol]
+ if totalwidth < self._rows[row][cell]["widget"].rect.w:
+ for acol in columns:
+ columnsizes[acol] += _table_div(self._rows[row][cell]["widget"].rect.w - totalwidth, self._rows[row][cell]["colspan"],acol)
+ if self._rows[row][cell]["rowspan"] > 1:
+ rows = xrange(row, row + self._rows[row][cell]["rowspan"])
+ totalheight = 0
+ for arow in rows:
+ totalheight += rowsizes[arow]
+ if totalheight < self._rows[row][cell]["widget"].rect.h:
+ for arow in rows:
+ rowsizes[arow] += _table_div(self._rows[row][cell]["widget"].rect.h - totalheight, self._rows[row][cell]["rowspan"],arow)
+
+ #make everything fill out to self.style.width, self.style.heigh, not exact, but pretty close...
+ w, h = sum(columnsizes), sum(rowsizes)
+ if w > 0 and w < self.style.width and len(columnsizes):
+ d = (self.style.width - w)
+ for n in xrange(0, len(columnsizes)):
+ v = columnsizes[n]
+ columnsizes[n] += v * d / w
+ if h > 0 and h < self.style.height and len(rowsizes):
+ d = (self.style.height - h) / len(rowsizes)
+ for n in xrange(0, len(rowsizes)):
+ v = rowsizes[n]
+ rowsizes[n] += v * d / h
+
+ #set the widget's position by calculating their row/column x/y offset
+ cellpositions = [[[sum(columnsizes[0:cell]), sum(rowsizes[0:row])] for cell in xrange(self.getColumns())] for row in xrange(self.getRows())]
+ for row in xrange(self.getRows()):
+ for cell in xrange(self.getColumns()):
+ if self._rows[row][cell] and self._rows[row][cell] is not True:
+ x, y = cellpositions[row][cell]
+ w = sum(columnsizes[cell:cell+self._rows[row][cell]["colspan"]])
+ h = sum(rowsizes[row:row+self._rows[row][cell]["rowspan"]])
+
+ widget = self._rows[row][cell]["widget"]
+ widget.rect.x = x
+ widget.rect.y = y
+ if 1 and (w,h) != (widget.rect.w,widget.rect.h):
+# if h > 20:
+# print widget.widget.__class__.__name__, (widget.rect.w,widget.rect.h),'=>',(w,h)
+ widget.rect.w, widget.rect.h = widget.resize(w, h)
+
+ #print self._rows[row][cell]["widget"].rect
+
+ print 'columnsizes', columnsizes
+ print sum(columnsizes)
+ size = sum(columnsizes), sum(rowsizes); print size
+
+ #return the tables final size
+ return sum(columnsizes),sum(rowsizes)
+
+
+def _table_div(a,b,c):
+ v,r = a/b, a%b
+ if r != 0 and (c%b)<r: v += 1
+ return v
+
+class _Table_td(container.Container):
+ def __init__(self,widget,**params):#hexpand=0,vexpand=0,
+ container.Container.__init__(self,**params)
+ self.widget = widget
+ #self.hexpand=hexpand
+ #self.vexpand=vexpand
+ widget._table_td = self
+ self.add(widget,0,0)
+
+ def resize(self,width=None,height=None):
+ w = self.widget
+
+ #expansion code, but i didn't like the idea that much..
+ #a bit obscure, fairly useless when a user can just
+ #add a widget to a table instead of td it in.
+ #ww,hh=None,None
+ #if self.hexpand: ww = self.style.width
+ #if self.vexpand: hh = self.style.height
+ #if self.hexpand and width != None: ww = max(ww,width)
+ #if self.vexpand and height != None: hh = max(hh,height)
+ #w.rect.w,w.rect.h = w.resize(ww,hh)
+
+ #why bother, just do the lower mentioned item...
+ w.rect.w,w.rect.h = w.resize()
+
+ #this should not be needed, widgets should obey their sizing on their own.
+
+# if (self.style.width!=0 and w.rect.w > self.style.width) or (self.style.height!=0 and w.rect.h > self.style.height):
+# ww,hh = None,None
+# if self.style.width: ww = self.style.width
+# if self.style.height: hh = self.style.height
+# w.rect.w,w.rect.h = w.resize(ww,hh)
+
+
+ #in the case that the widget is too big, we try to resize it
+ if (width != None and width < w.rect.w) or (height != None and height < w.rect.h):
+ w.rect.w,w.rect.h = w.resize(width,height)
+
+ width = max(width,w.rect.w,self.style.width) #,self.style.cell_width)
+ height = max(height,w.rect.h,self.style.height) #,self.style.cell_height)
+
+ dx = width-w.rect.w
+ dy = height-w.rect.h
+ w.rect.x = (self.style.align+1)*dx/2
+ w.rect.y = (self.style.valign+1)*dy/2
+
+ return width,height
diff --git a/pgu/gui/table.pyc b/pgu/gui/table.pyc
new file mode 100644
index 0000000..cc1e5d8
--- /dev/null
+++ b/pgu/gui/table.pyc
Binary files differ
diff --git a/pgu/gui/textarea.py b/pgu/gui/textarea.py
new file mode 100644
index 0000000..667076a
--- /dev/null
+++ b/pgu/gui/textarea.py
@@ -0,0 +1,287 @@
+"""
+"""
+import pygame
+from pygame.locals import *
+
+from const import *
+import widget
+
+class TextArea(widget.Widget):
+ """A multi-line text input.
+
+ <pre>TextArea(value="",width = 120, height = 30, size=20)</pre>
+
+ <dl>
+ <dt>value<dd>initial text
+ <dt>size<dd>size for the text box, in characters
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ w = TextArea(value="Cuzco the Goat",size=20)
+
+ w = TextArea("Marbles")
+
+ w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12)
+ </code>
+
+ """
+ def __init__(self,value="",width = 120, height = 30, size=20,**params):
+ params.setdefault('cls','input')
+ params.setdefault('width', width)
+ params.setdefault('height', height)
+
+ widget.Widget.__init__(self,**params)
+ self.value = value # The value of the TextArea
+ self.pos = len(str(value)) # The position of the cursor
+ self.vscroll = 0 # The number of lines that the TextArea is currently scrolled
+ self.font = self.style.font # The font used for rendering the text
+ self.cursor_w = 2 # Cursor width (NOTE: should be in a style)
+ w,h = self.font.size("e"*size)
+ if not self.style.height: self.style.height = h
+ if not self.style.width: self.style.width = w
+
+ def resize(self,width=None,height=None):
+ if (width != None) and (height != None):
+ self.rect = pygame.Rect(self.rect.x, self.rect.y, width, height)
+ return self.rect.w, self.rect.h
+
+ def paint(self,s):
+
+ # TODO: What's up with this 20 magic number? It's the margin of the left and right sides, but I'm not sure how this should be gotten other than by trial and error.
+ max_line_w = self.rect.w - 20
+
+ # Update the line allocation for the box's value
+ self.doLines(max_line_w)
+
+ # Make sure that the vpos and hpos of the cursor is set properly
+ self.updateCursorPos()
+
+ # Make sure that we're scrolled vertically such that the cursor is visible
+ if (self.vscroll < 0):
+ self.vscroll = 0
+ if (self.vpos < self.vscroll):
+ self.vscroll = self.vpos
+ elif ((self.vpos - self.vscroll + 1) * self.line_h > self.rect.h):
+ self.vscroll = - (self.rect.h / self.line_h - self.vpos - 1)
+
+ # Blit each of the lines in turn
+ cnt = 0
+ for line in self.lines:
+ line_pos = (0, (cnt - self.vscroll) * self.line_h)
+ if (line_pos[1] >= 0) and (line_pos[1] < self.rect.h):
+ s.blit( self.font.render(line, 1, self.style.color), line_pos )
+ cnt += 1
+
+ # If the textarea is focused, then also show the cursor
+ if self.container.myfocus is self:
+ r = self.getCursorRect()
+ s.fill(self.style.color,r)
+
+ # This function updates self.vpos and self.hpos based on self.pos
+ def updateCursorPos(self):
+ self.vpos = 0 # Reset the current line that the cursor is on
+ self.hpos = 0
+
+ line_cnt = 0
+ char_cnt = 0
+
+ for line in self.lines:
+ line_char_start = char_cnt # The number of characters at the start of the line
+
+ # Keep track of the character count for words
+ char_cnt += len(line)
+
+ # If our cursor count is still less than the cursor position, then we can update our cursor line to assume that it's at least on this line
+ if (char_cnt > self.pos):
+ self.vpos = line_cnt
+ self.hpos = self.pos - line_char_start
+
+ break # Now that we know where our cursor is, we exit the loop
+
+ line_cnt += 1
+
+ if (char_cnt <= self.pos) and (len(self.lines) > 0):
+ self.vpos = len(self.lines) - 1
+ self.hpos = len(self.lines[ self.vpos ] )
+
+ # Returns a rectangle that is of the size and position of where the cursor is drawn
+ def getCursorRect(self):
+ lw = 0
+ if (len(self.lines) > 0):
+ lw, lh = self.font.size( self.lines[ self.vpos ][ 0:self.hpos ] )
+
+ r = pygame.Rect(lw, (self.vpos - self.vscroll) * self.line_h, self.cursor_w, self.line_h)
+ return r
+
+ # This function sets the cursor position according to an x/y value (such as by from a mouse click)
+ def setCursorByXY(self, (x, y)):
+ self.vpos = ((int) (y / self.line_h)) + self.vscroll
+ if (self.vpos >= len(self.lines)):
+ self.vpos = len(self.lines) - 1
+
+ currentLine = self.lines[ self.vpos ]
+
+ for cnt in range(0, len(currentLine) ):
+ self.hpos = cnt
+ lw, lh = self.font.size( currentLine[ 0:self.hpos + 1 ] )
+ if (lw > x):
+ break
+
+ lw, lh = self.font.size( currentLine )
+ if (lw < x):
+ self.hpos = len(currentLine)
+
+ self.setCursorByHVPos()
+
+ # This function sets the cursor position by the horizontal/vertical cursor position.
+ def setCursorByHVPos(self):
+ line_cnt = 0
+ char_cnt = 0
+
+ for line in self.lines:
+ line_char_start = char_cnt # The number of characters at the start of the line
+
+ # Keep track of the character count for words
+ char_cnt += len(line)
+
+ # If we're on the proper line
+ if (line_cnt == self.vpos):
+ # Make sure that we're not trying to go over the edge of the current line
+ if ( self.hpos >= len(line) ):
+ self.hpos = len(line) - 1
+ # Set the cursor position
+ self.pos = line_char_start + self.hpos
+ break # Now that we've set our cursor position, we exit the loop
+
+ line_cnt += 1
+
+ # Splits up the text found in the control's value, and assigns it into the lines array
+ def doLines(self, max_line_w):
+ self.line_h = 10
+ self.lines = [] # Create an empty starter list to start things out.
+
+ inx = 0
+ line_start = 0
+ while inx >= 0:
+ # Find the next breakable whitespace
+ # HACK: Find a better way to do this to include tabs and system characters and whatnot.
+ prev_word_start = inx # Store the previous whitespace
+ spc_inx = self.value.find(' ', inx+1)
+ nl_inx = self.value.find('\n', inx+1)
+
+ if (min(spc_inx, nl_inx) == -1):
+ inx = max(spc_inx, nl_inx)
+ else:
+ inx = min(spc_inx, nl_inx)
+
+ # Measure the current line
+ lw, self.line_h = self.font.size( self.value[ line_start : inx ] )
+
+ # If we exceeded the max line width, then create a new line
+ if (lw > max_line_w):
+ #Fall back to the previous word start
+ self.lines.append(self.value[ line_start : prev_word_start + 1 ])
+ line_start = prev_word_start + 1
+ # TODO: Check for extra-long words here that exceed the length of a line, to wrap mid-word
+
+ # If we reached the end of our text
+ if (inx < 0):
+ # Then make sure we added the last of the line
+ if (line_start < len( self.value ) ):
+ self.lines.append( self.value[ line_start : len( self.value ) ] )
+ # If we reached a hard line break
+ elif (self.value[inx] == "\n"):
+ # Then make a line break here as well.
+ newline = self.value[ line_start : inx + 1 ]
+ newline = newline.replace("\n", " ") # HACK: We know we have a newline character, which doesn't print nicely, so make it into a space. Comment this out to see what I mean.
+ self.lines.append( newline )
+
+ line_start = inx + 1
+ else:
+ # Otherwise, we just continue progressing to the next space
+ pass
+
+ def _setvalue(self,v):
+ self.__dict__['value'] = v
+ self.send(CHANGE)
+
+ def event(self,e):
+ used = None
+ if e.type == KEYDOWN:
+ if e.key == K_BACKSPACE:
+ if self.pos:
+ self._setvalue(self.value[:self.pos-1] + self.value[self.pos:])
+ self.pos -= 1
+ elif e.key == K_DELETE:
+ if len(self.value) > self.pos:
+ self._setvalue(self.value[:self.pos] + self.value[self.pos+1:])
+ elif e.key == K_HOME:
+ # Find the previous newline
+ newPos = self.value.rfind('\n', 0, self.pos)
+ if (newPos >= 0):
+ self.pos = newPos
+ elif e.key == K_END:
+ # Find the previous newline
+ newPos = self.value.find('\n', self.pos, len(self.value) )
+ if (newPos >= 0):
+ self.pos = newPos
+ elif e.key == K_LEFT:
+ if self.pos > 0: self.pos -= 1
+ used = True
+ elif e.key == K_RIGHT:
+ if self.pos < len(self.value): self.pos += 1
+ used = True
+ elif e.key == K_UP:
+ self.vpos -= 1
+ self.setCursorByHVPos()
+ elif e.key == K_DOWN:
+ self.vpos += 1
+ self.setCursorByHVPos()
+ # The following return/tab keys are standard for PGU widgets, but I took them out here to facilitate multi-line text editing
+# elif e.key == K_RETURN:
+# self.next()
+# elif e.key == K_TAB:
+# pass
+ else:
+ #c = str(e.unicode)
+ try:
+ if (e.key == K_RETURN):
+ c = "\n"
+ elif (e.key == K_TAB):
+ c = " "
+ else:
+ c = (e.unicode).encode('latin-1')
+ if c:
+ self._setvalue(self.value[:self.pos] + c + self.value[self.pos:])
+ self.pos += len(c)
+ except: #ignore weird characters
+ pass
+ self.repaint()
+ elif e.type == MOUSEBUTTONDOWN:
+ self.setCursorByXY(e.pos)
+ self.repaint()
+
+ elif e.type == FOCUS:
+ self.repaint()
+ elif e.type == BLUR:
+ self.repaint()
+
+ self.pcls = ""
+ if self.container.myfocus is self: self.pcls = "focus"
+
+ return used
+
+ def __setattr__(self,k,v):
+ if k == 'value':
+ if v == None: v = ''
+ v = str(v)
+ self.pos = len(v)
+ _v = self.__dict__.get(k,NOATTR)
+ self.__dict__[k]=v
+ if k == 'value' and _v != NOATTR and _v != v:
+ self.send(CHANGE)
+ self.repaint()
+
+# The first version of this code was done by Clint Herron, and is a modified version of input.py (by Phil Hassey).
+# It is under the same license as the rest of the PGU library. \ No newline at end of file
diff --git a/pgu/gui/textarea.pyc b/pgu/gui/textarea.pyc
new file mode 100644
index 0000000..8c94884
--- /dev/null
+++ b/pgu/gui/textarea.pyc
Binary files differ
diff --git a/pgu/gui/theme.py b/pgu/gui/theme.py
new file mode 100644
index 0000000..8c75329
--- /dev/null
+++ b/pgu/gui/theme.py
@@ -0,0 +1,486 @@
+"""
+"""
+import os, re
+import pygame
+
+from const import *
+import surface
+from basic import parse_color
+
+__file__ = os.path.abspath(__file__)
+
+def _list_themes(dir):
+ d = {}
+ for entry in os.listdir(dir):
+ if os.path.exists(os.path.join(dir, entry, 'config.txt')):
+ d[entry] = os.path.join(dir, entry)
+ return d
+
+class Theme:
+ """Theme interface.
+
+ <p>If you wish to create your own theme, create a class with this interface, and
+ pass it to gui.App via <tt>gui.App(theme=MyTheme())</tt>.</p>
+
+ <strong>Default Theme</strong>
+
+ <pre>Theme(dirs='default')</pre>
+ <dl>
+ <dt>dirs<dd>Name of the theme dir to load a theme from. May be an absolute path to a theme, if pgu is not installed, or if you created your own theme. May include several dirs in a list if data is spread across several themes.
+ </dl>
+
+ <strong>Example</strong>
+
+ <code>
+ theme = gui.Theme("default")
+ theme = gui.Theme(["mytheme","mytheme2"])
+ </code>
+ """
+ def __init__(self,dirs='default'):
+ self.config = {}
+ self.dict = {}
+ self._loaded = []
+ self.cache = {}
+ self._preload(dirs)
+ pygame.font.init()
+
+ def _preload(self,ds):
+ if not isinstance(ds, list):
+ ds = [ds]
+ for d in ds:
+ if d not in self._loaded:
+ self._load(d)
+ self._loaded.append(d)
+
+ def _load(self, name):
+ #theme_dir = themes[name]
+
+ #try to load the local dir, or absolute path
+ dnames = [name]
+
+ #if the package isn't installed and people are just
+ #trying out the scripts or examples
+ dnames.append(os.path.join(os.path.dirname(__file__),"..","..","data","themes",name))
+
+ #if the package is installed, and the package is installed
+ #in /usr/lib/python2.3/site-packages/pgu/
+ #or c:\python23\lib\site-packages\pgu\
+ #the data is in ... lib/../share/ ...
+ dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","share","pgu","themes",name))
+ dnames.append(os.path.join(os.path.dirname(__file__),"..","..","..","..","..","share","pgu","themes",name))
+ dnames.append(os.path.join(os.path.dirname(__file__),"..","..","share","pgu","themes",name))
+ for dname in dnames:
+ if os.path.isdir(dname): break
+ if not os.path.isdir(dname):
+ raise 'could not find theme '+name
+
+ fname = os.path.join(dname,"config.txt")
+ if os.path.isfile(fname):
+ try:
+ f = open(fname)
+ for line in f.readlines():
+ vals = line.strip().split()
+ if len(vals) < 3: continue
+ cls = vals[0]
+ del vals[0]
+ pcls = ""
+ if cls.find(":")>=0:
+ cls,pcls = cls.split(":")
+ attr = vals[0]
+ del vals[0]
+ self.config[cls+":"+pcls+" "+attr] = (dname, vals)
+ finally:
+ f.close()
+ fname = os.path.join(dname,"style.ini")
+ if os.path.isfile(fname):
+ import ConfigParser
+ cfg = ConfigParser.ConfigParser()
+ f = open(fname,'r')
+ cfg.readfp(f)
+ for section in cfg.sections():
+ cls = section
+ pcls = ''
+ if cls.find(":")>=0:
+ cls,pcls = cls.split(":")
+ for attr in cfg.options(section):
+ vals = cfg.get(section,attr).strip().split()
+ self.config[cls+':'+pcls+' '+attr] = (dname,vals)
+
+ is_image = re.compile('\.(gif|jpg|bmp|png|tga)$', re.I)
+ def _get(self,key):
+ if not key in self.config: return
+ if key in self.dict: return self.dict[key]
+ dvals = self.config[key]
+ dname, vals = dvals
+ #theme_dir = themes[name]
+ v0 = vals[0]
+ if v0[0] == '#':
+ v = parse_color(v0)
+ #if (len(v0) == 7):
+ # # Due to a bug in pygame 1.8 (?) we need to explicitly
+ # # specify the alpha value (otherwise it defaults to zero)
+ # v0 += "FF"
+ #v = pygame.color.Color(v0)
+ elif v0.endswith(".ttf") or v0.endswith(".TTF"):
+ v = pygame.font.Font(os.path.join(dname, v0),int(vals[1]))
+ elif self.is_image.search(v0) is not None:
+ v = pygame.image.load(os.path.join(dname, v0))
+ else:
+ try: v = int(v0)
+ except: v = pygame.font.SysFont(v0, int(vals[1]))
+ self.dict[key] = v
+ return v
+
+ def get(self,cls,pcls,attr):
+ """Interface method -- get the value of a style attribute.
+
+ <pre>Theme.get(cls,pcls,attr): return value</pre>
+
+ <dl>
+ <dt>cls<dd>class, for example "checkbox", "button", etc.
+ <dt>pcls<dd>pseudo class, for example "hover", "down", etc.
+ <dt>attr<dd>attribute, for example "image", "background", "font", "color", etc.
+ </dl>
+
+ <p>returns the value of the attribute.</p>
+
+ <p>This method is called from [[gui-style]].</p>
+ """
+
+ if not self._loaded: self._preload("default")
+
+ o = cls+":"+pcls+" "+attr
+
+ #if not hasattr(self,'_count'):
+ # self._count = {}
+ #if o not in self._count: self._count[o] = 0
+ #self._count[o] += 1
+
+ if o in self.cache:
+ return self.cache[o]
+
+ v = self._get(cls+":"+pcls+" "+attr)
+ if v:
+ self.cache[o] = v
+ return v
+
+ pcls = ""
+ v = self._get(cls+":"+pcls+" "+attr)
+ if v:
+ self.cache[o] = v
+ return v
+
+ cls = "default"
+ v = self._get(cls+":"+pcls+" "+attr)
+ if v:
+ self.cache[o] = v
+ return v
+
+ v = 0
+ self.cache[o] = v
+ return v
+
+ def box(self,w,s):
+ style = w.style
+
+ c = (0,0,0)
+ if style.border_color != 0: c = style.border_color
+ w,h = s.get_width(),s.get_height()
+
+ s.fill(c,(0,0,w,style.border_top))
+ s.fill(c,(0,h-style.border_bottom,w,style.border_bottom))
+ s.fill(c,(0,0,style.border_left,h))
+ s.fill(c,(w-style.border_right,0,style.border_right,h))
+
+
+ def getspacing(self,w):
+ # return the top, right, bottom, left spacing around the widget
+ if not hasattr(w,'_spacing'): #HACK: assume spacing doesn't change re pcls
+ s = w.style
+ xt = s.margin_top+s.border_top+s.padding_top
+ xr = s.padding_right+s.border_right+s.margin_right
+ xb = s.padding_bottom+s.border_bottom+s.margin_bottom
+ xl = s.margin_left+s.border_left+s.padding_left
+ w._spacing = xt,xr,xb,xl
+ return w._spacing
+
+
+ def resize(self,w,m):
+ # Returns the rectangle expanded in each direction
+ def expand_rect(rect, left, top, right, bottom):
+ return pygame.Rect(rect.x - left,
+ rect.y - top,
+ rect.w + left + right,
+ rect.h + top + bottom)
+
+ def func(width=None,height=None):
+ s = w.style
+
+ pt,pr,pb,pl = s.padding_top,s.padding_right,s.padding_bottom,s.padding_left
+ bt,br,bb,bl = s.border_top,s.border_right,s.border_bottom,s.border_left
+ mt,mr,mb,ml = s.margin_top,s.margin_right,s.margin_bottom,s.margin_left
+ # Calculate the total space on each side
+ top = pt+bt+mt
+ right = pr+br+mr
+ bottom = pb+bb+mb
+ left = pl+bl+ml
+ ttw = left+right
+ tth = top+bottom
+
+ ww,hh = None,None
+ if width != None: ww = width-ttw
+ if height != None: hh = height-tth
+ ww,hh = m(ww,hh)
+
+ if width == None: width = ww
+ if height == None: height = hh
+
+ #if the widget hasn't respected the style.width,
+ #style height, we'll add in the space for it...
+ width = max(width-ttw, ww, w.style.width)
+ height = max(height-tth, hh, w.style.height)
+
+ #width = max(ww,w.style.width-tw)
+ #height = max(hh,w.style.height-th)
+
+ r = pygame.Rect(left,top,width,height)
+
+ w._rect_padding = expand_rect(r, pl, pt, pr, pb)
+ w._rect_border = expand_rect(w._rect_padding, bl, bt, br, bb)
+ w._rect_margin = expand_rect(w._rect_border, ml, mt, mr, mb)
+
+ #w._rect_padding = pygame.Rect(r.x-pl,r.y-pt,r.w+pl+pr,r.h+pt+pb)
+ #r = w._rect_padding
+ #w._rect_border = pygame.Rect(r.x-bl,r.y-bt,r.w+bl+br,r.h+bt+bb)
+ #r = w._rect_border
+ #w._rect_margin = pygame.Rect(r.x-ml,r.y-mt,r.w+ml+mr,r.h+mt+mb)
+
+ # align it within it's zone of power.
+ rect = pygame.Rect(left, top, ww, hh)
+ dx = width-rect.w
+ dy = height-rect.h
+ rect.x += (w.style.align+1)*dx/2
+ rect.y += (w.style.valign+1)*dy/2
+
+ w._rect_content = rect
+
+ return (w._rect_margin.w, w._rect_margin.h)
+ return func
+
+
+ def paint(self,w,m):
+ def func(s):
+# if w.disabled:
+# if not hasattr(w,'_disabled_bkgr'):
+# w._disabled_bkgr = s.convert()
+# orig = s
+# s = w._disabled_bkgr.convert()
+
+# if not hasattr(w,'_theme_paint_bkgr'):
+# w._theme_paint_bkgr = s.convert()
+# else:
+# s.blit(w._theme_paint_bkgr,(0,0))
+#
+# if w.disabled:
+# orig = s
+# s = w._theme_paint_bkgr.convert()
+
+ if w.disabled:
+ if (not (hasattr(w,'_theme_bkgr') and
+ w._theme_bkgr.get_width() == s.get_width() and
+ w._theme_bkgr.get_height() == s.get_height())):
+ w._theme_bkgr = s.copy()
+ orig = s
+ s = w._theme_bkgr
+ s.fill((0,0,0,0))
+ s.blit(orig,(0,0))
+
+ if hasattr(w,'background'):
+ w.background.paint(surface.subsurface(s,w._rect_border))
+ self.box(w,surface.subsurface(s,w._rect_border))
+ r = m(surface.subsurface(s,w._rect_content))
+
+ if w.disabled:
+ s.set_alpha(128)
+ orig.blit(s,(0,0))
+
+# if w.disabled:
+# orig.blit(w._disabled_bkgr,(0,0))
+# s.set_alpha(128)
+# orig.blit(s,(0,0))
+
+ w._painted = True
+ return r
+ return func
+
+ def event(self,w,m):
+ def func(e):
+ rect = w._rect_content
+ if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN:
+ sub = pygame.event.Event(e.type,{
+ 'button':e.button,
+ 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)})
+ elif e.type == CLICK:
+ sub = pygame.event.Event(e.type,{
+ 'button':e.button,
+ 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y)})
+ elif e.type == MOUSEMOTION:
+ sub = pygame.event.Event(e.type,{
+ 'buttons':e.buttons,
+ 'pos':(e.pos[0]-rect.x,e.pos[1]-rect.y),
+ 'rel':e.rel})
+ else:
+ sub = e
+ r = m(sub)
+ return r
+ return func
+
+ def update(self,w,m):
+ def func(s):
+ if w.disabled: return []
+ r = m(surface.subsurface(s,w._rect_content))
+ if type(r) == list:
+ dx,dy = w._rect_content.topleft
+ for rr in r:
+ rr.x,rr.y = rr.x+dx,rr.y+dy
+ return r
+ return func
+
+ def open(self,w,m):
+ def func(widget=None,x=None,y=None):
+ if not hasattr(w,'_rect_content'): w.rect.w,w.rect.h = w.resize() #HACK: so that container.open won't resize again!
+ rect = w._rect_content
+ ##print w.__class__.__name__, rect
+ if x != None: x += rect.x
+ if y != None: y += rect.y
+ return m(widget,x,y)
+ return func
+
+ #def open(self,w,m):
+ # def func(widget=None):
+ # return m(widget)
+ # return func
+
+ def decorate(self,widget,level):
+ """Interface method -- decorate a widget.
+
+ <p>The theme system is given the opportunity to decorate a widget methods at the
+ end of the Widget initializer.</p>
+
+ <pre>Theme.decorate(widget,level)</pre>
+
+ <dl>
+ <dt>widget<dd>the widget to be decorated
+ <dt>level<dd>the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects.
+ </dl>
+ """
+
+ w = widget
+ if level == False: return
+
+ if type(w.style.background) != int:
+ w.background = Background(w,self)
+
+ if level == 'app': return
+
+ for k,v in w.style.__dict__.items():
+ if k in ('border','margin','padding'):
+ for kk in ('top','bottom','left','right'):
+ setattr(w.style,'%s_%s'%(k,kk),v)
+
+ w.paint = self.paint(w,w.paint)
+ w.event = self.event(w,w.event)
+ w.update = self.update(w,w.update)
+ w.resize = self.resize(w,w.resize)
+ w.open = self.open(w,w.open)
+
+ def render(self,s,box,r):
+ """Interface method - render a special widget feature.
+
+ <pre>Theme.render(s,box,r)</pre>
+
+ <dl>
+ <dt>s<dt>pygame.Surface
+ <dt>box<dt>box data, a value returned from Theme.get, typically a pygame.Surface
+ <dt>r<dt>pygame.Rect with the size that the box data should be rendered
+ </dl>
+
+ """
+
+ if box == 0: return
+
+ if isinstance(box, pygame.Color) or type(box) == tuple:
+ s.fill(box,r)
+ return
+
+ x,y,w,h=r.x,r.y,r.w,r.h
+ ww,hh=box.get_width()/3,box.get_height()/3
+ xx,yy=x+w,y+h
+ src = pygame.rect.Rect(0,0,ww,hh)
+ dest = pygame.rect.Rect(0,0,ww,hh)
+
+ s.set_clip(pygame.Rect(x+ww,y+hh,w-ww*2,h-hh*2))
+ src.x,src.y = ww,hh
+ for dest.y in xrange(y+hh,yy-hh,hh):
+ for dest.x in xrange(x+ww,xx-ww,ww): s.blit(box,dest,src)
+
+ s.set_clip(pygame.Rect(x+ww,y,w-ww*3,hh))
+ src.x,src.y,dest.y = ww,0,y
+ for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src)
+ dest.x = xx-ww*2
+ s.set_clip(pygame.Rect(x+ww,y,w-ww*2,hh))
+ s.blit(box,dest,src)
+
+ s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*3,hh))
+ src.x,src.y,dest.y = ww,hh*2,yy-hh
+ for dest.x in xrange(x+ww,xx-ww*2,ww): s.blit(box,dest,src)
+ dest.x = xx-ww*2
+ s.set_clip(pygame.Rect(x+ww,yy-hh,w-ww*2,hh))
+ s.blit(box,dest,src)
+
+ s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*3))
+ src.y,src.x,dest.x = hh,0,x
+ for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src)
+ dest.y = yy-hh*2
+ s.set_clip(pygame.Rect(x,y+hh,xx,h-hh*2))
+ s.blit(box,dest,src)
+
+ s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*3))
+ src.y,src.x,dest.x=hh,ww*2,xx-ww
+ for dest.y in xrange(y+hh,yy-hh*2,hh): s.blit(box,dest,src)
+ dest.y = yy-hh*2
+ s.set_clip(pygame.Rect(xx-ww,y+hh,xx,h-hh*2))
+ s.blit(box,dest,src)
+
+ s.set_clip()
+ src.x,src.y,dest.x,dest.y = 0,0,x,y
+ s.blit(box,dest,src)
+
+ src.x,src.y,dest.x,dest.y = ww*2,0,xx-ww,y
+ s.blit(box,dest,src)
+
+ src.x,src.y,dest.x,dest.y = 0,hh*2,x,yy-hh
+ s.blit(box,dest,src)
+
+ src.x,src.y,dest.x,dest.y = ww*2,hh*2,xx-ww,yy-hh
+ s.blit(box,dest,src)
+
+
+
+import pygame
+import widget
+
+class Background(widget.Widget):
+ def __init__(self,value,theme,**params):
+ params['decorate'] = False
+ widget.Widget.__init__(self,**params)
+ self.value = value
+ self.theme = theme
+
+ def paint(self,s):
+ r = pygame.Rect(0,0,s.get_width(),s.get_height())
+ v = self.value.style.background
+ if isinstance(v, pygame.Color) or type(v) == tuple:
+ s.fill(v)
+ else:
+ self.theme.render(s,v,r)
diff --git a/pgu/gui/theme.pyc b/pgu/gui/theme.pyc
new file mode 100644
index 0000000..ca21e90
--- /dev/null
+++ b/pgu/gui/theme.pyc
Binary files differ
diff --git a/pgu/gui/widget.py b/pgu/gui/widget.py
new file mode 100644
index 0000000..eb0a7bc
--- /dev/null
+++ b/pgu/gui/widget.py
@@ -0,0 +1,352 @@
+"""
+"""
+import pygame
+
+import pguglobals
+import style
+
+class SignalCallback:
+ # The function to call
+ func = None
+ # The parameters to pass to the function (as a list)
+ params = None
+
+class Widget:
+ """Template object - base for all widgets.
+
+ <pre>Widget(**params)</pre>
+
+ <p>A number of optional params may be passed to the Widget initializer.</p>
+
+ <dl>
+ <dt>decorate<dd>defaults to True. If true, will call <tt>theme.decorate(self)</tt> to allow the theme a chance to decorate the widget.
+ <dt>style<dd>a dict of style parameters.
+ <dt>x, y, width, height<dd>position and size parameters, passed along to style
+ <dt>align, valign<dd>alignment parameters, passed along to style
+ <dt>font, color, background<dd>other common parameters that are passed along to style
+ <dt>cls<dd>class name as used by Theme
+ <dt>name<dd>name of widget as used by Form. If set, will call <tt>form.add(self,name)</tt> to add the widget to the most recently created Form.
+ <dt>focusable<dd>True if this widget can receive focus via Tab, etc. Defaults to True.
+ <dt>disabled<dd>True of this widget is disabled. Defaults to False.
+ <dt>value<dd>initial value
+ </dl>
+
+ <strong>Example - Creating your own Widget</strong>
+ <p>This example shows which methods are template methods.</p>
+ <code>
+ class Draw(gui.Widget):
+ def paint(self,s):
+ #paint the pygame.Surface
+ return
+
+ def update(self,s):
+ #update the pygame.Surface and return the update rects
+ return [pygame.Rect(0,0,self.rect.w,self.rect.h)]
+
+ def event(self,e):
+ #handle the pygame.Event
+ return
+
+ def resize(self,width=None,height=None):
+ #return the width and height of this widget
+ return 256,256
+ </code>
+ """
+
+ def __init__(self,**params):
+ #object.Object.__init__(self)
+ self.connects = {}
+ params.setdefault('decorate',True)
+ params.setdefault('style',{})
+ params.setdefault('focusable',True)
+ params.setdefault('disabled',False)
+
+ self.focusable = params['focusable']
+ self.disabled = params['disabled']
+
+ self.rect = pygame.Rect(params.get('x',0),params.get('y',0),params.get('width',0),params.get('height',0))
+
+ s = params['style']
+ #some of this is a bit "theme-ish" but it is very handy, so these
+ #things don't have to be put directly into the style.
+ for att in ('align','valign','x','y','width','height','color','font','background'):
+ if att in params: s[att] = params[att]
+ self.style = style.Style(self,s)
+
+ self.cls = 'default'
+ if 'cls' in params: self.cls = params['cls']
+ if 'name' in params:
+ import form
+ self.name = params['name']
+ if hasattr(form.Form,'form') and form.Form.form != None:
+ form.Form.form.add(self)
+ self.form = form.Form.form
+ if 'value' in params: self.value = params['value']
+ self.pcls = ""
+
+ if params['decorate'] != False:
+ if (not pguglobals.app):
+ # TODO - fix this somehow
+ import app
+ print 'gui.widget: creating an App'
+ app.App()
+ pguglobals.app.theme.decorate(self,params['decorate'])
+
+ def focus(self):
+ """Focus this Widget.
+
+ <pre>Widget.focus()</pre>
+ """
+ if getattr(self,'container',None) != None:
+ if self.container.myfocus != self: ## by Gal Koren
+ self.container.focus(self)
+ def blur(self):
+ """Blur this Widget.
+
+ <pre>Widget.blur()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.blur(self)
+ def open(self):
+ """Open this Widget as a modal dialog.
+
+ <pre>Widget.open()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.open(self)
+ def close(self):
+ """Close this Widget (if it is a modal dialog.)
+
+ <pre>Widget.close()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.close(self)
+ def resize(self,width=None,height=None):
+ """Template method - return the size and width of this widget.
+
+ <p>Responsible for also resizing all sub-widgets.</p>
+
+ <pre>Widget.resize(width,height): return width,height</pre>
+
+ <dl>
+ <dt>width<dd>suggested width
+ <dt>height<dd>suggested height
+ </dl>
+
+ <p>If not overridden, will return self.style.width, self.style.height</p>
+ """
+ return self.style.width, self.style.height
+ def chsize(self):
+ """Change the size of this widget.
+
+ <p>Calling this method will cause a resize on all the widgets,
+ including this one.</p>
+
+ <pre>Widget.chsize()</pre>
+ """
+
+ if not hasattr(self,'_painted'): return
+
+ if not hasattr(self,'container'): return
+
+ if pguglobals.app:
+ if pguglobals.app._chsize:
+ return
+ pguglobals.app.chsize()
+ return
+
+ #if hasattr(app.App,'app'):
+ # w,h = self.rect.w,self.rect.h
+ # w2,h2 = self.resize()
+ # if w2 != w or h2 != h:
+ # app.App.app.chsize()
+ # else:
+ # self.repaint()
+
+
+ def update(self,s):
+ """Template method - update the surface
+
+ <pre>Widget.update(s): return list of pygame.Rect(s)</pre>
+
+ <dl>
+ <dt>s<dd>pygame.Surface to update
+ </dl>
+
+ <p>return - a list of the updated areas as pygame.Rect(s).</p>
+ """
+ return
+
+ def paint(self,s):
+ """Template method - paint the surface
+
+ <pre>Widget.paint(s)</pre>
+
+ <dl>
+ <dt>s<dd>pygame.Surface to paint
+ </dl>
+ """
+ return
+
+ def repaint(self):
+ """Request a repaint of this Widget.
+
+ <pre>Widget.repaint()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.repaint(self)
+ def repaintall(self):
+ """Request a repaint of all Widgets.
+
+ <pre>Widget.repaintall()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.repaintall()
+ def reupdate(self):
+ """Request a reupdate of this Widget
+
+ <pre>Widget.reupdate()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.reupdate(self)
+ def next(self):
+ """Pass focus to next Widget.
+
+ <p>Widget order determined by the order they were added to their container.</p>
+
+ <pre>Widget.next()</pre>
+ """
+ if getattr(self,'container',None) != None: self.container.next(self)
+ def previous(self):
+ """Pass focus to previous Widget.
+
+ <p>Widget order determined by the order they were added to their container.</p>
+
+ <pre>Widget.previous()</pre>
+ """
+
+ if getattr(self,'container',None) != None: self.container.previous(self)
+
+ def get_abs_rect(self):
+ """Get the absolute rect of this widget on the App screen
+
+ <pre>Widget.get_abs_rect(): return pygame.Rect</pre>
+ """
+ x, y = self.rect.x , self.rect.y
+ x += self._rect_content.x
+ y += self._rect_content.y
+ c = getattr(self,'container',None)
+ while c:
+ x += c.rect.x
+ y += c.rect.y
+ if hasattr(c,'_rect_content'):
+ x += c._rect_content.x
+ y += c._rect_content.y
+ c = getattr(c,'container',None)
+ return pygame.Rect(x, y, self.rect.w, self.rect.h)
+
+ def connect(self,code,func,*params):
+ """Connect a event code to a callback function.
+
+ <p>There may be multiple callbacks per event code.</p>
+
+ <pre>Object.connect(code,fnc,value)</pre>
+
+ <dl>
+ <dt>code<dd>event type [[gui-const]]
+ <dt>fnc<dd>callback function
+ <dt>*values<dd>values to pass to callback. Please note that callbacks may also have "magicaly" parameters. Such as:
+ <dl>
+ <dt>_event<dd>receive the event
+ <dt>_code<dd>receive the event code
+ <dt>_widget<dd>receive the sending widget
+ </dl>
+ </dl>
+
+ <strong>Example</strong>
+ <code>
+ def onclick(value):
+ print 'click',value
+
+ w = Button("PGU!")
+ w.connect(gui.CLICK,onclick,'PGU Button Clicked')
+ </code>
+ """
+ # Wrap the callback function and add it to the list
+ cb = SignalCallback()
+ cb.func = func
+ cb.params = params
+ if (not code in self.connects):
+ self.connects[code] = []
+ self.connects[code].append(cb)
+
+ # Remove signal handlers from the given event code. If func is specified,
+ # only those handlers will be removed. If func is None, all handlers
+ # will be removed.
+ def disconnect(self, code, func=None):
+ if (not code in self.connects):
+ return
+ if (not func):
+ # Remove all signal handlers
+ del self.connects[code]
+ else:
+ # Remove handlers that call 'func'
+ n = 0
+ callbacks = self.connects[code]
+ while (n < len(callbacks)):
+ if (callbacks[n].func == func):
+ # Remove this callback
+ del callbacks[n]
+ else:
+ n += 1
+
+ def send(self,code,event=None):
+ """Send a code, event callback trigger.
+
+ <pre>Object.send(code,event=None)</pre>
+
+ <dl>
+ <dt>code<dd>event code
+ <dt>event<dd>event
+ </dl>
+ """
+ if (not code in self.connects):
+ return
+ # Trigger all connected signal handlers
+ for cb in self.connects[code]:
+ func = cb.func
+ values = list(cb.params)
+
+ nargs = func.func_code.co_argcount
+ names = list(func.func_code.co_varnames)[:nargs]
+ if hasattr(func,'im_class'): names.pop(0)
+
+ args = []
+ magic = {'_event':event,'_code':code,'_widget':self}
+ for name in names:
+ if name in magic.keys():
+ args.append(magic[name])
+ elif len(values):
+ args.append(values.pop(0))
+ else:
+ break
+ args.extend(values)
+ func(*args)
+
+ def _event(self,e):
+ if self.disabled: return
+ self.send(e.type,e)
+ return self.event(e)
+# return
+# import app
+# if hasattr(app.App,'app'):
+# app.App.app.events.append((self,e))
+
+ def event(self,e):
+ """Template method - called when an event is passed to this object.
+
+ <p>Please note that if you use an event, returning the value True
+ will stop parent containers from also using the event. (For example, if
+ your widget handles TABs or arrow keys, and you don't want those to
+ also alter the focus.)</p>
+
+ <dl>
+ <dt>e<dd>event
+ </dl>
+ """
+
+ return
diff --git a/pgu/gui/widget.pyc b/pgu/gui/widget.pyc
new file mode 100644
index 0000000..b053a6c
--- /dev/null
+++ b/pgu/gui/widget.pyc
Binary files differ
diff --git a/pgu/hexvid.py b/pgu/hexvid.py
new file mode 100644
index 0000000..2d4156d
--- /dev/null
+++ b/pgu/hexvid.py
@@ -0,0 +1,127 @@
+"""Hexagonal tile engine.
+
+<p>Note -- this engine is not finished. Sprites are not supported. It
+can still be useful for using the level editor, and for rendering hex
+terrains, however. If you are able to update it and use it in a real game,
+help would be greatly appreciated!</p>
+
+<p>please note that this file is alpha, and is subject to modification in
+future versions of pgu!</p>
+
+"""
+print 'pgu.hexvid','This module is alpha, and is subject to change.'
+
+from pgu.vid import *
+import pygame
+
+
+class Hexvid(Vid):
+ """Create an hex vid engine. See [[vid]]"""
+ def update(self,screen):
+ return self.paint(screen)
+
+ def paint(self,screen):
+ sw,sh = screen.get_width(),screen.get_height()
+ self.view.w,self.view.h = sw,sh
+
+ tlayer = self.tlayer
+ blayer = self.blayer
+ #zlayer = self.zlayer
+ w,h = len(tlayer[0]),len(tlayer)
+
+ #iso_w,iso_h,iso_z,tile_w,tile_h,base_w,base_h = self.iso_w,self.iso_h,self.iso_z,self.tile_w,self.tile_h,self.base_w,self.base_h
+
+ tile_w,tile_h = self.tile_w,self.tile_h
+ tile_w2,tile_h2 = tile_w/2,tile_h/2
+
+ view = self.view
+ adj = self.adj = pygame.Rect(-self.view.x,-self.view.y,0,0)
+
+ w,h = len(tlayer[0]),len(tlayer)
+ tiles = self.tiles
+
+ #""
+ if self.bounds == None:
+ tmp,y1 = self.tile_to_view((0,0))
+ x1,tmp = self.tile_to_view((0,h+1))
+ tmp,y2 = self.tile_to_view((w+1,h+1))
+ x2,tmp = self.tile_to_view((w+1,0))
+ self.bounds = pygame.Rect(x1,y1,x2-x1,y2-y1)
+ print self.bounds
+ #""
+
+ if self.bounds != None: self.view.clamp_ip(self.bounds)
+
+ ox,oy = self.screen_to_tile((0,0))
+ sx,sy = self.tile_to_view((ox,oy))
+ dx,dy = sx - self.view.x,sy - self.view.y
+
+ bot = 1
+
+ tile_wi = tile_w + tile_w/2
+ tile_wi2 = tile_wi/2
+
+ #dx += tile_w/2
+
+ for i2 in xrange(-bot,self.view.h/tile_h2+bot*3): #NOTE: 3 seems a bit much, but it works.
+ tx,ty = ox + i2/2 + i2%2,oy + i2/2
+ x,y = (i2%2)*tile_wi2 + dx,i2*tile_h2 + dy
+
+ #to adjust for the -1 in i1
+ x,tx,ty = x-tile_wi,tx-1,ty+1
+
+ x -= tile_w/2
+ for i1 in xrange(-1,self.view.w/tile_wi+1):
+ if ty >= 0 and ty < h and tx >= 0 and tx < w:
+ if blayer != None:
+ n = blayer[ty][tx]
+ if n != 0:
+ t = tiles[n]
+ if t != None and t.image != None:
+ screen.blit(t.image,(x,y))
+ n = tlayer[ty][tx]
+ if n != 0:
+ t = tiles[n]
+ if t != None and t.image != None:
+ screen.blit(t.image,(x,y))
+
+
+ tx += 1
+ ty -= 1
+ x += tile_wi
+
+ return [pygame.Rect(0,0,screen.get_width(),screen.get_height())]
+
+ def view_to_tile(self,pos):
+ x,y = pos
+ #x = x + (self.tile_w*1/2)
+
+ x,y = int(x*4/(self.tile_w*3)), y*2/self.tile_h
+ nx = (x + y) / 2
+ ny = (y - x) / 2
+ return nx,ny
+
+ def tile_to_view(self,pos):
+ x,y = pos
+ nx = x - y
+ ny = x + y
+ nx,ny = int(nx*(self.tile_w*3)/4), ny*self.tile_h/2
+
+ #nx = nx - (self.tile_w*1/2)
+ return nx,ny
+
+ def screen_to_tile(self,pos): #NOTE HACK : not sure if the 3/8 is right or not, but it is pretty close...
+ pos = pos[0]+self.view.x + self.tile_w*3/8,pos[1]+self.view.y
+ pos = self.view_to_tile(pos)
+ return pos
+
+ def tile_to_screen(self,pos):
+ pos = self.tile_to_view(pos)
+ pos = pos[0]-self.view.x,pos[1]-self.view.y
+ return pos
+
+
+ def tga_load_tiles(self,fname,size,tdata={}):
+ Vid.tga_load_tiles(self,fname,size,tdata)
+
+ self.tile_w,self.tile_h = size \ No newline at end of file
diff --git a/pgu/hexvid.pyc b/pgu/hexvid.pyc
new file mode 100644
index 0000000..e29026f
--- /dev/null
+++ b/pgu/hexvid.pyc
Binary files differ
diff --git a/pgu/high.py b/pgu/high.py
new file mode 100644
index 0000000..e05d22a
--- /dev/null
+++ b/pgu/high.py
@@ -0,0 +1,154 @@
+"""Classes for handling high score tables.
+"""
+
+import os
+
+def High(fname,limit=10):
+ """Create a Highs object and returns the default high score table.
+
+ <pre>High(fname,limit=10)</pre>
+
+ <dl>
+ <dt>fname <dd>filename to store high scores in
+ <dt>limit <dd>limit of scores to be recorded, defaults to 10
+ </dl>
+ """
+ return Highs(fname,limit)['default']
+
+class _Score:
+ def __init__(self,score,name,data=None):
+ self.score,self.name,self.data=score,name,data
+
+class _High:
+ """A high score table. These objects are passed to the user, but should not be created directly.
+
+ <p>You can iterate them:</p>
+ <code>
+ for e in myhigh:
+ print e.score,e.name,e.data
+ </code>
+
+ <p>You can modify them:</p>
+ <code>
+ myhigh[0].name = 'Cuzco'
+ </code>
+
+ <p>You can find out their length:</p>
+ <code>
+ print len(myhigh)
+ </code>
+ """
+
+ def __init__(self,highs,limit=10):
+ self.highs = highs
+ self._list = []
+ self.limit = limit
+
+ def save(self):
+ """Save the high scores.
+
+ <pre>_High.save()</pre>
+ """
+ self.highs.save()
+
+ def submit(self,score,name,data=None):
+ """Submit a high score to this table.
+
+ <pre>_High.submit(score,name,data=None)</pre>
+
+ <p>return -- the position in the table that the score attained. None if the score did not attain a position in the table.</p>
+ """
+ n = 0
+ for e in self._list:
+ if score > e.score:
+ self._list.insert(n,_Score(score,name,data))
+ self._list = self._list[0:self.limit]
+ return n
+ n += 1
+ if len(self._list) < self.limit:
+ self._list.append(_Score(score,name,data))
+ return len(self._list)-1
+
+ def check(self,score):
+ """Check if a score will attain a position in the table.
+
+ <pre>_High.check(score)</pre>
+
+ <p>return -- the position the score will attain, else None</p>
+ """
+ n = 0
+ for e in self._list:
+ if score > e.score:
+ return n
+ n += 1
+ if len(self._list) < self.limit:
+ return len(self._list)
+
+
+ def __iter__(self):
+ return self._list.__iter__()
+
+ def __getitem__(self,key):
+ return self._list[key]
+
+ def __len__(self):
+ return self._list.__len__()
+
+
+class Highs:
+ """The high score object.
+
+ <pre>Highs(fname,limit=10)</pre>
+ <ul>
+ <dt>fname <dd>filename to store high scores in
+ <dt>limit <dd>limit of scores to be recorded, defaults to 10
+ </ul>
+
+ <p>You may access _High objects through this object:</p>
+
+ <code>
+ my_easy_hs = highs['easy']
+ my_hard_hs = highs['hard']
+ </code>
+
+ """
+ def __init__(self,fname,limit=10):
+ self.fname = fname
+ self.limit = limit
+ self.load()
+
+ def load(self):
+ """Re-load the high scores.
+
+ <pre>Highs.load()</pre>
+ """
+
+ self._dict = {}
+ try:
+ f = open(self.fname)
+ for line in f.readlines():
+ key,score,name,data = line.strip().split("\t")
+ if key not in self._dict:
+ self._dict[key] = _High(self,self.limit)
+ high = self._dict[key]
+ high.submit(int(score),name,data)
+ f.close()
+ except:
+ pass
+
+ def save(self):
+ """Save the high scores.
+
+ <pre>Highs.save()</pre>
+ """
+
+ f = open(self.fname,"w")
+ for key,high in self._dict.items():
+ for e in high:
+ f.write("%s\t%d\t%s\t%s\n"%(key,e.score,e.name,str(e.data)))
+ f.close()
+
+ def __getitem__(self,key):
+ if key not in self._dict:
+ self._dict[key] = _High(self,self.limit)
+ return self._dict[key]
diff --git a/pgu/html.py b/pgu/html.py
new file mode 100644
index 0000000..817c000
--- /dev/null
+++ b/pgu/html.py
@@ -0,0 +1,571 @@
+"""a html renderer
+"""
+
+import sys
+import htmllib
+import re
+import pygame
+from pygame.locals import *
+
+from pgu import gui
+
+_amap = {'left':-1,'right':1,'center':0,None:None,'':None,}
+_vamap = {'top':-1,'bottom':1,'center':0,'middle':0,None:None,'':None,}
+
+# Used by the HTML parser to load external resources (like images). This
+# class loads content from the local file system. But you can pass your own
+# resource loader to the HTML parser to find images by other means.
+class ResourceLoader(object):
+ # Loads an image and returns it as a pygame image
+ def load_image(this, path):
+ return pygame.image.load(path)
+
+class _dummy:
+ pass
+
+class _flush:
+ def __init__(self):
+ self.style = _dummy()
+ self.style.font = None
+ self.style.color = None
+ self.cls = None
+ def add(self,w): pass
+ def space(self,v): pass
+
+class _hr(gui.Color):
+ def __init__(self,**params):
+ gui.Color.__init__(self,(0,0,0),**params)
+ def resize(self,width=None,height=None):
+ w,h = self.style.width,self.style.height
+ #if width != None: self.rect.w = width
+ #else: self.rect.w = 1
+
+ #xt,xr,xb,xl = self.getspacing()
+
+ if width != None: w = max(w,width)
+ if height != None: h = max(h,height)
+ w = max(w,1)
+ h = max(h,1)
+
+ return w,h #self.container.rect.w,h
+
+ #self.rect.w = max(1,width,self.container.rect.w-(xl+xr))
+
+ #print self.rect
+ #self.rect.w = 1
+
+class _html(htmllib.HTMLParser):
+ def init(self,doc,font,color,_globals,_locals,loader=None):
+ self.mystack = []
+ self.document = doc
+ if (loader):
+ self.loader = loader
+ else:
+ # Use the default resource loader
+ self.loader = ResourceLoader()
+ self.myopen('document',self.document)
+
+ self.myfont = self.font = font
+ self.mycolor = self.color = color
+
+ self.form = None
+
+ self._globals = _globals
+ self._locals = _locals
+
+ def myopen(self,type_,w):
+
+ self.mystack.append((type_,w))
+ self.type,self.item = type_,w
+
+ self.font = self.item.style.font
+ self.color = self.item.style.color
+
+ if not self.font: self.font = self.myfont
+ if not self.color: self.color = self.mycolor
+
+ def myclose(self,type_):
+ t = None
+ self.mydone()
+ while t != type_:
+ #if len(self.mystack)==0: return
+ t,w = self.mystack.pop()
+ t,w = self.mystack.pop()
+ self.myopen(t,w)
+
+ def myback(self,type_):
+ if type(type_) == str: type_ = [type_,]
+ self.mydone()
+ #print 'myback',type_
+ t = None
+ while t not in type_:
+ #if len(self.mystack)==0: return
+ t,w = self.mystack.pop()
+ self.myopen(t,w)
+
+ def mydone(self):
+ #clearing out the last </p>
+ if not hasattr(self.item,'layout'): return
+ if len(self.item.layout._widgets) == 0: return
+ w = self.item.layout._widgets[-1]
+ if type(w) == tuple:
+ del self.item.layout._widgets[-1]
+
+
+ def start_b(self,attrs): self.font.set_bold(1)
+ def end_b(self): self.font.set_bold(0)
+ def start_i(self,attrs): self.font.set_italic(1)
+ def end_i(self): self.font.set_italic(0)
+ def start_u(self,attrs): self.font.set_underline(1)
+ def end_u(self): self.font.set_underline(0)
+ def start_br(self,attrs): self.do_br(attrs)
+ def do_br(self,attrs): self.item.br(self.font.size(" ")[1])
+ def attrs_to_map(self,attrs):
+ k = None
+ r = {}
+ for k,v in attrs: r[k] = v
+ return r
+
+ def map_to_params(self,r):
+ anum = re.compile("\D")
+
+ params = {'style':{}}
+ style = params['style']
+
+ if 'bgcolor' in r:
+ style['background'] = gui.parse_color(r['bgcolor'])
+ if 'background' in r:
+ style['background'] = self.loader.load_image(r['background'])
+ if 'border' in r: style['border'] = int(r['border'])
+
+ for k in ['width','height','colspan','rowspan','size','min','max']:
+ if k in r: params[k] = int(anum.sub("",r[k]))
+
+ for k in ['name','value']:
+ if k in r: params[k] = r[k]
+
+ if 'class' in r: params['cls'] = r['class']
+
+ if 'align' in r:
+ params['align'] = _amap[r['align']]
+ if 'valign' in r:
+ params['valign'] = _vamap[r['valign']]
+
+ if 'style' in r:
+ for st in r['style'].split(";"):
+ #print st
+ if ":" in st:
+ #print st.split(":")
+ k,v = st.split(":")
+ k = k.replace("-","_")
+ k = k.replace(" ","")
+ v = v.replace(" ","")
+ if k == 'color' or k == 'border_color' or k == 'background':
+ v = gui.parse_color(v)
+ else:
+ v = int(anum.sub("",v))
+ style[k] = v
+ return params
+
+ def map_to_connects(self,e,r):
+ for k,evt in [('onclick',gui.CLICK),('onchange',gui.CHANGE)]: #blah blah blah
+
+ if k in r:
+ #print k,r[k]
+ e.connect(evt,self.myexec,(e,r[k]))
+
+ def start_p(self,attrs):
+ r = self.attrs_to_map(attrs)
+ align = r.get("align","left")
+
+ self.check_p()
+ self.item.block(_amap[align])
+
+ def check_p(self):
+ if len(self.item.layout._widgets) == 0: return
+ if type(self.item.layout._widgets[-1]) == tuple:
+ w,h = self.item.layout._widgets[-1]
+ if w == 0: return
+ self.do_br(None)
+
+ def end_p(self):
+ #print 'end p'
+ self.check_p()
+
+
+ def start_block(self,t,attrs,align=-1):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ if 'cls' in params: params['cls'] = t+"."+params['cls']
+ else: params['cls'] = t
+ b = gui.Document(**params)
+ b.style.font = self.item.style.font
+ if 'align' in params:
+ align = params['align']
+ self.item.block(align)
+ self.item.add(b)
+ self.myopen(t,b)
+
+
+
+ def end_block(self,t):
+ self.myclose(t)
+ self.item.block(-1)
+
+ def start_div(self,attrs): self.start_block('div',attrs)
+ def end_div(self): self.end_block('div')
+ def start_center(self,attrs): self.start_block('div',attrs,0)
+ def end_center(self): self.end_block('div')
+
+ def start_h1(self,attrs): self.start_block('h1',attrs)
+ def end_h1(self): self.end_block('h1')
+ def start_h2(self,attrs): self.start_block('h2',attrs)
+ def end_h2(self): self.end_block('h2')
+ def start_h3(self,attrs): self.start_block('h3',attrs)
+ def end_h3(self): self.end_block('h3')
+ def start_h4(self,attrs): self.start_block('h4',attrs)
+ def end_h4(self): self.end_block('h4')
+ def start_h5(self,attrs): self.start_block('h5',attrs)
+ def end_h5(self): self.end_block('h5')
+ def start_h6(self,attrs): self.start_block('h6',attrs)
+ def end_h6(self): self.end_block('h6')
+
+ def start_ul(self,attrs): self.start_block('ul',attrs)
+ def end_ul(self): self.end_block('ul')
+ def start_ol(self,attrs):
+ self.start_block('ol',attrs)
+ self.item.counter = 0
+ def end_ol(self): self.end_block('ol')
+ def start_li(self,attrs):
+ self.myback(['ul','ol'])
+ cur = self.item
+ self.start_block('li',attrs)
+ if hasattr(cur,'counter'):
+ cur.counter += 1
+ self.handle_data("%d. "%cur.counter)
+ else:
+ self.handle_data("- ")
+ #def end_li(self): self.end_block('li') #this isn't needed because of how the parser works
+
+ def start_pre(self,attrs): self.start_block('pre',attrs)
+ def end_pre(self): self.end_block('pre')
+ def start_code(self,attrs): self.start_block('code',attrs)
+ def end_code(self): self.end_block('code')
+
+ def start_table(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+
+ align = r.get("align","left")
+ self.item.block(_amap[align])
+
+ t = gui.Table(**params)
+ self.item.add(t)
+
+ self.myopen('table',t)
+
+ def start_tr(self,attrs):
+ self.myback('table')
+ self.item.tr()
+
+ def _start_td(self,t,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ if 'cls' in params: params['cls'] = t+"."+params['cls']
+ else: params['cls'] = t
+ b = gui.Document(cls=t)
+
+ self.myback('table')
+ self.item.td(b,**params)
+ self.myopen(t,b)
+
+ self.font = self.item.style.font
+ self.color = self.item.style.color
+
+ def start_td(self,attrs):
+ self._start_td('td',attrs)
+
+ def start_th(self,attrs):
+ self._start_td('th',attrs)
+
+ def end_table(self):
+ self.myclose('table')
+ self.item.block(-1)
+
+ def start_form(self,attrs):
+ r = self.attrs_to_map(attrs)
+ e = self.form = gui.Form()
+ e.groups = {}
+
+ self._locals[r.get('id',None)] = e
+
+ def start_input(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r) #why bother
+ #params = {}
+
+ type_,name,value = r.get('type','text'),r.get('name',None),r.get('value',None)
+ f = self.form
+ if type_ == 'text':
+ e = gui.Input(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ elif type_ == 'radio':
+ if name not in f.groups:
+ f.groups[name] = gui.Group(name=name)
+ g = f.groups[name]
+ del params['name']
+ e = gui.Radio(group=g,**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ if 'checked' in r: g.value = value
+ elif type_ == 'checkbox':
+ if name not in f.groups:
+ f.groups[name] = gui.Group(name=name)
+ g = f.groups[name]
+ del params['name']
+ e = gui.Checkbox(group=g,**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ if 'checked' in r: g.value = value
+
+ elif type_ == 'button':
+ e = gui.Button(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ elif type_ == 'submit':
+ e = gui.Button(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ elif type_ == 'file':
+ e = gui.Input(**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ b = gui.Button(value='Browse...')
+ self.item.add(b)
+ def _browse(value):
+ d = gui.FileDialog();
+ d.connect(gui.CHANGE,gui.action_setvalue,(d,e))
+ d.open();
+ b.connect(gui.CLICK,_browse,None)
+
+ self._locals[r.get('id',None)] = e
+
+ def start_object(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ code = "e = %s(**params)"%r['type']
+ #print code
+ #print params
+ exec(code)
+ #print e
+ #print e.style.width,e.style.height
+ self.map_to_connects(e,r)
+ self.item.add(e)
+
+ self._locals[r.get('id',None)] = e
+
+ def start_select(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = {}
+
+ name,value = r.get('name',None),r.get('value',None)
+ e = gui.Select(name=name,value=value,**params)
+ self.map_to_connects(e,r)
+ self.item.add(e)
+ self.myopen('select',e)
+
+ def start_option(self,attrs):
+ r = self.attrs_to_map(attrs)
+ params = {} #style = self.map_to_style(r)
+
+ self.myback('select')
+ e = gui.Document(**params)
+ self.item.add(e,value=r.get('value',None))
+ self.myopen('option',e)
+
+
+ def end_select(self):
+ self.myclose('select')
+
+ def start_hr(self,attrs):
+ self.do_hr(attrs)
+ def do_hr(self,attrs):
+ h = self.font.size(" ")[1]/2
+
+ r = self.attrs_to_map(attrs)
+ params = self.map_to_params(r)
+ params['style']['padding'] = h
+ print params
+
+ self.item.block(0)
+ self.item.add(_hr(**params))
+ self.item.block(-1)
+
+ def anchor_begin(self,href,name,type_):
+ pass
+
+ def anchor_end(self):
+ pass
+
+ def start_title(self,attrs): self.myopen('title',_flush())
+ def end_title(self): self.myclose('title')
+
+ def myexec(self,value):
+ w,code = value
+ g = self._globals
+ l = self._locals
+ l['self'] = w
+ exec(code,g,l)
+
+ def handle_image(self,src,alt,ismap,align,width,height):
+ try:
+ w = gui.Image(self.loader.load_image(src))
+ if align != '':
+ self.item.add(w,_amap[align])
+ else:
+ self.item.add(w)
+ except:
+ print 'handle_image: missing %s'%src
+
+ def handle_data(self,txt):
+ if self.type == 'table': return
+ elif self.type in ('pre','code'):
+ txt = txt.replace("\t"," ")
+ ss = txt.split("\n")
+ if ss[-1] == "": del ss[-1]
+ for sentence in ss:
+ img = self.font.render(sentence,1,self.color)
+ w = gui.Image(img)
+ self.item.add(w)
+ self.item.block(-1)
+ return
+
+ txt = re.compile("^[\t\r\n]+").sub("",txt)
+ txt = re.compile("[\t\r\n]+$").sub("",txt)
+
+ tst = re.compile("[\t\r\n]+").sub("",txt)
+ if tst == "": return
+
+ txt = re.compile("\s+").sub(" ",txt)
+ if txt == "": return
+
+ if txt == " ":
+ self.item.space(self.font.size(" "))
+ return
+
+ for word in txt.split(" "):
+ word = word.replace(chr(160)," ") #&nbsp;
+ #print self.item.cls
+ w = gui.Image(self.font.render(word,1,self.color))
+ self.item.add(w)
+ self.item.space(self.font.size(" "))
+
+
+class HTML(gui.Document):
+ """a gui HTML object
+
+ <pre>HTML(data,globals=None,locals=None)</pre>
+
+ <dl>
+ <dt>data <dd>html data
+ <dt>globals <dd>global variables (for scripting)
+ <dt>locals <dd>local variables (for scripting)
+ <dt>loader <dd>the resource loader
+ </dl>
+
+ <p>you may access html elements that have an id via widget[id]</p>
+ """
+ def __init__(self,data,globals=None,locals=None,loader=None,**params):
+ gui.Document.__init__(self,**params)
+ # This ensures that the whole HTML document is left-aligned within
+ # the rendered surface.
+ self.style.align = -1
+
+ _globals,_locals = globals,locals
+
+ if _globals == None: _globals = {}
+ if _locals == None: _locals = {}
+ self._globals = _globals
+ self._locals = _locals
+
+ #font = gui.theme.get("label","","font")
+ p = _html(htmllib.AS_IS,0)
+ p.init(self,self.style.font,self.style.color,_globals,_locals,
+ loader=loader)
+ p.feed(data)
+ p.close()
+ p.mydone()
+
+
+ def __getitem__(self,k):
+ return self._locals[k]
+
+ # Returns a box (pygame rectangle) surrounding all widgets in this document
+ def get_bounding_box(this):
+ minx = miny = sys.maxint
+ maxx = maxy = -sys.maxint
+ for e in this.layout.widgets:
+ minx = min(minx, e.rect.left)
+ miny = min(miny, e.rect.top)
+ maxx = max(maxx, e.rect.right+1)
+ maxy = max(maxy, e.rect.bottom+1)
+ return pygame.Rect(minx, miny, maxx-minx, maxy-miny)
+
+
+def render_ext(font, rect, text, aa, color, bgcolor=(0,0,0,0), **params):
+ """Renders some html and returns the rendered surface, plus the
+ HTML instance that produced it.
+ """
+
+ htm = HTML(text, font=font, color=color, **params)
+
+ if (rect == -1):
+ # Make the surface large enough to fit the rendered text
+ htm.resize(width=sys.maxint)
+ (width, height) = htm.get_bounding_box().size
+ # Now set the proper document width (computed from the bounding box)
+ htm.resize(width=width)
+ elif (type(rect) == int):
+ # Fix the width of the document, while the height is variable
+ width = rect
+ height = htm.resize(width=width)[1]
+ else:
+ # Otherwise the width and height of the document is fixed
+ (width, height) = rect.size
+ htm.resize(width=width)
+
+ # Now construct a surface and paint to it
+ surf = pygame.Surface((width, height)).convert_alpha()
+ surf.fill(bgcolor)
+ htm.paint(surf)
+ return (surf, htm)
+
+def render(font, rect, text, aa, color, bgcolor=(0,0,0,0), **params):
+ """Renders some html
+
+ <pre>render(font,rect,text,aa,color,bgcolor=(0,0,0,0))</pre>
+ """
+ return render_ext(font, rect, text, aa, color, bgcolor, **params)[0]
+
+def rendertrim(font,rect,text,aa,color,bgcolor=(0,0,0,0),**params):
+ """render html, and make sure to trim the size
+
+ rendertrim(font,rect,text,aa,color,bgcolor=(0,0,0,0))
+ """
+ # Render the HTML
+ (surf, htm) = render_ext(font, rect, text, aa, color, bgcolor, **params)
+ return surf.subsurface(htm.get_bounding_box())
+
+
+def write(s,font,rect,text,aa=0,color=(0,0,0), **params):
+ """write html to a surface
+
+ write(s,font,rect,text,aa=0,color=(0,0,0))
+ """
+ htm = HTML(text, font=font, color=color, **params)
+ htm.resize(width=rect.w)
+ s = s.subsurface(rect)
+ htm.paint(s)
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/html.pyc b/pgu/html.pyc
new file mode 100644
index 0000000..6a1f3f8
--- /dev/null
+++ b/pgu/html.pyc
Binary files differ
diff --git a/pgu/isovid.py b/pgu/isovid.py
new file mode 100644
index 0000000..d5048ec
--- /dev/null
+++ b/pgu/isovid.py
@@ -0,0 +1,182 @@
+"""Isometric tile engine.
+
+<p>Note -- this engine is not finished, any may not work for your
+particular needs. If you are able to update it, help would be
+greatly appreciated!</p>
+
+<p>please note that this file is alpha, and is subject to modification in
+future versions of pgu!</p>
+
+"""
+print 'pgu.isovid','This module is alpha, and is subject to change.'
+
+from pgu.vid import *
+import pygame
+
+class Isovid(Vid):
+ """Create an iso vid engine. See [[vid]]"""
+ def update(self,screen):
+ return self.paint(screen)
+
+ def paint(self,screen):
+ sw,sh = screen.get_width(),screen.get_height()
+
+ tlayer = self.tlayer
+ blayer = self.blayer
+ zlayer = self.zlayer
+ w,h = len(tlayer[0]),len(tlayer)
+
+ iso_w,iso_h,iso_z,tile_w,tile_h,base_w,base_h = self.iso_w,self.iso_h,self.iso_z,self.tile_w,self.tile_h,self.base_w,self.base_h
+
+ base_h2 = base_h/2
+ base_w2 = base_w/2
+
+ bot = tile_h/base_h2
+ todo_max = sh/base_h2+bot
+ todo = [[] for y in xrange(0,todo_max)]
+
+ self.view.w,self.view.h = sw,sh
+ view = self.view
+ adj = self.adj = pygame.Rect(-self.view.x,-self.view.y,0,0)
+
+ for s in self.sprites:
+ self.sprite_calc_irect(s)
+ x,y = self.iso_to_view((s.rect.centerx,s.rect.centery))
+ v = (y+adj.y)/base_h2 - 1
+ if v >= 0 and v < todo_max:
+ todo[v].append((s.image,s.irect))
+ #else: print 'doesnt fit',v
+
+ w,h = len(tlayer[0]),len(tlayer)
+ tiles = self.tiles
+
+ #""
+ if self.bounds == None:
+ tmp,y1 = self.tile_to_view((0,0))
+ x1,tmp = self.tile_to_view((0,h+1))
+ tmp,y2 = self.tile_to_view((w+1,h+1))
+ x2,tmp = self.tile_to_view((w+1,0))
+ self.bounds = pygame.Rect(x1,y1,x2-x1,y2-y1)
+ #""
+
+ if self.bounds != None: self.view.clamp_ip(self.bounds)
+
+ ox,oy = self.screen_to_tile((0,0))
+ sx,sy = self.iso_to_view((ox*iso_w,oy*iso_h))
+ dx,dy = sx - self.view.x,sy - self.view.y
+
+ for i2 in xrange(-bot,self.view.h/base_h2+bot):
+ tx,ty = ox + i2/2 + i2%2,oy + i2/2
+ x,y = (i2%2)*base_w2 + dx,i2*base_h2 + dy
+
+ #to adjust for the -1 in i1
+ x,tx,ty = x-base_w,tx-1,ty+1
+ for i1 in xrange(-1,self.view.w/base_w+2): #NOTE: not sure why +2
+ if ty >= 0 and ty < h and tx >= 0 and tx < w:
+ z = zlayer[ty][tx]*iso_z
+ if blayer != None:
+ n = blayer[ty][tx]
+ if n != 0:
+ t = tiles[n]
+ if t != None and t.image != None:
+ screen.blit(t.image,(x-base_w2,y+z))
+ n = tlayer[ty][tx]
+ if n != 0:
+ t = tiles[n]
+ if t != None and t.image != None:
+ screen.blit(t.image,(x-base_w2,y-(t.image_h-base_h)+z))
+
+ tx += 1
+ ty -= 1
+ x += base_w
+ for img,irect in todo[y/base_h2]:
+ screen.blit(img,(irect.x+adj.x,irect.y+adj.y))
+
+ return [pygame.Rect(0,0,screen.get_width(),screen.get_height())]
+
+ def iso_to_view(self,pos):
+ tlayer = self.tlayer
+ w,h = len(tlayer[0]),len(tlayer)
+
+ x,y = pos
+
+ #nx,ny = (h*self.iso_w + x - y)/2, (0 + x + y)/2
+ nx,ny = (x - y)/2, (0 + x + y)/2
+
+ return (nx * self.base_w / self.iso_w), (ny * self.base_h / self.iso_h)
+
+ def view_to_iso(self,pos):
+ tlayer = self.tlayer
+ w,h = len(tlayer[0]),len(tlayer)
+
+ x,y = pos
+
+ x,y = x*self.iso_w/self.base_w, y*self.iso_h/self.base_h
+
+ #x -= (self.iso_w/2) * h
+ #x -= (self.iso_w/2) * h
+
+ nx = (x+y)
+ ny = y*2-nx
+
+ return nx,ny
+
+ def tile_to_view(self,pos):
+ return self.iso_to_view((pos[0]*self.iso_w,pos[1]*self.iso_h))
+
+ def screen_to_tile(self,pos):
+ x,y = pos
+ x += self.view.x
+ y += self.view.y
+ x,y = self.view_to_iso((x,y))
+ return x/self.iso_w,y/self.iso_h
+
+ def tile_to_screen(self,pos):
+ x,y = self.iso_to_view((pos[0]*self.iso_w,pos[1]*self.iso_h))
+ return x-self.view.x,y-self.view.y
+
+ def tga_load_tiles(self,fname,size,tdata={}):
+ Vid.tga_load_tiles(self,fname,size,tdata)
+
+ self.tile_w,self.tile_h = size
+ self.iso_w,self.iso_h,self.iso_z = self.tile_w,self.tile_w,1
+ self.base_w,self.base_h = self.tile_w,self.tile_w/2
+
+
+
+ def resize(self,size,bg=0):
+ Vid.resize(self,size,bg)
+
+ tlayer = self.tlayer
+ w,h = len(tlayer[0]),len(tlayer)
+
+ self.zlayer = [[0 for x in xrange(0,w)] for y in xrange(0,h)]
+
+
+
+
+ def sprite_calc_irect(self,s):
+ tlayer = self.tlayer
+ w,h = len(tlayer[0]),len(tlayer)
+ zlayer = self.zlayer
+
+ x,y = self.iso_to_view((s.rect.centerx,s.rect.centery))
+ tx,ty = s.rect.centerx/self.iso_w,s.rect.centery/self.iso_h
+ z = 0
+ if ty >= 0 and ty < h and tx >= 0 and tx < w:
+ z = zlayer[ty][tx]*self.iso_z
+
+ nx,ny = x - s.shape.centerx, y - s.shape.centery + z
+
+ s.irect.x,s.irect.y = nx,ny
+
+ def run_codes(self,cdata,rect):
+ #HACK to make run_codes work
+ w,h = self.iso_w,self.iso_h
+
+ img = self.tiles[0].image
+
+ self.tiles[0].image = pygame.Surface((w,h))
+ r = Vid.run_codes(self,cdata,rect)
+ self.tiles[0].image = img
+ return r
diff --git a/pgu/isovid.pyc b/pgu/isovid.pyc
new file mode 100644
index 0000000..d4f84bc
--- /dev/null
+++ b/pgu/isovid.pyc
Binary files differ
diff --git a/pgu/layout.py b/pgu/layout.py
new file mode 100644
index 0000000..75b6c9a
--- /dev/null
+++ b/pgu/layout.py
@@ -0,0 +1,4 @@
+print 'pgu.layout','Scheduled to be deprecated.'
+
+from pgu.gui.layout import *
+
diff --git a/pgu/layout.pyc b/pgu/layout.pyc
new file mode 100644
index 0000000..fbb09e4
--- /dev/null
+++ b/pgu/layout.pyc
Binary files differ
diff --git a/pgu/text.py b/pgu/text.py
new file mode 100644
index 0000000..1010a87
--- /dev/null
+++ b/pgu/text.py
@@ -0,0 +1,61 @@
+"""a collection of text rendering functions
+"""
+def write(s,font,pos,color,text,border=1):
+ """write text to a surface with a black border
+
+ <pre>write(s,font,pos,color,text,border=1)</pre>
+ """
+ i = font.render(text,1,(0,0,0))
+ si = border
+ dirs = [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]
+ for dx,dy in dirs: s.blit(i,(pos[0]+dx*si,pos[1]+dy*si))
+ i = font.render(text,1,color)
+ s.blit(i,pos)
+
+def writec(s,font,color,text,border=1):
+ """write centered text to a surface with a black border
+
+ <pre>writec(s,font,color,text,border=1)</pre>
+ """
+ w,h = font.size(text)
+ x = (s.get_width()-w)/2
+ y = (s.get_height()-h)/2
+ write(s,font,(x,y),color,text,border)
+
+def writepre(s,font,rect,color,text):
+ """write preformatted text
+
+ <pre>writepre(s,font,rect,color,text)</pre>
+ """
+ r,c,txt = rect,color,text
+ txt = txt.replace("\t"," ")
+ i = font.render(" ",1,c)
+ sw,sh = i.get_width(),i.get_height()
+ y = r.top
+ for sentence in txt.split("\n"):
+ x = r.left
+ i = font.render(sentence,1,c)
+ s.blit(i,(x,y))
+ y += sh
+
+def writewrap(s,font,rect,color,text):
+ """write wrapped text
+
+ <pre>writewrap(s,font,rect,color,text)</pre>
+ """
+ r,c,txt = rect,color,text
+ txt = txt.replace("\t"," ")
+ i = font.render(" ",1,c)
+ sw,sh = i.get_width(),i.get_height()
+ y = r.top
+ for sentence in txt.split("\n"):
+ x = r.left
+ for word in sentence.split(" "):
+ i = font.render(word,1,c)
+ iw,ih = i.get_width(),i.get_height()
+ if x+iw > r.right: x,y = r.left,y+sh
+ s.blit(i,(x,y))
+ x += iw+sw
+ y += sh
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/text.pyc b/pgu/text.pyc
new file mode 100644
index 0000000..90037e2
--- /dev/null
+++ b/pgu/text.pyc
Binary files differ
diff --git a/pgu/tilevid.py b/pgu/tilevid.py
new file mode 100644
index 0000000..00f730d
--- /dev/null
+++ b/pgu/tilevid.py
@@ -0,0 +1,195 @@
+"""Square tile based engine."""
+
+from pgu.vid import *
+import pygame
+
+class Tilevid(Vid):
+ """Based on [[vid]] -- see for reference."""
+ def paint(self,s):
+ sw,sh = s.get_width(),s.get_height()
+ self.view.w,self.view.h = sw,sh
+
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ w,h = self.size
+
+ if self.bounds != None: self.view.clamp_ip(self.bounds)
+
+ ox,oy = self.view.x,self.view.y
+ tlayer = self.tlayer
+ blayer = self.blayer
+ alayer = self.alayer
+ sprites = self.sprites
+
+ blit = s.blit
+ yy = - (self.view.y%th)
+ my = (oy+sh)/th
+ if (oy+sh)%th: my += 1
+
+ if blayer != None:
+ for y in xrange(oy/th,my):
+ if y >=0 and y < h:
+ trow = tlayer[y]
+ brow = blayer[y]
+ arow = alayer[y]
+ xx= - (self.view.x%tw)
+ mx = (ox+sw)/tw
+ #if (ox+sh)%tw: mx += 1
+ for x in xrange(ox/tw,mx+1):
+ if x >=0and x<w:
+ blit(tiles[brow[x]].image,(xx,yy))
+ blit(tiles[trow[x]].image,(xx,yy))
+ arow[x]=0
+ xx += tw
+ yy+=th
+ else:
+ for y in xrange(oy/th,my):
+ if y >=0 and y<h:
+ trow = tlayer[y]
+ arow = alayer[y]
+ xx= - (self.view.x%tw)
+ mx = (ox+sw)/tw
+ #if (ox+sh)%tw: mx += 1
+ for x in xrange(ox/tw,mx+1):
+ if x >=0 and x<w:
+ blit(tiles[trow[x]].image,(xx,yy))
+ arow[x]=0
+ xx += tw
+ yy+=th
+
+ for s in sprites:
+ s.irect.x = s.rect.x-s.shape.x
+ s.irect.y = s.rect.y-s.shape.y
+ blit(s.image,(s.irect.x-ox,s.irect.y-oy))
+ s.updated=0
+ s._irect = Rect(s.irect)
+ #s._rect = Rect(s.rect)
+
+ self.updates = []
+ self._view = pygame.Rect(self.view)
+ return [Rect(0,0,sw,sh)]
+
+ def update(self,s):
+ sw,sh = s.get_width(),s.get_height()
+ self.view.w,self.view.h = sw,sh
+
+ if self.bounds != None: self.view.clamp_ip(self.bounds)
+ if self.view.x != self._view.x or self.view.y != self._view.y:
+ return self.paint(s)
+
+ ox,oy = self.view.x,self.view.y
+ sw,sh = s.get_width(),s.get_height()
+ w,h = self.size
+ tlayer = self.tlayer
+ blayer = self.blayer
+ alayer = self.alayer
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ sprites = self.sprites
+ blit = s.blit
+
+ us = []
+
+ #mark places where sprites have moved, or been removed
+
+ ss = self.sprites.removed
+ self.sprites.removed = []
+ ss.extend(sprites)
+ for s in ss:
+ #figure out what has been updated.
+ s.irect.x = s.rect.x-s.shape.x
+ s.irect.y = s.rect.y-s.shape.y
+ if (s.irect.x != s._irect.x or s.irect.y != s._irect.y
+ or s.image != s._image):
+ #w,h can be skipped, image covers that...
+ s.updated = 1
+ if s.updated:
+ r = s._irect
+ y = max(0,r.y/th)
+ yy = min(h,r.bottom/th+1)
+ while y < yy:
+ x = max(0,r.x/tw)
+ xx = min(w,r.right/tw+1)
+ while x < xx:
+ if alayer[y][x] == 0:
+ self.updates.append((x,y))
+ alayer[y][x]=1
+ x += 1
+ y += 1
+
+ r = s.irect
+ y = max(0,r.y/th)
+ yy = min(h,r.bottom/th+1)
+ while y < yy:
+ x = r.x/tw
+ xx = min(w,r.right/tw+1)
+ while x < xx:
+ if alayer[y][x]==0:
+ alayer[y][x]=2
+ self.updates.append((x,y))
+ x += 1
+ y += 1
+
+
+ #mark sprites that are not being updated that need to be updated because
+ #they are being overwritte by sprites / tiles
+ for s in sprites:
+ if s.updated==0:
+ r = s.irect
+ y = max(0,r.y/th)
+ yy = min(h,r.bottom/th+1)
+ while y < yy:
+ x = max(0,r.x/tw)
+ xx = min(w,r.right/tw+1)
+ while x < xx:
+ if alayer[y][x]==1:
+ s.updated=1
+ x += 1
+ y += 1
+
+
+ for u in self.updates:
+ x,y=u
+ xx,yy=x*tw-ox,y*th-oy
+ if alayer[y][x] == 1:
+ if blayer != None: blit(tiles[blayer[y][x]].image,(xx,yy))
+ blit(tiles[tlayer[y][x]].image,(xx,yy))
+ alayer[y][x]=0
+ us.append(Rect(xx,yy,tw,th))
+
+ for s in sprites:
+ if s.updated:
+ blit(s.image,(s.irect.x-ox, s.irect.y-oy))
+ s.updated=0
+ s._irect = Rect(s.irect)
+ s._image = s.image
+
+ self.updates = []
+ return us
+
+ def view_to_tile(self,pos):
+ x,y = pos
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ return x/tw,y/th
+
+ def tile_to_view(self,pos):
+ x,y = pos
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ x,y = x*tw, y*th
+ return x,y
+
+
+ def screen_to_tile(self,pos):
+ x,y = pos
+ x,y = x+self.view.x,y+self.view.y
+ return self.view_to_tile((x,y))
+
+ def tile_to_screen(self,pos):
+ x,y = pos
+ x,y = self.tile_to_view(pos)
+ x,y = x - self.view.x, y - self.view.y
+ return x,y
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/tilevid.pyc b/pgu/tilevid.pyc
new file mode 100644
index 0000000..98c5dc4
--- /dev/null
+++ b/pgu/tilevid.pyc
Binary files differ
diff --git a/pgu/timer.py b/pgu/timer.py
new file mode 100644
index 0000000..1201b7a
--- /dev/null
+++ b/pgu/timer.py
@@ -0,0 +1,68 @@
+"""A timer for games with set-rate FPS.
+"""
+
+import pygame
+
+class Timer:
+ """A timer for games with set-rate FPS.
+
+ <pre>Timer(fps)</pre>
+ """
+
+ def __init__(self,fps):
+ if fps == 0:
+ self.tick = self._blank
+ return
+ self.wait = 1000/fps
+ self.nt = pygame.time.get_ticks()
+ pygame.time.wait(0)
+
+ def _blank(self):
+ pass
+
+ def tick(self):
+ """Wait correct amount of time each frame. Call this once per frame.
+
+ <pre>Timer.tick()</pre>
+ """
+ self.ct = pygame.time.get_ticks()
+ if self.ct < self.nt:
+ pygame.time.wait(self.nt-self.ct)
+ self.nt+=self.wait
+ else:
+ self.nt = pygame.time.get_ticks()+self.wait
+
+
+class Speedometer:
+ """A timer replacement that returns out FPS once a second.
+ <pre>Speedometer()</pre>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>fps <dd>always set to the current FPS
+ </dl>
+ """
+ def __init__(self):
+ self.frames = 0
+ self.st = pygame.time.get_ticks()
+ self.fps = 0
+
+ def tick(self):
+ """ Call this once per frame.
+
+ <pre>Speedometer.tick()</pre>
+ """
+ r = None
+ self.frames += 1
+ self.ct = pygame.time.get_ticks()
+ if (self.ct - self.st) >= 1000:
+ r = self.fps = self.frames
+ #print "%s: %d fps"%(self.__class__.__name__,self.fps)
+ self.frames = 0
+ self.st += 1000
+ pygame.time.wait(0) #NOTE: not sure why, but you gotta call this now and again
+ return r
+
+
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/timer.pyc b/pgu/timer.pyc
new file mode 100644
index 0000000..3cf833d
--- /dev/null
+++ b/pgu/timer.pyc
Binary files differ
diff --git a/pgu/vid.py b/pgu/vid.py
new file mode 100644
index 0000000..6890e5d
--- /dev/null
+++ b/pgu/vid.py
@@ -0,0 +1,560 @@
+"""Sprite and tile engine.
+
+<p>[[tilevid]], [[isovid]], [[hexvid]] are all subclasses of
+this interface.</p>
+
+<p>Includes support for:</p>
+
+<ul>
+<li> Foreground Tiles
+<li> Background Tiles
+<li> Sprites
+<li> Sprite-Sprite Collision handling
+<li> Sprite-Tile Collision handling
+<li> Scrolling
+<li> Loading from PGU tile and sprite formats (optional)
+<li> Set rate FPS (optional)
+</ul>
+
+<p>This code was previously known as the King James Version (named after the
+Bible of the same name for historical reasons.)</p>
+"""
+
+import pygame
+from pygame.rect import Rect
+from pygame.locals import *
+import math
+
+class Sprite:
+ """The object used for Sprites.
+
+ <pre>Sprite(ishape,pos)</pre>
+
+ <dl>
+ <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will
+ describe the shape of the image, used for collision
+ detection.
+ <dt>pos <dd>initial (x,y) position of the Sprite.
+ </dl>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>rect <dd>the current position of the Sprite
+ <dt>_rect <dd>the previous position of the Sprite
+ <dt>groups <dd>the groups the Sprite is in
+ <dt>agroups <dd>the groups the Sprite can hit in a collision
+ <dt>hit <dd>the handler for hits -- hit(g,s,a)
+ <dt>loop <dd>the loop handler, called once a frame
+ </dl>
+ """
+ def __init__(self,ishape,pos):
+ if not isinstance(ishape, tuple):
+ ishape = ishape,None
+ image,shape = ishape
+ if shape == None:
+ shape = pygame.Rect(0,0,image.get_width(),image.get_height())
+ if isinstance(shape, tuple): shape = pygame.Rect(shape)
+ self.image = image
+ self._image = self.image
+ self.shape = shape
+ self.rect = pygame.Rect(pos[0],pos[1],shape.w,shape.h)
+ self._rect = pygame.Rect(self.rect)
+ self.irect = pygame.Rect(pos[0]-self.shape.x,pos[1]-self.shape.y,
+ image.get_width(),image.get_height())
+ self._irect = pygame.Rect(self.irect)
+ self.groups = 0
+ self.agroups = 0
+ self.updated = 1
+
+ def setimage(self,ishape):
+ """Set the image of the Sprite.
+
+ <pre>Sprite.setimage(ishape)</pre>
+
+ <dl>
+ <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will
+ describe the shape of the image, used for collision detection.
+ </dl>
+ """
+ if not isinstance(ishape, tuple):
+ ishape = ishape,None
+ image,shape = ishape
+ if shape == None:
+ shape = pygame.Rect(0,0,image.get_width(),image.get_height())
+ if isinstance(shape, tuple):
+ shape = pygame.Rect(shape)
+ self.image = image
+ self.shape = shape
+ self.rect.w,self.rect.h = shape.w,shape.h
+ self.irect.w,self.irect.h = image.get_width(),image.get_height()
+ self.updated = 1
+
+
+class Tile:
+ """Tile Object used by TileCollide.
+
+ <pre>Tile(image=None)</pre>
+ <dl>
+ <dt>image <dd>an image for the Tile.
+ </dl>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>agroups <dd>the groups the Tile can hit in a collision
+ <dt>hit <dd>the handler for hits -- hit(g,t,a)
+ </dl>
+ """
+ def __init__(self,image=None):
+ self.image = image
+ self.agroups = 0
+
+ def __setattr__(self,k,v):
+ if k == 'image' and v != None:
+ self.image_h = v.get_height()
+ self.image_w = v.get_width()
+ self.__dict__[k] = v
+
+class _Sprites(list):
+ def __init__(self):
+ list.__init__(self)
+ self.removed = []
+
+ def append(self,v):
+ list.append(self,v)
+ v.updated = 1
+
+ def remove(self,v):
+ list.remove(self,v)
+ v.updated = 1
+ self.removed.append(v)
+
+class Vid:
+ """An engine for rendering Sprites and Tiles.
+
+ <pre>Vid()</pre>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>sprites <dd>a list of the Sprites to be displayed. You may append and
+ remove Sprites from it.
+ <dt>images <dd>a dict for images to be put in.
+ <dt>size <dd>the width, height in Tiles of the layers. Do not modify.
+ <dt>view <dd>a pygame.Rect of the viewed area. You may change .x, .y,
+ etc to move the viewed area around.
+ <dt>bounds <dd>a pygame.Rect (set to None by default) that sets the bounds
+ of the viewable area. Useful for setting certain borders
+ as not viewable.
+ <dt>tlayer <dd>the foreground tiles layer
+ <dt>clayer <dd>the code layer (optional)
+ <dt>blayer <dd>the background tiles layer (optional)
+ <dt>groups <dd>a hash of group names to group values (32 groups max, as a tile/sprites
+ membership in a group is determined by the bits in an integer)
+ </dl>
+ """
+
+ def __init__(self):
+ self.tiles = [None for x in xrange(0,256)]
+ self.sprites = _Sprites()
+ self.images = {} #just a store for images.
+ self.layers = None
+ self.size = None
+ self.view = pygame.Rect(0,0,0,0)
+ self._view = pygame.Rect(self.view)
+ self.bounds = None
+ self.updates = []
+ self.groups = {}
+
+
+ def resize(self,size,bg=0):
+ """Resize the layers.
+
+ <pre>Vid.resize(size,bg=0)</pre>
+
+ <dl>
+ <dt>size <dd>w,h in Tiles of the layers
+ <dt>bg <dd>set to 1 if you wish to use both a foreground layer and a
+ background layer
+ </dl>
+ """
+ self.size = size
+ w,h = size
+ self.layers = [[[0 for x in xrange(0,w)] for y in xrange(0,h)]
+ for z in xrange(0,4)]
+ self.tlayer = self.layers[0]
+ self.blayer = self.layers[1]
+ if not bg: self.blayer = None
+ self.clayer = self.layers[2]
+ self.alayer = self.layers[3]
+
+ self.view.x, self.view.y = 0,0
+ self._view.x, self.view.y = 0,0
+ self.bounds = None
+
+ self.updates = []
+
+ def set(self,pos,v):
+ """Set a tile in the foreground to a value.
+
+ <p>Use this method to set tiles in the foreground, as it will make
+ sure the screen is updated with the change. Directly changing
+ the tlayer will not guarantee updates unless you are using .paint()
+ </p>
+
+ <pre>Vid.set(pos,v)</pre>
+
+ <dl>
+ <dt>pos <dd>(x,y) of tile
+ <dt>v <dd>value
+ </dl>
+ """
+ if self.tlayer[pos[1]][pos[0]] == v: return
+ self.tlayer[pos[1]][pos[0]] = v
+ self.alayer[pos[1]][pos[0]] = 1
+ self.updates.append(pos)
+
+ def get(self,pos):
+ """Get the tlayer at pos.
+
+ <pre>Vid.get(pos): return value</pre>
+
+ <dl>
+ <dt>pos <dd>(x,y) of tile
+ </dl>
+ """
+ return self.tlayer[pos[1]][pos[0]]
+
+ def paint(self,s):
+ """Paint the screen.
+
+ <pre>Vid.paint(screen): return [updates]</pre>
+
+ <dl>
+ <dt>screen <dd>a pygame.Surface to paint to
+ </dl>
+
+ <p>returns the updated portion of the screen (all of it)</p>
+ """
+ return []
+
+ def update(self,s):
+ """Update the screen.
+
+ <pre>Vid.update(screen): return [updates]</pre>
+
+ <dl>
+ <dt>screen <dd>a pygame.Rect to update
+ </dl>
+
+ <p>returns a list of updated rectangles.</p>
+ """
+ self.updates = []
+ return []
+
+ def tga_load_level(self,fname,bg=0):
+ """Load a TGA level.
+
+ <pre>Vid.tga_load_level(fname,bg=0)</pre>
+
+ <dl>
+ <dt>g <dd>a Tilevid instance
+ <dt>fname <dd>tga image to load
+ <dt>bg <dd>set to 1 if you wish to load the background layer
+ </dl>
+ """
+ if type(fname) == str: img = pygame.image.load(fname)
+ else: img = fname
+ w,h = img.get_width(),img.get_height()
+ self.resize((w,h),bg)
+ for y in range(0,h):
+ for x in range(0,w):
+ t,b,c,_a = img.get_at((x,y))
+ self.tlayer[y][x] = t
+ if bg: self.blayer[y][x] = b
+ self.clayer[y][x] = c
+
+ def tga_save_level(self,fname):
+ """Save a TGA level.
+
+ <pre>Vid.tga_save_level(fname)</pre>
+
+ <dl>
+ <dt>fname <dd>tga image to save to
+ </dl>
+ """
+ w,h = self.size
+ img = pygame.Surface((w,h),SWSURFACE,32)
+ img.fill((0,0,0,0))
+ for y in range(0,h):
+ for x in range(0,w):
+ t = self.tlayer[y][x]
+ b = 0
+ if self.blayer:
+ b = self.blayer[y][x]
+ c = self.clayer[y][x]
+ _a = 0
+ img.set_at((x,y),(t,b,c,_a))
+ pygame.image.save(img,fname)
+
+
+
+ def tga_load_tiles(self,fname,size,tdata={}):
+ """Load a TGA tileset.
+
+ <pre>Vid.tga_load_tiles(fname,size,tdata={})</pre>
+
+ <dl>
+ <dt>g <dd>a Tilevid instance
+ <dt>fname <dd>tga image to load
+ <dt>size <dd>(w,h) size of tiles in pixels
+ <dt>tdata <dd>tile data, a dict of tile:(agroups, hit handler, config)
+ </dl>
+ """
+ TW,TH = size
+ if type(fname) == str: img = pygame.image.load(fname).convert_alpha()
+ else: img = fname
+ w,h = img.get_width(),img.get_height()
+
+ n = 0
+ for y in range(0,h,TH):
+ for x in range(0,w,TW):
+ i = img.subsurface((x,y,TW,TH))
+ tile = Tile(i)
+ self.tiles[n] = tile
+ if n in tdata:
+ agroups,hit,config = tdata[n]
+ tile.agroups = self.string2groups(agroups)
+ tile.hit = hit
+ tile.config = config
+ n += 1
+
+
+ def load_images(self,idata):
+ """Load images.
+
+ <pre>Vid.load_images(idata)</pre>
+
+ <dl>
+ <dt>idata <dd>a list of (name, fname, shape)
+ </dl>
+ """
+ for name,fname,shape in idata:
+ self.images[name] = pygame.image.load(fname).convert_alpha(),shape
+
+ def run_codes(self,cdata,rect):
+ """Run codes.
+
+ <pre>Vid.run_codes(cdata,rect)</pre>
+
+ <dl>
+ <dt>cdata <dd>a dict of code:(handler function, value)
+ <dt>rect <dd>a tile rect of the parts of the layer that should have
+ their codes run
+ </dl>
+ """
+ tw,th = self.tiles[0].image.get_width(),self.tiles[0].image.get_height()
+
+ x1,y1,w,h = rect
+ clayer = self.clayer
+ t = Tile()
+ for y in range(y1,y1+h):
+ for x in range(x1,x1+w):
+ n = clayer[y][x]
+ if n in cdata:
+ fnc,value = cdata[n]
+ t.tx,t.ty = x,y
+ t.rect = pygame.Rect(x*tw,y*th,tw,th)
+ fnc(self,t,value)
+
+
+ def string2groups(self,str):
+ """Convert a string to groups.
+
+ <pre>Vid.string2groups(str): return groups</pre>
+ """
+ if str == None: return 0
+ return self.list2groups(str.split(","))
+
+ def list2groups(self,igroups):
+ """Convert a list to groups.
+ <pre>Vid.list2groups(igroups): return groups</pre>
+ """
+ for s in igroups:
+ if not s in self.groups:
+ self.groups[s] = 2**len(self.groups)
+ v = 0
+ for s,n in self.groups.items():
+ if s in igroups: v|=n
+ return v
+
+ def groups2list(self,groups):
+ """Convert a groups to a list.
+ <pre>Vid.groups2list(groups): return list</pre>
+ """
+ v = []
+ for s,n in self.groups.items():
+ if (n&groups)!=0: v.append(s)
+ return v
+
+ def hit(self,x,y,t,s):
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ t.tx = x
+ t.ty = y
+ t.rect = Rect(x*tw,y*th,tw,th)
+ t._rect = t.rect
+ if hasattr(t,'hit'):
+ t.hit(self,t,s)
+
+ def loop(self):
+ """Update and hit testing loop. Run this once per frame.
+ <pre>Vid.loop()</pre>
+ """
+ self.loop_sprites() #sprites may move
+ self.loop_tilehits() #sprites move
+ self.loop_spritehits() #no sprites should move
+ for s in self.sprites:
+ s._rect = pygame.Rect(s.rect)
+
+ def loop_sprites(self):
+ as_ = self.sprites[:]
+ for s in as_:
+ if hasattr(s,'loop'):
+ s.loop(self,s)
+
+ def loop_tilehits(self):
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+
+ layer = self.layers[0]
+
+ as_ = self.sprites[:]
+ for s in as_:
+ self._tilehits(s)
+
+ def _tilehits(self,s):
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ layer = self.layers[0]
+
+ for _z in (0,):
+ if s.groups != 0:
+
+ _rect = s._rect
+ rect = s.rect
+
+ _rectx = _rect.x
+ _recty = _rect.y
+ _rectw = _rect.w
+ _recth = _rect.h
+
+ rectx = rect.x
+ recty = rect.y
+ rectw = rect.w
+ recth = rect.h
+
+ rect.y = _rect.y
+ rect.h = _rect.h
+
+ hits = []
+ ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
+ #nasty ol loops
+ y = ct/th*th
+ while y < cb:
+ x = cl/tw*tw
+ yy = y/th
+ while x < cr:
+ xx = x/tw
+ t = tiles[layer[yy][xx]]
+ if (s.groups & t.agroups)!=0:
+ #self.hit(xx,yy,t,s)
+ d = math.hypot(rect.centerx-(xx*tw+tw/2),
+ rect.centery-(yy*th+th/2))
+ hits.append((d,t,xx,yy))
+
+ x += tw
+ y += th
+
+ hits.sort()
+ #if len(hits) > 0: print self.frame,hits
+ for d,t,xx,yy in hits:
+ self.hit(xx,yy,t,s)
+
+ #switching directions...
+ _rect.x = rect.x
+ _rect.w = rect.w
+ rect.y = recty
+ rect.h = recth
+
+ hits = []
+ ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
+ #nasty ol loops
+ y = ct/th*th
+ while y < cb:
+ x = cl/tw*tw
+ yy = y/th
+ while x < cr:
+ xx = x/tw
+ t = tiles[layer[yy][xx]]
+ if (s.groups & t.agroups)!=0:
+ d = math.hypot(rect.centerx-(xx*tw+tw/2),
+ rect.centery-(yy*th+th/2))
+ hits.append((d,t,xx,yy))
+ #self.hit(xx,yy,t,s)
+ x += tw
+ y += th
+
+ hits.sort()
+ #if len(hits) > 0: print self.frame,hits
+ for d,t,xx,yy in hits:
+ self.hit(xx,yy,t,s)
+
+ #done with loops
+ _rect.x = _rectx
+ _rect.y = _recty
+
+
+ def loop_spritehits(self):
+ as_ = self.sprites[:]
+
+ groups = {}
+ for n in range(0,31):
+ groups[1<<n] = []
+ for s in as_:
+ g = s.groups
+ n = 1
+ while g:
+ if (g&1)!=0: groups[n].append(s)
+ g >>= 1
+ n <<= 1
+
+ for s in as_:
+ if s.agroups!=0:
+ rect1,rect2 = s.rect,Rect(s.rect)
+ #if rect1.centerx < 320: rect2.x += 640
+ #else: rect2.x -= 640
+ g = s.agroups
+ n = 1
+ while g:
+ if (g&1)!=0:
+ for b in groups[n]:
+ if (s != b and (s.agroups & b.groups)!=0
+ and s.rect.colliderect(b.rect)):
+ s.hit(self,s,b)
+
+ g >>= 1
+ n <<= 1
+
+
+ def screen_to_tile(self,pos):
+ """Convert a screen position to a tile position.
+ <pre>Vid.screen_to_tile(pos): return pos</pre>
+ """
+ return pos
+
+ def tile_to_screen(self,pos):
+ """Convert a tile position to a screen position.
+ <pre>Vid.tile_to_screen(pos): return pos</pre>
+ """
+ return pos
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/pgu/vid.pyc b/pgu/vid.pyc
new file mode 100644
index 0000000..2d59c98
--- /dev/null
+++ b/pgu/vid.pyc
Binary files differ
diff --git a/pgutest.py b/pgutest.py
new file mode 100644
index 0000000..0ec7191
--- /dev/null
+++ b/pgutest.py
@@ -0,0 +1,168 @@
+class CurrentQuestion:
+ id = 0
+ prompt = u''
+ response = u''
+ imgfn = u''
+ sndfn = u''
+ map = u''
+ answer_link = u''
+
+import pygame
+from pygame.locals import *
+
+# the following line is not needed if pgu is installed
+import sys; sys.path.insert(0, "..")
+
+from pgu import gui
+
+cq = CurrentQuestion()
+
+class EditDialog(gui.Dialog):
+ def __init__(self, editsave, cq):
+ title = gui.Label("Edit Question")
+
+ t = gui.Table()
+ self.form = gui.Form()
+
+ t.tr()
+ t.td(gui.Label("Prompt: "))
+ t.td(gui.Input(name = 'prompt', value = cq.prompt),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Response: "))
+ t.td(gui.Input(name = 'response', value = cq.response),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Image: "))
+ t.td(gui.Input(name = 'image', value = cq.imgfn),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Sound: "))
+ t.td(gui.Input(name = 'sound', value = cq.sndfn),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Map: "))
+ t.td(gui.Input(name = 'map', value = cq.map),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Answer_link: "))
+ t.td(gui.Input(name = 'answer_link', value = cq.answer_link),colspan=3)
+
+ t.tr()
+ saveButton = gui.Button("Save")
+ saveButton.connect(gui.CLICK, editsave)
+ t.td(saveButton,colspan=3)
+
+ gui.Dialog.__init__(self,title,t)
+
+
+class ImageQuizActivity:
+ user_name = u"chris"
+
+ def game(self):
+ global edit_d
+ global c
+ clock = pygame.time.Clock()
+ cq.prompt = "prompt"
+ cq.response = "response"
+ cq.imgfn = "image"
+ cq.sndfn = "sound"
+ cq.map = "map"
+ cq.answer_link = "answerlink"
+ app = gui.App()
+
+ c = gui.Container(width = 100, height = 50)
+ app.init(c)
+
+ edit_d = EditDialog(self.editsave, cq)
+ edit_d.open()
+
+ # Pygame event loop.
+ while True:
+ clock.tick(20)
+ app.paint(self.screen)
+ pygame.display.update()
+
+ for event in [ pygame.event.wait() ] + pygame.event.get( ):
+ self.input(event)
+ app.event(event)
+
+ def editsave(cq):
+ global edit_d
+ global c
+ print 'editsave'
+ form = edit_d.form
+ print 'items', len(form.items()), form.items()
+ cq.prompt = form['prompt'].value
+ cq.response = form['response'].value
+ cq.imgfn = form['image'].value
+ cq.sndfn = form['sound'].value
+ cq.map = form['map'].value
+ cq.answer_link = form['answer_link'].value
+ print 'on save:'
+ print 'prompt', cq.prompt
+ print 'response', cq.response
+ print 'image', cq.imgfn
+ print 'sound', cq.sndfn
+ print 'map', cq.map
+ print 'answer_link', cq.answer_link
+ app.close(edit_d)
+
+ # Handle a pygame event
+ def exit_all(self):
+ global edit_d
+ form = edit_d.form
+ cq.prompt = form['prompt'].value
+ cq.response = form['response'].value
+ cq.imgfn = form['image'].value
+ cq.sndfn = form['sound'].value
+ cq.map = form['map'].value
+ cq.answer_link = form['answer_link'].value
+ print 'on exit:'
+ print 'prompt', cq.prompt
+ print 'response', cq.response
+ print 'image', cq.imgfn
+ print 'sound', cq.sndfn
+ print 'map', cq.map
+ print 'answer_link', cq.answer_link
+# plugger.close_plugins()
+# time.sleep(3)
+ sys.exit(0)
+
+ def input(self, event):
+ if event.type == QUIT:
+ self.exit_all()
+
+ elif event.type == MOUSEBUTTONUP:
+ #display.check_click(event.pos)
+ pass
+
+ elif event.type == KEYUP:
+ if event.key == 113: # 'q'
+ self.exit_all()
+ elif event.key == 102: # 'f'
+ pygame.display.toggle_fullscreen()
+
+# elif event.type == MOUSEMOTION:
+# pass
+
+# else:
+# x = event.type
+# display.show_tool_tip((200, 10), "%i" % int(x))
+# pygame.display.update()
+
+
+ def __init__(self):
+ # Fire Up Display
+ self.window = pygame.display.set_mode((1200, 825))
+ pygame.display.set_caption('PGU test')
+
+ # Init Surface
+ self.screen = pygame.display.get_surface()
+ self.game()
+
+def main():
+ ImageQuiz = ImageQuizActivity()
+
+if __name__ == "__main__" or __name__ == "ImageQuiz":
+ main()
diff --git a/pgutest1.py b/pgutest1.py
new file mode 100644
index 0000000..5932477
--- /dev/null
+++ b/pgutest1.py
@@ -0,0 +1,148 @@
+class CurrentQuestion:
+ id = 0
+ prompt = u''
+ response = u''
+ imgfn = u''
+ sndfn = u''
+ map = u''
+ answer_link = u''
+
+import pygame
+from pygame.locals import *
+
+# the following line is not needed if pgu is installed
+import sys; sys.path.insert(0, "..")
+
+from pgu import gui
+
+cq = CurrentQuestion()
+
+class EditDialog(gui.Dialog):
+ def __init__(self, editsave, cq):
+ title = gui.Label("Edit Question")
+
+ t = gui.Table()
+ self.form = gui.Form()
+
+ t.tr()
+ saveButton = gui.Button("Save")
+ saveButton.connect(gui.CLICK, editsave, cq)
+ t.td(saveButton,colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Prompt: "))
+ t.td(gui.Input(name = 'prompt', value = cq.prompt),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Response: "))
+ t.td(gui.Input(name = 'response', value = cq.response),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Image: "))
+ t.td(gui.Input(name = 'image', value = cq.imgfn),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Sound: "))
+ t.td(gui.Input(name = 'suound', value = cq.sndfn),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Map: "))
+ t.td(gui.Input(name = 'map', value = cq.map),colspan=3)
+
+ t.tr()
+ t.td(gui.Label("Answer_link: "))
+ t.td(gui.Input(name = 'answer_link', value = cq.answer_link),colspan=3)
+
+ gui.Dialog.__init__(self,title,t)
+
+
+class ImageQuizActivity:
+ user_name = u"chris"
+
+ def game(self):
+ global edit_d
+ clock = pygame.time.Clock()
+ cq.prompt = "prompt"
+ cq.response = "response"
+ cq.imgfn = "image"
+ cq.sndfn = "sound"
+ cq.map = "map"
+ cq.answer_link = "answerlink"
+ app = gui.App()
+
+ c = gui.Container(width=800, height=600
+
+ lbl = gui.Label("This is the main surface")
+ c.add(lbl,0,500)
+ app.init(c)
+
+ #edit_d = EditDialog(editsave, cq)
+ #edit_d.open()
+
+ # Pygame event loop.
+ while True:
+ clock.tick(20)
+ app.paint(self.screen)
+ pygame.display.update()
+
+ for event in [ pygame.event.wait() ] + pygame.event.get( ):
+ self.input(event)
+ app.event(event)
+
+ # Handle a pygame event
+ def exit_all(self):
+ global edit_d
+ form = edit_d.form
+ cq.prompt = form['prompt'].value
+ cq.response = form['response'].value
+ cq.imgfn = form['image'].value
+ cq.sndfn = form['sound'].value
+ cq.map = form['map'].value
+ cq.answer_link = form['answer_link'].value
+ print 'prompt', cq.prompt
+ print 'response', cq.response
+ print 'image', cq.imgfn
+ print 'sound', cq.sndfn
+ print 'map', cq.map
+ print 'answer_link', cq.answer_link
+# plugger.close_plugins()
+# time.sleep(3)
+ sys.exit(0)
+
+ def input(self, event):
+ if event.type == QUIT:
+ self.exit_all()
+
+ elif event.type == MOUSEBUTTONUP:
+# display.check_click(event.pos)
+ pass
+
+ elif event.type == KEYUP:
+ if event.key == 113: # 'q'
+ self.exit_all()
+ elif event.key == 102: # 'f'
+ pygame.display.toggle_fullscreen()
+
+# elif event.type == MOUSEMOTION:
+# pass
+
+# else:
+# x = event.type
+# display.show_tool_tip((200, 10), "%i" % int(x))
+# pygame.display.update()
+
+
+ def __init__(self):
+ # Fire Up Display
+ self.window = pygame.display.set_mode((1200, 825))
+ pygame.display.set_caption('PGU test')
+
+ # Init Surface
+ self.screen = pygame.display.get_surface()
+ self.game()
+
+def main():
+ ImageQuiz = ImageQuizActivity()
+
+if __name__ == "__main__" or __name__ == "ImageQuiz":
+ main()
diff --git a/plugins/_extract.py b/plugins/_extract.py
new file mode 100755
index 0000000..071a869
--- /dev/null
+++ b/plugins/_extract.py
@@ -0,0 +1,896 @@
+# Modified to play flashcards from .xml decks in flashcard folder
+# Currently
+# * Displaying Categories in set language,
+# * Pick English Questions
+
+import pygame
+from pygame import *
+
+import random
+from layout import *
+from frontend import hex2rgb
+import time
+import threading
+import shutil
+
+import xml.etree.ElementTree as ET
+import os, sys
+
+__PLUGIN_NAME__ = 'flashcard'
+
+# reads and writes xml files
+# output files indented
+
+# import xml.etree.ElementTree as ET
+
+#tree = Xmlio(path)
+#elem = tree.getroot()
+#tree.save(path) --- uses root of tree
+#tree.save(path,root=elem) -- saves tree from supplied root element
+
+class Xmlio():
+
+ def __init__(self, path = '', root = ''):
+ if root:
+ if path:
+ self.root = root
+ self.tree = ET.ElementTree(self.root)
+ else:
+ self.root = ET.Element(root)
+ self.tree = ET.ElementTree(self.root)
+ else:
+ self.tree = ET.parse(path)
+ self.root = self.tree.getroot()
+
+ def getroot(self, root = ''):
+ if not root:
+ return self.root
+ else:
+ return ET.Element(root)
+
+ def indent(self, elem, level=0):
+ i = "\n" + level * " "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+ def save(self, path, root = ''):
+ if not root:
+ root = self.root
+ self.indent(root) #prettyprint
+ ET.ElementTree(root).write(path)
+
+class CurrentQuestion:
+ id = 0
+ imgfn = u''
+ map = u''
+ cat = 0
+ subcat = 0
+ text = u''
+ answer = u''
+ answer_link = ''
+ hint = ''
+ more = ''
+ sound = ''
+ image = ''
+
+class Answer:
+ display = True
+ display_line = False
+ sound_enabled = True
+
+ link = u''
+ text = u''
+
+ img_found_count = 0
+ img_notfound_count = 0
+# imgfn_found = ["Emotes/face-grin.png", "Emotes/face-devil-grin.png", "Emotes/face-glasses.png"]
+# imgfn_notfound = ["Emotes/face-monkey.png", "Emotes/face-crying.png"]
+ imgfn_found = ["Emotes/face-grin.png"]
+ imgfn_notfound = ["Emotes/face-devil-grin.png"]
+ random.shuffle(imgfn_found)
+ random.shuffle(imgfn_notfound)
+
+ found_straight = 0
+ straight_count = 0
+ icon_left = 0
+ bg_icons = pygame.Surface((500, 32))
+ bg_icons_straight = pygame.Surface((500, 32))
+
+ def __init__(self):
+ self.sound_found = sf.sound_load("accessed.wav")
+ self.sound_notfound = sf.sound_load("sorry.wav")
+
+ def reset_points(self):
+ self.found_straight = 0
+ self.straight_count = 0
+ self.icon_left = 0
+
+ self.bg_icons.fill(hex2rgb(Layout().Toolbar.background))
+ self.bg_icons_straight.fill(hex2rgb(Layout().Toolbar.background))
+
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar")
+ sf.refresh()
+
+ def add_straight(self):
+ # 5 in a row -> special reward
+ self.straight_count += 1
+ im, im_rect = sf.image_load("images/%s" % "Icons/bulb.png")
+ self.bg_icons_straight.blit(im, ((self.straight_count-1) * (im_rect[2] + 4), 0))
+ sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar")
+ sf.refresh()
+
+ def display_icon(self, icon_name):
+ if icon_name == "found":
+ self.found_straight += 1
+ if self.found_straight == 5:
+ # Found Straight 5!
+ self.bg_icons.fill(hex2rgb(Layout().Toolbar.background))
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ self.add_straight()
+ self.icon_left = 0
+ self.found_straight = 1
+
+ fn = self.imgfn_found[self.img_found_count % len(self.imgfn_found)]
+ self.img_found_count += 1
+
+ elif icon_name == "not-found":
+ self.found_straight = 0
+ fn = self.imgfn_notfound[self.img_notfound_count % len(self.imgfn_notfound)]
+ self.img_notfound_count += 1
+
+ img, img_rect = sf.image_load("images/%s" % fn)
+ self.bg_icons.blit(img, (self.icon_left, 0))
+
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ sf.refresh()
+
+ self.icon_left += img_rect[2] + 4
+
+ class PlaySoundThread (threading.Thread):
+ def set(self, sound, interval):
+ self.sound = sound
+ self.i = interval
+
+ def run(self):
+ time.sleep(self.i)
+ self.sound.play()
+
+ def play_sound(self, i):
+ if self.sound_enabled:
+ t = self.PlaySoundThread()
+ if i == 1: t.set(self.sound_found, 0)
+ else: t.set(self.sound_notfound, 0.5)
+ t.start()
+
+ def display_answer(self):
+ if self.display_line: sf.draw_lines()
+ if self.display == False: return False
+
+ # Get Top Right Spot of list
+ print "showing answer"
+ map_arr = Q.question.map.split(", ")
+
+ # Widths & Heights
+ bubble_width = 400
+ bubble_height = 300
+ textfield_width = 270
+ textfield_height = 200
+
+ # Extremas of the Polygone will be saved in here:
+ x_max = 0
+ y_max = 0
+ x_min = 1000
+ y_min = 1000
+
+ # Extract Extremas from the polygon
+ o = []
+ a = "x"
+ for s in map_arr:
+ if a == "x": a = s
+ else:
+ # Take care of border pixels
+ if int(a) > x_max: x_max = int(a)
+ if int(s) > y_max: y_max = int(s)
+ if int(a) < x_min: x_min = int(a)
+ if int(s) < y_min: y_min = int(s)
+ a = "x"
+
+ # Set x and y for the Answer Bubble
+ y_med = (y_min + y_max) / 2
+ x_max -= 5
+
+ y_med = 150
+ x_max = 400
+
+ x_max += Layout().Question.x + Layout().Question.Image.x + 2
+ y_med += Layout().Question.y + Layout().Question.Image.y + 2
+
+# sf.draw_polygon()
+
+ # Draw Answer Bubble Image & Text
+ im, im_rect = sf.image_load('images/bubble.gif')
+
+ text_arr = self.text.split(' ')
+ cur_line = ''
+
+ cur_x = 0
+ cur_y = 0
+
+ # 'textfield' contains the answer text
+ textfield = pygame.Surface((textfield_width, textfield_height))
+ textfield.fill((255, 255, 255))
+
+ # Make line breaks after reaching width of 'textfield'
+ for t in text_arr:
+ cur_line = "%s %s" % (cur_line, t)
+
+ font = pygame.font.Font(None, 38)
+ n_text = font.render(cur_line, 1, (0,0,0))
+ textpos = n_text.get_rect()
+# print cur_line,
+
+ x,y,w,h = list(textpos)
+ if w > (textfield_width):
+ textfield.blit(text, (cur_x, cur_y))
+ cur_line = t
+ cur_y += 30
+ written = 1
+ else:
+ written = 0
+ text = n_text
+
+# print textpos
+
+ # Draw leftovers on textfield
+ if written == 0:
+ textfield.blit(n_text, (cur_x, cur_y))
+ else:
+ font = pygame.font.Font(None, 38)
+ n_text = font.render(cur_line, 1, (0,0,0))
+ textfield.blit(n_text, (cur_x, cur_y))
+
+# print "draw"
+
+ # Draw on Screen
+ sf.display_surface(im, (x_max, y_med))
+ sf.display_surface(textfield, (x_max+25, y_med+20))
+
+ pygame.display.update()
+
+
+class Questions:
+ _cat_id = -1
+ lang_id = 0
+ lang_name = 0
+ in_question = False
+
+ played = 0
+ count_won = 0
+ count_lost = 0
+ questions = []
+
+ def load(self, lang_id, lang_name):
+ self.lang_id = lang_id
+ self.lang_name = lang_name
+ self.question = CurrentQuestion()
+ self.load_categories()
+
+ def walk( self, root, recurse=0, pattern='*', return_folders=0 ):
+ import fnmatch, os, string
+
+ # initialize
+ result = []
+
+ # must have at least root folder
+ try:
+ names = os.listdir(root)
+ except os.error:
+ return result
+
+ # expand pattern
+ pattern = pattern or '*'
+ pat_list = string.splitfields( pattern , ';' )
+
+ # check each file
+ for name in names:
+ fullname = os.path.normpath(os.path.join(root, name))
+
+ # grab if it matches our pattern and entry type
+ for pat in pat_list:
+ if fnmatch.fnmatch(name, pat):
+ if os.path.isfile(fullname) or (return_folders and os.path.isdir(fullname)):
+ result.append(fullname)
+ continue
+
+ # recursively scan other folders, appending results
+ if recurse:
+ if os.path.isdir(fullname) and not os.path.islink(fullname):
+ result = result + self.walk( fullname, recurse, pattern, return_folders )
+
+ return result
+
+ def path_strip(self, pathname):
+ """pathname = path[0] + path[1] + path[2] - splits pathname into components"""
+ # get part after last /, if any
+ filename = os.path.basename(pathname)
+ #decktype determines whether it is a deck (.xml) or superdeck (.sbj)
+ decktype = filename[-4:]
+ #deckname strips off path
+ deckname = filename[:-4]
+ path = (pathname[:-len(filename)],deckname,decktype)
+ return path
+
+ def load_categories(self):
+ global selectedlanguage
+ #here we need to get the names of folders and files in the flashcard folder
+ folder = 'flashcards/' + selectedlanguage
+ decks = self.walk(folder, 1, '*.xml')
+ print decks
+ #make a list 'self.categories' with each entry a list containing:
+ #cat_id, text (name of category), count, parent_id, base_parent_id
+ #sorted alphabetically by text (name of category)
+ self.categories = []
+ folders = []
+ for file in decks:
+ print file
+ tree = Xmlio(file)
+ root = tree.getroot()
+ count = len(root)
+ path = self.path_strip(file)
+ if path[0] in folders:
+ catid += 1
+ parent = folders.index(path[0]) + 1
+ else:
+ folders.append(path[0])
+ parent = len(folders)
+ catid = 1
+ self.categories.append([catid, path, count, parent, 0])
+ self.categories.sort()
+ print selectedlanguage, len(self.categories)
+
+ def question_pick(self):
+ # Check if Questions are left to play
+ if self.played >= len(self.questions):
+ # Game / Cat Finished!
+ return False
+
+ # Okay, Next One!
+ print 'next', self.questions[self.played]
+ self.question.id = self.questions[self.played][0]
+# self.question.imgfn = self.questions[self.played][1]
+ self.question.imgfn = 'bubble.gif'
+ self.question.map = self.questions[self.played][2]
+ self.question.cat = self.questions[self.played][3]
+ self.question.subcat = self.questions[self.played][4]
+ self.question.text = self.questions[self.played][5]
+ self.question.answer = self.questions[self.played][6]
+ self.question.answer_link = self.questions[self.played][7]
+ self.question.hint = self.questions[self.played][8]
+ self.question.more = self.questions[self.played][9]
+ self.question.sound = self.questions[self.played][10]
+ self.question.image = self.questions[self.played][11]
+
+ A.text = self.question.answer
+ A.link = self.question.answer_link
+
+
+ self.played += 1
+
+ return True
+
+ def load_questions(self, cat_id):
+ print 'cat_id = ', cat_id
+ # 2. Load Questions
+ self._cat_id = cat_id[0]
+ self._subcat_id = cat_id[1]
+
+ self.played = 0
+ self.count_won = 0
+ self.count_lost = 0
+
+ #ImageQuiz allows user to select one deck, all decks in category, or all decks
+ #Assume self.categories is built correctly
+ files = []
+ for cat in self.categories:
+ if self._cat_id < 0: #all selected
+ files.append(cat)
+ elif self._cat_id == cat[3] and self._subcat_id == 0: #all subcategories selected
+ files.append(cat)
+ print 'cat', cat[3], cat[0]
+ elif self._cat_id == cat[3] and self._subcat_id == cat[0]: #specific subcategory within category selected
+ files.append(cat)
+ print 'subcat', cat[3], cat[0], cat
+ flashcards = []
+ for file in files:
+ pathname = file[1][0] + file[1][1] + file[1][2]
+ print 'loading', pathname + '\n'
+ tree = Xmlio(pathname)
+ deck = tree.getroot()
+ for card in deck:
+ cardid = int(card.get('id'))
+ question = card.findtext('question')
+ question_node = card.find('question')
+ if question_node:
+ sound = question_node.findtext('sound')
+ hint = question_node.findtext('hint')
+ else:
+ sound = ''
+ hint = ''
+ image = ''
+ answer_node = card.find('answer')
+ answer = card.findtext('answer')
+ if answer_node:
+ temp = answer_node.findtext('image')
+ if temp:
+ image = temp[:-4] + '.png'
+ else:
+ image = ''
+ more = answer_node.findtext('more')
+ else:
+ more = ''
+ image = ''
+ cat = file[3]
+ subcat = file[0]
+ flashcard = [cardid,'','',cat, subcat, question, answer, '', hint, more, sound, image]
+ flashcards.append(flashcard)
+ print flashcard
+ random.shuffle(flashcards)
+ self.questions = flashcards
+ print 'number of questions=', len(self.questions)
+
+def create_flashcard(card):
+ cardid = int(card.get('id'))
+ question = card.findtext('question')
+ question_node = card.find('question')
+ if question_node:
+ sound = question_node.findtext('sound')
+ hint = question_node.findtext('hint')
+ else:
+ sound = ''
+ hint = ''
+ image = ''
+ temp = sound.split('/')
+ print 'temp', temp
+ sound = []
+ for item in temp:
+ print 'before', item
+ item = item[:-4] + '.ogg'
+ print 'after', item
+ sound.append(item)
+ print 'sound list is', sound
+ answer_node = card.find('answer')
+ answer = card.findtext('answer')
+ if answer_node:
+ temp = answer_node.findtext('image')
+ if temp:
+ image = temp[:-4] + '.png'
+ else:
+ image = ''
+ temp = answer_node.findtext('more')
+ if temp:
+ more = temp
+ else:
+ more = ''
+ else:
+ more = ''
+ image = ''
+ cat = ' '
+ subcat = ' '
+ flashcard = [cardid,'','',cat, subcat, question, answer, '', hint, more, sound, image]
+ print flashcard
+ return (sound, image)
+
+
+def extract():
+ global selectedlanguage
+ #here we need to get the names of folders and files in the flashcard folder
+ folder = 'flashcards/' + selectedlanguage
+ print 'folder=', folder
+ decks = Q.walk(folder, 1, '*.xml')
+ print 'decks=', decks
+ for deck in decks:
+ pathname = deck
+ src = os.path.dirname(os.path.dirname(pathname))
+ print 'loading', pathname + '\n'
+ tree = Xmlio(pathname)
+ deck = tree.getroot()
+ for card in deck:
+ sound, image = create_flashcard(card)
+ #copy image.jpg from image to img
+ print os.path.dirname(pathname)
+ print os.path.dirname(os.path.dirname(pathname))
+ print 'copy image', src, image
+ try:
+ shutil.copyfile(src + '/image/' + image, src + '/img/' + image)
+ except:
+ print 'not found', pathname, image
+ #copy sound.mp3 from sound to snd
+ for item in sound:
+ print 'copy sound', src, item
+ try:
+ shutil.copyfile(src + '/sound/' + item, src + '/snd/' + item)
+ except:
+ print 'not found', pathname, item
+
+def show_answer():
+ A.display_answer()
+
+def finished():
+ sf.refresh()
+ sf.add_text_item("You have finished this category!", (180, 80))
+ sf.add_text_item("Won: %i" % Q.count_won, (220, 120))
+ sf.add_text_item("Lost: %i" % Q.count_lost, (220, 160))
+ pygame.display.update()
+# ask_category(130)
+
+
+def startgame(db_cat_id):
+ print "flashcard"
+ print "* Starting Game, Category:", db_cat_id
+ print "** Loading Questions"
+
+ Q.count_won = 0
+ Q.count_won = 0
+ A.reset_points()
+
+ Q.load_questions(db_cat_id)
+ display_points()
+
+ sf.clear_text_items()
+ next_question()
+
+
+def ask_subcat(c):
+ y = 110
+ print 'subcat=', c
+ sf.add_text_item("Subcategory:", (580,y))
+ y += 50
+
+ sf.add_text_item('All', (600,y), startgame, (c, 0), True)
+
+ i = 0
+ for q in Q.categories:
+ if q[3] == c:
+ y += 50
+ sf.add_text_item("%s (%s)" % (q[1][1], q[2]), (600,y), startgame, (q[3], q[0]), True)
+ i += 1
+
+ #print c
+
+def ask_category(offset_y = 0):
+ global selectedlanguage
+ Q.load(__SERVICES__.locale.lang_id, __SERVICES__.locale.lang_name)
+
+# sf.clear_text_items()
+
+ i = 1
+ y = 110 + offset_y
+
+ sf.add_text_item("Next Category:", (280,y))
+
+ y += 50
+ sf.add_text_item('All', (300,y), startgame, (0, 0), True)
+
+ for q in Q.categories:
+ if q[0] == 1:
+ if q[1][0] != 'flashcards/' + selectedlanguage + '/':
+ name = q[1][0][len('flashcards/' + selectedlanguage + '/') - len(q[1][0]):-1]
+ sf.add_text_item("%s (%s)" % (name, q[2]), (300,y + 50 * q[3]), ask_subcat, q[3], True)
+ else:
+ name = q[1][1]
+ sf.add_text_item("%s (%s)" % (name, q[2]), (300,y + 50 * q[3]), startgame, (q[3], q[0]), True)
+ i += 1
+
+ display_points()
+
+def display_points():
+ format = TextFormat(None, 30)
+ max_q = len(Q.questions)
+
+# sf.display_tooltip("%i" % (Q.count_won), (480, 14), format)
+# sf.display_tooltip("%i" % Q.count_lost, (480, 42), format)
+
+ if max_q == 0:
+ sf.display_line(0, True)
+ sf.display_line(0, False)
+ else:
+ sf.display_line(100 * Q.count_won / max_q, True)
+ sf.display_line(100 * Q.count_lost / max_q, False)
+ pygame.display.update()
+
+def picture_tl():
+ global images
+ if Q.in_question:
+ print images[0], Q.question.image
+ print 'tl'
+ if images[0] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_tr():
+ global images
+ if Q.in_question:
+ print 'tr'
+ print images[1], Q.question.image
+ if images[1] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_ll():
+ global images
+ if Q.in_question:
+ print 'll'
+ print images[2], Q.question.image
+ if images[2] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_lr():
+ global images
+ if Q.in_question:
+ print 'lr'
+ print images[3], Q.question.image
+ if images[3] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def delete_reacts(response):
+ if response:
+ correct_answer()
+ else:
+ wrong_answer()
+ return True
+
+def getpossibles(teststr):
+ #there may be multiple correct answers separated by a '/'
+ lst = []
+ test = ''
+ for i in range(len(teststr)):
+ if teststr[i] != '/':
+ test = test + teststr[i]
+ else:
+ lst.append(test)
+ test = ''
+ if len(test) > 0:
+ lst.append(test)
+ return lst
+
+def checkanswer(response):
+ possibles = getpossibles(Q.question.answer)
+ return response.strip() in possibles
+
+def self_test():
+ show_answer()
+ #show two clickable items: smiley face, sad face
+ image_right, xy = sf.image_load("images/Emotes/face-grin.png")
+ sf.display_surface(image_right, (750,100))
+ image_wrong, xy = sf.image_load("images/Emotes/face-devil-grin.png")
+ sf.display_surface(image_wrong, (750,150))
+ pygame.display.update()
+
+def play_query_again():
+ global query
+ if Q.in_question:
+ print 'play_query'
+ query.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+
+def next_question():
+ print "* Next Question"
+ global images
+ global query
+ global rtr, rtl, rll, rlr
+
+ # Select Category
+ if Q._cat_id == -1:
+ sf.clear_text_items()
+ sf.clear_question_frame(True)
+ ask_category()
+ return 1
+
+ # Pick only locale language's categories
+ if Q.question_pick():
+ sf.clear_text_items()
+ Renyi = False
+ print 'image = ', Q.question.image
+ Q.in_question = True
+ if Q.question.image and len(Q.question.image) > 0:
+ Renyi = True
+ candidate_images = []
+ for question in Q.questions:
+ candidate_images.append(question[11])
+ random.shuffle(candidate_images)
+ images = candidate_images[:3]
+ if Q.question.image in images:
+ images.append(candidate_images[3])
+ else:
+ images.append(Q.question.image)
+ random.shuffle(images)
+ #load image
+ image_tl, xy = sf.image_load(images[0],path = 'flashcards/' + selectedlanguage + '/image')
+ if image_tl == None:
+ image_tl, xy = sf.image_load(error.png, path = 'flashcards/')
+ image_tr, xy = sf.image_load(images[1],path = 'flashcards/' + selectedlanguage + '/image')
+ if image_tr == None:
+ image_tr, xy = sf.image_load(error.png, path = 'flashcards/')
+ image_ll, xy = sf.image_load(images[2],path = 'flashcards/' + selectedlanguage + '/image')
+ if image_ll == None:
+ image_ll, xy = sf.image_load(error.png, path = 'flashcards/')
+ image_lr, xy = sf.image_load(images[3],path = 'flashcards/' + selectedlanguage + '/image')
+ if image_lr == None:
+ image_lr, xy = sf.image_load(error.png, path = 'flashcards/')
+ #display image
+ sf.display_surface(image_tl, (50, 100))
+ sf.display_surface(image_tr, (400,100))
+ sf.display_surface(image_ll, (50,360))
+ sf.display_surface(image_lr, (400, 360))
+ pygame.display.update()
+
+ print 'sound = ', Q.question.sound
+ if Q.question.sound and len(Q.question.sound) > 0:
+ query = sf.sound_load(Q.question.sound[:-4] + '.ogg', path='flashcards/' + selectedlanguage + '/sound')
+ query.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+ image_play, xy = sf.image_load("images/xclock.png")
+ sf.display_surface(image_play, (300,0))
+ pygame.display.update()
+
+ if not Renyi:
+ response = __SERVICES__.frontend.ask(Q.question.text)
+ print 'response=', response
+
+ if len(response) == 0 or len(Q.question.answer) == 0:
+ self_test()
+ else:
+ correct = checkanswer(response)
+ y = 100
+ sf.add_text_item("ok", (750,y), next_question)
+ if correct:
+ correct_answer(False)
+ else:
+ wrong_answer(False)
+
+ pass
+ else:
+ # game / cat finished!
+ Q.in_question = False
+ print "finished"
+ Q._cat_id = -1
+ finished()
+ pass
+
+def correct_answer(next=True):
+ correct_sounds = ['i-like-it.wav', 'ooh-yeah.wav', 'impressive.wav', 'dramatic_chord.wav', 'sweet.wav',
+ 'brain-yes.wav', 'mk-excellent.wav', 'that-was-cool.wav', 'bt-excellent.wav', 'groovy.wav',
+ 'yes-i-like-it.wav', 'burns-excellent.wav', 'oh-yeah.wav']
+ print 'correct answer next =', next, Q.in_question
+ if Q.in_question:
+ A.display_icon("found")
+ #A.play_sound(1)
+ Q.in_question = False
+ Q.count_won += 1
+ display_points()
+ good = sf.sound_load(random.choice(correct_sounds))
+ good.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+ if next:
+ next_question()
+ else:
+ show_answer()
+
+def wrong_answer(next=True):
+ wrong_sounds = ['db_forgetaboutit.wav', 'alf_wrong.wav', 'doh.wav', 'sorry.wav', 'awh_man.wav', 'metal_clang_2.wav',
+ 'excuse_me.wav', 'negative.wav', 'bunny_awful.wav', 'gwarsh.wav', 'not.wav', 'haha.wav', 'oh_no.wav',
+ 'compute.wav', 'hoo-ah.wav']
+ print 'wrong answer next =', next, Q.in_question
+ if Q.in_question:
+ A.display_icon("not-found")
+ #A.play_sound(0)
+ Q.in_question = False
+ Q.count_lost += 1
+ display_points()
+ bad = sf.sound_load(random.choice(wrong_sounds))
+ bad.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+ if next:
+ next_question()
+ else:
+ show_answer()
+
+def click_russian_renyi_1():
+ global selectedlanguage
+ print 'russian renyi_1 selected'
+ selectedlanguage = 'russian/renyi_1'
+ Q.cat_id = -1
+ extract()
+
+def click_russian_renyi_2():
+ global selectedlanguage
+ print 'russian renyi_2 selected'
+ selectedlanguage = 'russian/renyi_2'
+ Q.cat_id = -1
+ extract()
+
+def click_russian_renyi_3():
+ global selectedlanguage
+ print 'russian renyi_3 selected'
+ selectedlanguage = 'russian/renyi_3'
+ Q.cat_id = -1
+ extract()
+
+def click_russian_renyi_4():
+ global selectedlanguage
+ print 'russian renyi_4 selected'
+ selectedlanguage = 'russian/renyi_4'
+ Q.cat_id = -1
+ extract()
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+ global Q
+ Q = Questions()
+
+ global A
+ A = Answer()
+
+ global clock
+ clock = pygame.time.Clock()
+
+ global button1
+ global button2
+ global button3
+ global button4
+
+ global rtl, rtr, rll, rlr, knew_it, forgot_it, play_again
+ rtl = sf.add_react(50, 100, 320, 240, picture_tl)
+ print 'rtl=', rtl
+ rtr = sf.add_react(400, 100, 320, 240, picture_tr)
+ print 'rtr=', rtr
+ rll = sf.add_react(50, 360, 320, 240, picture_ll)
+ print 'rll=', rll
+ rlr = sf.add_react(400, 360, 320, 240, picture_lr)
+ print 'rlr=', rlr
+ knew_it = sf.add_react(750,100,50,50, correct_answer)
+ print 'knew_it=', knew_it
+ forgot_it = sf.add_react(750,150,50,50, wrong_answer)
+ print 'forgot_it=', forgot_it
+ play_again = sf.add_react(300,0,100,100, play_query_again)
+ print 'play_again=', play_again
+
+ sf.add_menu_dir('/extract', "Extract")
+ button1 = sf.add_menu_item('/extract', 'Russian Renyi_1', click_russian_renyi_1)
+ print 'button', button1
+ button2 = sf.add_menu_item('/extract', 'Russian Renyi_2', click_russian_renyi_2)
+ print 'button', button2
+ button3 = sf.add_menu_item('/extract', 'Russian Renyi_3', click_russian_renyi_3)
+ print 'button', button3
+ button4 = sf.add_menu_item('/extract', 'Russian Renyi_4', click_russian_renyi_4)
+ print 'button', button4
+
+ pass
+
+
+def close():
+ pass
diff --git a/plugins/_flashcard.py b/plugins/_flashcard.py
new file mode 100755
index 0000000..e1869ac
--- /dev/null
+++ b/plugins/_flashcard.py
@@ -0,0 +1,728 @@
+# Modified to play flashcards from .xml decks in flashcard folder
+# Currently
+# * Displaying Categories in set language,
+# * Pick English Questions
+
+
+import pygame
+from pygame import *
+
+import random
+from layout import *
+from frontend import hex2rgb
+from datetime import datetime
+import time
+import threading
+
+from xmlio import Xmlio
+import xml.etree.ElementTree as ET
+from path import path
+
+import os
+
+__PLUGIN_NAME__ = 'flashcard'
+QUIZSOURCE = '/home/olpc/Activities/ImageQuizPlus.activity/flashcards/flashcards'
+ICONSOURCE = '/home/olpc/Activities/ImageQuizPlus.activity/images'
+
+class CurrentQuestion:
+ id = 0
+ imgfn = u''
+ map = u''
+ cat = 0
+ subcat = 0
+ text = u''
+ answer = u''
+ answer_link = ''
+ hint = ''
+ more = ''
+ sound = ''
+ image = ''
+
+class Answer:
+ display = True
+ display_line = False
+ sound_enabled = True
+
+ link = u''
+ text = u''
+
+ img_found_count = 0
+ img_notfound_count = 0
+# imgfn_found = ["Emotes/face-grin.png", "Emotes/face-devil-grin.png", "Emotes/face-glasses.png"]
+# imgfn_notfound = ["Emotes/face-monkey.png", "Emotes/face-crying.png"]
+ imgfn_found = ["Emotes/face-grin.png"]
+ imgfn_notfound = ["Emotes/face-devil-grin.png"]
+ random.shuffle(imgfn_found)
+ random.shuffle(imgfn_notfound)
+
+ found_straight = 0
+ straight_count = 0
+ icon_left = 0
+ bg_icons = pygame.Surface((500, 32))
+ bg_icons_straight = pygame.Surface((500, 32))
+
+ def __init__(self):
+ self.sound_found = sf.sound_load("accessed.wav")
+ self.sound_notfound = sf.sound_load("sorry.wav")
+
+ def reset_points(self):
+ self.found_straight = 0
+ self.straight_count = 0
+ self.icon_left = 0
+
+ self.bg_icons.fill(hex2rgb(Layout().Toolbar.background))
+ self.bg_icons_straight.fill(hex2rgb(Layout().Toolbar.background))
+
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar")
+ sf.refresh()
+
+ def add_straight(self):
+ # 5 in a row -> special reward
+ self.straight_count += 1
+ im, im_rect = sf.image_load("images/%s" % "Icons/bulb.png")
+ self.bg_icons_straight.blit(im, ((self.straight_count-1) * (im_rect[2] + 4), 0))
+ sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar")
+ sf.refresh()
+
+ def display_icon(self, icon_name):
+ if icon_name == "found":
+ self.found_straight += 1
+ if self.found_straight == 5:
+ # Found Straight 5!
+ self.bg_icons.fill(hex2rgb(Layout().Toolbar.background))
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ self.add_straight()
+ self.icon_left = 0
+ self.found_straight = 1
+
+ fn = self.imgfn_found[self.img_found_count % len(self.imgfn_found)]
+ self.img_found_count += 1
+
+ elif icon_name == "not-found":
+ self.found_straight = 0
+ fn = self.imgfn_notfound[self.img_notfound_count % len(self.imgfn_notfound)]
+ self.img_notfound_count += 1
+
+ img, img_rect = sf.image_load("images/%s" % fn)
+ self.bg_icons.blit(img, (self.icon_left, 0))
+
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ sf.refresh()
+
+ self.icon_left += img_rect[2] + 4
+
+ class PlaySoundThread (threading.Thread):
+ def set(self, sound, interval):
+ self.sound = sound
+ self.i = interval
+
+ def run(self):
+ time.sleep(self.i)
+ self.sound.play()
+
+ def play_sound(self, i):
+ if self.sound_enabled:
+ t = self.PlaySoundThread()
+ if i == 1: t.set(self.sound_found, 0)
+ else: t.set(self.sound_notfound, 0.5)
+ t.start()
+
+ def display_answer(self):
+ if self.display_line: sf.draw_lines()
+ if self.display == False: return False
+
+ # Get Top Right Spot of list
+ print "showing answer"
+ map_arr = Q.question.map.split(", ")
+
+ # Widths & Heights
+ bubble_width = 400
+ bubble_height = 300
+ textfield_width = 270
+ textfield_height = 200
+
+ # Extremas of the Polygone will be saved in here:
+ x_max = 0
+ y_max = 0
+ x_min = 1000
+ y_min = 1000
+
+ # Extract Extremas from the polygon
+ o = []
+ a = "x"
+ for s in map_arr:
+ if a == "x": a = s
+ else:
+ # Take care of border pixels
+ if int(a) > x_max: x_max = int(a)
+ if int(s) > y_max: y_max = int(s)
+ if int(a) < x_min: x_min = int(a)
+ if int(s) < y_min: y_min = int(s)
+ a = "x"
+
+ # Set x and y for the Answer Bubble
+ y_med = (y_min + y_max) / 2
+ x_max -= 5
+
+ y_med = 150
+ x_max = 400
+
+ x_max += Layout().Question.x + Layout().Question.Image.x + 2
+ y_med += Layout().Question.y + Layout().Question.Image.y + 2
+
+# sf.draw_polygon()
+
+ # Draw Answer Bubble Image & Text
+ im, im_rect = sf.image_load('images/bubble.gif')
+
+ text_arr = self.text.split(' ')
+ cur_line = ''
+
+ cur_x = 0
+ cur_y = 0
+
+ # 'textfield' contains the answer text
+ textfield = pygame.Surface((textfield_width, textfield_height))
+ textfield.fill((255, 255, 255))
+
+ # Make line breaks after reaching width of 'textfield'
+ for t in text_arr:
+ cur_line = "%s %s" % (cur_line, t)
+
+ font = pygame.font.Font(None, 38)
+ n_text = font.render(cur_line, 1, (0,0,0))
+ textpos = n_text.get_rect()
+# #print cur_line,
+
+ x,y,w,h = list(textpos)
+ if w > (textfield_width):
+ textfield.blit(text, (cur_x, cur_y))
+ cur_line = t
+ cur_y += 30
+ written = 1
+ else:
+ written = 0
+ text = n_text
+
+# #print textpos
+
+ # Draw leftovers on textfield
+ if written == 0:
+ textfield.blit(n_text, (cur_x, cur_y))
+ else:
+ font = pygame.font.Font(None, 38)
+ n_text = font.render(cur_line, 1, (0,0,0))
+ textfield.blit(n_text, (cur_x, cur_y))
+
+# #print "draw"
+
+ # Draw on Screen
+ sf.display_surface(im, (x_max, y_med))
+ sf.display_surface(textfield, (x_max+25, y_med+20))
+
+ pygame.display.update()
+
+
+class Questions:
+ global path
+ _cat_id = -1
+ lang_id = 0
+ lang_name = 0
+ in_question = False
+
+ played = 0
+ count_won = 0
+ count_lost = 0
+ questions = []
+
+ def load(self, lang_id, lang_name):
+ self.lang_id = lang_id
+ self.lang_name = lang_name
+ self.question = CurrentQuestion()
+ self.load_categories()
+
+ def load_categories(self):
+ #here we need to get the names of folders and files in the flashcard folder
+ #make a list 'self.categories' with each entry a list containing:
+ #cat_id, text (name of category), count, parent_id, base_parent_id
+ #sorted alphabetically by text (name of category)
+ decks = path(QUIZSOURCE)
+ self.categories = []
+ for f in decks.files('*.xml'):
+ tree = Xmlio(f)
+ root = tree.getroot()
+ count = len(root)
+ catid = 1
+ parent = len(self.categories)
+ self.categories.append([catid, f, count, parent, 0])
+ self.categories.sort()
+
+ def question_pick(self):
+ # Check if Questions are left to play
+ if self.played >= len(self.questions):
+ # Game / Cat Finished!
+ return False
+
+ # Okay, Next One!
+ #print 'next', self.questions[self.played]
+ self.question.id = self.questions[self.played][0]
+# self.question.imgfn = self.questions[self.played][1]
+ self.question.imgfn = 'bubble.gif'
+ self.question.map = self.questions[self.played][2]
+ self.question.cat = self.questions[self.played][3]
+ self.question.subcat = self.questions[self.played][4]
+ self.question.text = self.questions[self.played][5]
+ self.question.answer = self.questions[self.played][6]
+ self.question.answer_link = self.questions[self.played][7]
+ self.question.hint = self.questions[self.played][8]
+ self.question.more = self.questions[self.played][9]
+ self.question.sound = self.questions[self.played][10]
+ self.question.image = self.questions[self.played][11]
+
+ A.text = self.question.answer
+ A.link = self.question.answer_link
+
+
+ self.played += 1
+
+ return True
+
+ def load_questions(self, cat_id):
+ #print 'cat_id = ', cat_id
+ # 2. Load Questions
+ self._cat_id = cat_id[0]
+ self._subcat_id = cat_id[1]
+
+ self.played = 0
+ self.count_won = 0
+ self.count_lost = 0
+
+ #ImageQuiz allows user to select one deck, all decks in category, or all decks
+ #Assume self.categories is built correctly
+ files = []
+ for cat in self.categories:
+ if self._cat_id < 0: #all selected
+ files.append(cat)
+ elif self._cat_id == cat[3] and self._subcat_id == 0: #all subcategories selected
+ files.append(cat)
+ #print 'cat', cat[3], cat[0]
+ elif self._cat_id == cat[3] and self._subcat_id == cat[0]: #specific subcategory within category selected
+ files.append(cat)
+ #print 'subcat', cat[3], cat[0], cat
+ flashcards = []
+ for file in files:
+ #print 'file', file
+ pathname = file[1]
+ #print 'loading', pathname + '\n'
+ tree = Xmlio(pathname)
+ deck = tree.getroot()
+ self.deck = deck
+ for card in deck:
+ cardid = int(card.get('id'))
+ question = card.findtext('question')
+ question_node = card.find('question')
+ if question_node:
+ sound = question_node.findtext('sound')
+ hint = question_node.findtext('hint')
+ else:
+ sound = ''
+ hint = ''
+ image = ''
+ answer_node = card.find('answer')
+ answer = card.findtext('answer')
+ if answer_node:
+ temp = answer_node.findtext('image')
+ if temp:
+ image = temp[:-4] + '.png'
+ else:
+ image = ''
+ more = answer_node.findtext('more')
+ else:
+ more = ''
+ image = ''
+ cat = file[3]
+ subcat = file[0]
+ flashcard = [cardid,'','',cat, subcat, question, answer,
+'', hint, more, sound, image]
+ flashcards.append(flashcard)
+ random.shuffle(flashcards)
+ self.questions = flashcards
+ #print 'number of questions=', len(self.questions)
+
+def show_answer():
+ A.display_answer()
+
+def finished():
+ sf.refresh()
+ sf.add_text_item("You have finished this category!", (180, 80))
+ sf.add_text_item("Won: %i" % Q.count_won, (220, 120))
+ sf.add_text_item("Lost: %i" % Q.count_lost, (220, 160))
+ pygame.display.update()
+# ask_category(130)
+
+
+def startgame(db_cat_id):
+ #print "flashcard"
+ #print "* Starting Game, Category:", db_cat_id
+ #print "** Loading Questions"
+
+ Q.count_won = 0
+ Q.count_won = 0
+ Q.shown = datetime.now()
+ A.reset_points()
+
+ Q.load_questions(db_cat_id)
+ display_points()
+
+ sf.clear_text_items()
+ next_question()
+
+
+def ask_subcat(c):
+ y = 110
+ #print 'subcat=', c
+ sf.add_text_item("Subcategory:", (580,y))
+ y += 50
+
+ sf.add_text_item('All', (600,y), startgame, (c, 0), True)
+
+ i = 0
+ for q in Q.categories:
+ if q[3] == c:
+ y += 50
+ sf.add_text_item("%s (%s)" % (q[1][1], q[2]), (600,y), startgame, (q[3], q[0]), True)
+ i += 1
+
+ #print c
+
+def ask_category(offset_y = 0):
+ Q.load(__SERVICES__.locale.lang_id, __SERVICES__.locale.lang_name)
+
+# sf.clear_text_items()
+
+ i = 1
+ y = 110 + offset_y
+
+ sf.add_text_item("Next Category:", (280,y))
+
+ y += 50
+ sf.add_text_item('All', (300,y), startgame, (0, 0), True)
+
+ for q in Q.categories:
+ #print 'q', q
+ if q[0] == 1:
+ fname = q[1].name
+ sf.add_text_item("%s (%s)" % (fname, q[2]), (300,y + 50 * q[3]), startgame, (q[3], q[0]), True)
+ i += 1
+
+ display_points()
+
+def display_points():
+ format = TextFormat(None, 30)
+ max_q = len(Q.questions)
+
+# sf.display_tooltip("%i" % (Q.count_won), (480, 14), format)
+# sf.display_tooltip("%i" % Q.count_lost, (480, 42), format)
+
+ if max_q == 0:
+ sf.display_line(0, True)
+ sf.display_line(0, False)
+ else:
+ sf.display_line(100 * Q.count_won / max_q, True)
+ sf.display_line(100 * Q.count_lost / max_q, False)
+ pygame.display.update()
+
+def picture_tl():
+ global images
+ if Q.in_question:
+ #print images[0], Q.question.image
+ #print 'tl'
+ if images[0] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_tr():
+ global images
+ if Q.in_question:
+ #print 'tr'
+ #print images[1], Q.question.image
+ if images[1] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_ll():
+ global images
+ if Q.in_question:
+ #print 'll'
+ #print images[2], Q.question.image
+ if images[2] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_lr():
+ global images
+ if Q.in_question:
+ #print 'lr'
+ #print images[3], Q.question.image
+ if images[3] == Q.question.image:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def delete_reacts(response):
+ if response:
+ correct_answer()
+ else:
+ wrong_answer()
+ return True
+
+def getpossibles(teststr):
+ #there may be multiple correct answers separated by a '/'
+ lst = []
+ test = ''
+ for i in range(len(teststr)):
+ if teststr[i] != '/':
+ test = test + teststr[i]
+ else:
+ lst.append(test)
+ test = ''
+ if len(test) > 0:
+ lst.append(test)
+ return lst
+
+def checkanswer(response):
+ possibles = getpossibles(Q.question.answer)
+ return response.strip() in possibles
+
+def self_test():
+ show_answer()
+ #show two clickable items: smiley face, sad face
+ image_right, xy = sf.image_load("images/Emotes/face-grin.png")
+ sf.display_surface(image_right, (750,100))
+ image_wrong, xy = sf.image_load("images/Emotes/face-devil-grin.png")
+ sf.display_surface(image_wrong, (750,150))
+ pygame.display.update()
+
+def play_query_again():
+ global query
+ if Q.in_question:
+ #print 'play_query'
+ query.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+
+def next_question():
+ #print "* Next Question"
+ global images
+ global query
+ global rtr, rtl, rll, rlr
+
+ # Select Category
+ if Q._cat_id == -1:
+ sf.clear_text_items()
+ sf.clear_question_frame(True)
+ ask_category()
+ #print 'ask_category'
+ return 1
+
+ # Pick only locale language's categories
+ if Q.question_pick():
+ sf.clear_text_items()
+ Renyi = False
+ #print 'image = ', Q.question.image
+ Q.in_question = True
+ Q.starttime = time.time()
+ if Q.question.image and len(Q.question.image) > 0:
+ Renyi = True
+ candidate_images = []
+ for question in Q.questions:
+ candidate_images.append(question[11])
+ random.shuffle(candidate_images)
+ #print 'candidate_images', candidate_images
+ if Q.question.image in candidate_images[:4]:
+ images = candidate_images[:4]
+ else:
+ images = candidate_images[:3]
+ images.append(Q.question.image)
+ random.shuffle(images)
+ #print 'images', images
+ #load image
+ imagepath = path(QUIZSOURCE).joinpath('image')
+ #print 'imagepath', imagepath
+ image_tl, xy = sf.image_load(images[0],path = imagepath)
+ if image_tl == None:
+ image_tl, xy = sf.image_load(error.png, path = imagepath)
+ image_tr, xy = sf.image_load(images[1],path = imagepath)
+ if image_tr == None:
+ image_tr, xy = sf.image_load(error.png, path = imagepath)
+ image_ll, xy = sf.image_load(images[2],path = imagepath)
+ if image_ll == None:
+ image_ll, xy = sf.image_load(error.png, imagepath)
+ image_lr, xy = sf.image_load(images[3],path = imagepath)
+ if image_lr == None:
+ image_lr, xy = sf.image_load(error.png, path = imagepath)
+ #display image
+ sf.display_surface(image_tl, (250, 150))
+ sf.display_surface(image_tr, (520,150))
+ sf.display_surface(image_ll, (250,420))
+ sf.display_surface(image_lr, (520, 420))
+ pygame.display.update()
+
+ #print 'sound = ', Q.question.sound
+ soundpath = path(QUIZSOURCE).joinpath('sound')
+ soundlst = getpossibles(soundpath)
+ random.shuffle(soundlst)
+ iconpath = path(ICONSOURCE)
+ if Q.question.sound and len(Q.question.sound) > 0:
+ temp = getpossibles(Q.question.sound)
+ random.shuffle(temp)
+ selected = temp[0]
+ print 'sounds', Q.question.sound, temp, selected
+ query = sf.sound_load(selected[:-4] + '.ogg',
+ path=soundpath)
+ query.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+ #image_play, xy = sf.image_load("images/play.svg")
+
+ image_play, xy = sf.image_load("play_button.png", path = iconpath)
+ sf.display_surface(image_play, (300,30))
+ pygame.display.update()
+
+ if not Renyi:
+ response = __SERVICES__.frontend.ask(Q.question.text)
+ #print 'response=', response
+
+ if len(response) == 0 or len(Q.question.answer) == 0:
+ self_test()
+ else:
+ correct = checkanswer(response)
+ y = 100
+ sf.add_text_item("ok", (750,y), next_question)
+ if correct:
+ correct_answer(False)
+ else:
+ wrong_answer(False)
+
+ pass
+ else:
+ # game / cat finished!
+ Q.in_question = False
+ #print "finished"
+ Q._cat_id = -1
+ finished()
+ pass
+
+def find_card(cardid):
+ for card in Q.deck:
+ if int(card.get('id')) == cardid:
+ return card
+
+def correct_answer(next=True):
+ correct_sounds = ['i-like-it.wav', 'ooh-yeah.wav', 'impressive.wav', 'dramatic_chord.wav', 'sweet.wav',
+ 'brain-yes.wav', 'mk-excellent.wav', 'that-was-cool.wav', 'bt-excellent.wav', 'groovy.wav',
+ 'yes-i-like-it.wav', 'burns-excellent.wav', 'oh-yeah.wav']
+ #print 'correct answer next =', next, Q.in_question
+ if Q.in_question:
+ A.display_icon("found")
+ #A.play_sound(1)
+ Q.in_question = False
+ Q.count_won += 1
+ display_points()
+ good = sf.sound_load(random.choice(correct_sounds))
+ good.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+ #update Leitner attributes
+ think = '%0.2f' % float( time.time() - Q.starttime)
+ print 'think',think
+ card = find_card(Q.question.id)
+ card.attrib['think'] = think
+ card.attrib['shown'] = Q.shown
+ count = int(card.attrib['count'])
+ count +=1
+ card.attrib['count'] = str(count)
+ bin = int(card.attrib['bin'])
+ bin += 1
+ card.attrib['bin'] = str(bin)
+ if next:
+ next_question()
+ else:
+ show_answer()
+
+def wrong_answer(next=True):
+ wrong_sounds = ['db_forgetaboutit.wav', 'alf_wrong.wav', 'doh.wav', 'sorry.wav', 'awh_man.wav', 'metal_clang_2.wav',
+ 'excuse_me.wav', 'negative.wav', 'bunny_awful.wav', 'gwarsh.wav', 'not.wav', 'haha.wav', 'oh_no.wav',
+ 'compute.wav', 'hoo-ah.wav']
+ #print 'wrong answer next =', next, Q.in_question
+ if Q.in_question:
+ #update Leitner attributes
+ card = find_card(Q.question.id)
+ card.attrib['think'] = \
+ '%0.2f' % float(time.time() - Q.starttime)
+ card.attrib['shown'] = Q.shown
+ card.attrib['bin'] = 1
+ count = int(card.attrib['count'])
+ count += 1
+ card.attrib['count'] = str(count)
+ A.display_icon("not-found")
+ #A.play_sound(0)
+ Q.in_question = False
+ Q.count_lost += 1
+ display_points()
+ bad = sf.sound_load(random.choice(wrong_sounds))
+ bad.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+ if next:
+ next_question()
+ else:
+ show_answer()
+
+def click_on_flashcards():
+ global selectedlanguage
+ #print 'flashcards selected'
+ selectedlanguage = 'flashcards'
+ Q.cat_id = -1
+ next_question()
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+ global Q
+ Q = Questions()
+
+ global A
+ A = Answer()
+
+ global clock
+ clock = pygame.time.Clock()
+
+ global rtl, rtr, rll, rlr, knew_it, forgot_it, play_again
+ rtl = sf.add_react(50, 100, 320, 240, picture_tl)
+ #print 'rtl=', rtl
+ rtr = sf.add_react(400, 100, 320, 240, picture_tr)
+ #print 'rtr=', rtr
+ rll = sf.add_react(50, 360, 320, 240, picture_ll)
+ #print 'rll=', rll
+ rlr = sf.add_react(400, 360, 320, 240, picture_lr)
+ #print 'rlr=', rlr
+ knew_it = sf.add_react(750,100,50,50, correct_answer)
+ #print 'knew_it=', knew_it
+ forgot_it = sf.add_react(750,150,50,50, wrong_answer)
+ #print 'forgot_it=', forgot_it
+ play_again = sf.add_react(300,0,100,100, play_query_again)
+ #print 'play_again=', play_again
+
+ sf.add_menu_item('/', 'Flashcards', click_on_flashcards)
+
+def close():
+ pass
diff --git a/plugins/_library.py b/plugins/_library.py
new file mode 100755
index 0000000..60e6922
--- /dev/null
+++ b/plugins/_library.py
@@ -0,0 +1,198 @@
+__PLUGIN_NAME__ = 'library'
+LIB = '/home/olpc/Activities/ImageQuizPlus.activity/flashcards/library'
+OUTPATH = '/home/olpc/Activities/ImageQuizPlus.activity/flashcards/flashcards'
+
+from path import path
+from xmlio import Xmlio
+import xml.etree.ElementTree as ET
+
+'''
+ List of Services:
+ - ImageQuiz.py, Line 37: "Hook-In Services"
+ - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview
+'''
+#
+# /media/SDCARD02 should be global for easy change
+# top level menu items are folders in /media/SDCARD02/library
+# change is to make this happen dynamically based on directory (using path.py)
+# top level categories are folders in selected top level item
+# sub categories are decks in selected category
+# selected deck is copied to /home/olpc/ImageQuizPlus.activity/flashcards (adding Leitner attributes)
+# if deck references image or sound files, they are copied to /home/olpc/ImageQuizPlus.activity/flashcards/image or sound.
+# finish by returning to main menu
+#
+# strategy
+# first get menu working without doing anything (print xxx.xml selected)
+# next add print for image copied or sound copied
+# debug source is /home/tonya/Desktop/ImageQuizPlus.activity/flashcards/library
+# debug destination is /home/tonya/Desktop/ImageQuizPlus.activity/flashcards
+#
+# next actually copy deck adding Leitner attributes
+#
+# modify flashcard.py to use Leitner attributes (first ignore as in quiz mode)
+
+def clickOnItem2():
+ #here we need to display list of decks already checked out
+ #print "Return to Library"
+ d = path(OUTPATH)
+ count = 0
+ y = 110
+ for item in d.files():
+ brdr = False
+ sf.add_text_item(item.name, (300, y+50*count), remove_deck, (d, item.name), brdr)
+ count += 1
+
+def remove_deck(s):
+ #print s[0], s[1], 'selected'
+ d = path(OUTPATH)
+ deck = d.joinpath(s[1])
+ #print 'remove deck', deck
+ path.remove(deck)
+ #we need to redisplay menu with deck removed
+ sf.clear_text_items()
+ clickOnItem2()
+
+def clickOnItem1():
+ #here we need to display categories (folders in selected folder)
+ #print "Demoplugin Menu Item 1"
+ #make path to folder
+ #here we need to use path to make a list of directories
+ brdr = False
+ d = path(LIB)
+ dsel = d.joinpath(sf.current_caption())
+ count = 0
+ y = 110
+ for folder in dsel.dirs():
+ #print folder, folder.name, len(folder.files()), len(folder.dirs())
+ sf.add_text_item("%s (%s)" % (unicode(folder.name), str(len(folder.dirs()))), (300,y + 50 * count),
+ ask_subcat, (dsel, folder.name), brdr)
+ count += 1
+
+def ask_subcat(s):
+ global sourcepath
+ #print s[0], s[1], 'selected'
+ brdr = True
+ sf.clear_text_items()
+ d = path(s[0])
+ dsel = d.joinpath(s[1])
+ #this is source for image and sound files
+ sourcepath = d
+ count = 0
+ y = 70
+ for f in dsel.listdir():
+ #may be deck or folder
+ if f.isdir():
+ cnt = len(f.listdir())
+ brdr = False
+ else:
+ tree = Xmlio(f)
+ root = tree.getroot()
+ cnt = len(root)
+ brdr = True
+ if count < 12:
+ sf.add_text_item("%s (%s)" % (unicode(f.namebase), str(cnt)), (200,y + 50 * count),
+ make_local, (dsel,f.name), brdr)
+ elif count < 24:
+ sf.add_text_item("%s (%s)" % (unicode(f.namebase), str(cnt)), (450,y + 50 * (count - 12)),
+ make_local, (dsel,f.name), brdr)
+ else:
+ sf.add_text_item("%s (%s)" % (unicode(f.namebase), str(cnt)), (700,y + 50 * (count - 24)),
+ make_local, (dsel,f.name), brdr)
+ count += 1
+
+def make_local(s):
+ global sourcepath
+ #this could be deck or folder
+ #print s[0], s[1], 'deck selected'
+ count = 0
+ sel = path(s[0])
+ selp = sel.joinpath(s[1])
+ if selp.isdir():
+ ask_subcat(s)
+ return
+ #print 'should be file', selp
+ deck = Xmlio(selp)
+ cards = deck.getroot()
+ outpath = path(OUTPATH)
+ fullpath = outpath.joinpath(s[1])
+ #print fullpath
+ outpath = path(OUTPATH)
+ soundsource = path(sourcepath).joinpath('sound')
+ imagesource = path(sourcepath).joinpath('image')
+ outdeck = Xmlio(root = "quiz")
+ outcards = outdeck.getroot()
+ for card in cards:
+ #copy card to outcard
+ question_node = card.find('question')
+ if question_node:
+ temp = question_node.findtext('sound')
+ if temp:
+ sounds = temp.split('/')
+ for item in sounds:
+ item = item[:-4] + '.ogg'
+ #copy sounds here
+ source = soundsource.joinpath(item)
+ temp = outpath.joinpath('sound')
+ destination = temp.joinpath(item)
+ #print 'sound to copy', item, 'from', source, 'to', destination
+ path.copy(source,destination)
+ sound = '/'.join(sounds)
+ else:
+ sound = ''
+ hint = question_node.findtext('hint')
+ answer_node = card.find('answer')
+ if answer_node:
+ more = answer_node.findtext('more')
+ temp = answer_node.findtext('image')
+ if temp:
+ image = temp[:-4] + '.png'
+ else:
+ image = ''
+ count += 1
+ outcard = ET.SubElement(outcards, 'card', id = str(count), bin = str(0),
+ count = str(0), shown = '', think = '')
+ question = ET.SubElement(outcard, 'question')
+ question.text = question_node.text
+ qsound = ET.SubElement(question, 'sound')
+ qsound.text = sound
+ #copy sound
+ #print 'sound', sound
+ qhint = ET.SubElement(question, 'hint')
+ qhint.text = hint
+ answer = ET.SubElement(outcard, 'answer')
+ answer.text = answer_node.text
+ aimage = ET.SubElement(answer, 'image')
+ aimage.text = image
+ #copy image
+ source = path(imagesource).joinpath(image)
+ temp = outpath.joinpath('image')
+ destination = temp.joinpath(image)
+ #print 'image to copy', image, 'from', source, 'to', destination
+ path.copy(source,destination)
+ amore = ET.SubElement(answer, 'more')
+ amore.text = more
+
+ #write outdeck
+ outdeck.save(fullpath)
+ sf.clear_text_items()
+
+def debug():
+ pass
+
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+# #print __SERVICES__.db.query("SELECT * FROM xoquiz WHERE 1")
+ sf.add_menu_dir('/library', 'Library')
+ #here we need to use path to make a list of directories
+ d = path(LIB)
+ for folder in d.dirs():
+ #print folder
+ if not folder[-3:] == 'bzr':
+ sf.add_menu_item('/library', folder.name, clickOnItem1)
+ #to remove completed decks
+ sf.add_menu_item('/library','return quiz', clickOnItem2)
+
+def close():
+ pass
diff --git a/plugins/_quizsocket.py b/plugins/_quizsocket.py
new file mode 100755
index 0000000..12e9e07
--- /dev/null
+++ b/plugins/_quizsocket.py
@@ -0,0 +1,247 @@
+#! /usr/bin/env python
+
+#!/usr/bin/env python
+
+import select
+import socket
+import sys
+import threading
+import time
+
+class Interact:
+# status = Status()
+ def __init__(self, status):
+ self.status = status
+
+ def strip(self, str, what):
+ str = str.strip()
+ while len(str) > 0 and str[-1:] == what: str = str[:-1]
+ while len(str) > 0 and str[:1] == what: str = str[1:]
+ return str
+
+ def process_input(self, s):
+ """ input comes as arg:param """
+ s = self.strip(s, "'")
+ print "processing: %s" % s
+
+ if len(s) == 0: return False
+ if ":" not in s: return False
+
+ s1 = s[:s.index(":")]
+ s2 = s[s.index(":")+1:]
+
+
+ if s1 == 'get' and s2 == 'userlist':
+ return self.status.get_user_names()
+
+ elif s1 == 'get' and s2 == 'status':
+ return self.status.get_status_line()
+
+ elif s1 == 'reguser' and len(s2) > 1:
+ i = 1
+ user_name = s2
+ while self.status.add_user(user_name) == False:
+ user_name = "%s-%i" % (s2, i)
+ i += 1
+
+ print "interact: registered user '%s'" % s2
+ return user_name
+
+class Server(threading.Thread):
+
+ def __init__(self, host, port, status):
+ threading.Thread.__init__(self)
+ self.host = host
+ self.port = port
+ self.backlog = 5
+ self.size = 1024
+ self.server = None
+ self.threads = []
+ self.interact = Interact(status)
+
+
+ def open_socket(self):
+ try:
+ self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.server.setblocking(0)
+ self.server.bind((self.host,self.port))
+ self.server.listen(5)
+ except socket.error, (value,message):
+ if self.server:
+ self.server.close()
+ print "Could not open socket: " + message
+ if message == "Address already in use":
+ self.__init__(self.host, self.port + 5)
+ else:
+ sys.exit(1)
+
+ def run(self):
+ self.open_socket()
+ input = [self.server]
+ self.running = True
+ while self.running:
+ inputready,outputready,exceptready = select.select(input,[],[],3)
+
+ for s in inputready:
+
+ if self.running and s == self.server:
+ # handle the server socket
+ print "start new client"
+ c = ClientServer(self.server.accept(), self.interact)
+ c.start()
+ self.threads.append(c)
+
+ # close all threads
+ print "closing server.run()"
+
+ def quit(self):
+ print "server quitting"
+ self.running = False
+ self.server.close()
+ for c in self.threads:
+ c.running = 0
+ c.join()
+
+class ClientServer(threading.Thread):
+ def __init__(self,(client,address), interact):
+ threading.Thread.__init__(self)
+ self.client = client
+ self.client.setblocking(0)
+ self.address = address
+ self.size = 1024
+ self.interact = interact
+
+ def run(self):
+ self.running = 1
+ while self.running:
+ try:
+ data = self.client.recv(self.size)
+ if self.running and data:
+ if self.running:
+ if data[:3] == "bye" and ":" in data:
+ self.interact.status.del_user(data[4:])
+ data_back = "bye"
+ else:
+ data_back = self.interact.process_input(repr(data))
+ else:
+ data_back = "bye"
+
+ print "sending back:", data_back
+ self.client.send("%s" % data_back)
+ else:
+ self.client.close()
+ self.running = 0
+
+ except Exception, inst:
+ # socket is in non-blocking mode, so it raises a lot of exceptions
+ if inst.args[0] == 104:
+ # connection reset by peer
+ self.interact.status.del_user(data[4:])
+ self.running = False
+
+ elif inst.args[0] != 11:
+ print type(inst)
+ print inst.args
+
+# time.sleep(0.3)
+
+class Client(threading.Thread):
+ def __init__(self, host, port, username, nonet_function=None):
+ threading.Thread.__init__(self)
+ self.host = host
+ self.port = port
+ self.username = username
+ self.nonet_function = nonet_function
+
+ def close(self):
+ print "sending bye to server"
+ try:
+ self.s.send("bye:%s" % self.username)
+ time.sleep(1)
+ except: pass
+ self.running = False
+
+ def run(self):
+ host = self.host
+ port = self.port
+ size = 1024
+ self.size = size
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ try: s.connect((host,port))
+ except:
+ if self.nonet_function != None: self.nonet_function()
+ return False
+
+ s.setblocking(0)
+
+ self.s = s
+
+ # Register Username
+ cmd = "reguser:%s" % self.username
+ self.username = self.talk(cmd)
+ print "received username: %s" % self.username
+
+ self.running = True
+ while self.running:
+ time.sleep(2)
+# print self.talk("get:userlist")
+
+ s.close()
+
+ def talk(self, s):
+# cmd = "get:userlist"
+ print "< %s" % s
+ loop = True
+ while loop:
+ try:
+ self.s.send(s)
+ loop = False
+ except Exception, inst:
+ print type(inst)
+ print inst.args
+
+# errno, errstr = list(inst.args)
+ if inst.args[0] == 11: time.sleep(0.5)
+ else:
+ print "Host has quit"
+ self.running = False
+ return False
+
+ loop = True
+ while loop:
+ try:
+ data = self.s.recv(self.size)
+ loop = False
+ return data
+ except Exception, inst:
+# print type(inst)
+# print inst.args
+ errno, errstr = list(inst.args)
+ if errno == 11: time.sleep(0.5)
+ else:
+ print "Host has quit"
+ self.running = False
+ return False
+
+
+
+if __name__ == "__main__":
+ host = "localhost"
+ port = 50056
+ username = "chris"
+
+ if len(sys.argv) > 1:
+ if sys.argv[1] == "0":
+ s = Server('', port)
+ s.start()
+
+ time.sleep(10)
+ s.quit()
+
+ print "ciao ciao"
+
+ else:
+ c = Client(host, port, username)
+ c.start()
diff --git a/plugins/dbtool.py b/plugins/dbtool.py
new file mode 100755
index 0000000..db706c4
--- /dev/null
+++ b/plugins/dbtool.py
@@ -0,0 +1,99 @@
+'''
+ List of Services:
+ - ImageQuiz.py, Line 37: "Hook-In Services"
+ - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview
+'''
+import subprocess
+from path import path
+from sugar.activity import activity
+
+__PLUGIN_NAME__ = 'dbtool'
+
+DATADIR = path(activity.get_activity_root()) / 'data'
+ACTIVITYDIR = path(activity.get_bundle_path())
+print 'directories', DATADIR, ACTIVITYDIR
+
+def dbdelete():
+ sf.clear_text_items()
+ sf.add_text_item("Clear database", (280,110))
+ cmd = "rm -rf *"
+ #cmd = "ls -l > " + DATADIR + "/iq.log"
+ subprocess.call(cmd, shell=True, cwd=DATADIR)
+ print "Clear database"
+
+
+#TABLE 'categories' ('id','cat_id', 'lang_id', 'text', 'parent_id', 'base_parent_id')
+#TABLE 'questions' ('id', 'image_id', 'sound_id', 'map', 'cat', 'subcat', 'answer_link', 'count_found', 'count notfound', 'box', 'time', 'day')
+#TABLE 'quizlinks' ('id', 'quiz_id', 'question_id')
+#TABLE 'catlinks' ('id', 'parent_id', 'child_id')
+#TABLE 'Leitner' ('id', 'question_id', etc )
+
+def dbcategories():
+ sf.clear_text_items()
+ sf.add_text_item("Show categories", (280,110))
+ cats = __SERVICES__.db.query("SELECT id, text FROM categories;")
+ count = 0
+ for cat in cats:
+ count += 1
+ catstr = str(cat[0]) + ' category: ' + str(cat[1])
+ sf.add_text_item(catstr, (180 + 50, 110 +count * 50))
+ print "Show categories"
+
+def dbquestions(count):
+ sf.clear_text_items()
+ sf.add_text_item("Show questions", (280,110))
+ #allow user to go to next page
+ sf.add_text_item("next page", (400,110), dbquestions, count + 12, True)
+ q = "SELECT id, prompt, response, image_fn, sound_fn, map, answer_link FROM questions;"
+ questions = __SERVICES__.db.query(q)
+ if count < len(questions):
+ showpage(questions, count)
+
+def showpage(questions, count):
+ line = 0
+ for i in range(12):
+ if count < len(questions):
+ question = questions[count]
+ qstr = str(question[0]) + ' prompt:' + question[1]
+ qstr = qstr + ' response:' + question[2]
+ qstr = qstr + ' img: ' + question[3]
+ qstr = qstr + ' snd: ' + question[4] + ' map ' + question[5]
+ qstr = qstr + ' answer_link: ' + question[6]
+ sf.add_text_item(qstr, (180 + 50, 160 + line * 25))
+ else:
+ return True
+ count += 1
+ line += 1
+ return False
+
+def dbquizlinks():
+ sf.clear_text_items()
+ sf.add_text_item("Show questions in quiz", (280,110))
+ q = "SELECT id, quiz_id, question_id FROM quizlink;"
+ links = __SERVICES__.db.query(q)
+ count = 0
+ for link in links:
+ count += 1
+ qstr = str(link[0]) + ' quiz: ' + str(link[1]) + ' question ' + str(link[2]) + ');'
+ sf.add_text_item(qstr, (180 + 50, 110 + count * 25))
+
+
+def debug():
+ pass
+
+def dbquestions1():
+ dbquestions(0)
+
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+ sf.add_menu_dir('/dbtool', 'DB Tool')
+ sf.add_menu_item('/dbtool', 'show categories', dbcategories)
+ sf.add_menu_item('/dbtool', 'show questions', dbquestions1)
+ sf.add_menu_item('/dbtool', 'show quizlinks', dbquizlinks)
+ #sf.add_menu_item('/dbtool', 'clear database', dbdelete)
+
+ pass
+
+def close():
+ pass
diff --git a/plugins/demoplugin.py b/plugins/demoplugin.py
new file mode 100755
index 0000000..a6f8093
--- /dev/null
+++ b/plugins/demoplugin.py
@@ -0,0 +1,25 @@
+__PLUGIN_NAME__ = 'demo plugin 1'
+
+'''
+ List of Services:
+ - ImageQuiz.py, Line 37: "Hook-In Services"
+ - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview
+'''
+
+def clickOnItem1():
+ print "Demoplugin Menu Item 1"
+
+def debug():
+ pass
+
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+# print __SERVICES__.db.query("SELECT * FROM xoquiz WHERE 1")
+# sf.add_menu_dir('/demodir', 'Demo Directory')
+# sf.add_menu_item('/', 'Demo Item 3', clickOnItem1)
+ pass
+
+def close():
+ pass \ No newline at end of file
diff --git a/plugins/ink.py b/plugins/ink.py
new file mode 100755
index 0000000..544bc78
--- /dev/null
+++ b/plugins/ink.py
@@ -0,0 +1,61 @@
+# ink.py
+#
+# B. Mayton <bmayton@cs.washington.edu>
+#
+# 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.
+#
+# -*- mode:python; tab-width:4; indent-tabs-mode:t; -*-
+
+# 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import random
+import logging
+
+class Path:
+
+ def __init__(self, inkstr=None):
+ self.__logger = logging.getLogger('Path')
+ self.points=[]
+ self.color = (0,0,1.0)
+ self.pen = 4
+ self.uid = random.randint(0, 2147483647)
+ if inkstr:
+ try:
+ i=0
+ parts = inkstr.split('#')
+ if len(parts) > 1:
+ params = parts[i].split(';')
+ self.uid = int(params[0])
+ colorparts = params[1].split(',')
+ self.color = (float(colorparts[0]),float(colorparts[1]),float(colorparts[2]))
+ self.pen = float(params[2])
+ i = i + 1
+ pathstr = parts[i]
+ pointstrs = pathstr.split(';')
+ for pointstr in pointstrs:
+ pparts = pointstr.split(',')
+ if len(pparts) == 2:
+ self.add((int(pparts[0]), int(pparts[1])))
+ except Exception, e:
+ self.__logger.debug('Could not unserialize ink string (old ink?)')
+
+ def add(self, point):
+ self.points.append(point)
+
+ def __str__(self):
+ s = str(self.uid) + ";"
+ s = s + str(self.color[0]) + "," + str(self.color[1]) + "," + str(self.color[2]) + ";"
+ s = s + str(self.pen) + "#"
+ for p in self.points:
+ s = s + str(int(p[0])) + "," + str(int(p[1])) + ";"
+ return s
diff --git a/plugins/make.py b/plugins/make.py
new file mode 100755
index 0000000..e6f6189
--- /dev/null
+++ b/plugins/make.py
@@ -0,0 +1,422 @@
+__PLUGIN_NAME__ = 'make'
+
+'''
+ List of Services:
+ - ImageQuiz.py, Line 37: "Hook-In Services"
+ - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview
+'''
+
+import os, sys
+
+import pygame
+from pygame.locals import *
+
+from sugar.activity import activity
+
+# the following line is not needed if pgu is installed
+import sys; sys.path.insert(0, "..")
+
+from pgu import gui
+
+import pygst
+pygst.require("0.10")
+import gst
+
+import ink
+
+from path import path
+
+#set up paths to for adding images and sounds
+DATAPATH = os.path.join(activity.get_activity_root(), "data")
+ACTIVITYPATH = activity.get_bundle_path()
+IMAGEPATH = os.path.join(DATAPATH, 'image')
+SOUNDPATH = os.path.join(DATAPATH, 'sound')
+
+clock = pygame.time.Clock()
+
+class CurrentQuestion:
+ id = 0
+ prompt = u''
+ response = u''
+ imgfn = u''
+ sndfn = u''
+ map = u''
+ answer_link = u''
+
+class EditDialog(gui.Dialog):
+ def __init__(self, editsave, cq):
+ max = 100
+ title = gui.Label("Edit Question")
+
+ t = gui.Table()
+ self.form = gui.Form()
+
+ t.tr()
+ print 'cq.imgfn', len(cq.imgfn.split()), len(cq.imgfn), cq.imgfn
+ imgpath = path(IMAGEPATH) / 'blank.png'
+ if len(cq.imgfn) > 0:
+ temp = path(IMAGEPATH) / cq.imgfn
+ if temp.exists():
+ imgpath = temp
+ print 'load image', imgpath.exists(), imgpath
+ self.img = pygame.image.load(imgpath)
+ t.td(gui.Image(self.img), align=-1, valign=-1, colspan = 8)
+
+ if len(cq.map) > 0:
+ self.draw_map(cq.map)
+
+ print 'table', t.getRows(), t.getColumns(), t.resize()
+
+ t.tr()
+ t.td(gui.Label(""))
+
+ t.tr()
+ t.td(gui.Label("Image: "), align = -1)
+ t.td(gui.Input(name = 'image', value = cq.imgfn, size = len(cq.imgfn) + 10), align = -1, colspan = 3)
+ imgBrowseButton = gui.Button("Browse...")
+ imgBrowseButton.connect(gui.CLICK, imgbrowse, cq)
+ t.td(imgBrowseButton)
+
+ print 'table0', t.getRows(), t.getColumns(), t.resize()
+
+ t.tr()
+ t.td(gui.Label("Clip: "), align = -1)
+ print 'clip', len(cq.sndfn), cq.sndfn
+ t.td(gui.Input(name = 'sound', value = cq.sndfn, size = len(cq.sndfn) + 10), align = -1, colspan = 3)
+ clipBrowseButton = gui.Button("Browse...")
+ clipBrowseButton.connect(gui.CLICK, clipbrowse, cq)
+ t.td(clipBrowseButton)
+ clipBrowseButton = gui.Button("Record")
+ clipBrowseButton.connect(gui.CLICK, record, cq)
+ t.td(clipBrowseButton)
+ clipBrowseButton = gui.Button("Stop")
+ clipBrowseButton.connect(gui.CLICK, stop, cq)
+ t.td(clipBrowseButton)
+ clipBrowseButton = gui.Button("Play")
+ clipBrowseButton.connect(gui.CLICK, play, cq)
+ t.td(clipBrowseButton)
+
+ print 'table1', t.getRows(), t.getColumns(), t.resize()
+
+ t.tr()
+ t.td(gui.Label("Prompt: "), align = -1)
+ sz = len(cq.prompt) + 10
+ if sz > max:
+ sz = max
+ print 'prompt', sz, len(cq.prompt), cq.prompt
+ t.td(gui.Input(name = 'prompt', value = cq.prompt, size = sz), align = -1, colspan=6)
+
+ print 'table2', t.getRows(), t.getColumns(), t.resize()
+
+ t.tr()
+ t.td(gui.Label("Response: "), align = -1)
+ sz = len(cq.response) + 10
+ if sz > max:
+ sz = max
+ print 'response',sz, len(cq.response), cq.response
+ t.td(gui.Input(name = 'response', value = cq.response, size = sz), align = -1, colspan=6)
+
+ print 'table3', t.getRows(), t.getColumns(), t.resize()
+
+ t.tr()
+ t.td(gui.Label("Answer_link: "), align = -1)
+ sz = len(cq.answer_link) + 10
+ if sz > max:
+ sz = max
+ print 'answer_link', sz, len(cq.answer_link), cq.answer_link
+ t.td(gui.Input(name = 'answer_link', value = cq.answer_link, size = sz), align = -1, colspan=7)
+
+ print 'table4', t.getRows(), t.getColumns(), t.resize()
+
+ t.tr()
+ t.td(gui.Label(""))
+
+ t.tr()
+ saveButton = gui.Button("Save")
+ saveButton.connect(gui.CLICK, editsave, cq)
+ t.td(saveButton,colspan=3)
+
+ self.t = t
+ print 'tablef', self.t.getRows(), self.t.getColumns(), self.t.resize()
+ gui.Dialog.__init__(self,title,self.t)
+
+ def draw_map(self, map):
+ color = (100,0,0)
+ pen = 4
+ pts = []
+ coords = []
+ maplst = map.split(',')
+ for pt in maplst:
+ try:
+ coords.append(int(pt))
+ except:
+ pass
+ i = 0
+ while i < len(coords):
+ pts.append((coords[i],coords[i+1]))
+ i += 2
+ pygame.draw.lines(self.img, color, True, pts, pen)
+
+def clickOnMake():
+ # Select Category
+ cat_id = -1
+ sf.clear_text_items()
+ sf.clear_question_frame(True)
+ print 'ask_category', cat_id
+ ask_category(cat_id)
+
+def ask_category(cat_id, offset_y = 0):
+ global c
+ global new_cat
+
+ i = 1
+ y = 110 + offset_y
+
+ sf.clear_text_items()
+ sf.add_text_item("Next Category:", (280,y))
+
+
+ new_cat = gui.Input(value="new", size = 20)
+ new_cat.connect(gui.ENTER,add_cat, 0)
+ c.add(new_cat, 0, 0)
+ app = sf.app()
+ app.init(c)
+
+
+ #first we need to query for categories
+ #might be nice to show them in alphabetical order
+ #when y gets too big, we should change x
+ if cat_id == -1:
+ cats = __SERVICES__.db.query("SELECT id, text FROM categories;")
+ print 'cats', len(cats), cats
+ for cat in cats:
+ print 'cat', cat[0], cat[1]
+ cat_id = cat[0]
+ category = cat[1]
+ try:
+ q = 'SELECT count(*) FROM quizlink WHERE quiz_id=%i' % cat_id
+ res1 = __SERVICES__.db.query(q)
+ except:
+ print 'query error', q
+ count = res1[0][0]
+ print 'count=', count
+ if count > 0:
+ # this is a quiz, display in green
+ y += 50
+ sf.add_text_item("%s (%s)" % (category, count), (300,y), ask_question, cat_id)
+ else:
+ # this is a category - get number of children
+ q = 'SELECT count(*) FROM catlink WHERE parent_id=%i' % cat_id
+ res2 = __SERVICES__.db.query(q)
+ count = res2[0][0]
+ y += 50
+ sf.add_text_item("%s (%s)" % (category, count), (300,y), ask_category, cat_id)
+ i += 1
+ else:
+ #we need to pass a cat_id from the user's selection
+ print 'second level selection not implemented - must select quiz'
+
+def add_cat(params):
+ global new_cat
+ global CATID
+ #add cat to db with new_cat.value as text
+ CATID= __SERVICES__.db.add_cat(new_cat.value)
+ #create empty question to pass to startedit
+ cq = CurrentQuestion()
+ cq.id = -1
+ cq.prompt = ""
+ cq.response = ""
+ cq.imgfn = ""
+ cq.sndfn = ""
+ cq.map = ""
+ cq.answer_link = ""
+ startedit(cq)
+
+def ask_question(cat_id, offset_y = 0):
+ global CATID
+
+ print 'ask_question', cat_id, offset_y
+
+ i = 1
+ y = 110 + offset_y
+ CATID = cat_id
+
+
+ sf.clear_text_items()
+ sf.add_text_item("Select question:", (280,y))
+
+ #we need to query for questions
+ #when y gets too big, we should change x
+ try:
+ q = 'SELECT question_id FROM quizlink WHERE quiz_id=%i' % cat_id
+ print 'query=', q
+ res = __SERVICES__.db.query(q)
+ except:
+ print 'failure in query', q
+ print 'res', len(res), res
+
+ count = 0
+ for question in res:
+ count += 1
+ try:
+ q = 'SELECT prompt, response, image_fn, sound_fn, map, answer_link FROM questions WHERE id = %i' % question[0]
+ print 'query=', q
+ res2 = __SERVICES__.db.query(q)
+ except:
+ print 'error in query', q
+ print 'res2', len(res2), res2
+ res1 = res2[0]
+ cq = CurrentQuestion()
+ cq.id = question[0]
+ cq.prompt = res1[0]
+ cq.response = res1[1]
+ cq.imgfn = res1[2]
+ cq.sndfn = res1[3]
+ cq.map = res1[4]
+ cq.answer_link = res1[5]
+ if len(cq.imgfn) > 0:
+ ipath = path(IMAGEPATH) /cq.imgfn
+ if len(cq.imgfn)>0 and ipath.exists():
+ y += 50
+ image, xy = sf.image_load(ipath)
+ sf.display_surface(pygame.transform.scale(image,(128,128)), (300,y))
+ y += 128
+ if len(cq.prompt.strip()) > 0:
+ sf.add_text_item(cq.prompt,(300,y),startedit, cq)
+ else:
+ sf.add_text_item(cq.imgfn,(300,y),startedit, cq)
+ elif len(cq.prompt.strip()) > 0:
+ y += 50
+ sf.add_text_item(cq.prompt,(300,y),startedit, cq)
+ else:
+ y += 50
+ sf.add_text_item(cq.sndfn,(300,y), startedit, cq)
+
+def startedit(question, offset_y = 0):
+ global edit_d
+
+ cq = question
+
+ #if len(cq.imgfn) > 0 and path(cq.imgfn).exists:
+ #display cq.imgfn
+ #else:
+ #display 'no image' image
+ #display cq.imgfn entry as caption
+ #display audio controls (right of sound entry)
+ #if not (len(cq.imgfn) > 0 and path(cq.imgfn).exists()):
+ #grey 'play' control
+ #if len(cq.sndfn) > 0 and path(cq.sndfn) exists():
+ #display audio controls (to right of sound entry)
+ #if sound recorded, ungrey 'play' control
+ #if sound path changed, reset 'play' control depending on whether path exists
+
+
+ i = 1
+ print 'i=', i
+ y = 110 + offset_y
+ print 'y=', y
+
+ print 'clear text items'
+ sf.clear_text_items()
+
+ edit_d = EditDialog(editsave, cq)
+ edit_d.open()
+
+def editsave(cq):
+ global CATID
+ global edit_d
+
+ form = edit_d.form
+ cq.prompt = form['prompt'].value
+ cq.response = form['response'].value
+ cq.imgfn = form['image'].value
+ cq.sndfn = form['sound'].value
+ cq.answer_link = form['answer_link'].value
+
+ #if a new question, it needs to be inserted into database
+ #insert question
+ #add quizlink with quiz id and question id
+
+ #insert updated question into database
+ q = "UPDATE questions SET prompt='%s' WHERE id=%i" % (cq.prompt, cq.id)
+ updatedb(q)
+ q = "UPDATE questions SET response = '%s' WHERE id = %i" % (cq.response, cq.id)
+ updatedb(q)
+ q = "UPDATE questions SET image_fn = '%s' WHERE id = %i" % (cq.imgfn, cq.id)
+ updatedb(q)
+ q = "UPDATE questions SET sound_fn = '%s' WHERE id = %i" % (cq.sndfn, cq.id)
+ updatedb(q)
+ q = "UPDATE questions SET map = '%s' WHERE id = %i" % (cq.map, cq.id)
+ updatedb(q)
+ q = "UPDATE questions SET answer_link = '%s' WHERE id = %i" % (cq.answer_link, cq.id)
+ updatedb(q)
+
+ edit_d.close()
+ print 'edit_d closed', edit_d.t.getRows(), edit_d.t.getColumns()
+ edit_d.t.clear()
+ print 'edit_d closed and table cleared', edit_d.t.getRows(), edit_d.t.getColumns()
+ cat_id = CATID
+ ask_question(cat_id)
+
+def updatedb(q):
+ print 'updatedb'
+ __SERVICES__.db.commit(q)
+
+def imgbrowse(cq):
+ print 'imgbrowse not implemented'
+
+def clipbrowse(cq):
+ print 'clipbrowse not implemented'
+
+def record(cq):
+ global player
+ global fileout
+ fileout.set_property("location", os.path.join(SOUNDPATH, cq.sndfn))
+ print player.get_state()
+ player.set_state(gst.STATE_PLAYING)
+
+def stop(cq):
+ player.set_state(gst.STATE_READY)
+ pygame.mixer.stop()
+
+def play(cq):
+ sound = sf.sound_load(os.path.join(SOUNDPATH, cq.sndfn))
+ sound.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+
+def debug():
+ pass
+
+def load():
+ global sf
+ global player
+ global fileout
+ global c
+ sf = __SERVICES__.frontend
+ #print __SERVICES__.db.query("SELECT text FROM categories")
+ #sf.add_menu_dir('/demodir', 'Demo Directory')
+ sf.add_menu_item('/', 'Make', clickOnMake)
+ #initialize audio record pipeline
+ player = gst.Pipeline("player")
+ source = gst.element_factory_make("alsasrc", "alsa-source")
+ player.add(source)
+ convert = gst.element_factory_make("audioconvert", "converter")
+ player.add(convert)
+ enc = gst.element_factory_make("vorbisenc", "vorbis-encoder")
+ player.add(enc)
+ create = gst.element_factory_make("oggmux", "ogg-create")
+ player.add(create)
+ fileout = gst.element_factory_make("filesink", "sink")
+ fileout.set_property("location", "test.ogg")
+ player.add(fileout)
+ gst.element_link_many(source, convert, enc, create, fileout)
+
+
+ #intialize gui
+ c = gui.Container(width = 400, height = 600)
+ app = sf.get_app()
+ app.init(c)
+
+def close():
+ pass
diff --git a/plugins/multi_player.py b/plugins/multi_player.py
new file mode 100755
index 0000000..68acc63
--- /dev/null
+++ b/plugins/multi_player.py
@@ -0,0 +1,552 @@
+import pygame
+from pygame import *
+from layout import *
+from frontend import hex2rgb
+import threading
+import time
+import traceback
+
+__PLUGIN_NAME__ = 'multi player'
+#import single_player
+
+class Status:
+ """ edited by the hosts """
+
+ game_state = '0' # 0=open_before_game, 1=..?
+
+ status_line = 'statusline'
+ clients = []
+
+ class Client:
+ def __init__(self, name, status=''):
+ self.name = name
+ self.status = status
+
+ def set_status_line(self, s1, s2):
+ return True
+
+ def get_status_line(self):
+ self.status_line = "%s;;%s" % (self.game_state, self.get_user_names(";"))
+ return self.status_line
+
+ def get_user_names(self, split_str=';;'):
+ usernames = []
+ for c in self.clients:
+ usernames.append(c.name)
+ return split_str.join(usernames)
+
+ def add_user(self, user_name):
+ for c in self.clients:
+ if c.name.upper() == user_name.upper(): return False
+
+ new_client = self.Client(user_name)
+ self.clients.append(new_client)
+ return True
+
+ def del_user(self, user_name):
+ print "deleting user %s" % user_name
+ new_clients = []
+ for c in self.clients:
+ if c.name.upper() != user_name.upper():
+ new_clients.append(c)
+
+ self.clients = new_clients
+ return True
+
+
+def show_answer():
+ A.display_answer()
+
+def finished():
+ sf.refresh()
+ sf.add_text_item("You have finished this category!", (180, 80))
+ sf.add_text_item("Won: %i" % Q.count_won, (220, 120))
+ sf.add_text_item("Lost: %i" % Q.count_lost, (220, 160))
+ pygame.display.update()
+ ask_category(130)
+
+
+def startgame(db_cat_id):
+ print "* Starting Game, Category:", db_cat_id
+ print "** Loading Questions"
+
+ Q.count_won = 0
+ Q.count_won = 0
+ A.reset_points()
+
+ Q.load_questions(db_cat_id)
+ display_points()
+
+ sf.clear_text_items()
+ next_question(0)
+
+
+def ask_category(offset_y = 0, offset_x = 0, cat_id=0):
+ Q.load(__SERVICES__.locale.lang_id, __SERVICES__.locale.lang_name)
+
+# sf.clear_text_items()
+
+ i = 1
+ y = 110 + offset_y
+ x = 340 + offset_x
+
+ sf.add_text_item("Category:", (x,y))
+ y += 50
+ x += 50
+ sf.add_text_item('All', (x,y), game_host.set_category, 0, True)
+# global game_host
+ for q in Q.categories:
+ y += 50
+ sf.add_text_item("%s (%s)" % (q[1], q[2]), (x,y), game_host.set_category, q[0], True)
+ i += 1
+
+ display_points()
+
+def display_points():
+ format = TextFormat(None, 30)
+ max_q = len(Q.questions)
+
+# sf.display_tooltip("%i" % (Q.count_won), (480, 14), format)
+# sf.display_tooltip("%i" % Q.count_lost, (480, 42), format)
+
+ if max_q == 0:
+ sf.display_line(0, True)
+ sf.display_line(0, False)
+ else:
+ sf.display_line(100 * Q.count_won / max_q, True)
+ sf.display_line(100 * Q.count_lost / max_q, False)
+
+def click_on_next_question():
+ next_question(0)
+
+def next_question(params):
+ print "* Next Question"
+
+ # Select Category
+ if Q._cat_id == -1:
+ ask_category()
+ return 1
+
+
+ # Pick only locale language's categories
+ if Q.question_pick():
+ __SERVICES__.frontend.question_display(Q.question)
+ Q.in_question = True
+
+ pass
+ else:
+ # game / cat finished!
+ in_question = False
+ print "finished"
+ Q._cat_id = -1
+ finished()
+ pass
+
+def spot_found(params):
+ if Q.in_question:
+ print "found"
+ A.display_icon("found")
+ A.play_sound(1)
+ Q.in_question = False
+ Q.count_won += 1
+ display_points()
+ show_answer()
+
+def spot_not_found(params):
+ print params
+ if Q.in_question:
+ print "not found"
+ A.display_icon("not-found")
+ A.play_sound(0)
+ Q.in_question = False
+ Q.count_lost += 1
+ display_points()
+ show_answer()
+ return "ok"
+
+
+
+
+class getStatus(threading.Thread):
+ running = True
+
+ def set(self, client_talk, status_call_function, abort_function=None):
+ self.talk = client_talk
+ self.status_call_function = status_call_function
+ self.abort_function = abort_function
+ self.running = True
+
+ def run(self):
+ while self.running:
+ time.sleep(2)
+ try:
+ self.status = self.talk('get:status')
+ self.status_call_function(self.status)
+ except:
+ traceback.print_exc()
+ print "getstatus aborted"
+ self.running = False
+ if self.abort_function != None: self.abort_function()
+
+
+class waitForUsersToJoin(threading.Thread):
+ running = True
+ last_users = ''
+
+ def set(self, talk, waittext_id, textid_connectedto=None):
+ self.talk = talk
+ self.waittext_id = waittext_id
+ self.textid_connectedto = textid_connectedto
+
+ def display_server_quit(self):
+ self.running = False
+
+ def run(self):
+ format = TextFormat()
+ font = pygame.font.Font(None, format.size)
+ bg = pygame.Surface((560, 30))
+
+ while self.running:
+ time.sleep(2)
+
+ users = self.talk("get:userlist")
+ if users == False: return
+
+ self.users = users.split(";;")
+ users = users.replace(";;", ", ")
+
+ print self.users
+ print users
+
+ text = font.render("> %s" % users, 1, hex2rgb(format.color))
+ textpos = text.get_rect()
+
+ bg.fill(hex2rgb(Layout().background))
+ bg.blit(text, (0,0))
+ sf.display_surface(bg, (48, 610))
+
+ pygame.display.update((48, 610, 560, 30))
+
+class DrawTimer:
+ def __init__(self):
+ self.bg = pygame.Surface((100, 60))
+
+ def draw(self, time):
+ self.bg.fill(hex2rgb(Layout().Question.background))
+
+ format = TextFormat(None, 46, None)
+ font = pygame.font.Font(None, format.size)
+ text = font.render("%i sec" % time, 1, hex2rgb(format.color))
+
+ self.bg.blit(text, (0,0))
+ sf.display_surface(self.bg, (150, 440))
+ pygame.display.update()
+
+class MultiPlayerHost:
+ category = 0
+ game_time = 0
+ textid_timer = -1
+ status = Status()
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.socket = __import__("plugins/_quizsocket")
+
+ print "starting server"
+ self.server = self.socket.Server('', port, self.status)
+ self.server.start()
+
+ def close(self):
+ self.client.running = False
+ self.waituser.running = False
+ self.server.quit()
+
+ def update_status(self, params):
+ print params
+
+ def load(self):
+ print "Creating a new game"
+ sf.clear_question_frame(True)
+
+ # Display some text
+ format = TextFormat(None, 40)
+ sf.add_text_item("New Multiplayer Game", (340, 45), None, None, False, format)
+ waittext_id = sf.add_text_item("Waiting for users to join...", (40, 570))
+
+ # let's spawn our own client now
+ self.client = self.socket.Client(self.host, self.port, Layout().user_name)
+ self.client.start()
+
+ # Display "Waiting for users to join..."
+ self.waituser = waitForUsersToJoin()
+ self.waituser.set(self.client.talk, waittext_id, None)
+ self.waituser.start()
+
+ # Constantly Get Status Updates from the Host
+# self.getstatus = getStatus()
+# self.getstatus.set(self.Spawner.client_talk, self.update_status, self.abort_net)
+# self.getstatus.start()
+
+ # Display Start Button
+ format = TextFormat(None, None, Colors().green)
+ sf.add_text_item("Start", (Layout().Question.x + Layout().Question.width - 150, 570), None, None, True, format)
+
+ # Display Categories
+ ask_category()
+ self.set_category(0)
+ pygame.display.update()
+
+ self.hookid_click = sf.add_event_hook("onclick", self.click)
+
+
+ # Draw Timer Option
+ img, rect = sf.image_load("images/xclock.png")
+ sf.display_surface(img, (40, 400))
+ self.draw_timer = DrawTimer()
+ self.click_timer()
+
+
+ def click(self, params):
+ x, y = list(params)
+ if x > 40 and x < (40 + 96) and y > 400 and y < (400 + 96):
+ # Click on Clock
+ self.click_timer()
+
+ def click_timer(self):
+ if self.game_time == 30:
+ self.game_time = 10
+ elif self.game_time == 20:
+ self.game_time = 30
+ elif self.game_time == 10:
+ self.game_time = 20
+ elif self.game_time == 0:
+ self.game_time = 10
+
+# self.Spawner.master_set_status("time:%i" % self.game_time)
+ self.draw_timer.draw(self.game_time)
+
+ def set_category(self, param):
+ cat_id = int(param)
+ self.category = cat_id
+
+ cat_name = 'All'
+ if cat_id > 0:
+ q = "SELECT text FROM categories WHERE cat_id=%i AND lang_id=%s" % (cat_id, __SERVICES__.locale.lang_id)
+ res = __SERVICES__.db.query(q)
+ print res
+ cat_name = res[0][0]
+
+# self.Spawner.master_set_status(u"cat:%s" % cat_name)
+
+ bg = pygame.Surface((360, 30))
+ bg.fill(hex2rgb(Layout().background))
+
+ format = TextFormat()
+ font = pygame.font.Font(None, format.size)
+
+ text = font.render("%s" % cat_name, 1, hex2rgb(format.color))
+ bg.blit(text, (0,0))
+ sf.display_surface(bg, (480, 115))
+ pygame.display.update()
+
+class MultiPlayerClient:
+ connected = False
+ game_time = 0
+ cat_name = u''
+
+ socket = None
+ waittext_id = None
+
+ format = TextFormat()
+ font = pygame.font.Font(None, format.size)
+ bg = pygame.Surface((560, 30))
+
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.connected = False
+ if self.socket == None: self.socket = __import__("plugins/_quizsocket")
+
+ def close(self):
+ self.getstatus.running = False
+ self.client.close()
+ self.connected = False
+
+ def load(self):
+ print "Joining a multiplayer game"
+
+ sf.clear_question_frame(True)
+
+ format = TextFormat(None, 40)
+ sf.add_text_item("Joining a Multiplayer Game", (340, 45), None, None, False, format)
+ pygame.display.update()
+
+# print self.connected
+ if self.connected == False:
+ host = sf.ask("host")
+ sf.refresh()
+ print "host: ", host
+ self.host = host
+
+ if len(host) == 0:
+ return False
+
+# if host == "l": host = "localhost"
+ print "starting client"
+ textid_connectedto = sf.add_text_item("[connecting to %s]" % host, (340, 80))
+ self.textid_connectedto = textid_connectedto
+
+ self.client = self.socket.Client(self.host, self.port, Layout().user_name, self.nonet_function)
+ self.client.start()
+
+ self.getstatus = getStatus()
+ self.getstatus.set(self.client.talk, self.update_status, self.nonet_function)
+ self.getstatus.start()
+
+ def display_catname(self):
+ bg = pygame.Surface((300, 60))
+ bg.fill(hex2rgb(Layout().Question.background))
+
+ format = TextFormat(None, 40)
+ font = pygame.font.Font(None, format.size)
+ text = font.render("Category: %s" % self.cat_name, 1, hex2rgb(format.color))
+
+ bg.blit(text, (0,0))
+ sf.display_surface(bg, (350, 190))
+ pygame.display.update()
+
+ def update_status(self, params):
+ self.status = params
+ print "STATUS UPDATE:",params
+
+ if params == False or len(params) == 0 or params == None or params == "None":
+ self.close()
+ return
+
+ if self.connected == False:
+ self.connection_ok()
+
+ n = params.split(";;")
+
+ users = n[1].replace(";", ", ")
+
+ text = self.font.render("> %s" % users, 1, hex2rgb(self.format.color))
+ textpos = text.get_rect()
+
+ self.bg.fill(hex2rgb(Layout().background))
+ self.bg.blit(text, (0,0))
+ sf.display_surface(self.bg, (48, 610))
+
+ pygame.display.update((48, 610, 560, 30))
+ print params
+
+# for n in params:
+# m = n.split(":")
+# if m[0] == "time":
+# if int(m[1]) != self.game_time:
+# self.game_time = int(m[1])
+# self.draw_timer.draw(self.game_time)
+#
+# if m[0] == "cat":
+# if m[1] != self.cat_name:
+# self.cat_name = m[1]
+# self.display_catname()
+
+
+ def connection_ok(self):
+ self.connected = True
+ sf.del_text_item(self.textid_connectedto)
+
+ self.textid_connectedto = sf.add_text_item("[connected to %s]" % self.host, (340, 80))
+ self.waittext_id = sf.add_text_item("Waiting for users to join...", (40, 570))
+
+ pygame.display.update()
+
+ # Draw Timer Option
+ img, rect = sf.image_load("images/xclock.png")
+ sf.display_surface(img, (40, 400))
+ self.draw_timer = DrawTimer()
+
+ def nonet_function(self):
+ print "client net aborted, quitting MultiPlayer-Client"
+
+ self.running = False
+
+ if self.waittext_id != None: sf.del_text_item(self.waittext_id)
+ if self.textid_connectedto != None: sf.del_text_item(self.textid_connectedto)
+
+ sf.add_text_item("Server quit the game", (340, 84))
+
+ # clear player
+ bg = pygame.Surface((560, 30))
+ bg.fill(hex2rgb(Layout().background))
+ sf.display_surface(bg, (48, 610))
+ pygame.display.update()
+
+ self.close()
+
+ global game_client
+ game_client = None
+
+
+
+
+def click_create():
+ global game_client
+ global game_host
+
+ if game_client == None and game_host == None:
+ game_host = MultiPlayerHost("", Layout().port)
+ sf.clear_text_items(False)
+ game_host.load()
+
+
+def click_join():
+ global game_client
+ global game_host
+
+ if game_client == None and game_host == None:
+ game_client = ''
+ game_client = MultiPlayerClient("localhost", Layout().port)
+
+ sf.clear_text_items()
+
+ res = game_client.load()
+ if res == False:
+ game_client = None
+
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+ sp = __import__("plugins/single_player")
+ sp.sf = sf
+
+ global Q
+ Q = sp.Questions()
+
+ global A
+ A = sp.Answer()
+
+ global game_host
+ game_host = None
+
+ global game_client
+ game_client = None
+
+ sf.add_menu_dir('/multi-player', 'Multiplayer', 1)
+ sf.add_menu_item('/multi-player', 'Create Game', click_create, 1)
+ sf.add_menu_item('/multi-player', 'Join Game', click_join, 2)
+
+ __SERVICES__.add_service("next_question", next_question)
+ __SERVICES__.add_service("spot_found", spot_found)
+ __SERVICES__.add_service("spot_not_found", spot_not_found)
+
+def close():
+ global game_host
+ global game_client
+
+ if game_host != None: game_host.close()
+ if game_client != None: game_client.close()
+
diff --git a/plugins/path.py b/plugins/path.py
new file mode 100755
index 0000000..01c2c04
--- /dev/null
+++ b/plugins/path.py
@@ -0,0 +1,971 @@
+""" path.py - An object representing a path to a file or directory.
+
+Example:
+
+from path import path
+d = path('/home/guido/bin')
+for f in d.files('*.py'):
+ f.chmod(0755)
+
+This module requires Python 2.2 or later.
+
+
+URL: http://www.jorendorff.com/articles/python/path
+Author: Jason Orendorff <jason.orendorff\x40gmail\x2ecom> (and others - see the url!)
+Date: 9 Mar 2007
+"""
+
+
+# TODO
+# - Tree-walking functions don't avoid symlink loops. Matt Harrison
+# sent me a patch for this.
+# - Bug in write_text(). It doesn't support Universal newline mode.
+# - Better error message in listdir() when self isn't a
+# directory. (On Windows, the error message really sucks.)
+# - Make sure everything has a good docstring.
+# - Add methods for regex find and replace.
+# - guess_content_type() method?
+# - Perhaps support arguments to touch().
+
+from __future__ import generators
+
+import sys, warnings, os, fnmatch, glob, shutil, codecs, md5
+
+__version__ = '2.2'
+__all__ = ['path']
+
+# Platform-specific support for path.owner
+if os.name == 'nt':
+ try:
+ import win32security
+ except ImportError:
+ win32security = None
+else:
+ try:
+ import pwd
+ except ImportError:
+ pwd = None
+
+# Pre-2.3 support. Are unicode filenames supported?
+_base = str
+_getcwd = os.getcwd
+try:
+ if os.path.supports_unicode_filenames:
+ _base = unicode
+ _getcwd = os.getcwdu
+except AttributeError:
+ pass
+
+# Pre-2.3 workaround for booleans
+try:
+ True, False
+except NameError:
+ True, False = 1, 0
+
+# Pre-2.3 workaround for basestring.
+try:
+ basestring
+except NameError:
+ basestring = (str, unicode)
+
+# Universal newline support
+_textmode = 'r'
+if hasattr(file, 'newlines'):
+ _textmode = 'U'
+
+
+class TreeWalkWarning(Warning):
+ pass
+
+class path(_base):
+ """ Represents a filesystem path.
+
+ For documentation on individual methods, consult their
+ counterparts in os.path.
+ """
+
+ # --- Special Python methods.
+
+ def __repr__(self):
+ return 'path(%s)' % _base.__repr__(self)
+
+ # Adding a path and a string yields a path.
+ def __add__(self, more):
+ try:
+ resultStr = _base.__add__(self, more)
+ except TypeError: #Python bug
+ resultStr = NotImplemented
+ if resultStr is NotImplemented:
+ return resultStr
+ return self.__class__(resultStr)
+
+ def __radd__(self, other):
+ if isinstance(other, basestring):
+ return self.__class__(other.__add__(self))
+ else:
+ return NotImplemented
+
+ # The / operator joins paths.
+ def __div__(self, rel):
+ """ fp.__div__(rel) == fp / rel == fp.joinpath(rel)
+
+ Join two path components, adding a separator character if
+ needed.
+ """
+ return self.__class__(os.path.join(self, rel))
+
+ # Make the / operator work even when true division is enabled.
+ __truediv__ = __div__
+
+ def getcwd(cls):
+ """ Return the current working directory as a path object. """
+ return cls(_getcwd())
+ getcwd = classmethod(getcwd)
+
+
+ # --- Operations on path strings.
+
+ isabs = os.path.isabs
+ def abspath(self): return self.__class__(os.path.abspath(self))
+ def normcase(self): return self.__class__(os.path.normcase(self))
+ def normpath(self): return self.__class__(os.path.normpath(self))
+ def realpath(self): return self.__class__(os.path.realpath(self))
+ def expanduser(self): return self.__class__(os.path.expanduser(self))
+ def expandvars(self): return self.__class__(os.path.expandvars(self))
+ def dirname(self): return self.__class__(os.path.dirname(self))
+ basename = os.path.basename
+
+ def expand(self):
+ """ Clean up a filename by calling expandvars(),
+ expanduser(), and normpath() on it.
+
+ This is commonly everything needed to clean up a filename
+ read from a configuration file, for example.
+ """
+ return self.expandvars().expanduser().normpath()
+
+ def _get_namebase(self):
+ base, ext = os.path.splitext(self.name)
+ return base
+
+ def _get_ext(self):
+ f, ext = os.path.splitext(_base(self))
+ return ext
+
+ def _get_drive(self):
+ drive, r = os.path.splitdrive(self)
+ return self.__class__(drive)
+
+ parent = property(
+ dirname, None, None,
+ """ This path's parent directory, as a new path object.
+
+ For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
+ """)
+
+ name = property(
+ basename, None, None,
+ """ The name of this file or directory without the full path.
+
+ For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
+ """)
+
+ namebase = property(
+ _get_namebase, None, None,
+ """ The same as path.name, but with one file extension stripped off.
+
+ For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
+ but path('/home/guido/python.tar.gz').namebase == 'python.tar'
+ """)
+
+ ext = property(
+ _get_ext, None, None,
+ """ The file extension, for example '.py'. """)
+
+ drive = property(
+ _get_drive, None, None,
+ """ The drive specifier, for example 'C:'.
+ This is always empty on systems that don't use drive specifiers.
+ """)
+
+ def splitpath(self):
+ """ p.splitpath() -> Return (p.parent, p.name). """
+ parent, child = os.path.split(self)
+ return self.__class__(parent), child
+
+ def splitdrive(self):
+ """ p.splitdrive() -> Return (p.drive, <the rest of p>).
+
+ Split the drive specifier from this path. If there is
+ no drive specifier, p.drive is empty, so the return value
+ is simply (path(''), p). This is always the case on Unix.
+ """
+ drive, rel = os.path.splitdrive(self)
+ return self.__class__(drive), rel
+
+ def splitext(self):
+ """ p.splitext() -> Return (p.stripext(), p.ext).
+
+ Split the filename extension from this path and return
+ the two parts. Either part may be empty.
+
+ The extension is everything from '.' to the end of the
+ last path segment. This has the property that if
+ (a, b) == p.splitext(), then a + b == p.
+ """
+ filename, ext = os.path.splitext(self)
+ return self.__class__(filename), ext
+
+ def stripext(self):
+ """ p.stripext() -> Remove one file extension from the path.
+
+ For example, path('/home/guido/python.tar.gz').stripext()
+ returns path('/home/guido/python.tar').
+ """
+ return self.splitext()[0]
+
+ if hasattr(os.path, 'splitunc'):
+ def splitunc(self):
+ unc, rest = os.path.splitunc(self)
+ return self.__class__(unc), rest
+
+ def _get_uncshare(self):
+ unc, r = os.path.splitunc(self)
+ return self.__class__(unc)
+
+ uncshare = property(
+ _get_uncshare, None, None,
+ """ The UNC mount point for this path.
+ This is empty for paths on local drives. """)
+
+ def joinpath(self, *args):
+ """ Join two or more path components, adding a separator
+ character (os.sep) if needed. Returns a new path
+ object.
+ """
+ return self.__class__(os.path.join(self, *args))
+
+ def splitall(self):
+ r""" Return a list of the path components in this path.
+
+ The first item in the list will be a path. Its value will be
+ either os.curdir, os.pardir, empty, or the root directory of
+ this path (for example, '/' or 'C:\\'). The other items in
+ the list will be strings.
+
+ path.path.joinpath(*result) will yield the original path.
+ """
+ parts = []
+ loc = self
+ while loc != os.curdir and loc != os.pardir:
+ prev = loc
+ loc, child = prev.splitpath()
+ if loc == prev:
+ break
+ parts.append(child)
+ parts.append(loc)
+ parts.reverse()
+ return parts
+
+ def relpath(self):
+ """ Return this path as a relative path,
+ based from the current working directory.
+ """
+ cwd = self.__class__(os.getcwd())
+ return cwd.relpathto(self)
+
+ def relpathto(self, dest):
+ """ Return a relative path from self to dest.
+
+ If there is no relative path from self to dest, for example if
+ they reside on different drives in Windows, then this returns
+ dest.abspath().
+ """
+ origin = self.abspath()
+ dest = self.__class__(dest).abspath()
+
+ orig_list = origin.normcase().splitall()
+ # Don't normcase dest! We want to preserve the case.
+ dest_list = dest.splitall()
+
+ if orig_list[0] != os.path.normcase(dest_list[0]):
+ # Can't get here from there.
+ return dest
+
+ # Find the location where the two paths start to differ.
+ i = 0
+ for start_seg, dest_seg in zip(orig_list, dest_list):
+ if start_seg != os.path.normcase(dest_seg):
+ break
+ i += 1
+
+ # Now i is the point where the two paths diverge.
+ # Need a certain number of "os.pardir"s to work up
+ # from the origin to the point of divergence.
+ segments = [os.pardir] * (len(orig_list) - i)
+ # Need to add the diverging part of dest_list.
+ segments += dest_list[i:]
+ if len(segments) == 0:
+ # If they happen to be identical, use os.curdir.
+ relpath = os.curdir
+ else:
+ relpath = os.path.join(*segments)
+ return self.__class__(relpath)
+
+ # --- Listing, searching, walking, and matching
+
+ def listdir(self, pattern=None):
+ """ D.listdir() -> List of items in this directory.
+
+ Use D.files() or D.dirs() instead if you want a listing
+ of just files or just subdirectories.
+
+ The elements of the list are path objects.
+
+ With the optional 'pattern' argument, this only lists
+ items whose names match the given pattern.
+ """
+ names = os.listdir(self)
+ if pattern is not None:
+ names = fnmatch.filter(names, pattern)
+ return [self / child for child in names]
+
+ def dirs(self, pattern=None):
+ """ D.dirs() -> List of this directory's subdirectories.
+
+ The elements of the list are path objects.
+ This does not walk recursively into subdirectories
+ (but see path.walkdirs).
+
+ With the optional 'pattern' argument, this only lists
+ directories whose names match the given pattern. For
+ example, d.dirs('build-*').
+ """
+ return [p for p in self.listdir(pattern) if p.isdir()]
+
+ def files(self, pattern=None):
+ """ D.files() -> List of the files in this directory.
+
+ The elements of the list are path objects.
+ This does not walk into subdirectories (see path.walkfiles).
+
+ With the optional 'pattern' argument, this only lists files
+ whose names match the given pattern. For example,
+ d.files('*.pyc').
+ """
+
+ return [p for p in self.listdir(pattern) if p.isfile()]
+
+ def walk(self, pattern=None, errors='strict'):
+ """ D.walk() -> iterator over files and subdirs, recursively.
+
+ The iterator yields path objects naming each child item of
+ this directory and its descendants. This requires that
+ D.isdir().
+
+ This performs a depth-first traversal of the directory tree.
+ Each directory is returned just before all its children.
+
+ The errors= keyword argument controls behavior when an
+ error occurs. The default is 'strict', which causes an
+ exception. The other allowed values are 'warn', which
+ reports the error via warnings.warn(), and 'ignore'.
+ """
+ if errors not in ('strict', 'warn', 'ignore'):
+ raise ValueError("invalid errors parameter")
+
+ try:
+ childList = self.listdir()
+ except Exception:
+ if errors == 'ignore':
+ return
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to list directory '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ return
+ else:
+ raise
+
+ for child in childList:
+ if pattern is None or child.fnmatch(pattern):
+ yield child
+ try:
+ isdir = child.isdir()
+ except Exception:
+ if errors == 'ignore':
+ isdir = False
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to access '%s': %s"
+ % (child, sys.exc_info()[1]),
+ TreeWalkWarning)
+ isdir = False
+ else:
+ raise
+
+ if isdir:
+ for item in child.walk(pattern, errors):
+ yield item
+
+ def walkdirs(self, pattern=None, errors='strict'):
+ """ D.walkdirs() -> iterator over subdirs, recursively.
+
+ With the optional 'pattern' argument, this yields only
+ directories whose names match the given pattern. For
+ example, mydir.walkdirs('*test') yields only directories
+ with names ending in 'test'.
+
+ The errors= keyword argument controls behavior when an
+ error occurs. The default is 'strict', which causes an
+ exception. The other allowed values are 'warn', which
+ reports the error via warnings.warn(), and 'ignore'.
+ """
+ if errors not in ('strict', 'warn', 'ignore'):
+ raise ValueError("invalid errors parameter")
+
+ try:
+ dirs = self.dirs()
+ except Exception:
+ if errors == 'ignore':
+ return
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to list directory '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ return
+ else:
+ raise
+
+ for child in dirs:
+ if pattern is None or child.fnmatch(pattern):
+ yield child
+ for subsubdir in child.walkdirs(pattern, errors):
+ yield subsubdir
+
+ def walkfiles(self, pattern=None, errors='strict'):
+ """ D.walkfiles() -> iterator over files in D, recursively.
+
+ The optional argument, pattern, limits the results to files
+ with names that match the pattern. For example,
+ mydir.walkfiles('*.tmp') yields only files with the .tmp
+ extension.
+ """
+ if errors not in ('strict', 'warn', 'ignore'):
+ raise ValueError("invalid errors parameter")
+
+ try:
+ childList = self.listdir()
+ except Exception:
+ if errors == 'ignore':
+ return
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to list directory '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ return
+ else:
+ raise
+
+ for child in childList:
+ try:
+ isfile = child.isfile()
+ isdir = not isfile and child.isdir()
+ except:
+ if errors == 'ignore':
+ continue
+ elif errors == 'warn':
+ warnings.warn(
+ "Unable to access '%s': %s"
+ % (self, sys.exc_info()[1]),
+ TreeWalkWarning)
+ continue
+ else:
+ raise
+
+ if isfile:
+ if pattern is None or child.fnmatch(pattern):
+ yield child
+ elif isdir:
+ for f in child.walkfiles(pattern, errors):
+ yield f
+
+ def fnmatch(self, pattern):
+ """ Return True if self.name matches the given pattern.
+
+ pattern - A filename pattern with wildcards,
+ for example '*.py'.
+ """
+ return fnmatch.fnmatch(self.name, pattern)
+
+ def glob(self, pattern):
+ """ Return a list of path objects that match the pattern.
+
+ pattern - a path relative to this directory, with wildcards.
+
+ For example, path('/users').glob('*/bin/*') returns a list
+ of all the files users have in their bin directories.
+ """
+ cls = self.__class__
+ return [cls(s) for s in glob.glob(_base(self / pattern))]
+
+
+ # --- Reading or writing an entire file at once.
+
+ def open(self, mode='r'):
+ """ Open this file. Return a file object. """
+ return file(self, mode)
+
+ def bytes(self):
+ """ Open this file, read all bytes, return them as a string. """
+ f = self.open('rb')
+ try:
+ return f.read()
+ finally:
+ f.close()
+
+ def write_bytes(self, bytes, append=False):
+ """ Open this file and write the given bytes to it.
+
+ Default behavior is to overwrite any existing file.
+ Call p.write_bytes(bytes, append=True) to append instead.
+ """
+ if append:
+ mode = 'ab'
+ else:
+ mode = 'wb'
+ f = self.open(mode)
+ try:
+ f.write(bytes)
+ finally:
+ f.close()
+
+ def text(self, encoding=None, errors='strict'):
+ r""" Open this file, read it in, return the content as a string.
+
+ This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r'
+ are automatically translated to '\n'.
+
+ Optional arguments:
+
+ encoding - The Unicode encoding (or character set) of
+ the file. If present, the content of the file is
+ decoded and returned as a unicode object; otherwise
+ it is returned as an 8-bit str.
+ errors - How to handle Unicode errors; see help(str.decode)
+ for the options. Default is 'strict'.
+ """
+ if encoding is None:
+ # 8-bit
+ f = self.open(_textmode)
+ try:
+ return f.read()
+ finally:
+ f.close()
+ else:
+ # Unicode
+ f = codecs.open(self, 'r', encoding, errors)
+ # (Note - Can't use 'U' mode here, since codecs.open
+ # doesn't support 'U' mode, even in Python 2.3.)
+ try:
+ t = f.read()
+ finally:
+ f.close()
+ return (t.replace(u'\r\n', u'\n')
+ .replace(u'\r\x85', u'\n')
+ .replace(u'\r', u'\n')
+ .replace(u'\x85', u'\n')
+ .replace(u'\u2028', u'\n'))
+
+ def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False):
+ r""" Write the given text to this file.
+
+ The default behavior is to overwrite any existing file;
+ to append instead, use the 'append=True' keyword argument.
+
+ There are two differences between path.write_text() and
+ path.write_bytes(): newline handling and Unicode handling.
+ See below.
+
+ Parameters:
+
+ - text - str/unicode - The text to be written.
+
+ - encoding - str - The Unicode encoding that will be used.
+ This is ignored if 'text' isn't a Unicode string.
+
+ - errors - str - How to handle Unicode encoding errors.
+ Default is 'strict'. See help(unicode.encode) for the
+ options. This is ignored if 'text' isn't a Unicode
+ string.
+
+ - linesep - keyword argument - str/unicode - The sequence of
+ characters to be used to mark end-of-line. The default is
+ os.linesep. You can also specify None; this means to
+ leave all newlines as they are in 'text'.
+
+ - append - keyword argument - bool - Specifies what to do if
+ the file already exists (True: append to the end of it;
+ False: overwrite it.) The default is False.
+
+
+ --- Newline handling.
+
+ write_text() converts all standard end-of-line sequences
+ ('\n', '\r', and '\r\n') to your platform's default end-of-line
+ sequence (see os.linesep; on Windows, for example, the
+ end-of-line marker is '\r\n').
+
+ If you don't like your platform's default, you can override it
+ using the 'linesep=' keyword argument. If you specifically want
+ write_text() to preserve the newlines as-is, use 'linesep=None'.
+
+ This applies to Unicode text the same as to 8-bit text, except
+ there are three additional standard Unicode end-of-line sequences:
+ u'\x85', u'\r\x85', and u'\u2028'.
+
+ (This is slightly different from when you open a file for
+ writing with fopen(filename, "w") in C or file(filename, 'w')
+ in Python.)
+
+
+ --- Unicode
+
+ If 'text' isn't Unicode, then apart from newline handling, the
+ bytes are written verbatim to the file. The 'encoding' and
+ 'errors' arguments are not used and must be omitted.
+
+ If 'text' is Unicode, it is first converted to bytes using the
+ specified 'encoding' (or the default encoding if 'encoding'
+ isn't specified). The 'errors' argument applies only to this
+ conversion.
+
+ """
+ if isinstance(text, unicode):
+ if linesep is not None:
+ # Convert all standard end-of-line sequences to
+ # ordinary newline characters.
+ text = (text.replace(u'\r\n', u'\n')
+ .replace(u'\r\x85', u'\n')
+ .replace(u'\r', u'\n')
+ .replace(u'\x85', u'\n')
+ .replace(u'\u2028', u'\n'))
+ text = text.replace(u'\n', linesep)
+ if encoding is None:
+ encoding = sys.getdefaultencoding()
+ bytes = text.encode(encoding, errors)
+ else:
+ # It is an error to specify an encoding if 'text' is
+ # an 8-bit string.
+ assert encoding is None
+
+ if linesep is not None:
+ text = (text.replace('\r\n', '\n')
+ .replace('\r', '\n'))
+ bytes = text.replace('\n', linesep)
+
+ self.write_bytes(bytes, append)
+
+ def lines(self, encoding=None, errors='strict', retain=True):
+ r""" Open this file, read all lines, return them in a list.
+
+ Optional arguments:
+ encoding - The Unicode encoding (or character set) of
+ the file. The default is None, meaning the content
+ of the file is read as 8-bit characters and returned
+ as a list of (non-Unicode) str objects.
+ errors - How to handle Unicode errors; see help(str.decode)
+ for the options. Default is 'strict'
+ retain - If true, retain newline characters; but all newline
+ character combinations ('\r', '\n', '\r\n') are
+ translated to '\n'. If false, newline characters are
+ stripped off. Default is True.
+
+ This uses 'U' mode in Python 2.3 and later.
+ """
+ if encoding is None and retain:
+ f = self.open(_textmode)
+ try:
+ return f.readlines()
+ finally:
+ f.close()
+ else:
+ return self.text(encoding, errors).splitlines(retain)
+
+ def write_lines(self, lines, encoding=None, errors='strict',
+ linesep=os.linesep, append=False):
+ r""" Write the given lines of text to this file.
+
+ By default this overwrites any existing file at this path.
+
+ This puts a platform-specific newline sequence on every line.
+ See 'linesep' below.
+
+ lines - A list of strings.
+
+ encoding - A Unicode encoding to use. This applies only if
+ 'lines' contains any Unicode strings.
+
+ errors - How to handle errors in Unicode encoding. This
+ also applies only to Unicode strings.
+
+ linesep - The desired line-ending. This line-ending is
+ applied to every line. If a line already has any
+ standard line ending ('\r', '\n', '\r\n', u'\x85',
+ u'\r\x85', u'\u2028'), that will be stripped off and
+ this will be used instead. The default is os.linesep,
+ which is platform-dependent ('\r\n' on Windows, '\n' on
+ Unix, etc.) Specify None to write the lines as-is,
+ like file.writelines().
+
+ Use the keyword argument append=True to append lines to the
+ file. The default is to overwrite the file. Warning:
+ When you use this with Unicode data, if the encoding of the
+ existing data in the file is different from the encoding
+ you specify with the encoding= parameter, the result is
+ mixed-encoding data, which can really confuse someone trying
+ to read the file later.
+ """
+ if append:
+ mode = 'ab'
+ else:
+ mode = 'wb'
+ f = self.open(mode)
+ try:
+ for line in lines:
+ isUnicode = isinstance(line, unicode)
+ if linesep is not None:
+ # Strip off any existing line-end and add the
+ # specified linesep string.
+ if isUnicode:
+ if line[-2:] in (u'\r\n', u'\x0d\x85'):
+ line = line[:-2]
+ elif line[-1:] in (u'\r', u'\n',
+ u'\x85', u'\u2028'):
+ line = line[:-1]
+ else:
+ if line[-2:] == '\r\n':
+ line = line[:-2]
+ elif line[-1:] in ('\r', '\n'):
+ line = line[:-1]
+ line += linesep
+ if isUnicode:
+ if encoding is None:
+ encoding = sys.getdefaultencoding()
+ line = line.encode(encoding, errors)
+ f.write(line)
+ finally:
+ f.close()
+
+ def read_md5(self):
+ """ Calculate the md5 hash for this file.
+
+ This reads through the entire file.
+ """
+ f = self.open('rb')
+ try:
+ m = md5.new()
+ while True:
+ d = f.read(8192)
+ if not d:
+ break
+ m.update(d)
+ finally:
+ f.close()
+ return m.digest()
+
+ # --- Methods for querying the filesystem.
+
+ exists = os.path.exists
+ isdir = os.path.isdir
+ isfile = os.path.isfile
+ islink = os.path.islink
+ ismount = os.path.ismount
+
+ if hasattr(os.path, 'samefile'):
+ samefile = os.path.samefile
+
+ getatime = os.path.getatime
+ atime = property(
+ getatime, None, None,
+ """ Last access time of the file. """)
+
+ getmtime = os.path.getmtime
+ mtime = property(
+ getmtime, None, None,
+ """ Last-modified time of the file. """)
+
+ if hasattr(os.path, 'getctime'):
+ getctime = os.path.getctime
+ ctime = property(
+ getctime, None, None,
+ """ Creation time of the file. """)
+
+ getsize = os.path.getsize
+ size = property(
+ getsize, None, None,
+ """ Size of the file, in bytes. """)
+
+ if hasattr(os, 'access'):
+ def access(self, mode):
+ """ Return true if current user has access to this path.
+
+ mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
+ """
+ return os.access(self, mode)
+
+ def stat(self):
+ """ Perform a stat() system call on this path. """
+ return os.stat(self)
+
+ def lstat(self):
+ """ Like path.stat(), but do not follow symbolic links. """
+ return os.lstat(self)
+
+ def get_owner(self):
+ r""" Return the name of the owner of this file or directory.
+
+ This follows symbolic links.
+
+ On Windows, this returns a name of the form ur'DOMAIN\User Name'.
+ On Windows, a group can own a file or directory.
+ """
+ if os.name == 'nt':
+ if win32security is None:
+ raise Exception("path.owner requires win32all to be installed")
+ desc = win32security.GetFileSecurity(
+ self, win32security.OWNER_SECURITY_INFORMATION)
+ sid = desc.GetSecurityDescriptorOwner()
+ account, domain, typecode = win32security.LookupAccountSid(None, sid)
+ return domain + u'\\' + account
+ else:
+ if pwd is None:
+ raise NotImplementedError("path.owner is not implemented on this platform.")
+ st = self.stat()
+ return pwd.getpwuid(st.st_uid).pw_name
+
+ owner = property(
+ get_owner, None, None,
+ """ Name of the owner of this file or directory. """)
+
+ if hasattr(os, 'statvfs'):
+ def statvfs(self):
+ """ Perform a statvfs() system call on this path. """
+ return os.statvfs(self)
+
+ if hasattr(os, 'pathconf'):
+ def pathconf(self, name):
+ return os.pathconf(self, name)
+
+
+ # --- Modifying operations on files and directories
+
+ def utime(self, times):
+ """ Set the access and modified times of this file. """
+ os.utime(self, times)
+
+ def chmod(self, mode):
+ os.chmod(self, mode)
+
+ if hasattr(os, 'chown'):
+ def chown(self, uid, gid):
+ os.chown(self, uid, gid)
+
+ def rename(self, new):
+ os.rename(self, new)
+
+ def renames(self, new):
+ os.renames(self, new)
+
+
+ # --- Create/delete operations on directories
+
+ def mkdir(self, mode=0777):
+ os.mkdir(self, mode)
+
+ def makedirs(self, mode=0777):
+ os.makedirs(self, mode)
+
+ def rmdir(self):
+ os.rmdir(self)
+
+ def removedirs(self):
+ os.removedirs(self)
+
+
+ # --- Modifying operations on files
+
+ def touch(self):
+ """ Set the access/modified times of this file to the current time.
+ Create the file if it does not exist.
+ """
+ fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666)
+ os.close(fd)
+ os.utime(self, None)
+
+ def remove(self):
+ os.remove(self)
+
+ def unlink(self):
+ os.unlink(self)
+
+
+ # --- Links
+
+ if hasattr(os, 'link'):
+ def link(self, newpath):
+ """ Create a hard link at 'newpath', pointing to this file. """
+ os.link(self, newpath)
+
+ if hasattr(os, 'symlink'):
+ def symlink(self, newlink):
+ """ Create a symbolic link at 'newlink', pointing here. """
+ os.symlink(self, newlink)
+
+ if hasattr(os, 'readlink'):
+ def readlink(self):
+ """ Return the path to which this symbolic link points.
+
+ The result may be an absolute or a relative path.
+ """
+ return self.__class__(os.readlink(self))
+
+ def readlinkabs(self):
+ """ Return the path to which this symbolic link points.
+
+ The result is always an absolute path.
+ """
+ p = self.readlink()
+ if p.isabs():
+ return p
+ else:
+ return (self.parent / p).abspath()
+
+
+ # --- High-level functions from shutil
+
+ copyfile = shutil.copyfile
+ copymode = shutil.copymode
+ copystat = shutil.copystat
+ copy = shutil.copy
+ copy2 = shutil.copy2
+ copytree = shutil.copytree
+ if hasattr(shutil, 'move'):
+ move = shutil.move
+ rmtree = shutil.rmtree
+
+
+ # --- Special stuff from os
+
+ if hasattr(os, 'chroot'):
+ def chroot(self):
+ os.chroot(self)
+
+ if hasattr(os, 'startfile'):
+ def startfile(self):
+ os.startfile(self)
+
+
diff --git a/plugins/readme b/plugins/readme
new file mode 100755
index 0000000..5e6f5fe
--- /dev/null
+++ b/plugins/readme
@@ -0,0 +1,11 @@
+ How to use / write Plugins:
+ - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins
+
+ List of Services:
+ - ImageQuiz.py, Line 37: "Hook-In Services"
+ - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview
+
+ Services can accessed via the Class __SERVICES__
+ __SERVICES__.db.query(q)
+ __SERVICES__.db.commit(q)
+ __SERVICES__.frontend.add_menu_item(path, caption, onclick_function)
diff --git a/plugins/single_player.py b/plugins/single_player.py
new file mode 100755
index 0000000..ef0936e
--- /dev/null
+++ b/plugins/single_player.py
@@ -0,0 +1,744 @@
+# Currently
+# * Displaying Categories in set language,
+# * Pick English Questions
+
+# To Do
+# change to update Leitner info
+# display questions based on box
+# fix accessed wav
+
+# structure
+# class CurrentQuestion
+# class Answer
+# class Questions
+
+# functions
+
+# show_answer
+# finished
+# startgame
+# ask_subcat
+# ask_category
+# display_points
+# click_on_
+# next_question
+# next_question
+# spot_found
+# spot_not_found
+# load
+
+import pygame
+from pygame import *
+from sugar.activity import activity
+import os
+
+import random
+from layout import *
+from frontend import hex2rgb
+import time
+import threading
+
+__PLUGIN_NAME__ = 'single player'
+
+#set up paths to for adding images and sounds
+DATAPATH = os.path.join(activity.get_activity_root(), "data")
+ACTIVITYPATH = activity.get_bundle_path()
+IMAGEPATH = os.path.join(DATAPATH, 'image')
+SOUNDPATH = os.path.join(DATAPATH, 'sound')
+ICONPATH = os.path.join(ACTIVITYPATH, 'images')
+CORRECT_SOUNDS = ['i-like-it.wav', 'ooh-yeah.wav', 'impressive.wav',
+ 'dramatic_chord.wav', 'sweet.wav',
+ 'brain-yes.wav', 'mk-excellent.wav', 'that-was-cool.wav',
+ 'bt-excellent.wav', 'groovy.wav',
+ 'yes-i-like-it.wav', 'burns-excellent.wav', 'oh-yeah.wav']
+WRONG_SOUNDS = ['db_forgetaboutit.wav', 'alf_wrong.wav', 'doh.wav', 'sorry.wav',
+ 'awh_man.wav', 'metal_clang_2.wav',
+ 'excuse_me.wav', 'negative.wav', 'bunny_awful.wav', 'gwarsh.wav',
+ 'not.wav', 'haha.wav', 'oh_no.wav', 'compute.wav', 'hoo-ah.wav']
+
+clock = pygame.time.Clock()
+
+class CurrentQuestion:
+ id = 0
+ prompt = u''
+ response = u''
+ imgfn = u''
+ sndfn = u''
+ map = u''
+ answer_link = u''
+
+class Imagequiz_question:
+ id = 0
+ map = u''
+ cat = 0
+ subcat = 0
+ text = u''
+ answer = u''
+ answer_link = u''
+
+class Answer:
+ display = True
+ display_line = True
+ sound_enabled = True
+
+ link = u''
+ text = u''
+
+ img_found_count = 0
+ img_notfound_count = 0
+ imgfn_found = ["Emotes/face-grin.png"]
+ imgfn_notfound = ["Emotes/face-devil-grin.png"]
+
+ found_straight = 0
+ straight_count = 0
+ icon_left = 0
+ bg_icons = pygame.Surface((500, 32))
+ bg_icons_straight = pygame.Surface((500, 32))
+
+ def __init__(self):
+ self.sound_found = sf.sound_load("accessed.wav")
+ self.sound_notfound = sf.sound_load("sorry.wav")
+
+ def reset_points(self):
+ self.found_straight = 0
+ self.straight_count = 0
+ self.icon_left = 0
+
+ self.bg_icons.fill(hex2rgb(Layout().Toolbar.background))
+ self.bg_icons_straight.fill(hex2rgb(Layout().Toolbar.background))
+
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar")
+ sf.refresh()
+
+ def add_straight(self):
+ # 5 in a row -> special reward
+ self.straight_count += 1
+ im, im_rect = sf.image_load("images/%s" % "Icons/bulb.png")
+ self.bg_icons_straight.blit(im, ((self.straight_count-1) * (im_rect[2] + 4), 0))
+ sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar")
+ sf.refresh()
+
+ def display_icon(self, icon_name):
+ if icon_name == "found":
+ self.found_straight += 1
+ if self.found_straight == 5:
+ # Found Straight 5!
+ self.bg_icons.fill(hex2rgb(Layout().Toolbar.background))
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ self.add_straight()
+ self.icon_left = 0
+ self.found_straight = 1
+
+ fn = self.imgfn_found[self.img_found_count % len(self.imgfn_found)]
+ self.img_found_count += 1
+
+ elif icon_name == "not-found":
+ self.found_straight = 0
+ fn = self.imgfn_notfound[self.img_notfound_count % len(self.imgfn_notfound)]
+ self.img_notfound_count += 1
+
+ img, img_rect = sf.image_load("images/%s" % fn)
+ self.bg_icons.blit(img, (self.icon_left, 0))
+
+ sf.display_surface(self.bg_icons, (540, 10), "toolbar")
+ sf.refresh()
+
+ self.icon_left += img_rect[2] + 4
+
+ class PlaySoundThread (threading.Thread):
+ def set(self, sound, interval):
+ self.sound = sound
+ self.i = interval
+
+ def run(self):
+ time.sleep(self.i)
+ self.sound.play()
+
+ def play_sound(self, i):
+ if self.sound_enabled:
+ t = self.PlaySoundThread()
+ if i == 1: t.set(self.sound_found, 0)
+ else: t.set(self.sound_notfound, 0.5)
+ t.start()
+
+ def display_answer(self):
+ if self.display_line: sf.draw_lines()
+ if self.display == False: return False
+
+ # Get Top Right Spot of list
+ map_arr = Q.question.map.split(", ")
+
+ # Widths & Heights
+ bubble_width = 400
+ bubble_height = 300
+ textfield_width = 270
+ textfield_height = 200
+
+ # Extremas of the Polygone will be saved in here:
+ x_max = 0
+ y_max = 0
+ x_min = 1000
+ y_min = 1000
+
+ # Extract Extremas from the polygon
+ o = []
+ a = "x"
+ for s in map_arr:
+ if a == "x": a = s
+ else:
+ # Take care of border pixels
+ if int(a) > x_max: x_max = int(a)
+ if int(s) > y_max: y_max = int(s)
+ if int(a) < x_min: x_min = int(a)
+ if int(s) < y_min: y_min = int(s)
+ a = "x"
+
+ # Set x and y for the Answer Bubble
+ y_med = (y_min + y_max) / 2
+ x_max -= 5
+
+
+ x_max += Layout().Question.x + Layout().Question.Image.x + 2
+ y_med += Layout().Question.y + Layout().Question.Image.y + 2
+
+# sf.draw_polygon()
+
+ # Draw Answer Bubble Image & Text
+ im, im_rect = sf.image_load('images/bubble.gif')
+
+ text_arr = self.text.split(' ')
+ cur_line = ''
+
+ cur_x = 0
+ cur_y = 0
+
+ # 'textfield' contains the answer text
+ textfield = pygame.Surface((textfield_width, textfield_height))
+ textfield.fill((255, 255, 255))
+
+ # Make line breaks after reaching width of 'textfield'
+ for t in text_arr:
+ cur_line = "%s %s" % (cur_line, t)
+
+ font = pygame.font.Font(None, 38)
+ n_text = font.render(cur_line, 1, (0,0,0))
+ textpos = n_text.get_rect()
+# print cur_line,
+
+ x,y,w,h = list(textpos)
+ if w > (textfield_width):
+ textfield.blit(text, (cur_x, cur_y))
+ cur_line = t
+ cur_y += 30
+ written = 1
+ else:
+ written = 0
+ text = n_text
+
+# print textpos
+
+ # Draw leftovers on textfield
+ if written == 0:
+ textfield.blit(n_text, (cur_x, cur_y))
+ else:
+ font = pygame.font.Font(None, 38)
+ n_text = font.render(cur_line, 1, (0,0,0))
+ textfield.blit(n_text, (cur_x, cur_y))
+
+# print "draw"
+
+ # Draw on Screen
+ sf.display_surface(im, (x_max, y_med))
+ sf.display_surface(textfield, (x_max+25, y_med+20))
+
+ pygame.display.update()
+
+
+class Questions:
+
+ def __init__(self):
+ in_question = False
+
+ played = 0
+ count_won = 0
+ count_lost = 0
+ id = u''
+ questions = []
+ question = CurrentQuestion()
+
+ def question_pick(self):
+ # Check if Questions are left to play
+ if self.played >= len(self.questions):
+ # Game / Cat Finished!
+ return False
+
+ # Okay, Next One!
+ self.question = self.questions[self.played]
+ self.id = self.question.id
+ A.text = self.question.response
+ A.link = self.question.answer_link
+
+ self.played += 1
+ return True
+
+ def load_questions(self, cat_id):
+ self._cat_id = cat_id
+
+ self.played = 0
+ self.count_won = 0
+ self.count_lost = 0
+
+ q = 'SELECT text FROM categories WHERE id = %i' % cat_id
+ res = __SERVICES__.db.query(q)
+ Q.id = res[0][0]
+
+ #get list of questions (by question_id)
+ try:
+ q = "SELECT question_id FROM quizlink WHERE quiz_id = %i" % cat_id
+ questionlist = __SERVICES__.db.query(q)
+ except:
+ questionlist = []
+
+ #get actual questions
+ self.questions = []
+ for questionid in questionlist:
+ q = 'SELECT * from questions WHERE id = %i' % questionid[0]
+ res = __SERVICES__.db.query(q)
+ question = CurrentQuestion()
+ question.id = res[0][0]
+ question.prompt = res[0][1]
+ question.response = res[0][2]
+ question.imgfn = res[0][3]
+ question.sndfn = res[0][4]
+ question.map = res[0][5]
+ question.answer_link=res[0][6]
+ self.questions.append(question)
+
+ #present questions randomly
+ random.shuffle(self.questions)
+
+def show_answer():
+ A.display_answer()
+ Q.in_question = True
+ next_question()
+
+def finished():
+ sf.clear_text_items()
+ sf.clear_question_frame(True)
+ sf.refresh()
+ sf.add_text_item("You have finished this category!", (180, 80))
+ sf.add_text_item("Won: %i" % Q.count_won, (220, 120))
+ sf.add_text_item("Lost: %i" % Q.count_lost, (220, 160))
+ pygame.display.update()
+# ask_category(130)
+
+def display_points():
+ format = TextFormat(None, 30)
+ max_q = len(Q.questions)
+
+# sf.display_tooltip("%i" % (Q.count_won), (480, 14), format)
+# sf.display_tooltip("%i" % Q.count_lost, (480, 42), format)
+
+ if max_q == 0:
+ sf.display_line(0, True)
+ sf.display_line(0, False)
+ else:
+ sf.display_line(100 * Q.count_won / max_q, True)
+ sf.display_line(100 * Q.count_lost / max_q, False)
+ sf.add_text_item("this is the score " + str(Q.count_won * 10), (600,0))
+ pygame.display.update()
+
+def self_test():
+ #show_answer()
+ #show two clickable items: smiley face, sad face
+ image_right, xy = sf.image_load("images/Emotes/face-grin.png")
+ sf.display_surface(image_right, (750,100))
+ image_wrong, xy = sf.image_load("images/Emotes/face-devil-grin.png")
+ sf.display_surface(image_wrong, (750,100))
+ pygame.display.update()
+ #this should provide for click on image_right or image_wrong
+ response = True
+ return response
+
+def display_images():
+ global images
+ candidate_images = []
+ for question in Q.questions:
+ candidate_images.append(question.imgfn)
+ random.shuffle(candidate_images)
+ if Q.question.imgfn in candidate_images[:4]:
+ images = candidate_images[:4]
+ else:
+ images = candidate_images[:3]
+ images.append(Q.question.imgfn)
+ random.shuffle(images)
+ image_tl, xy = sf.image_load(images[0], path = IMAGEPATH)
+ image_tr, xy = sf.image_load(images[1], path = IMAGEPATH)
+ image_ll, xy = sf.image_load(images[2], path = IMAGEPATH)
+ image_lr, xy = sf.image_load(images[3], path = IMAGEPATH)
+ sf.display_surface(image_tl, (250, 150))
+ sf.display_surface(image_tr, (520, 150))
+ sf.display_surface(image_ll, (250, 420))
+ sf.display_surface(image_lr, (520, 420))
+ pygame.display.update
+
+def play_query_again():
+ global query
+ if Q.in_question:
+ #print 'play_query'
+ query.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+
+def play_sound(fn):
+ global query
+ query = sf.sound_load(Q.question.sndfn, path=SOUNDPATH)
+ image_play, xy = sf.image_load("play_button.png", path = ICONPATH)
+ sf.display_surface(image_play, (300,30))
+ pygame.display.update()
+ query.play()
+ while pygame.mixer.get_busy():
+ clock.tick(30)
+
+def picture_tl():
+ global images
+ if Q.in_question:
+ if images[0] == Q.question.imgfn:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_tr():
+ global images
+ if Q.in_question:
+ if images[1] == Q.question.imgfn:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_ll():
+ global images
+ if Q.in_question:
+ if images[2] == Q.question.imgfn:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def picture_lr():
+ global images
+ if Q.in_question:
+ if images[3] == Q.question.imgfn:
+ response = True
+ else:
+ response = False
+ delete_reacts(response)
+
+def delete_reacts(response):
+ if response:
+ correct_answer()
+ else:
+ wrong_answer()
+ return True
+
+def play_correct_response_sound():
+ good = sf.sound_load(random.choice(CORRECT_SOUNDS))
+ good.play()
+ while pygame.mixer.get_busy():
+ pygame.time.wait(30)
+
+def play_wrong_response_sound():
+ good = sf.sound_load(random.choice(WRONG_SOUNDS))
+ good.play()
+ while pygame.mixer.get_busy():
+ pygame.time.wait(30)
+
+
+def update_leitner_attributes():
+ #update Leitner attributes
+ #but need to get from db when loading questions
+ #need to save in db before next_question
+ #and update attributes actually in db
+ #finally display 'boxes' and 'points'
+ try:
+ print 'update Leitner attributes'
+ think = '%0.2f' % float( time.time() - Q.starttime)
+ Q.time = think
+ Q.count_found += 1
+ if Q.box < 5:
+ Q.box += 1
+ Q.date = sys.date()
+ except:
+ print 'Leitner update failed'
+ #now update database
+ try:
+ q = "UPDATE questions SET time = Q.time, count_found = Q.count_found, count_unfound = Q.count_unfound, box = Q.box, time = Q.time, date = Q.date WHERE id = Q.id"
+ res = __SERVICES__.db.query(q)
+ except:
+ print 'Leitner db update failed'
+
+def correct_answer(next=True):
+ if Q.in_question:
+ A.display_icon("found")
+ #A.play_sound(1)
+ Q.in_question = False
+ Q.count_won += 1
+ display_points()
+ try:
+ play_correct_response_sound()
+ except:
+ print 'play_correct_response_sound failed'
+ update_leitner_attributes()
+
+ if next:
+ next_question()
+ else:
+ show_answer()
+
+def wrong_answer(next=True):
+ if Q.in_question:
+ A.display_icon("not-found")
+ Q.in_question = False
+ Q.count_lost += 1
+ display_points()
+ play_wrong_response_sound()
+ update_leitner_attributes()
+ if next:
+ next_question()
+ else:
+ show_answer()
+
+def getpossibles(teststr):
+ #there may be multiple correct answers separated by a '/'
+ lst = []
+ test = ''
+ for i in range(len(teststr)):
+ if teststr[i] != '/':
+ test = test + teststr[i]
+ else:
+ lst.append(test)
+ test = ''
+ if len(test) > 0:
+ lst.append(test)
+ return lst
+
+def checkanswer(response):
+ possibles = getpossibles(Q.question.response)
+ return response.strip() in possibles
+
+
+def startgame(db_cat_id):
+
+ Q.count_won = 0
+ A.reset_points()
+
+ Q.load_questions(db_cat_id)
+ display_points()
+
+ sf.clear_text_items()
+ next_question()
+
+def next_question():
+ #Q._cat_id
+ #Q.questions
+ global reacts
+
+ if Q.question_pick():
+ clear_reacts()
+ Q.in_question = True
+ Q.starttime = time.time()
+ if len(Q.question.map) > 0: #this is an imagequiz question
+ iq = Imagequiz_question()
+ iq.id = Q.question.id
+ iq.imgfn = Q.question.imgfn
+ iq.map = Q.question.map
+ iq.cat = 0
+ iq.subcat = 0
+ iq.text = Q.question.prompt
+ iq.answer = Q.question.response
+ iq.answer_link = Q.question.answer_link
+ __SERVICES__.frontend.question_display(iq)
+ elif len(Q.question.imgfn) > 0 and len(Q.question.sndfn) > 0: #this is Renyi
+ print 'Renyi', Q.question.imgfn, Q.question.sndfn
+ #get four images (one is correct), shuffle them, put them in 'react' squares
+ set_reacts()
+ print 'display_images'
+ display_images()
+ print 'update pygame display'
+ pygame.display.update()
+ #load and play sound
+ play_sound(Q.question.sndfn)
+ elif len(Q.question.sndfn) > 0: #this in 'phrase' question
+ #load and play sound
+ print 'phrase question'
+ play_sound(Q.question.sndfn)
+ response = __SERVICES__.frontend.ask(Q.question.text)
+ if len(response) == 0 or len(Q.question.answer) == 0:
+ self_test()
+ else:
+ correct = checkanswer(response)
+ y = 100
+ sf.add_text_item("ok", (750,y), next_question)
+ if correct:
+ correct_answer(False)
+ else:
+ wrong_answer(False)
+ else: # this is flashcard
+ print 'flashcard'
+ print 'prompt', len(Q.question.prompt), Q.question.prompt
+ print 'answer', len(Q.question.response), Q.question.response
+ response = __SERVICES__.frontend.ask(Q.question.prompt)
+ print 'response', len(response), response
+ if len(response) == 0 or len(Q.question.response) == 0:
+ correct = self_test()
+ else:
+ correct = checkanswer(response)
+ delete_reacts(correct)
+ next_question()
+ Q.in_question = True
+ else:
+ # game / cat finished
+ Q.in_question = False
+ print "finished"
+ Q._cat_id = -1
+ finished()
+
+def spot_found():
+ if Q.in_question:
+ A.display_icon("found")
+ play_correct_response_sound()
+ #A.play_sound(1)
+ Q.in_question = False
+ Q.count_won += 1
+ display_points()
+ show_answer()
+
+def spot_not_found():
+ if Q.in_question:
+ A.display_icon("not-found")
+ play_wrong_response_sound()
+ #A.play_sound(0)
+ Q.in_question = False
+ Q.count_lost += 1
+ display_points()
+ show_answer()
+
+def set_reacts():
+ global reacts
+ reacts = []
+ reacts.append(sf.add_react(250, 150, 320, 240, picture_tl, stopsearch = True))
+ reacts.append(sf.add_react(520, 150, 320, 240, picture_tr, stopsearch = True))
+ reacts.append(sf.add_react(250, 420, 320, 240, picture_ll, stopsearch = True))
+ reacts.append(sf.add_react(520, 420, 320, 240, picture_lr, stopsearch = True))
+ reacts.append(sf.add_react(750,100,50,50, correct_answer, stopsearch = True))
+ reacts.append(sf.add_react(750,150,50,50, wrong_answer, stopsearch = True))
+ reacts.append(sf.add_react(300,30,100,100, play_query_again, stopsearch = True))
+
+def clear_reacts():
+ global reacts
+ for react in reacts:
+ sf.del_react(react)
+
+def click_on_pick_category():
+ # Select Category
+ cat_id = -1
+ sf.clear_text_items()
+ sf.clear_question_frame(True)
+ ask_category(cat_id)
+
+def ask_category(cat_id, offset_y = 0):
+
+ i = 1
+ y = 110 + offset_y
+
+ sf.add_text_item("Next Category:", (280,y))
+
+ #first we need to query for categories
+ #might be nice to show them in alphabetical order
+ #when y gets too big, we should change x
+ if cat_id == -1:
+ q = 'SELECT id, text FROM categories'
+ res = __SERVICES__.db.query(q)
+ for cat in res:
+ cat_id = cat[0]
+ category = cat[1]
+ q = 'SELECT count(*) FROM quizlink WHERE quiz_id=%i' % cat_id
+ res1 = __SERVICES__.db.query(q)
+ count = res1[0][0]
+ if count > 0:
+ # this is a quiz, display in green
+ y += 50
+ sf.add_text_item("%s (%s)" % (category, count), (300,y), startgame, cat_id, True)
+ else:
+ # this is a category - get number of children
+ q = 'SELECT count(*) FROM catlink WHERE parent_id=%i' % cat_id
+ res2 = __SERVICES__.db.query(q)
+ count = res2[0][0]
+ y += 50
+ sf.add_text_item("%s (%s)" % (category, count), (300,y), ask_category, cat_id, True)
+ i += 1
+ else:
+ #we need to pass a cat_id from the user's selection
+ print 'second level selection not implemented - must select quiz'
+
+
+#initialization called by quiz.py
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+ global Q
+ Q = Questions()
+
+ global A
+ A = Answer()
+
+ global button1
+ global button2
+ global button3
+
+ global reacts
+ reacts = []
+
+ sf.add_menu_item('/', 'Pick Category', click_on_pick_category, 0)
+
+ sf.add_menu_dir('/options', "Options")
+ button1 = sf.add_menu_item('/options', 'Display Answer [ On ]', click_options_answer)
+ button2 = sf.add_menu_item('/options', 'Play Sound [ On ]', click_options_sound)
+ button3 = sf.add_menu_item('/options', 'Draw Line [ On ]', click_options_line)
+
+ __SERVICES__.add_service("next_question", next_question)
+ __SERVICES__.add_service("spot_found", spot_found)
+ __SERVICES__.add_service("spot_not_found", spot_not_found)
+
+#clean close
+def close():
+ pass
+
+# handle options tab on main menu
+def click_options_answer():
+ if A.display:
+ A.display = False
+ sf.change_menu_item("change_caption", button1, "Display Answer [ Off ]")
+ else:
+ A.display = True
+ sf.change_menu_item("change_caption", button1, "Display Answer [ On ]")
+
+def click_options_line():
+ if A.display_line:
+ A.display_line = False
+ sf.change_menu_item("change_caption", button3, "Draw Line [ Off ]")
+ else:
+ A.display_line = True
+ sf.change_menu_item("change_caption", button3, "Draw Line [ On ]")
+
+def click_options_sound():
+ if A.sound_enabled:
+ A.sound_enabled = False
+ sf.change_menu_item("change_caption", button2, "Play Sound [ Off ]")
+ else:
+ A.sound_enabled = True
+ sf.change_menu_item("change_caption", button2, "Play Sound [ On ]")
diff --git a/plugins/tools.py b/plugins/tools.py
new file mode 100755
index 0000000..d453c2c
--- /dev/null
+++ b/plugins/tools.py
@@ -0,0 +1,100 @@
+import sys
+import pygame
+
+__PLUGIN_NAME__ = 'db tools'
+
+''' Mixed Functions '''
+def clickOnDBSave():
+ pass
+
+def clickOnDBRevert():
+ pass
+
+def clickOnDBWipe():
+ pass
+
+def clickOnQuitYes():
+ sys.exit(0)
+
+def clickOnToggleFS():
+ pygame.display.toggle_fullscreen()
+
+def clickOnOptions_Exchange():
+ print "Click on Options - Exchange"
+
+def event1(params):
+ print "Event 1 Started :-) params:", params
+
+def React1():
+ print "React 1"
+
+def clickOnQuestionItem(params):
+ print params
+
+
+# Click on Test()
+def clickOnTest():
+ print "Start Test"
+ sf.clear_question_items()
+
+ # Clear the Frame and display Stuff
+ sf.question_frame_clear()
+ sf.question_frame_show_text('images/europe.gif',0,0)
+ sf.question_frame_show_image('images/europe.gif',0,50)
+ pygame.display.update()
+
+ # Add Hook to onclick
+ # hook_id = sf.add_event_hook('onclick', event1)
+ # print "- added hook", hook_id
+
+
+''' Click on Test2() '''
+def clickOnTest2():
+ # Show last Question
+ # sf.question_display()
+
+ # Add Reacts
+ # a = sf.add_react(0, 0, 100, 100, React1, False, True)
+ # b = sf.add_react(50, 50, 100, 100, React1, False, True)
+
+ # Remove React a
+ # sf.del_react(a)
+
+ x = sf.ask("Your name:")
+ print ">", x
+
+ sf.add_question_item(x, (100, 100), clickOnQuestionItem, 0)
+ sf.add_question_item(x*2, (100, 150), clickOnQuestionItem, 1)
+
+ sf.draw_question_items()
+ pygame.display.update()
+
+''' Init Part '''
+def load():
+ global sf
+ sf = __SERVICES__.frontend;
+
+ # Use DB like that:
+ # print __SERVICES__.db.query("SELECT * FROM xoquiz WHERE 1")
+
+ # Menu /test
+# sf.add_menu_dir('/test', 'Test')
+# sf.add_menu_item('/test', 'Test()', clickOnTest,3)
+# sf.add_menu_item('/test', 'Test2()', clickOnTest2,3)
+
+ # Menu /options
+ sf.add_menu_dir('/options', 'Options')
+ sf.add_menu_item('/options', 'Toggle Fullscreen', clickOnToggleFS, 0)
+
+ # Menu /options/database
+# sf.add_menu_dir('/options/database', 'Database')
+# sf.add_menu_item('/options/database', 'Save', clickOnDBSave, 0)
+# sf.add_menu_item('/options/database', 'Revert', clickOnDBRevert, 1)
+# sf.add_menu_item('/options/database', 'Wipe All', clickOnDBWipe, 2)
+
+ # Menu /quit
+ sf.add_menu_dir('/quit', 'Quit', 5)
+ sf.add_menu_item('/quit', 'Yes, really Quit', clickOnQuitYes, 3)
+
+def close():
+ pass \ No newline at end of file
diff --git a/quiz.py b/quiz.py
new file mode 100755
index 0000000..aae1dba
--- /dev/null
+++ b/quiz.py
@@ -0,0 +1,167 @@
+#! /usr/bin/env python
+# -*- coding: latin-1 -*-
+
+import os
+import sys
+import sqlite3
+import time
+
+import pygame
+from pygame.locals import *
+
+import olpcgames
+
+from layout import *
+
+import backend
+import frontend
+
+''' Definitions '''
+show_infos_pygame_events = False
+
+'''This class provides functions to the Plugins'''
+class Services:
+ class locale:
+ lang_id = 1
+ lang_name = "English"
+ class db:
+ query = ''
+ commit = ''
+ class brain:
+ question_pick = ''
+ class frontend:
+ add_menu_item = ''
+ change_dir = ''
+ class plugins:
+ services = []
+
+
+services = Services()
+
+''' INIT Part '''
+# Init All Modules
+db = backend.Database()
+brain = backend.Kernel()
+plugger = backend.PluginManager()
+display = frontend.Kernel()
+
+''' Hook-In Services for the Plugins '''
+services.Layout = Layout()
+services.Question = backend.Question
+
+services.add_service = brain.add_service
+services.start_service = brain.start_service
+services.hex2rgb = brain.hex2rgb
+
+services.db.query = db.query
+services.db.commit = db.commit
+services.db.add_cat = db.add_cat
+services.db.add_question = db.add_question
+
+services.frontend.add_menu_dir = display.add_menu_dir
+services.frontend.add_menu_item = display.add_menu_item
+services.frontend.change_menu_item = display.change_menu_item
+services.frontend.change_dir = display.change_dir
+
+services.frontend.image_load = display.image_load
+services.frontend.sound_load = display.sound_load
+services.frontend.point_inside_polygon = display.point_inside_polygon
+
+services.frontend.draw_polygon = display.draw_polygon
+services.frontend.draw_lines = display.draw_lines
+
+services.frontend.display_line = display.display_line
+
+#services.frontend.show_image = display.show_image
+#services.frontend.show_surface = display.show_surface
+
+services.frontend.question_display = display.question_display
+services.frontend.clear_question_frame = display.clear_question_frame
+
+services.frontend.add_react = display.add_react
+services.frontend.del_react = display.del_react
+
+services.frontend.current_caption = display.current_caption
+services.frontend.add_event_hook = display.add_event_hook
+services.frontend.del_event_hook = display.del_event_hook
+services.frontend.list_event_hooks = display.list_event_hooks
+
+services.frontend.refresh = display.refresh
+services.frontend.display_surface = display.display_surface
+services.frontend.get_screen = display.get_screen
+services.frontend.app = display.get_app
+
+services.frontend.ask = display.ask
+services.frontend.display_tooltip = display.display_tooltip
+
+services.frontend.add_text_item = display.add_text_item
+services.frontend.del_text_item = display.del_text_item
+services.frontend.clear_text_items = display.clear_text_items
+
+''' Load all Modules '''
+db.load (services)
+brain.load (services)
+
+plugger.load (services)
+plugger.load_plugins()
+
+display.load (services)
+
+''' Game Control Start '''
+print 'game control start'
+
+class ImageQuizActivity:
+ user_name = u"chris"
+
+ def game(self):
+ clock = pygame.time.Clock()
+ app = display.get_app()
+ screen = display.get_screen()
+
+ # Pygame event loop.
+ while True:
+ clock.tick(20)
+ app.paint(screen)
+ pygame.display.update()
+
+ for event in [ pygame.event.wait() ] + pygame.event.get( ):
+ self.input(event)
+ app.event(event)
+
+ # Handle a pygame event
+ def exit_all(self):
+ plugger.close_plugins()
+# time.sleep(3)
+ sys.exit(0)
+
+ def input(self, event):
+ if show_infos_pygame_events: print event
+ if event.type == QUIT:
+ self.exit_all()
+
+ elif event.type == MOUSEBUTTONUP:
+ display.check_click(event.pos)
+
+ elif event.type == KEYUP:
+ if event.key == 113: # 'q'
+ self.exit_all()
+ elif event.key == 102: # 'f'
+ pygame.display.toggle_fullscreen()
+
+# elif event.type == MOUSEMOTION:
+# pass
+
+# else:
+# x = event.type
+# display.show_tool_tip((200, 10), "%i" % int(x))
+# pygame.display.update()
+
+
+ def __init__(self):
+ self.game()
+
+def main():
+ ImageQuiz = ImageQuizActivity()
+
+if __name__ == "__main__" or __name__ == "ImageQuiz":
+ main()
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..e3052d1
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+try:
+ from sugar.activity import bundlebuilder
+ bundlebuilder.start("ImageQuiz")
+except ImportError:
+ import os
+ os.system("find ./ | sed 's,^./,ImageQuiz.activity/,g' > MANIFEST")
+ os.chdir('..')
+ os.system('zip -r ImageQuiz.xo ImageQuiz.activity')
+ os.system('mv ImageQuiz.xo ./ImageQuiz.activity')
+ os.chdir('ImageQuiz.activity')
diff --git a/sounds/accessed.wav b/sounds/accessed.wav
new file mode 100755
index 0000000..594249d
--- /dev/null
+++ b/sounds/accessed.wav
Binary files differ
diff --git a/sounds/alf_wrong.wav b/sounds/alf_wrong.wav
new file mode 100755
index 0000000..793b2af
--- /dev/null
+++ b/sounds/alf_wrong.wav
Binary files differ
diff --git a/sounds/awh_man.wav b/sounds/awh_man.wav
new file mode 100755
index 0000000..820af7a
--- /dev/null
+++ b/sounds/awh_man.wav
Binary files differ
diff --git a/sounds/brain-yes.wav b/sounds/brain-yes.wav
new file mode 100755
index 0000000..708ced3
--- /dev/null
+++ b/sounds/brain-yes.wav
Binary files differ
diff --git a/sounds/bt-excellent.wav b/sounds/bt-excellent.wav
new file mode 100755
index 0000000..de9b604
--- /dev/null
+++ b/sounds/bt-excellent.wav
Binary files differ
diff --git a/sounds/bunny_awful.wav b/sounds/bunny_awful.wav
new file mode 100755
index 0000000..dc023e9
--- /dev/null
+++ b/sounds/bunny_awful.wav
Binary files differ
diff --git a/sounds/burns-excellent.wav b/sounds/burns-excellent.wav
new file mode 100755
index 0000000..354585a
--- /dev/null
+++ b/sounds/burns-excellent.wav
Binary files differ
diff --git a/sounds/compute.wav b/sounds/compute.wav
new file mode 100755
index 0000000..e68a05e
--- /dev/null
+++ b/sounds/compute.wav
Binary files differ
diff --git a/sounds/db_forgetaboutit.wav b/sounds/db_forgetaboutit.wav
new file mode 100755
index 0000000..a1e5288
--- /dev/null
+++ b/sounds/db_forgetaboutit.wav
Binary files differ
diff --git a/sounds/doh.wav b/sounds/doh.wav
new file mode 100755
index 0000000..8e40371
--- /dev/null
+++ b/sounds/doh.wav
Binary files differ
diff --git a/sounds/dramatic_chord.wav b/sounds/dramatic_chord.wav
new file mode 100755
index 0000000..1e0c501
--- /dev/null
+++ b/sounds/dramatic_chord.wav
Binary files differ
diff --git a/sounds/excuse_me.wav b/sounds/excuse_me.wav
new file mode 100755
index 0000000..2bbfde0
--- /dev/null
+++ b/sounds/excuse_me.wav
Binary files differ
diff --git a/sounds/groovy.wav b/sounds/groovy.wav
new file mode 100755
index 0000000..1d0c1aa
--- /dev/null
+++ b/sounds/groovy.wav
Binary files differ
diff --git a/sounds/gwarsh.wav b/sounds/gwarsh.wav
new file mode 100755
index 0000000..b759303
--- /dev/null
+++ b/sounds/gwarsh.wav
Binary files differ
diff --git a/sounds/haha.wav b/sounds/haha.wav
new file mode 100755
index 0000000..7897b8d
--- /dev/null
+++ b/sounds/haha.wav
Binary files differ
diff --git a/sounds/hoo-ah.wav b/sounds/hoo-ah.wav
new file mode 100755
index 0000000..bf8df33
--- /dev/null
+++ b/sounds/hoo-ah.wav
Binary files differ
diff --git a/sounds/i-like-it.wav b/sounds/i-like-it.wav
new file mode 100755
index 0000000..9f8437f
--- /dev/null
+++ b/sounds/i-like-it.wav
Binary files differ
diff --git a/sounds/impressive.wav b/sounds/impressive.wav
new file mode 100755
index 0000000..0069b4a
--- /dev/null
+++ b/sounds/impressive.wav
Binary files differ
diff --git a/sounds/metal_clang_2.wav b/sounds/metal_clang_2.wav
new file mode 100755
index 0000000..22d74ab
--- /dev/null
+++ b/sounds/metal_clang_2.wav
Binary files differ
diff --git a/sounds/mk-excellent.wav b/sounds/mk-excellent.wav
new file mode 100755
index 0000000..1797565
--- /dev/null
+++ b/sounds/mk-excellent.wav
Binary files differ
diff --git a/sounds/negative.wav b/sounds/negative.wav
new file mode 100755
index 0000000..17a0589
--- /dev/null
+++ b/sounds/negative.wav
Binary files differ
diff --git a/sounds/not.wav b/sounds/not.wav
new file mode 100755
index 0000000..338c87d
--- /dev/null
+++ b/sounds/not.wav
Binary files differ
diff --git a/sounds/oh-yeah.wav b/sounds/oh-yeah.wav
new file mode 100755
index 0000000..a9bcf3a
--- /dev/null
+++ b/sounds/oh-yeah.wav
Binary files differ
diff --git a/sounds/oh_no.wav b/sounds/oh_no.wav
new file mode 100755
index 0000000..fb769b4
--- /dev/null
+++ b/sounds/oh_no.wav
Binary files differ
diff --git a/sounds/ooh-yeah.wav b/sounds/ooh-yeah.wav
new file mode 100755
index 0000000..a47d41e
--- /dev/null
+++ b/sounds/ooh-yeah.wav
Binary files differ
diff --git a/sounds/sorry.wav b/sounds/sorry.wav
new file mode 100755
index 0000000..0cddd4c
--- /dev/null
+++ b/sounds/sorry.wav
Binary files differ
diff --git a/sounds/sweet.wav b/sounds/sweet.wav
new file mode 100755
index 0000000..3dd6533
--- /dev/null
+++ b/sounds/sweet.wav
Binary files differ
diff --git a/sounds/that-was-cool.wav b/sounds/that-was-cool.wav
new file mode 100755
index 0000000..228e589
--- /dev/null
+++ b/sounds/that-was-cool.wav
Binary files differ
diff --git a/sounds/transfer_data.wav b/sounds/transfer_data.wav
new file mode 100755
index 0000000..23bfba0
--- /dev/null
+++ b/sounds/transfer_data.wav
Binary files differ
diff --git a/sounds/yes-i-like-it.wav b/sounds/yes-i-like-it.wav
new file mode 100755
index 0000000..dbb322a
--- /dev/null
+++ b/sounds/yes-i-like-it.wav
Binary files differ
diff --git a/wiki b/wiki
new file mode 100755
index 0000000..e19b6f9
--- /dev/null
+++ b/wiki
@@ -0,0 +1,105 @@
+'''XO ImageQuiz''' is an open-source game for kids aged 6-14 with a simple concept: one question, one image, one click; becoming one of the greatest learning experiences of the future :-)
+
+* <font style='color:green;font-weight:bold;'>Learning Tool</font> ([http://www.linuxuser.at/xoquiz/game.php Demo])
+** Kids can explore, create, share and translate questions
+** Mess around with the database: create, delete, download and share bundles
+** Play single-player games with flashcard-system or just a plain quick quiz-game
+** Multi-player games over the mesh - either vs or in coop mode
+** Extend the game by plugins they create (and share) or download via web / mesh / schoolserver
+** Get introductive reading and further links for each question
+
+* <font style='color:green;font-weight:bold;'>Teaching Tool</font>
+** Teachers can easily build individual question-packages accompanying their lessions (and share / download them)
+** Resarching a topic and creating questions for subtopics, as well as engaging in those of other groups, can be part of classes and homeworks
+
+
+* Current status: In development (~ 30% done)
+* [http://www.olpcaustria.org/mediawiki/index.php/XO_ImageQuiz/Screenshots Screenshots], [http://www.olpcaustria.org/mediawiki/index.php/XO_ImageQuiz/Plugins Plugin How-To], [http://dev.laptop.org/git?p=projects/xo-quiz Source]
+ git clone git://dev.laptop.org/projects/xo-quiz
+
+= Summary =
+* <font style='color:green;font-weight:bold;'>Usability</font>: Easy to use tool for learning and teaching
+* <font style='color:green;font-weight:bold;'>Interactivity</font>: Messing around with all parts of the activity is encouraged and simplified
+* <font style='color:green;font-weight:bold;'>Collaboration</font>: Create, download and share questions, and have multi-player games
+* <font style='color:green;font-weight:bold;'>Localization</font>: Supporting all languages (unicode) and providing ways to translate questions (in-game & gettext)
+* <font style='color:green;font-weight:bold;'>Extensiblity</font>: Categories and their images can be shared over internet, schoolserver and mesh
+* <font style='color:green;font-weight:bold;'>Balance</font>: Easy, medium and hard questions, each for different ages
+* <font style='color:green;font-weight:bold;'>Modular Layout</font>: Written as a plugin framework; even the game-modes are plugins
+* <font style='color:green;font-weight:bold;'>Constructive</font>: Building up knowledge with fun and [http://en.wikipedia.org/wiki/Flashcard system]
+* <font style='color:green;font-weight:bold;'>XO Featured</font>: Integrating the xo-cam, mesh and the users friends
+* <font style='color:green;font-weight:bold;'>Categorized</font>: like arts, astronomy, food, geography, health, technics, wildlife, ...
+
+
+[[Image:Xoquiz_astro.gif]]
+
+
+= Development =
+
+== Overview ==
+The activity for the xo will be written in Python using:
+
+* [[Pygame]] / the [[Pygame_wrapper|olpc pygames wrapper]] for display and xo interaction (cam, net, mesh)
+* [[Pysqlite]] as data-storage system
+* [[Unicode]]
+
+
+== Source ==
+The current source-code is available via git from [http://dev.laptop.org/git?p=projects/xo-quiz dev.laptop.org]:
+ git clone git://dev.laptop.org/projects/xo-quiz
+
+
+== Roadmap ==
+ * November 07: - Finishing the web-application
+ - Start of Python Activity
+
+ * December 07: - Building basic high-quality database
+ - Start of Single Player
+
+ * January 08: - Single Player Mode
+
+ * February 08: - Tests and Multiplayer
+
+ * March 08: - Final Design and Testing
+
+ * April 08: - Release of version 1.0
+
+
+== Involved ==
+* Chris Hager (Austria) (chris(at)linuxuser.at)
+* Georg Witwer (Austria)
+* [http://www.olpcaustria.org OLPC Austria]
+
+
+== Help ==
+We are looking for people with:
+* Python knowledge
+* Design / Layout experience
+* Time to create a few questions
+* Ideas for the concept
+
+If you have any of those, and are willing to contribute a few hours, please drop me a line: chris (at) linuxuser.at
+
+
+= Related Links =
+* General
+** [[Game_development]]
+** [[Game_development_HOWTO]]
+** [[Development_issues]]
+** [http://en.wikipedia.org/wiki/Flashcard Flashcard Learning System]
+
+
+* [http://python.org/ Python]
+** [http://www.brunningonline.net/simon/python/quick-ref2_0.html Python Quick Reference]
+** [http://www.jorendorff.com/articles/unicode/python.html Unicode in Python]
+
+
+* [http://pygame.org Pygame]
+** [http://pygame.org/docs/ref/index.html PyGame Reference]
+** [http://rene.f0o.com/mywiki/PythonGameProgramming PythonGameProgramming] (Excellent, quick diy-introduction on images, sound, display, event handling, ...)
+** [http://www.pygame.org/wiki/tutorials PyGame Tutorials Overview]
+
+
+* [http://trac.edgewall.org/wiki/PySqlite PySqlite]
+** [http://initd.org/pub/software/pysqlite/doc/usage-guide.html Usage Guide]
+** [http://lists.initd.org/pipermail/pysqlite/2005-November/000218.html Unicode and PySqlite]
+
diff --git a/xmlio.py b/xmlio.py
new file mode 100755
index 0000000..86b3b9e
--- /dev/null
+++ b/xmlio.py
@@ -0,0 +1,56 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# reads and writes xml files
+# output files indented
+
+import xml.etree.ElementTree as ET
+
+#tree = Xmlio(path)
+#elem = tree.getroot()
+#tree.save(path) --- uses root of tree
+#tree.save(path,root=elem) -- saves tree from supplied root element
+
+class Xmlio():
+
+ def __init__(self, path = None, root = None):
+ print 'params: path=', path, 'root=', root
+ if root:
+ if path:
+ self.root = root
+ self.tree = ET.ElementTree(self.root)
+ else:
+ self.root = ET.Element(root)
+ self.tree = ET.ElementTree(self.root)
+ else:
+ try:
+ self.tree = ET.parse(path)
+ except:
+ print "error parsing xml file", path
+ return
+ self.root = self.tree.getroot()
+
+ def getroot(self, root = ''):
+ if not root:
+ return self.root
+ else:
+ return ET.Element(root)
+
+ def indent(self, elem, level=0):
+ i = "\n" + level * " "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ for elem in elem:
+ self.indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
+
+ def save(self, path, root = ''):
+ if not root:
+ root = self.root
+ self.indent(root) #prettyprint
+ ET.ElementTree(root).write(path)