Création branche pinbe à partir d'une copie de branches/V7@73.
authorpin <pin@fe552daf-6dbe-4428-90eb-1537e0879342>
Thu, 4 Feb 2010 14:32:46 +0000 (14:32 +0000)
committerpin <pin@fe552daf-6dbe-4428-90eb-1537e0879342>
Thu, 4 Feb 2010 14:32:46 +0000 (14:32 +0000)
git-svn-id: https://svn.cri.ensmp.fr/svn/minwii/trunk@1 fe552daf-6dbe-4428-90eb-1537e0879342

354 files changed:
.project [new file with mode: 0755]
.pydevproject [new file with mode: 0755]
.settings/org.eclipse.core.resources.prefs [new file with mode: 0644]
WiiMouse IR G.PIE [new file with mode: 0755]
WiiMouse IR coeff0.1.PIE [new file with mode: 0755]
WiiMouse2IR coeff0.1.PIE [new file with mode: 0644]
WiiMouse4IR coeff0.1.PIE [new file with mode: 0644]
src/controllers/Wiimote.py [new file with mode: 0755]
src/controllers/__init__.py [new file with mode: 0755]
src/cursor/WarpingCursor.py [new file with mode: 0755]
src/cursor/__init__.py [new file with mode: 0755]
src/cursor/cursorImages/black/0.png [new file with mode: 0755]
src/cursor/cursorImages/black/1.png [new file with mode: 0755]
src/cursor/cursorImages/black/10.png [new file with mode: 0755]
src/cursor/cursorImages/black/2.png [new file with mode: 0755]
src/cursor/cursorImages/black/3.png [new file with mode: 0755]
src/cursor/cursorImages/black/4.png [new file with mode: 0755]
src/cursor/cursorImages/black/5.png [new file with mode: 0755]
src/cursor/cursorImages/black/6.png [new file with mode: 0755]
src/cursor/cursorImages/black/7.png [new file with mode: 0755]
src/cursor/cursorImages/black/8.png [new file with mode: 0755]
src/cursor/cursorImages/black/9.png [new file with mode: 0755]
src/cursor/cursorImages/black/cursorBlack.svg [new file with mode: 0755]
src/cursor/cursorImages/black/flash.png [new file with mode: 0644]
src/cursor/cursorImages/blue/0.png [new file with mode: 0644]
src/cursor/cursorImages/blue/1.png [new file with mode: 0644]
src/cursor/cursorImages/blue/10.png [new file with mode: 0644]
src/cursor/cursorImages/blue/2.png [new file with mode: 0644]
src/cursor/cursorImages/blue/3.png [new file with mode: 0644]
src/cursor/cursorImages/blue/4.png [new file with mode: 0644]
src/cursor/cursorImages/blue/5.png [new file with mode: 0644]
src/cursor/cursorImages/blue/6.png [new file with mode: 0644]
src/cursor/cursorImages/blue/7.png [new file with mode: 0644]
src/cursor/cursorImages/blue/8.png [new file with mode: 0644]
src/cursor/cursorImages/blue/9.png [new file with mode: 0644]
src/cursor/cursorImages/blue/cursorBlue.svg [new file with mode: 0644]
src/cursor/cursorImages/green/0.png [new file with mode: 0644]
src/cursor/cursorImages/green/1.png [new file with mode: 0644]
src/cursor/cursorImages/green/10.png [new file with mode: 0644]
src/cursor/cursorImages/green/2.png [new file with mode: 0644]
src/cursor/cursorImages/green/3.png [new file with mode: 0644]
src/cursor/cursorImages/green/4.png [new file with mode: 0644]
src/cursor/cursorImages/green/5.png [new file with mode: 0644]
src/cursor/cursorImages/green/6.png [new file with mode: 0644]
src/cursor/cursorImages/green/7.png [new file with mode: 0644]
src/cursor/cursorImages/green/8.png [new file with mode: 0644]
src/cursor/cursorImages/green/9.png [new file with mode: 0644]
src/cursor/cursorImages/green/cursorGreen.svg [new file with mode: 0644]
src/cursor/cursorImages/red/0.png [new file with mode: 0644]
src/cursor/cursorImages/red/1.png [new file with mode: 0644]
src/cursor/cursorImages/red/10.png [new file with mode: 0644]
src/cursor/cursorImages/red/2.png [new file with mode: 0644]
src/cursor/cursorImages/red/3.png [new file with mode: 0644]
src/cursor/cursorImages/red/4.png [new file with mode: 0644]
src/cursor/cursorImages/red/5.png [new file with mode: 0644]
src/cursor/cursorImages/red/6.png [new file with mode: 0644]
src/cursor/cursorImages/red/7.png [new file with mode: 0644]
src/cursor/cursorImages/red/8.png [new file with mode: 0644]
src/cursor/cursorImages/red/9.png [new file with mode: 0644]
src/cursor/cursorImages/red/cursorRed.svg [new file with mode: 0644]
src/data/themes/default/Vera.ttf [new file with mode: 0644]
src/data/themes/default/box.down.png [new file with mode: 0644]
src/data/themes/default/box.hover.png [new file with mode: 0644]
src/data/themes/default/box.normal.png [new file with mode: 0644]
src/data/themes/default/box.xcf [new file with mode: 0644]
src/data/themes/default/button.down.tga [new file with mode: 0644]
src/data/themes/default/button.hover.tga [new file with mode: 0644]
src/data/themes/default/button.normal.tga [new file with mode: 0644]
src/data/themes/default/check.png [new file with mode: 0644]
src/data/themes/default/checkbox.off.hover.tga [new file with mode: 0644]
src/data/themes/default/checkbox.off.normal.tga [new file with mode: 0644]
src/data/themes/default/checkbox.on.hover.tga [new file with mode: 0644]
src/data/themes/default/checkbox.on.normal.tga [new file with mode: 0644]
src/data/themes/default/config.txt [new file with mode: 0644]
src/data/themes/default/console.input.focus.png [new file with mode: 0644]
src/data/themes/default/console.input.normal.png [new file with mode: 0644]
src/data/themes/default/console.png [new file with mode: 0644]
src/data/themes/default/desktop.png [new file with mode: 0644]
src/data/themes/default/desktop.xcf [new file with mode: 0644]
src/data/themes/default/dialog.bar.png [new file with mode: 0644]
src/data/themes/default/dialog.close.down.tga [new file with mode: 0644]
src/data/themes/default/dialog.close.hover.tga [new file with mode: 0644]
src/data/themes/default/dialog.close.normal.tga [new file with mode: 0644]
src/data/themes/default/dialog.png [new file with mode: 0644]
src/data/themes/default/dot.down.png [new file with mode: 0644]
src/data/themes/default/dot.hover.png [new file with mode: 0644]
src/data/themes/default/dot.normal.png [new file with mode: 0644]
src/data/themes/default/dot.xcf [new file with mode: 0644]
src/data/themes/default/down.png [new file with mode: 0644]
src/data/themes/default/filebrowser.folder.png [new file with mode: 0644]
src/data/themes/default/generate.py [new file with mode: 0644]
src/data/themes/default/hslider.bar.hover.tga [new file with mode: 0644]
src/data/themes/default/hslider.bar.normal.tga [new file with mode: 0644]
src/data/themes/default/hslider.left.tga [new file with mode: 0644]
src/data/themes/default/hslider.right.tga [new file with mode: 0644]
src/data/themes/default/hslider.tga [new file with mode: 0644]
src/data/themes/default/idot.normal.png [new file with mode: 0644]
src/data/themes/default/input.focus.png [new file with mode: 0644]
src/data/themes/default/input.normal.png [new file with mode: 0644]
src/data/themes/default/left.png [new file with mode: 0644]
src/data/themes/default/list.item.down.png [new file with mode: 0644]
src/data/themes/default/list.item.hover.png [new file with mode: 0644]
src/data/themes/default/list.item.normal.png [new file with mode: 0644]
src/data/themes/default/list.png [new file with mode: 0644]
src/data/themes/default/listitem.down.tga [new file with mode: 0644]
src/data/themes/default/listitem.hover.tga [new file with mode: 0644]
src/data/themes/default/listitem.normal.tga [new file with mode: 0644]
src/data/themes/default/menu.down.tga [new file with mode: 0644]
src/data/themes/default/menu.hover.tga [new file with mode: 0644]
src/data/themes/default/menu.normal.tga [new file with mode: 0644]
src/data/themes/default/notes.txt [new file with mode: 0644]
src/data/themes/default/out.tga [new file with mode: 0644]
src/data/themes/default/progressbar.bar.tga [new file with mode: 0644]
src/data/themes/default/progressbar.tga [new file with mode: 0644]
src/data/themes/default/radio.off.hover.tga [new file with mode: 0644]
src/data/themes/default/radio.off.normal.tga [new file with mode: 0644]
src/data/themes/default/radio.on.hover.tga [new file with mode: 0644]
src/data/themes/default/radio.on.normal.tga [new file with mode: 0644]
src/data/themes/default/radio.png [new file with mode: 0644]
src/data/themes/default/rdot.down.png [new file with mode: 0644]
src/data/themes/default/rdot.hover.png [new file with mode: 0644]
src/data/themes/default/rdot.normal.png [new file with mode: 0644]
src/data/themes/default/right.png [new file with mode: 0644]
src/data/themes/default/sbox.normal.png [new file with mode: 0644]
src/data/themes/default/scroller.slide.bar.hover.tga [new file with mode: 0644]
src/data/themes/default/scroller.slide.bar.normal.tga [new file with mode: 0644]
src/data/themes/default/scroller.slide.h.tga [new file with mode: 0644]
src/data/themes/default/scroller.slide.v.tga [new file with mode: 0644]
src/data/themes/default/select.arrow.down.tga [new file with mode: 0644]
src/data/themes/default/select.arrow.hover.tga [new file with mode: 0644]
src/data/themes/default/select.arrow.normal.tga [new file with mode: 0644]
src/data/themes/default/select.arrow.png [new file with mode: 0644]
src/data/themes/default/select.option.hover.png [new file with mode: 0644]
src/data/themes/default/select.option.normal.png [new file with mode: 0644]
src/data/themes/default/select.options.png [new file with mode: 0644]
src/data/themes/default/select.selected.down.tga [new file with mode: 0644]
src/data/themes/default/select.selected.hover.tga [new file with mode: 0644]
src/data/themes/default/select.selected.normal.tga [new file with mode: 0644]
src/data/themes/default/slider.bar.hover.tga [new file with mode: 0644]
src/data/themes/default/slider.bar.normal.tga [new file with mode: 0644]
src/data/themes/default/slider.tga [new file with mode: 0644]
src/data/themes/default/tool.down.tga [new file with mode: 0644]
src/data/themes/default/tool.hover.tga [new file with mode: 0644]
src/data/themes/default/tool.normal.tga [new file with mode: 0644]
src/data/themes/default/up.png [new file with mode: 0644]
src/data/themes/default/vbox.normal.png [new file with mode: 0644]
src/data/themes/default/vdot.down.png [new file with mode: 0644]
src/data/themes/default/vdot.hover.png [new file with mode: 0644]
src/data/themes/default/vdot.normal.png [new file with mode: 0644]
src/data/themes/default/vsbox.normal.png [new file with mode: 0644]
src/data/themes/default/vslider.bar.hover.tga [new file with mode: 0644]
src/data/themes/default/vslider.bar.normal.tga [new file with mode: 0644]
src/data/themes/default/vslider.down.tga [new file with mode: 0644]
src/data/themes/default/vslider.tga [new file with mode: 0644]
src/data/themes/default/vslider.up.tga [new file with mode: 0644]
src/data/themes/default/x.png [new file with mode: 0644]
src/data/themes/gray/Vera.ttf [new file with mode: 0644]
src/data/themes/gray/box.down.png [new file with mode: 0644]
src/data/themes/gray/box.normal.png [new file with mode: 0644]
src/data/themes/gray/button.down.png [new file with mode: 0644]
src/data/themes/gray/button.normal.png [new file with mode: 0644]
src/data/themes/gray/checkbox.off.down.png [new file with mode: 0644]
src/data/themes/gray/checkbox.off.normal.png [new file with mode: 0644]
src/data/themes/gray/checkbox.on.down.png [new file with mode: 0644]
src/data/themes/gray/checkbox.on.normal.png [new file with mode: 0644]
src/data/themes/gray/config.txt [new file with mode: 0644]
src/data/themes/gray/console.input.focus.png [new file with mode: 0644]
src/data/themes/gray/console.input.normal.png [new file with mode: 0644]
src/data/themes/gray/console.png [new file with mode: 0644]
src/data/themes/gray/desktop.png [new file with mode: 0644]
src/data/themes/gray/dialog.bar.png [new file with mode: 0644]
src/data/themes/gray/dialog.close.down.png [new file with mode: 0644]
src/data/themes/gray/dialog.close.normal.png [new file with mode: 0644]
src/data/themes/gray/dialog.png [new file with mode: 0644]
src/data/themes/gray/filebrowser.folder.png [new file with mode: 0644]
src/data/themes/gray/input.focus.png [new file with mode: 0644]
src/data/themes/gray/input.normal.png [new file with mode: 0644]
src/data/themes/gray/list.item.normal.png [new file with mode: 0644]
src/data/themes/gray/list.png [new file with mode: 0644]
src/data/themes/gray/menu.down.png [new file with mode: 0644]
src/data/themes/gray/menu.hover.png [new file with mode: 0644]
src/data/themes/gray/menu.normal.png [new file with mode: 0644]
src/data/themes/gray/menu.option.hover.png [new file with mode: 0644]
src/data/themes/gray/menu.option.normal.png [new file with mode: 0644]
src/data/themes/gray/radio.off.down.png [new file with mode: 0644]
src/data/themes/gray/radio.off.normal.png [new file with mode: 0644]
src/data/themes/gray/radio.on.down.png [new file with mode: 0644]
src/data/themes/gray/radio.on.normal.png [new file with mode: 0644]
src/data/themes/gray/select.arrow.down.png [new file with mode: 0644]
src/data/themes/gray/select.arrow.normal.png [new file with mode: 0644]
src/data/themes/gray/select.arrow.png [new file with mode: 0644]
src/data/themes/gray/select.option.normal.png [new file with mode: 0644]
src/data/themes/gray/select.options.png [new file with mode: 0644]
src/data/themes/gray/select.selected.normal.png [new file with mode: 0644]
src/data/themes/gray/slider.bar.normal.png [new file with mode: 0644]
src/data/themes/gray/slider.png [new file with mode: 0644]
src/data/themes/gray/tool.down.png [new file with mode: 0644]
src/data/themes/gray/tool.normal.png [new file with mode: 0644]
src/data/themes/tools/config.txt [new file with mode: 0644]
src/data/themes/tools/icons48.bkgr.tga [new file with mode: 0644]
src/data/themes/tools/icons48.code.tga [new file with mode: 0644]
src/data/themes/tools/icons48.draw.tga [new file with mode: 0644]
src/data/themes/tools/icons48.eraser.tga [new file with mode: 0644]
src/data/themes/tools/icons48.fill.tga [new file with mode: 0644]
src/data/themes/tools/icons48.line.tga [new file with mode: 0644]
src/data/themes/tools/icons48.pixel.tga [new file with mode: 0644]
src/data/themes/tools/icons48.select.tga [new file with mode: 0644]
src/data/themes/tools/icons48.tile.tga [new file with mode: 0644]
src/dataTools/__init__.py [new file with mode: 0644]
src/dataTools/__init__.pyc [new file with mode: 0644]
src/dataTools/odict.py [new file with mode: 0644]
src/dataTools/odict.pyc [new file with mode: 0644]
src/gradients/__init__.py [new file with mode: 0755]
src/gradients/gradients.py [new file with mode: 0755]
src/gui/DefaultFamiliarizer.py [new file with mode: 0644]
src/gui/DummyInstrumentChoice.py [new file with mode: 0644]
src/gui/Familiarizer.py [new file with mode: 0644]
src/gui/FamiliarizerPGUConfiguration.py [new file with mode: 0644]
src/gui/InstrumentChoice.py [new file with mode: 0755]
src/gui/MINWiiDialog.py [new file with mode: 0644]
src/gui/PGUConfiguration.py [new file with mode: 0644]
src/gui/PlayingScreen.py [new file with mode: 0755]
src/gui/SongFamiliarizer.py [new file with mode: 0644]
src/gui/SongPlayingScreen.py [new file with mode: 0644]
src/gui/StaticFamiliarizer.py [new file with mode: 0644]
src/gui/__init__.py [new file with mode: 0755]
src/gui/constants.py [new file with mode: 0755]
src/instruments/Instrument.py [new file with mode: 0755]
src/instruments/__init__.py [new file with mode: 0755]
src/instruments/instrumentImages/accordeon.jpg [new file with mode: 0755]
src/instruments/instrumentImages/accordeonOld.jpg [new file with mode: 0755]
src/instruments/instrumentImages/celesta.jpg [new file with mode: 0755]
src/instruments/instrumentImages/celestaOld.jpg [new file with mode: 0755]
src/instruments/instrumentImages/flute.jpg [new file with mode: 0755]
src/instruments/instrumentImages/guitare.jpg [new file with mode: 0755]
src/instruments/instrumentImages/orgue.jpg [new file with mode: 0755]
src/instruments/instrumentImages/piano.jpg [new file with mode: 0755]
src/instruments/instrumentImages/tuba.jpg [new file with mode: 0755]
src/instruments/instrumentImages/violon.jpg [new file with mode: 0755]
src/instruments/instrumentImages/violoncelle.jpg [new file with mode: 0755]
src/launcher/__init__.py [new file with mode: 0755]
src/logging/EventLog.py [new file with mode: 0755]
src/logging/FamiliarizerLog.py [new file with mode: 0644]
src/logging/Log.py [new file with mode: 0755]
src/logging/LogPGUAnalyzer.py [new file with mode: 0644]
src/logging/LogPGUPlayer.py [new file with mode: 0644]
src/logging/PickleableEvent.py [new file with mode: 0755]
src/logging/__init__.py [new file with mode: 0755]
src/mxmMidi/DataTypeConverters.py [new file with mode: 0644]
src/mxmMidi/DataTypeConverters.pyc [new file with mode: 0644]
src/mxmMidi/EventDispatcher.py [new file with mode: 0644]
src/mxmMidi/EventDispatcher.pyc [new file with mode: 0644]
src/mxmMidi/MidiFileParser.py [new file with mode: 0644]
src/mxmMidi/MidiFileParser.pyc [new file with mode: 0644]
src/mxmMidi/MidiInFile.py [new file with mode: 0644]
src/mxmMidi/MidiInFile.pyc [new file with mode: 0644]
src/mxmMidi/MidiInStream.py [new file with mode: 0644]
src/mxmMidi/MidiOutFile.py [new file with mode: 0644]
src/mxmMidi/MidiOutStream.py [new file with mode: 0644]
src/mxmMidi/MidiOutStream.pyc [new file with mode: 0644]
src/mxmMidi/MidiToText.py [new file with mode: 0644]
src/mxmMidi/RawInstreamFile.py [new file with mode: 0644]
src/mxmMidi/RawInstreamFile.pyc [new file with mode: 0644]
src/mxmMidi/RawOutstreamFile.py [new file with mode: 0644]
src/mxmMidi/__init__.py [new file with mode: 0644]
src/mxmMidi/changes.txt [new file with mode: 0644]
src/mxmMidi/constants.py [new file with mode: 0644]
src/mxmMidi/constants.pyc [new file with mode: 0644]
src/mxmMidi/example_mimimal_type0.py [new file with mode: 0644]
src/mxmMidi/example_print_channel_0.py [new file with mode: 0644]
src/mxmMidi/example_print_events.py [new file with mode: 0644]
src/mxmMidi/example_print_file.py [new file with mode: 0644]
src/mxmMidi/example_transpose_octave.py [new file with mode: 0644]
src/mxmMidi/experimental/EventDispatcherBase.py [new file with mode: 0644]
src/mxmMidi/experimental/MidiOutPassThrough.py [new file with mode: 0644]
src/mxmMidi/experimental/MidiOutStreamBase.py [new file with mode: 0644]
src/mxmMidi/experimental/readme.txt [new file with mode: 0644]
src/mxmMidi/midiout/minimal_type0.mid [new file with mode: 0644]
src/mxmMidi/midiout/transposed.mid [new file with mode: 0644]
src/mxmMidi/readme.txt [new file with mode: 0644]
src/mxmMidi/test/midifiles/midiout.mid [new file with mode: 0644]
src/mxmMidi/test/midifiles/minimal-cubase-type0.mid [new file with mode: 0644]
src/mxmMidi/test/midifiles/minimal-cubase-type1.mid [new file with mode: 0644]
src/mxmMidi/test/midifiles/minimal.mid [new file with mode: 0644]
src/mxmMidi/test/midifiles/minimal.txt [new file with mode: 0644]
src/mxmMidi/test/midifiles/minimal_analyse.txt [new file with mode: 0644]
src/mxmMidi/test/midifiles/readme.txt [new file with mode: 0644]
src/mxmMidi/test/readme.txt [new file with mode: 0644]
src/mxmMidi/version.txt [new file with mode: 0644]
src/pgu/__init__.py [new file with mode: 0644]
src/pgu/algo.py [new file with mode: 0644]
src/pgu/ani.py [new file with mode: 0644]
src/pgu/engine.py [new file with mode: 0644]
src/pgu/fonts.py [new file with mode: 0644]
src/pgu/gui/__init__.py [new file with mode: 0644]
src/pgu/gui/app.py [new file with mode: 0644]
src/pgu/gui/area.py [new file with mode: 0644]
src/pgu/gui/basic.py [new file with mode: 0644]
src/pgu/gui/button.py [new file with mode: 0644]
src/pgu/gui/const.py [new file with mode: 0644]
src/pgu/gui/container.py [new file with mode: 0644]
src/pgu/gui/deprecated.py [new file with mode: 0644]
src/pgu/gui/dialog.py [new file with mode: 0644]
src/pgu/gui/document.py [new file with mode: 0644]
src/pgu/gui/form.py [new file with mode: 0644]
src/pgu/gui/group.py [new file with mode: 0644]
src/pgu/gui/input.py [new file with mode: 0644]
src/pgu/gui/keysym.py [new file with mode: 0644]
src/pgu/gui/layout.py [new file with mode: 0644]
src/pgu/gui/menus.py [new file with mode: 0644]
src/pgu/gui/misc.py [new file with mode: 0644]
src/pgu/gui/pguglobals.py [new file with mode: 0644]
src/pgu/gui/select.py [new file with mode: 0644]
src/pgu/gui/slider.py [new file with mode: 0644]
src/pgu/gui/style.py [new file with mode: 0644]
src/pgu/gui/surface.py [new file with mode: 0644]
src/pgu/gui/table.py [new file with mode: 0644]
src/pgu/gui/textarea.py [new file with mode: 0644]
src/pgu/gui/theme.py [new file with mode: 0644]
src/pgu/gui/widget.py [new file with mode: 0644]
src/pgu/hexvid.py [new file with mode: 0644]
src/pgu/high.py [new file with mode: 0644]
src/pgu/html.py [new file with mode: 0644]
src/pgu/isovid.py [new file with mode: 0644]
src/pgu/layout.py [new file with mode: 0644]
src/pgu/text.py [new file with mode: 0644]
src/pgu/tilevid.py [new file with mode: 0644]
src/pgu/timer.py [new file with mode: 0644]
src/pgu/vid.py [new file with mode: 0644]
src/songs/MidiToSong.py [new file with mode: 0644]
src/songs/Song.py [new file with mode: 0755]
src/songs/__init__.py [new file with mode: 0755]
src/songs/midis/bergere.mid [new file with mode: 0644]
src/songs/midis/boheme.mid [new file with mode: 0644]
src/songs/midis/cerises.mid [new file with mode: 0644]
src/songs/midis/feuillesmortes.mid [new file with mode: 0644]
src/songs/midis/midi_export.mid [new file with mode: 0644]
src/songs/midis/test.mid [new file with mode: 0644]
src/songs/midis/vierose.mid [new file with mode: 0644]
src/songs/smwis/Charles Aznavour/boheme.smwi [new file with mode: 0644]
src/songs/smwis/Edith Piaf/foule.smwi [new file with mode: 0644]
src/songs/smwis/Edith Piaf/vierose.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/bergere.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/cerises.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/clairdelalune.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/clairefontaine.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/eauvive.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/frerejacques.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/jadbt.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/marseillaise.smwi [new file with mode: 0644]
src/songs/smwis/Grands Classiques/papanoel.smwi [new file with mode: 0644]
src/songs/smwis/Jean Drejac/vinblanc.smwi [new file with mode: 0644]
src/songs/smwis/Johnny Hallyday/penitencier.smwi [new file with mode: 0644]
src/songs/smwis/Yves Montand/feuillesmortes.smwi [new file with mode: 0644]

diff --git a/.project b/.project
new file mode 100755 (executable)
index 0000000..30d02f5
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>MINWii</name>\r
+       <comment></comment>\r
+       <projects>\r
+       </projects>\r
+       <buildSpec>\r
+               <buildCommand>\r
+                       <name>org.python.pydev.PyDevBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+       </buildSpec>\r
+       <natures>\r
+               <nature>org.python.pydev.pythonNature</nature>\r
+       </natures>\r
+</projectDescription>\r
diff --git a/.pydevproject b/.pydevproject
new file mode 100755 (executable)
index 0000000..e35390d
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r
+<?eclipse-pydev version="1.0"?>\r
+\r
+<pydev_project>\r
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">\r
+<path>/MINWiiV7/src</path>\r
+</pydev_pathproperty>\r
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>\r
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>\r
+</pydev_project>\r
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644 (file)
index 0000000..6a3e2eb
--- /dev/null
@@ -0,0 +1,14 @@
+#Mon Dec 07 03:31:40 CET 2009\r
+eclipse.preferences.version=1\r
+encoding//src/mxmMidi/DataTypeConverters.py=ISO-8859-1\r
+encoding//src/mxmMidi/EventDispatcher.py=ISO-8859-1\r
+encoding//src/mxmMidi/MidiFileParser.py=ISO-8859-1\r
+encoding//src/mxmMidi/MidiInFile.py=ISO-8859-1\r
+encoding//src/mxmMidi/MidiInStream.py=ISO-8859-1\r
+encoding//src/mxmMidi/MidiOutFile.py=ISO-8859-1\r
+encoding//src/mxmMidi/MidiOutStream.py=ISO-8859-1\r
+encoding//src/mxmMidi/MidiToText.py=ISO-8859-1\r
+encoding//src/mxmMidi/RawInstreamFile.py=ISO-8859-1\r
+encoding//src/mxmMidi/RawOutstreamFile.py=ISO-8859-1\r
+encoding//src/mxmMidi/__init__.py=ISO-8859-1\r
+encoding//src/mxmMidi/constants.py=ISO-8859-1\r
diff --git a/WiiMouse IR G.PIE b/WiiMouse IR G.PIE
new file mode 100755 (executable)
index 0000000..dad484d
--- /dev/null
@@ -0,0 +1,157 @@
+Wiimote1.led1 = true\r
+Wiimote2.led1 = true\r
+Wiimote2.led4 = true\r
+Wiimote3.led2 = true\r
+Wiimote4.led2 = true\r
+Wiimote4.led4 = true\r
+Wiimote5.led3 = true\r
+Wiimote6.led3 = true\r
+Wiimote6.led4 = true\r
+//Mouse Control Script using IR\r
+//by vkapadia with much assistance from inio\r
+//vkapadia@vkapadia.com\r
+//\r
+//Calibration:\r
+//To calibrate, run this program and put the Wiimote on a flat surface face-up.\r
+//Then read the values in the debug line (next to the run button).\r
+//Change these values until the debug line reads approx. all zeros.\r
+var.xtrim = -7\r
+var.ytrim = -17\r
+var.ztrim = -7\r
+//\r
+//Options:\r
+var.deadzone = 5 //distance in pixels that you have to move the wiimote in\r
+   //order for it to register movement. Creates a "dead zone" around the pointer\r
+   //to make it easier to click. Higher = smoother but less accurate.\r
+//fake cursor init\r
+cursor2.visible = true\r
+//more options to be added later\r
+\r
+//Controls:\r
+//Point Wiimote = Move Mouse\r
+//D-Pad = Arrow Keys\r
+//B-Button = Left Click\r
+//Home = Middle Click\r
+//A-Button = Right Click\r
+//Plus and Minus = Control Volume\r
+//One = Unmapped\r
+//Two = Unmapped\r
+//\r
+//If the pointer hits the edge of the screen, the Wiimote will rumble a bit.\r
+//\r
+//The LEDs attempt to emulate KITT's grill from Knight Rider\r
+\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx = wiimote.RawForceX + var.xtrim\r
+var.accy = wiimote.RawForceY + var.ytrim\r
+var.accz = wiimote.RawForceZ + var.ztrim\r
+\r
+if wiimote.dot1vis and wiimote.dot2vis then\r
+\r
+  if var.accy > -7 then\r
+    var.orientation = 0\r
+  elseif var.accy > -45 then\r
+    if var.accx < 0 then\r
+      var.orientation = 3\r
+    else\r
+      var.orientation = 1\r
+    endif\r
+  else\r
+    var.orientation = 2\r
+  endif\r
+\r
+  if var.leftpoint = 0 then\r
+    if var.orientation = 0 then\r
+      if wiimote.dot1x < wiimote.dot2x then\r
+        var.leftpoint = 1\r
+      else\r
+        var.leftpoint = 2\r
+      endif\r
+    endif\r
+    if var.orientation = 1 then\r
+      if wiimote.dot1y > wiimote.dot2y then\r
+        var.leftpoint = 1\r
+      else\r
+        var.leftpoint = 2\r
+      endif\r
+    endif\r
+    if var.orientation = 2 then\r
+      if wiimote.dot1x > wiimote.dot2x then\r
+        var.leftpoint = 1\r
+      else\r
+        var.leftpoint = 2\r
+      endif\r
+    endif\r
+    if var.orientation = 3 then\r
+      if wiimote.dot1y < wiimote.dot2y then\r
+        var.leftpoint = 1\r
+      else\r
+        var.leftpoint = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint = 1 then\r
+    var.fix1x = wiimote.dot1x\r
+    var.fix1y = wiimote.dot1y\r
+    var.fix2x = wiimote.dot2x\r
+    var.fix2y = wiimote.dot2y\r
+  else\r
+    var.fix1x = wiimote.dot2x\r
+    var.fix1y = wiimote.dot2y\r
+    var.fix2x = wiimote.dot1x\r
+    var.fix2y = wiimote.dot1y\r
+  endif\r
+\r
+  var.dx = var.fix2x - var.fix1x\r
+  var.dy = var.fix2y - var.fix1y\r
+  var.cx = (var.fix1x+var.fix2x)/1024.0 - 1\r
+  var.cy = (var.fix1y+var.fix2y)/1024.0 - .75\r
+\r
+  var.d = sqrt(var.dx*var.dx+var.dy*var.dy)\r
+\r
+  var.dx = var.dx / var.d\r
+  var.dy = var.dy / var.d\r
+\r
+  var.ox = -var.dy*var.cy-var.dx*var.cx;\r
+  var.oy = -var.dx*var.cy+var.dy*var.cx;\r
+\r
+  var.ax = (var.ox * screen.desktopwidth) + (screen.desktopwidth / 2)\r
+  var.ay = (-var.oy * screen.desktopwidth) + (screen.desktopheight / 2)\r
+\r
+  var.dx = var.ax - cursor2.posx\r
+  var.dy = var.ay - cursor2.posy\r
+\r
+  var.d = sqrt((var.dx*var.dx)+(var.dy*var.dy))\r
+\r
+  var.a = 180 / (200 + var.d * var.d * var.d * .001)\r
+\r
+  if var.d <= var.deadzone then var.a = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx = cursor2.posx * var.a + var.ax * (1 - var.a)\r
+  var.finaly = cursor2.posy * var.a + var.ay * (1 - var.a)\r
+\r
+\r
+  cursor2.posx = smooth(var.finalx,3,5)\r
+  cursor2.posy = smooth(var.finaly,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint = 0\r
+\r
+endif\r
+\r
+var.xpos = var.finalx\r
+var.ypos = var.finaly\r
+ppjoy1.analog0 = ensuremaprange(var.xpos,0,screen.desktopwidth,-1,1)\r
+ppjoy1.analog1 = ensuremaprange(var.ypos,0,screen.desktopheight,-1,1)\r
+\r
+if wiimote1.B == true\r
+  ppjoy1.digital0 = true\r
+else\r
+  ppjoy1.digital0 = false\r
+endif\r
+\r
+debug = var.xpos+ "  " + var.ypos\r
diff --git a/WiiMouse IR coeff0.1.PIE b/WiiMouse IR coeff0.1.PIE
new file mode 100755 (executable)
index 0000000..8856f61
--- /dev/null
@@ -0,0 +1,166 @@
+Wiimote1.led1 = true\r
+Wiimote2.led1 = true\r
+Wiimote2.led4 = true\r
+Wiimote3.led2 = true\r
+Wiimote4.led2 = true\r
+Wiimote4.led4 = true\r
+Wiimote5.led3 = true\r
+Wiimote6.led3 = true\r
+Wiimote6.led4 = true\r
+//Mouse Control Script using IR\r
+//by vkapadia with much assistance from inio\r
+//vkapadia@vkapadia.com\r
+//\r
+//Calibration:\r
+//To calibrate, run this program and put the Wiimote on a flat surface face-up.\r
+//Then read the values in the debug line (next to the run button).\r
+//Change these values until the debug line reads approx. all zeros.\r
+var.xtrim1 = -1\r
+var.ytrim1 = -25\r
+var.ztrim1 = 2\r
+\r
+var.xtrim2 = -1\r
+var.ytrim2 = -25\r
+var.ztrim2 = 2\r
+\r
+var.coeff =  0.1\r
+\r
+//\r
+//Options:\r
+var.deadzone = 5 //distance in pixels that you have to move the wiimote in\r
+   //order for it to register movement. Creates a "dead zone" around the pointer\r
+   //to make it easier to click. Higher = smoother but less accurate.\r
+//fake cursor init\r
+\r
+//cursor2.visible = true\r
+\r
+//more options to be added later\r
+\r
+//Controls:\r
+//Point Wiimote = Move Mouse\r
+//D-Pad = Arrow Keys\r
+//B-Button = Left Click\r
+//Home = Middle Click\r
+//A-Button = Right Click\r
+//Plus and Minus = Control Volume\r
+//One = Unmapped\r
+//Two = Unmapped\r
+//\r
+//If the pointer hits the edge of the screen, the Wiimote will rumble a bit.\r
+//\r
+//The LEDs attempt to emulate KITT's grill from Knight Rider\r
+\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx1 = wiimote1.RawForceX1 + var.xtrim1\r
+var.accy1 = wiimote1.RawForceY1 + var.ytrim1\r
+var.accz1 = wiimote1.RawForceZ1 + var.ztrim1\r
+\r
+if wiimote1.dot1vis and wiimote1.dot2vis then\r
+\r
+  if var.accy1 > -7 then\r
+    var.orientation1 = 0\r
+  elseif var.accy1 > -45 then\r
+    if var.accx1 < 0 then\r
+      var.orientation1 = 3\r
+    else\r
+      var.orientation1 = 1\r
+    endif\r
+  else\r
+    var.orientation1 = 2\r
+  endif\r
+\r
+  if var.leftpoint1 = 0 then\r
+    if var.orientation1 = 0 then\r
+      if wiimote1.dot1x < wiimote1.dot2x then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 1 then\r
+      if wiimote1.dot1y > wiimote1.dot2y then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 2 then\r
+      if wiimote1.dot1x > wiimote1.dot2x then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation = 3 then\r
+      if wiimote1.dot1y < wiimote1.dot2y then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint1 = 1 then\r
+    var.fix1x1 = wiimote1.dot1x\r
+    var.fix1y1 = wiimote1.dot1y\r
+    var.fix2x1 = wiimote1.dot2x\r
+    var.fix2y1 = wiimote1.dot2y\r
+  else\r
+    var.fix1x1 = wiimote1.dot2x\r
+    var.fix1y1 = wiimote1.dot2y\r
+    var.fix2x1 = wiimote1.dot1x\r
+    var.fix2y1 = wiimote1.dot1y\r
+  endif\r
+\r
+  var.dx1 = var.fix2x1 - var.fix1x1\r
+  var.dy1 = var.fix2y1 - var.fix1y1\r
+  var.cx1 = (var.fix1x1+var.fix2x1)/1024.0 - 1\r
+  var.cy1 = (var.fix1y1+var.fix2y1)/1024.0 - .75\r
+\r
+  var.d1 = sqrt(var.dx1*var.dx1+var.dy1*var.dy1)\r
+\r
+  var.dx1 = var.dx1 / var.d1\r
+  var.dy1 = var.dy1 / var.d1\r
+\r
+  var.ox1 = -var.dy1*var.cy1-var.dx1*var.cx1;\r
+  var.oy1 = -var.dx1*var.cy1+var.dy1*var.cx1;\r
+\r
+  var.ax1 = (var.ox1 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay1 = (-var.oy1* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx1 = var.ax1 - cursor2.posx\r
+  var.dy1 = var.ay1 - cursor2.posy\r
+\r
+  var.d1 = sqrt((var.dx1*var.dx1)+(var.dy1*var.dy1))\r
+\r
+  var.a1 = 180 / (200 + var.d1 * var.d1 * var.d1 * .001)\r
+\r
+  if var.d1 <= var.deadzone then var.a1 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx1 = cursor2.posx * var.a1 + var.ax1 * (1 - var.a1)\r
+  var.finaly1 = cursor2.posy * var.a1 + var.ay1 * (1 - var.a1)\r
+\r
+\r
+  cursor2.posx = smooth(var.finalx1,3,5)\r
+  cursor2.posy = smooth(var.finaly1,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint1 = 0\r
+\r
+endif\r
+\r
+var.xpos1 = var.finalx1\r
+var.ypos1 = var.finaly1\r
+ppjoy1.analog0 = ensuremaprange(var.xpos1,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy1.analog1 = ensuremaprange(var.ypos1,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote1.B or wiimote1.A or wiimote1.Up or wiimote1.down or wiimote1.Left or wiimote1.Right == true\r
+  ppjoy1.digital0 = true\r
+else\r
+  ppjoy1.digital0 = false\r
+endif\r
+\r
+debug = var.accx1+ "  " + var.accy1+ " " + var.accz1\r
diff --git a/WiiMouse2IR coeff0.1.PIE b/WiiMouse2IR coeff0.1.PIE
new file mode 100644 (file)
index 0000000..27c9d52
--- /dev/null
@@ -0,0 +1,281 @@
+Wiimote1.led1 = true\r
+Wiimote2.led1 = true\r
+Wiimote2.led4 = true\r
+Wiimote3.led2 = true\r
+Wiimote4.led2 = true\r
+Wiimote4.led4 = true\r
+Wiimote5.led3 = true\r
+Wiimote6.led3 = true\r
+Wiimote6.led4 = true\r
+//Mouse Control Script using IR\r
+//by vkapadia with much assistance from inio\r
+//vkapadia@vkapadia.com\r
+//\r
+//Calibration:\r
+//To calibrate, run this program and put the Wiimote on a flat surface face-up.\r
+//Then read the values in the debug line (next to the run button).\r
+//Change these values until the debug line reads approx. all zeros.\r
+var.xtrim1 = -1\r
+var.ytrim1 = -25\r
+var.ztrim1 = 2\r
+\r
+var.xtrim2 = 2\r
+var.ytrim2 = -30\r
+var.ztrim2 = 2\r
+\r
+var.coeff =  0.1\r
+\r
+//\r
+//Options:\r
+var.deadzone = 5 //distance in pixels that you have to move the wiimote in\r
+   //order for it to register movement. Creates a "dead zone" around the pointer\r
+   //to make it easier to click. Higher = smoother but less accurate.\r
+//fake cursor init\r
+\r
+//cursor2.visible = true\r
+\r
+//more options to be added later\r
+\r
+//Controls:\r
+//Point Wiimote = Move Mouse\r
+//D-Pad = Arrow Keys\r
+//B-Button = Left Click\r
+//Home = Middle Click\r
+//A-Button = Right Click\r
+//Plus and Minus = Control Volume\r
+//One = Unmapped\r
+//Two = Unmapped\r
+//\r
+//If the pointer hits the edge of the screen, the Wiimote will rumble a bit.\r
+//\r
+//The LEDs attempt to emulate KITT's grill from Knight Rider\r
+\r
+//WIIMOTE 1\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx1 = wiimote1.RawForceX + var.xtrim1\r
+var.accy1 = wiimote1.RawForceY + var.ytrim1\r
+var.accz1 = wiimote1.RawForceZ + var.ztrim1\r
+\r
+if wiimote1.dot1vis and wiimote1.dot2vis then\r
+\r
+  if var.accy1 > -7 then\r
+    var.orientation1 = 0\r
+  elseif var.accy1 > -45 then\r
+    if var.accx1 < 0 then\r
+      var.orientation1 = 3\r
+    else\r
+      var.orientation1 = 1\r
+    endif\r
+  else\r
+    var.orientation1 = 2\r
+  endif\r
+\r
+  if var.leftpoint1 = 0 then\r
+    if var.orientation1 = 0 then\r
+      if wiimote1.dot1x < wiimote1.dot2x then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 1 then\r
+      if wiimote1.dot1y > wiimote1.dot2y then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 2 then\r
+      if wiimote1.dot1x > wiimote1.dot2x then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation = 3 then\r
+      if wiimote1.dot1y < wiimote1.dot2y then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint1 = 1 then\r
+    var.fix1x1 = wiimote1.dot1x\r
+    var.fix1y1 = wiimote1.dot1y\r
+    var.fix2x1 = wiimote1.dot2x\r
+    var.fix2y1 = wiimote1.dot2y\r
+  else\r
+    var.fix1x1 = wiimote1.dot2x\r
+    var.fix1y1 = wiimote1.dot2y\r
+    var.fix2x1 = wiimote1.dot1x\r
+    var.fix2y1 = wiimote1.dot1y\r
+  endif\r
+\r
+  var.dx1 = var.fix2x1 - var.fix1x1\r
+  var.dy1 = var.fix2y1 - var.fix1y1\r
+  var.cx1 = (var.fix1x1+var.fix2x1)/1024.0 - 1\r
+  var.cy1 = (var.fix1y1+var.fix2y1)/1024.0 - .75\r
+\r
+  var.d1 = sqrt(var.dx1*var.dx1+var.dy1*var.dy1)\r
+\r
+  var.dx1 = var.dx1 / var.d1\r
+  var.dy1 = var.dy1 / var.d1\r
+\r
+  var.ox1 = -var.dy1*var.cy1-var.dx1*var.cx1;\r
+  var.oy1 = -var.dx1*var.cy1+var.dy1*var.cx1;\r
+\r
+  var.ax1 = (var.ox1 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay1 = (-var.oy1* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx1 = var.ax1 - cursor2.posx\r
+  var.dy1 = var.ay1 - cursor2.posy\r
+\r
+  var.d1 = sqrt((var.dx1*var.dx1)+(var.dy1*var.dy1))\r
+\r
+  var.a1 = 180 / (200 + var.d1 * var.d1 * var.d1 * .001)\r
+\r
+  if var.d1 <= var.deadzone then var.a1 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx1 = cursor2.posx * var.a1 + var.ax1 * (1 - var.a1)\r
+  var.finaly1 = cursor2.posy * var.a1 + var.ay1 * (1 - var.a1)\r
+\r
+\r
+  cursor2.posx = smooth(var.finalx1,3,5)\r
+  cursor2.posy = smooth(var.finaly1,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint1 = 0\r
+\r
+endif\r
+\r
+var.xpos1 = var.finalx1\r
+var.ypos1 = var.finaly1\r
+ppjoy1.analog0 = ensuremaprange(var.xpos1,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy1.analog1 = ensuremaprange(var.ypos1,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote1.B or wiimote1.A or wiimote1.Up or wiimote1.down or wiimote1.Left or wiimote1.Right == true\r
+  ppjoy1.digital0 = true\r
+else\r
+  ppjoy1.digital0 = false\r
+endif\r
+\r
+//WIIMOTE 2\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx2 = wiimote2.RawForceX + var.xtrim2\r
+var.accy2 = wiimote2.RawForceY + var.ytrim2\r
+var.accz2 = wiimote2.RawForceZ + var.ztrim2\r
+\r
+if wiimote2.dot1vis and wiimote2.dot2vis then\r
+\r
+  if var.accy2 > -7 then\r
+    var.orientation2 = 0\r
+  elseif var.accy2 > -45 then\r
+    if var.accx2 < 0 then\r
+      var.orientation2 = 3\r
+    else\r
+      var.orientation2 = 1\r
+    endif\r
+  else\r
+    var.orientation2 = 2\r
+  endif\r
+\r
+  if var.leftpoint2 = 0 then\r
+    if var.orientation2 = 0 then\r
+      if wiimote2.dot1x < wiimote2.dot2x then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+    if var.orientation2 = 1 then\r
+      if wiimote2.dot1y > wiimote2.dot2y then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+    if var.orientation2 = 2 then\r
+      if wiimote2.dot1x > wiimote2.dot2x then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+    if var.orientation = 3 then\r
+      if wiimote2.dot1y < wiimote2.dot2y then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint2 = 1 then\r
+    var.fix1x2 = wiimote2.dot1x\r
+    var.fix1y2 = wiimote2.dot1y\r
+    var.fix2x2 = wiimote2.dot2x\r
+    var.fix2y2 = wiimote2.dot2y\r
+  else\r
+    var.fix1x2 = wiimote2.dot2x\r
+    var.fix1y2 = wiimote2.dot2y\r
+    var.fix2x2 = wiimote2.dot1x\r
+    var.fix2y2 = wiimote2.dot1y\r
+  endif\r
+\r
+  var.dx2 = var.fix2x2 - var.fix1x2\r
+  var.dy2 = var.fix2y2 - var.fix1y2\r
+  var.cx2 = (var.fix1x2+var.fix2x2)/1024.0 - 1\r
+  var.cy2 = (var.fix1y2+var.fix2y2)/1024.0 - .75\r
+\r
+  var.d2 = sqrt(var.dx2*var.dx2+var.dy2*var.dy2)\r
+\r
+  var.dx2 = var.dx2 / var.d2\r
+  var.dy2 = var.dy2 / var.d2\r
+\r
+  var.ox2 = -var.dy2*var.cy2-var.dx2*var.cx2;\r
+  var.oy2 = -var.dx2*var.cy2+var.dy2*var.cx2;\r
+\r
+  var.ax2 = (var.ox2 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay2 = (-var.oy2* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx2 = var.ax2 - cursor3.posx\r
+  var.dy2 = var.ay2 - cursor3.posy\r
+\r
+  var.d2 = sqrt((var.dx2*var.dx2)+(var.dy2*var.dy2))\r
+\r
+  var.a2 = 180 / (200 + var.d2 * var.d2 * var.d2 * .001)\r
+\r
+  if var.d2 <= var.deadzone then var.a2 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx2 = cursor3.posx * var.a2 + var.ax2 * (1 - var.a2)\r
+  var.finaly2 = cursor3.posy * var.a2 + var.ay2 * (1 - var.a2)\r
+\r
+\r
+  cursor3.posx = smooth(var.finalx2,3,5)\r
+  cursor3.posy = smooth(var.finaly2,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint2 = 0\r
+\r
+endif\r
+\r
+var.xpos2 = var.finalx2\r
+var.ypos2 = var.finaly2\r
+ppjoy2.analog0 = ensuremaprange(var.xpos2,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy2.analog1 = ensuremaprange(var.ypos2,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote2.B or wiimote2.A or wiimote2.Up or wiimote2.down or wiimote2.Left or wiimote2.Right == true\r
+  ppjoy2.digital0 = true\r
+else\r
+  ppjoy2.digital0 = false\r
+endif\r
+\r
+debug = var.accx2+ "  " + var.accy2+ " " + var.accz2\r
diff --git a/WiiMouse4IR coeff0.1.PIE b/WiiMouse4IR coeff0.1.PIE
new file mode 100644 (file)
index 0000000..0aac1ab
--- /dev/null
@@ -0,0 +1,520 @@
+Wiimote1.led1 = true\r
+Wiimote2.led1 = true\r
+Wiimote2.led4 = true\r
+Wiimote3.led2 = true\r
+Wiimote4.led2 = true\r
+Wiimote4.led4 = true\r
+Wiimote5.led3 = true\r
+Wiimote6.led3 = true\r
+Wiimote6.led4 = true\r
+//Mouse Control Script using IR\r
+//by vkapadia with much assistance from inio\r
+//vkapadia@vkapadia.com\r
+//\r
+//Calibration:\r
+//To calibrate, run this program and put the Wiimote on a flat surface face-up.\r
+//Then read the values in the debug line (next to the run button).\r
+//Change these values until the debug line reads approx. all zeros.\r
+var.xtrim1 = -1\r
+var.ytrim1 = -25\r
+var.ztrim1 = 2\r
+\r
+var.xtrim2 = 3\r
+var.ytrim2 = -30\r
+var.ztrim2 = 2\r
+\r
+var.xtrim3 = 2\r
+var.ytrim3 = -27\r
+var.ztrim3 = 2\r
+\r
+var.xtrim4 = 2\r
+var.ytrim4 = -30\r
+var.ztrim4 = 2\r
+\r
+var.coeff =  0.1\r
+\r
+//\r
+//Options:\r
+var.deadzone = 5 //distance in pixels that you have to move the wiimote in\r
+   //order for it to register movement. Creates a "dead zone" around the pointer\r
+   //to make it easier to click. Higher = smoother but less accurate.\r
+//fake cursor init\r
+\r
+//cursor2.visible = true\r
+\r
+//more options to be added later\r
+\r
+//Controls:\r
+//Point Wiimote = Move Mouse\r
+//D-Pad = Arrow Keys\r
+//B-Button = Left Click\r
+//Home = Middle Click\r
+//A-Button = Right Click\r
+//Plus and Minus = Control Volume\r
+//One = Unmapped\r
+//Two = Unmapped\r
+//\r
+//If the pointer hits the edge of the screen, the Wiimote will rumble a bit.\r
+//\r
+//The LEDs attempt to emulate KITT's grill from Knight Rider\r
+\r
+//WIIMOTE 1\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx1 = wiimote1.RawForceX + var.xtrim1\r
+var.accy1 = wiimote1.RawForceY + var.ytrim1\r
+var.accz1 = wiimote1.RawForceZ + var.ztrim1\r
+\r
+if wiimote1.dot1vis and wiimote1.dot2vis then\r
+\r
+  if var.accy1 > -7 then\r
+    var.orientation1 = 0\r
+  elseif var.accy1 > -45 then\r
+    if var.accx1 < 0 then\r
+      var.orientation1 = 3\r
+    else\r
+      var.orientation1 = 1\r
+    endif\r
+  else\r
+    var.orientation1 = 2\r
+  endif\r
+\r
+  if var.leftpoint1 = 0 then\r
+    if var.orientation1 = 0 then\r
+      if wiimote1.dot1x < wiimote1.dot2x then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 1 then\r
+      if wiimote1.dot1y > wiimote1.dot2y then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 2 then\r
+      if wiimote1.dot1x > wiimote1.dot2x then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+    if var.orientation1 = 3 then\r
+      if wiimote1.dot1y < wiimote1.dot2y then\r
+        var.leftpoint1 = 1\r
+      else\r
+        var.leftpoint1 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint1 = 1 then\r
+    var.fix1x1 = wiimote1.dot1x\r
+    var.fix1y1 = wiimote1.dot1y\r
+    var.fix2x1 = wiimote1.dot2x\r
+    var.fix2y1 = wiimote1.dot2y\r
+  else\r
+    var.fix1x1 = wiimote1.dot2x\r
+    var.fix1y1 = wiimote1.dot2y\r
+    var.fix2x1 = wiimote1.dot1x\r
+    var.fix2y1 = wiimote1.dot1y\r
+  endif\r
+\r
+  var.dx1 = var.fix2x1 - var.fix1x1\r
+  var.dy1 = var.fix2y1 - var.fix1y1\r
+  var.cx1 = (var.fix1x1+var.fix2x1)/1024.0 - 1\r
+  var.cy1 = (var.fix1y1+var.fix2y1)/1024.0 - .75\r
+\r
+  var.d1 = sqrt(var.dx1*var.dx1+var.dy1*var.dy1)\r
+\r
+  var.dx1 = var.dx1 / var.d1\r
+  var.dy1 = var.dy1 / var.d1\r
+\r
+  var.ox1 = -var.dy1*var.cy1-var.dx1*var.cx1;\r
+  var.oy1 = -var.dx1*var.cy1+var.dy1*var.cx1;\r
+\r
+  var.ax1 = (var.ox1 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay1 = (-var.oy1* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx1 = var.ax1 - cursor2.posx\r
+  var.dy1 = var.ay1 - cursor2.posy\r
+\r
+  var.d1 = sqrt((var.dx1*var.dx1)+(var.dy1*var.dy1))\r
+\r
+  var.a1 = 180 / (200 + var.d1 * var.d1 * var.d1 * .001)\r
+\r
+  if var.d1 <= var.deadzone then var.a1 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx1 = cursor2.posx * var.a1 + var.ax1 * (1 - var.a1)\r
+  var.finaly1 = cursor2.posy * var.a1 + var.ay1 * (1 - var.a1)\r
+\r
+\r
+  cursor2.posx = smooth(var.finalx1,3,5)\r
+  cursor2.posy = smooth(var.finaly1,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint1 = 0\r
+\r
+endif\r
+\r
+var.xpos1 = var.finalx1\r
+var.ypos1 = var.finaly1\r
+ppjoy1.analog0 = ensuremaprange(var.xpos1,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy1.analog1 = ensuremaprange(var.ypos1,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote1.B or wiimote1.A or wiimote1.Up or wiimote1.down or wiimote1.Left or wiimote1.Right == true\r
+  ppjoy1.digital0 = true\r
+else\r
+  ppjoy1.digital0 = false\r
+endif\r
+\r
+//WIIMOTE 2\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx2 = wiimote2.RawForceX + var.xtrim2\r
+var.accy2 = wiimote2.RawForceY + var.ytrim2\r
+var.accz2 = wiimote2.RawForceZ + var.ztrim2\r
+\r
+if wiimote2.dot1vis and wiimote2.dot2vis then\r
+\r
+  if var.accy2 > -7 then\r
+    var.orientation2 = 0\r
+  elseif var.accy2 > -45 then\r
+    if var.accx2 < 0 then\r
+      var.orientation2 = 3\r
+    else\r
+      var.orientation2 = 1\r
+    endif\r
+  else\r
+    var.orientation2 = 2\r
+  endif\r
+\r
+  if var.leftpoint2 = 0 then\r
+    if var.orientation2 = 0 then\r
+      if wiimote2.dot1x < wiimote2.dot2x then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+    if var.orientation2 = 1 then\r
+      if wiimote2.dot1y > wiimote2.dot2y then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+    if var.orientation2 = 2 then\r
+      if wiimote2.dot1x > wiimote2.dot2x then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+    if var.orientation2 = 3 then\r
+      if wiimote2.dot1y < wiimote2.dot2y then\r
+        var.leftpoint2 = 1\r
+      else\r
+        var.leftpoint2 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint2 = 1 then\r
+    var.fix1x2 = wiimote2.dot1x\r
+    var.fix1y2 = wiimote2.dot1y\r
+    var.fix2x2 = wiimote2.dot2x\r
+    var.fix2y2 = wiimote2.dot2y\r
+  else\r
+    var.fix1x2 = wiimote2.dot2x\r
+    var.fix1y2 = wiimote2.dot2y\r
+    var.fix2x2 = wiimote2.dot1x\r
+    var.fix2y2 = wiimote2.dot1y\r
+  endif\r
+\r
+  var.dx2 = var.fix2x2 - var.fix1x2\r
+  var.dy2 = var.fix2y2 - var.fix1y2\r
+  var.cx2 = (var.fix1x2+var.fix2x2)/1024.0 - 1\r
+  var.cy2 = (var.fix1y2+var.fix2y2)/1024.0 - .75\r
+\r
+  var.d2 = sqrt(var.dx2*var.dx2+var.dy2*var.dy2)\r
+\r
+  var.dx2 = var.dx2 / var.d2\r
+  var.dy2 = var.dy2 / var.d2\r
+\r
+  var.ox2 = -var.dy2*var.cy2-var.dx2*var.cx2;\r
+  var.oy2 = -var.dx2*var.cy2+var.dy2*var.cx2;\r
+\r
+  var.ax2 = (var.ox2 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay2 = (-var.oy2* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx2 = var.ax2 - cursor3.posx\r
+  var.dy2 = var.ay2 - cursor3.posy\r
+\r
+  var.d2 = sqrt((var.dx2*var.dx2)+(var.dy2*var.dy2))\r
+\r
+  var.a2 = 180 / (200 + var.d2 * var.d2 * var.d2 * .001)\r
+\r
+  if var.d2 <= var.deadzone then var.a2 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx2 = cursor3.posx * var.a2 + var.ax2 * (1 - var.a2)\r
+  var.finaly2 = cursor3.posy * var.a2 + var.ay2 * (1 - var.a2)\r
+\r
+\r
+  cursor3.posx = smooth(var.finalx2,3,5)\r
+  cursor3.posy = smooth(var.finaly2,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint2 = 0\r
+\r
+endif\r
+\r
+var.xpos2 = var.finalx2\r
+var.ypos2 = var.finaly2\r
+ppjoy2.analog0 = ensuremaprange(var.xpos2,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy2.analog1 = ensuremaprange(var.ypos2,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote2.B or wiimote2.A or wiimote2.Up or wiimote2.down or wiimote2.Left or wiimote2.Right == true\r
+  ppjoy2.digital0 = true\r
+else\r
+  ppjoy2.digital0 = false\r
+endif\r
+\r
+debug = var.accx2+ "  " + var.accy2+ " " + var.accz2\r
+\r
+//WIIMOTE 3\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx3 = wiimote3.RawForceX + var.xtrim3\r
+var.accy3 = wiimote3.RawForceY + var.ytrim3\r
+var.accz3 = wiimote3.RawForceZ + var.ztrim3\r
+\r
+if wiimote3.dot1vis and wiimote3.dot2vis then\r
+\r
+  if var.accy3 > -7 then\r
+    var.orientation3 = 0\r
+  elseif var.accy3 > -45 then\r
+    if var.accx3 < 0 then\r
+      var.orientation3 = 3\r
+    else\r
+      var.orientation3 = 1\r
+    endif\r
+  else\r
+    var.orientation3 = 2\r
+  endif\r
+\r
+  if var.leftpoint3 = 0 then\r
+    if var.orientation3 = 0 then\r
+      if wiimote3.dot1x < wiimote3.dot2x then\r
+        var.leftpoint3 = 1\r
+      else\r
+        var.leftpoint3 = 2\r
+      endif\r
+    endif\r
+    if var.orientation3 = 1 then\r
+      if wiimote3.dot1y > wiimote3.dot2y then\r
+        var.leftpoint3 = 1\r
+      else\r
+        var.leftpoint3 = 2\r
+      endif\r
+    endif\r
+    if var.orientation3 = 2 then\r
+      if wiimote3.dot1x > wiimote3.dot2x then\r
+        var.leftpoint3 = 1\r
+      else\r
+        var.leftpoint3 = 2\r
+      endif\r
+    endif\r
+    if var.orientation3 = 3 then\r
+      if wiimote3.dot1y < wiimote3.dot2y then\r
+        var.leftpoint3 = 1\r
+      else\r
+        var.leftpoint3 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint3 = 1 then\r
+    var.fix1x3 = wiimote3.dot1x\r
+    var.fix1y3 = wiimote3.dot1y\r
+    var.fix2x3 = wiimote3.dot2x\r
+    var.fix2y3 = wiimote3.dot2y\r
+  else\r
+    var.fix1x3 = wiimote3.dot2x\r
+    var.fix1y3 = wiimote3.dot2y\r
+    var.fix2x3 = wiimote3.dot1x\r
+    var.fix2y3 = wiimote3.dot1y\r
+  endif\r
+\r
+  var.dx3 = var.fix2x3 - var.fix1x3\r
+  var.dy3 = var.fix2y3 - var.fix1y3\r
+  var.cx3 = (var.fix1x3+var.fix2x3)/1024.0 - 1\r
+  var.cy3 = (var.fix1y3+var.fix2y3)/1024.0 - .75\r
+\r
+  var.d3 = sqrt(var.dx3*var.dx3+var.dy3*var.dy3)\r
+\r
+  var.dx3 = var.dx3 / var.d3\r
+  var.dy3 = var.dy3 / var.d3\r
+\r
+  var.ox3 = -var.dy3*var.cy3-var.dx3*var.cx3;\r
+  var.oy3 = -var.dx3*var.cy3+var.dy3*var.cx3;\r
+\r
+  var.ax3 = (var.ox3 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay3 = (-var.oy3* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx3 = var.ax3 - cursor4.posx\r
+  var.dy3 = var.ay3 - cursor4.posy\r
+\r
+  var.d3 = sqrt((var.dx3*var.dx3)+(var.dy3*var.dy3))\r
+\r
+  var.a3 = 180 / (200 + var.d3 * var.d3 * var.d3 * .001)\r
+\r
+  if var.d3 <= var.deadzone then var.a3 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx3 = cursor4.posx * var.a3 + var.ax3 * (1 - var.a3)\r
+  var.finaly3 = cursor4.posy * var.a3 + var.ay3 * (1 - var.a3)\r
+\r
+\r
+  cursor4.posx = smooth(var.finalx3,3,5)\r
+  cursor4.posy = smooth(var.finaly3,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint3 = 0\r
+\r
+endif\r
+\r
+var.xpos3 = var.finalx3\r
+var.ypos3 = var.finaly3\r
+ppjoy3.analog0 = ensuremaprange(var.xpos3,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy3.analog1 = ensuremaprange(var.ypos3,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote3.B or wiimote3.A or wiimote3.Up or wiimote3.down or wiimote3.Left or wiimote3.Right == true\r
+  ppjoy3.digital0 = true\r
+else\r
+  ppjoy3.digital0 = false\r
+endif\r
+\r
+debug = var.accx1+ "  " + var.accy1+ " " + var.accz1\r
+\r
+//WIIMOTE 3\r
+//***Do not edit anything below this line unless you know what you are doing.***\r
+var.accx4 = wiimote4.RawForceX + var.xtrim4\r
+var.accy4 = wiimote4.RawForceY + var.ytrim4\r
+var.accz4 = wiimote4.RawForceZ + var.ztrim4\r
+\r
+if wiimote4.dot1vis and wiimote4.dot2vis then\r
+\r
+  if var.accy4 > -7 then\r
+    var.orientation4 = 0\r
+  elseif var.accy4 > -45 then\r
+    if var.accx4 < 0 then\r
+      var.orientation4 = 3\r
+    else\r
+      var.orientation4 = 1\r
+    endif\r
+  else\r
+    var.orientation4 = 2\r
+  endif\r
+\r
+  if var.leftpoint4 = 0 then\r
+    if var.orientation4 = 0 then\r
+      if wiimote4.dot1x < wiimote4.dot2x then\r
+        var.leftpoint4 = 1\r
+      else\r
+        var.leftpoint4 = 2\r
+      endif\r
+    endif\r
+    if var.orientation4 = 1 then\r
+      if wiimote4.dot1y > wiimote4.dot2y then\r
+        var.leftpoint4 = 1\r
+      else\r
+        var.leftpoint4 = 2\r
+      endif\r
+    endif\r
+    if var.orientation4 = 2 then\r
+      if wiimote4.dot1x > wiimote4.dot2x then\r
+        var.leftpoint4 = 1\r
+      else\r
+        var.leftpoint4 = 2\r
+      endif\r
+    endif\r
+    if var.orientation4 = 3 then\r
+      if wiimote4.dot1y < wiimote4.dot2y then\r
+        var.leftpoint4 = 1\r
+      else\r
+        var.leftpoint4 = 2\r
+      endif\r
+    endif\r
+  endif\r
+\r
+  if var.leftpoint4 = 1 then\r
+    var.fix1x4 = wiimote4.dot1x\r
+    var.fix1y4 = wiimote4.dot1y\r
+    var.fix2x4 = wiimote4.dot2x\r
+    var.fix2y4 = wiimote4.dot2y\r
+  else\r
+    var.fix1x4 = wiimote4.dot2x\r
+    var.fix1y4 = wiimote4.dot2y\r
+    var.fix2x4 = wiimote4.dot1x\r
+    var.fix2y4 = wiimote4.dot1y\r
+  endif\r
+\r
+  var.dx4 = var.fix2x4 - var.fix1x4\r
+  var.dy4 = var.fix2y4 - var.fix1y4\r
+  var.cx4 = (var.fix1x4+var.fix2x4)/1024.0 - 1\r
+  var.cy4 = (var.fix1y4+var.fix2y4)/1024.0 - .75\r
+\r
+  var.d4 = sqrt(var.dx4*var.dx4+var.dy4*var.dy4)\r
+\r
+  var.dx4 = var.dx4 / var.d4\r
+  var.dy4 = var.dy4 / var.d4\r
+\r
+  var.ox4 = -var.dy4*var.cy4-var.dx4*var.cx4;\r
+  var.oy4 = -var.dx4*var.cy4+var.dy4*var.cx4;\r
+\r
+  var.ax4 = (var.ox4 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2)\r
+  var.ay4 = (-var.oy4* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2)\r
+\r
+  var.dx4 = var.ax4 - cursor5.posx\r
+  var.dy4 = var.ay4 - cursor5.posy\r
+\r
+  var.d4 = sqrt((var.dx4*var.dx4)+(var.dy4*var.dy4))\r
+\r
+  var.a4 = 180 / (200 + var.d4 * var.d4 * var.d4 * .001)\r
+\r
+  if var.d4 <= var.deadzone then var.a4 = 1\r
+\r
+  //debug = var.d + " " + var.a\r
+\r
+  var.finalx4 = cursor5.posx * var.a4 + var.ax4 * (1 - var.a4)\r
+  var.finaly4 = cursor5.posy * var.a4 + var.ay4 * (1 - var.a4)\r
+\r
+\r
+  cursor5.posx = smooth(var.finalx4,3,5)\r
+  cursor5.posy = smooth(var.finaly4,3,5)\r
+\r
+else\r
+\r
+  var.leftpoint4 = 0\r
+\r
+endif\r
+\r
+var.xpos4 = var.finalx4\r
+var.ypos4 = var.finaly4\r
+ppjoy4.analog0 = ensuremaprange(var.xpos4,0,screen.desktopwidth* var.coeff,-1,1)\r
+ppjoy4.analog1 = ensuremaprange(var.ypos4,0,screen.desktopheight* var.coeff,-1,1)\r
+\r
+if wiimote4.B or wiimote4.A or wiimote4.Up or wiimote4.down or wiimote4.Left or wiimote4.Right == true\r
+  ppjoy4.digital0 = true\r
+else\r
+  ppjoy4.digital0 = false\r
+endif\r
+\r
diff --git a/src/controllers/Wiimote.py b/src/controllers/Wiimote.py
new file mode 100755 (executable)
index 0000000..7536881
--- /dev/null
@@ -0,0 +1,106 @@
+'''\r
+Created on 15 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+from gui.constants import *\r
+\r
+class Wiimote:\r
+    '''\r
+    Object representing a Wiimote\r
+    \r
+        number:\r
+            The number of the Wiimote\r
+        port:\r
+            The pypm object representing the MIDI port on which the Wiimote emits\r
+        instrument:\r
+            The instrument associated with the Wiimote\r
+        cursor:\r
+            The cursor associated with the Wiimote\r
+    '''\r
+\r
+    def __init__(self, number, portNumber, port, instrument, cursor):\r
+        '''\r
+        Constructor\r
+        \r
+            number:\r
+                The number of the Wiimote\r
+            portNumber:\r
+                The number of the port (as numbered by pypm) on which the wiimote emits\r
+            port:\r
+                The pypm object representing the MIDI port on which the Wiimote emits\r
+            instrument:\r
+                The instrument associated with the Wiimote\r
+            cursor:\r
+                The cursor associated with the Wiimote            \r
+        '''\r
+        \r
+        self.number = number\r
+        self.portNumber = portNumber\r
+        self.port = port\r
+        self.instrument = instrument\r
+        self.cursor = cursor\r
+        self.numberPlayed = 0\r
+        \r
+    def getNoteOnHexCode(self):\r
+        return (0x90 + self.instrument.channel - 1)\r
+    \r
+    def getAftertouchHexCode(self):\r
+        return (0xA0 + self.instrument.channel - 1)\r
+    \r
+    def getCCHexCode(self):\r
+        return (0xB0 + self.instrument.channel - 1)\r
+    \r
+    def playNote(self, note, velocity):\r
+        noteNumber = self.instrument.getNote(note)\r
+        \r
+        if noteNumber != None :\r
+            noteOnHexCode = self.getNoteOnHexCode()\r
+            CCHexCode = self.getCCHexCode()\r
+        else :\r
+            noteNumber = defaultInstrumentNote\r
+            noteOnHexCode = defaultNoteOnHexCode\r
+            CCHexCode = defaultCCHexCode\r
+            \r
+        self.port.write_short(noteOnHexCode, noteNumber , 127)\r
+        self.port.write_short(CCHexCode, 07, velocity)\r
+\r
+    def playNoteByNoteNumber(self, midiNoteNumber, velocity):\r
+        noteNumber = self.instrument.getNoteByNoteNumber(midiNoteNumber)\r
+        \r
+        if noteNumber != None :\r
+            noteOnHexCode = self.getNoteOnHexCode()\r
+            CCHexCode = self.getCCHexCode()\r
+        else :\r
+            noteNumber = defaultInstrumentNote\r
+            noteOnHexCode = defaultNoteOnHexCode\r
+            CCHexCode = defaultCCHexCode\r
+            \r
+        self.port.write_short(noteOnHexCode, noteNumber , 127)\r
+        self.port.write_short(CCHexCode, 07, velocity)\r
+        \r
+        self.numberPlayed += 1\r
+        \r
+    def stopNote(self, note):\r
+        noteNumber = self.instrument.getNote(note)\r
+        if noteNumber != None :\r
+            noteOnHexCode = self.getNoteOnHexCode()\r
+        else :\r
+            noteNumber = defaultInstrumentNote\r
+            noteOnHexCode = defaultNoteOnHexCode\r
+            \r
+        self.port.write_short(noteOnHexCode, noteNumber, 0)\r
+        \r
+    def stopNoteByNoteNumber(self, midiNoteNumber):\r
+        noteNumber = self.instrument.getNoteByNoteNumber(midiNoteNumber)\r
+        if noteNumber != None :\r
+            noteOnHexCode = self.getNoteOnHexCode()\r
+        else :\r
+            noteNumber = defaultInstrumentNote\r
+            noteOnHexCode = defaultNoteOnHexCode\r
+            \r
+        self.port.write_short(noteOnHexCode, noteNumber, 0)\r
+        \r
+    def allNotesOff(self):\r
+        CCHexCode = self.getCCHexCode()\r
+        self.port.write_short(CCHexCode,123,0)
\ No newline at end of file
diff --git a/src/controllers/__init__.py b/src/controllers/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/cursor/WarpingCursor.py b/src/cursor/WarpingCursor.py
new file mode 100755 (executable)
index 0000000..57985e1
--- /dev/null
@@ -0,0 +1,155 @@
+'''\r
+Created on 29 mai 2009\r
+\r
+@author: samsam\r
+'''\r
+\r
+import os, sys, math\r
+\r
+import pygame\r
+from pygame.sprite import Sprite\r
+\r
+class WarpingCursor(Sprite):\r
+    '''\r
+    The class for animating the warping cursor\r
+        \r
+        screen:\r
+            the screen on which the WarpingCursor is painted\r
+        images:\r
+            The images constituting the animation of the cursor\r
+        durations:\r
+            The duration of each image in the animation\r
+        centerPosition:\r
+            The Position of the center of the cursor\r
+        _imagePointer:\r
+            A pointer to the current image\r
+        _animationOffset:\r
+            The time elapsed since when the current image should have been displayed\r
+    '''\r
+    screen = None\r
+    images = None\r
+    durations = None\r
+    centerPosition = None\r
+    _imagePointer = None\r
+    _animationOffset = None\r
+    \r
+\r
+    def __init__(self, scr, images, durations, initCenterPosition,flashImage):\r
+        '''\r
+        Constructor\r
+        \r
+            scr:\r
+                the screen on which the WarpingCursor is painted\r
+            images:\r
+                The images constituting the animation of the cursor\r
+            durations:\r
+                The duration of each image in the animation\r
+            initCenterPosition:\r
+                The Position of the center of the cursor at the beginning \r
+        '''\r
+        self.screen = scr\r
+        self.images = images\r
+        self.flashImagePath = flashImage\r
+        self.durations = durations\r
+        self.centerPosition = initCenterPosition\r
+        self.flashLength = 100\r
+        self.flashing = False\r
+        self.image = pygame.image.load(self.images[0]).convert_alpha()\r
+        self._imagePointer = 0\r
+        self._animationOffset = 0\r
+        self._flashTimer = 0        \r
+    \r
+    def update(self, elapsedTime, centerPosition):\r
+        '''\r
+        Update the cursor's look and position\r
+        \r
+            elapsedTime:\r
+                The time passed since the previous update\r
+            centerPosition:\r
+                the new position of the creep\r
+        '''\r
+        self._updateImage(elapsedTime)\r
+        self.centerPosition = centerPosition\r
+        if self.flashing :\r
+            self._flashTimer += elapsedTime\r
+            if self._flashTimer > self.flashLength:\r
+                self.flashing = False\r
+    \r
+    def _updateImage(self, elapsedTime):\r
+        '''\r
+        Update the cursor's image\r
+        \r
+            elapsedTime:\r
+                The time passed since the previous update\r
+        '''\r
+        self._animationOffset += elapsedTime\r
+        \r
+        if self._animationOffset > self.durations[self._imagePointer]:\r
+            #New animation offset is computed first, before updating the pointer\r
+            self._animationOffset -= self.durations[self._imagePointer]\r
+            #point to the next image (restarts from the beginning when it reaches the end)\r
+            self._imagePointer = (self._imagePointer + 1) % len(self.images)\r
+            \r
+        if self.flashing:\r
+            self.image = pygame.image.load(self.flashImagePath).convert_alpha()                       \r
+        else :    \r
+            self.image = pygame.image.load(self.images[self._imagePointer]).convert_alpha()\r
+        \r
+    def flash(self,flashLength = None):\r
+        self._flashTimer = 0\r
+        self.flashing = True\r
+        if flashLength:\r
+            self.flashlength = flashLength\r
+    \r
+    def blit(self,surface):\r
+        '''\r
+        Draw the circle on surface\r
+        '''\r
+        \r
+        newPos = (self.centerPosition[0] - self.image.get_width() / 2, self.centerPosition[1] - self.image.get_height() / 2)\r
+        surface.blit(self.image, newPos)\r
+\r
+def createImageListFromPath(path, imageCount):\r
+    '''\r
+    Create a list of images for a cursor (the concatenation of the original and reversed lists of images).\r
+    Images must be stored as path/imageNumber.png\r
+    \r
+        path:\r
+            The folder where the images for that cursor are stored\r
+        imageCount:\r
+            The number of images in the folder\r
+    '''\r
+    \r
+    tempImages = [''.join([path, '/', str(i), '.png']) for i in range(imageCount)]\r
+    #tempImagesReversed = tempImages[:]\r
+    #tempImagesReversed.reverse()\r
+    #return(tempImages+tempImagesReversed)\r
+    return(tempImages)  \r
+\r
+#testing\r
+if __name__ == "__main__" :\r
+    window = pygame.display.set_mode((1680, 1050), pygame.FULLSCREEN)\r
+    screen = pygame.display.get_surface()\r
+    clock = pygame.time.Clock()\r
+    \r
+    images = createImageListFromPath('cursorImages/black',11)\r
+    durations = [50 for i in range(22)]\r
+    position = (400, 300)\r
+    cursor = WarpingCursor(screen, images, durations, position)\r
+    \r
+    while True:\r
+        # Limit frame speed to 50 FPS\r
+        #\r
+        timePassed = clock.tick(50)\r
+        \r
+        for event in pygame.event.get():\r
+            if event.type == pygame.QUIT:\r
+                sys.exit()\r
+            if event.type == pygame.MOUSEMOTION:\r
+                position = event.pos\r
+        \r
+        cursor.update(timePassed, position)\r
+        cursor.blit(screen)\r
+        pygame.display.flip()\r
+    \r
+    \r
diff --git a/src/cursor/__init__.py b/src/cursor/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/cursor/cursorImages/black/0.png b/src/cursor/cursorImages/black/0.png
new file mode 100755 (executable)
index 0000000..d676349
Binary files /dev/null and b/src/cursor/cursorImages/black/0.png differ
diff --git a/src/cursor/cursorImages/black/1.png b/src/cursor/cursorImages/black/1.png
new file mode 100755 (executable)
index 0000000..7703e89
Binary files /dev/null and b/src/cursor/cursorImages/black/1.png differ
diff --git a/src/cursor/cursorImages/black/10.png b/src/cursor/cursorImages/black/10.png
new file mode 100755 (executable)
index 0000000..6c85004
Binary files /dev/null and b/src/cursor/cursorImages/black/10.png differ
diff --git a/src/cursor/cursorImages/black/2.png b/src/cursor/cursorImages/black/2.png
new file mode 100755 (executable)
index 0000000..9eec08e
Binary files /dev/null and b/src/cursor/cursorImages/black/2.png differ
diff --git a/src/cursor/cursorImages/black/3.png b/src/cursor/cursorImages/black/3.png
new file mode 100755 (executable)
index 0000000..ce93f45
Binary files /dev/null and b/src/cursor/cursorImages/black/3.png differ
diff --git a/src/cursor/cursorImages/black/4.png b/src/cursor/cursorImages/black/4.png
new file mode 100755 (executable)
index 0000000..7c9d191
Binary files /dev/null and b/src/cursor/cursorImages/black/4.png differ
diff --git a/src/cursor/cursorImages/black/5.png b/src/cursor/cursorImages/black/5.png
new file mode 100755 (executable)
index 0000000..34b149d
Binary files /dev/null and b/src/cursor/cursorImages/black/5.png differ
diff --git a/src/cursor/cursorImages/black/6.png b/src/cursor/cursorImages/black/6.png
new file mode 100755 (executable)
index 0000000..d118680
Binary files /dev/null and b/src/cursor/cursorImages/black/6.png differ
diff --git a/src/cursor/cursorImages/black/7.png b/src/cursor/cursorImages/black/7.png
new file mode 100755 (executable)
index 0000000..68ec2dd
Binary files /dev/null and b/src/cursor/cursorImages/black/7.png differ
diff --git a/src/cursor/cursorImages/black/8.png b/src/cursor/cursorImages/black/8.png
new file mode 100755 (executable)
index 0000000..c938fe9
Binary files /dev/null and b/src/cursor/cursorImages/black/8.png differ
diff --git a/src/cursor/cursorImages/black/9.png b/src/cursor/cursorImages/black/9.png
new file mode 100755 (executable)
index 0000000..e5e4f09
Binary files /dev/null and b/src/cursor/cursorImages/black/9.png differ
diff --git a/src/cursor/cursorImages/black/cursorBlack.svg b/src/cursor/cursorImages/black/cursorBlack.svg
new file mode 100755 (executable)
index 0000000..7c3c9b4
--- /dev/null
@@ -0,0 +1,99 @@
+<?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"
+   width="48px"
+   height="48px"
+   id="svg2397"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="cursor.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor00.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs2399">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       id="perspective2405" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7"
+     inkscape:cx="-1.2857143"
+     inkscape:cy="24.45"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1125"
+     inkscape:window-x="1680"
+     inkscape:window-y="25"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <sodipodi:guide
+       orientation="0,1"
+       position="-4.4285714,24"
+       id="guide3229" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="24,-18.571429"
+       id="guide3231" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata2402">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="path2413"
+       sodipodi:cx="37.57143"
+       sodipodi:cy="36.57143"
+       sodipodi:rx="21.571428"
+       sodipodi:ry="21.571428"
+       d="M 59.142859,36.57143 A 21.571428,21.571428 0 1 1 16.000002,36.57143 A 21.571428,21.571428 0 1 1 59.142859,36.57143 z"
+       transform="matrix(1.0873786,0,0,1.0873787,-16.782941,-15.838419)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:0;stroke:#000000;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3227"
+       sodipodi:cx="10.928572"
+       sodipodi:cy="13.928572"
+       sodipodi:rx="13.785714"
+       sodipodi:ry="13.785714"
+       d="M 24.714286,13.928572 A 13.785714,13.785714 0 1 1 -2.8571424,13.928572 A 13.785714,13.785714 0 1 1 24.714286,13.928572 z"
+       transform="matrix(1.5759606,0,0,1.567424,6.8484288,2.1217689)"
+       inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor100.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+  </g>
+</svg>
diff --git a/src/cursor/cursorImages/black/flash.png b/src/cursor/cursorImages/black/flash.png
new file mode 100644 (file)
index 0000000..412cbce
Binary files /dev/null and b/src/cursor/cursorImages/black/flash.png differ
diff --git a/src/cursor/cursorImages/blue/0.png b/src/cursor/cursorImages/blue/0.png
new file mode 100644 (file)
index 0000000..9535e33
Binary files /dev/null and b/src/cursor/cursorImages/blue/0.png differ
diff --git a/src/cursor/cursorImages/blue/1.png b/src/cursor/cursorImages/blue/1.png
new file mode 100644 (file)
index 0000000..894e8c4
Binary files /dev/null and b/src/cursor/cursorImages/blue/1.png differ
diff --git a/src/cursor/cursorImages/blue/10.png b/src/cursor/cursorImages/blue/10.png
new file mode 100644 (file)
index 0000000..8ef30f7
Binary files /dev/null and b/src/cursor/cursorImages/blue/10.png differ
diff --git a/src/cursor/cursorImages/blue/2.png b/src/cursor/cursorImages/blue/2.png
new file mode 100644 (file)
index 0000000..5dcd4a9
Binary files /dev/null and b/src/cursor/cursorImages/blue/2.png differ
diff --git a/src/cursor/cursorImages/blue/3.png b/src/cursor/cursorImages/blue/3.png
new file mode 100644 (file)
index 0000000..e547688
Binary files /dev/null and b/src/cursor/cursorImages/blue/3.png differ
diff --git a/src/cursor/cursorImages/blue/4.png b/src/cursor/cursorImages/blue/4.png
new file mode 100644 (file)
index 0000000..0fb1d9d
Binary files /dev/null and b/src/cursor/cursorImages/blue/4.png differ
diff --git a/src/cursor/cursorImages/blue/5.png b/src/cursor/cursorImages/blue/5.png
new file mode 100644 (file)
index 0000000..6f3b35a
Binary files /dev/null and b/src/cursor/cursorImages/blue/5.png differ
diff --git a/src/cursor/cursorImages/blue/6.png b/src/cursor/cursorImages/blue/6.png
new file mode 100644 (file)
index 0000000..7ca3eb6
Binary files /dev/null and b/src/cursor/cursorImages/blue/6.png differ
diff --git a/src/cursor/cursorImages/blue/7.png b/src/cursor/cursorImages/blue/7.png
new file mode 100644 (file)
index 0000000..0a2033b
Binary files /dev/null and b/src/cursor/cursorImages/blue/7.png differ
diff --git a/src/cursor/cursorImages/blue/8.png b/src/cursor/cursorImages/blue/8.png
new file mode 100644 (file)
index 0000000..c75055c
Binary files /dev/null and b/src/cursor/cursorImages/blue/8.png differ
diff --git a/src/cursor/cursorImages/blue/9.png b/src/cursor/cursorImages/blue/9.png
new file mode 100644 (file)
index 0000000..f013e60
Binary files /dev/null and b/src/cursor/cursorImages/blue/9.png differ
diff --git a/src/cursor/cursorImages/blue/cursorBlue.svg b/src/cursor/cursorImages/blue/cursorBlue.svg
new file mode 100644 (file)
index 0000000..34c72b0
--- /dev/null
@@ -0,0 +1,99 @@
+<?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"
+   width="48px"
+   height="48px"
+   id="svg2397"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="cursorBlue.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor00.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs2399">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       id="perspective2405" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7"
+     inkscape:cx="-31.071429"
+     inkscape:cy="24.45"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1125"
+     inkscape:window-x="1680"
+     inkscape:window-y="25"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <sodipodi:guide
+       orientation="0,1"
+       position="-4.4285714,24"
+       id="guide3229" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="24,-18.571429"
+       id="guide3231" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata2402">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="path2413"
+       sodipodi:cx="37.57143"
+       sodipodi:cy="36.57143"
+       sodipodi:rx="21.571428"
+       sodipodi:ry="21.571428"
+       d="M 59.142859,36.57143 A 21.571428,21.571428 0 1 1 16.000002,36.57143 A 21.571428,21.571428 0 1 1 59.142859,36.57143 z"
+       transform="matrix(1.0873786,0,0,1.0873787,-16.782941,-15.838419)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:0;stroke:#0000ff;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3227"
+       sodipodi:cx="10.928572"
+       sodipodi:cy="13.928572"
+       sodipodi:rx="13.785714"
+       sodipodi:ry="13.785714"
+       d="M 24.714286,13.928572 A 13.785714,13.785714 0 1 1 -2.8571424,13.928572 A 13.785714,13.785714 0 1 1 24.714286,13.928572 z"
+       transform="matrix(1.5759606,0,0,1.567424,6.8484293,2.1217687)"
+       inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor100.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+  </g>
+</svg>
diff --git a/src/cursor/cursorImages/green/0.png b/src/cursor/cursorImages/green/0.png
new file mode 100644 (file)
index 0000000..a601813
Binary files /dev/null and b/src/cursor/cursorImages/green/0.png differ
diff --git a/src/cursor/cursorImages/green/1.png b/src/cursor/cursorImages/green/1.png
new file mode 100644 (file)
index 0000000..a0343a6
Binary files /dev/null and b/src/cursor/cursorImages/green/1.png differ
diff --git a/src/cursor/cursorImages/green/10.png b/src/cursor/cursorImages/green/10.png
new file mode 100644 (file)
index 0000000..a57da5e
Binary files /dev/null and b/src/cursor/cursorImages/green/10.png differ
diff --git a/src/cursor/cursorImages/green/2.png b/src/cursor/cursorImages/green/2.png
new file mode 100644 (file)
index 0000000..3c611a1
Binary files /dev/null and b/src/cursor/cursorImages/green/2.png differ
diff --git a/src/cursor/cursorImages/green/3.png b/src/cursor/cursorImages/green/3.png
new file mode 100644 (file)
index 0000000..75169da
Binary files /dev/null and b/src/cursor/cursorImages/green/3.png differ
diff --git a/src/cursor/cursorImages/green/4.png b/src/cursor/cursorImages/green/4.png
new file mode 100644 (file)
index 0000000..19b9062
Binary files /dev/null and b/src/cursor/cursorImages/green/4.png differ
diff --git a/src/cursor/cursorImages/green/5.png b/src/cursor/cursorImages/green/5.png
new file mode 100644 (file)
index 0000000..f0d521c
Binary files /dev/null and b/src/cursor/cursorImages/green/5.png differ
diff --git a/src/cursor/cursorImages/green/6.png b/src/cursor/cursorImages/green/6.png
new file mode 100644 (file)
index 0000000..9732994
Binary files /dev/null and b/src/cursor/cursorImages/green/6.png differ
diff --git a/src/cursor/cursorImages/green/7.png b/src/cursor/cursorImages/green/7.png
new file mode 100644 (file)
index 0000000..8c77ca7
Binary files /dev/null and b/src/cursor/cursorImages/green/7.png differ
diff --git a/src/cursor/cursorImages/green/8.png b/src/cursor/cursorImages/green/8.png
new file mode 100644 (file)
index 0000000..a2a97a7
Binary files /dev/null and b/src/cursor/cursorImages/green/8.png differ
diff --git a/src/cursor/cursorImages/green/9.png b/src/cursor/cursorImages/green/9.png
new file mode 100644 (file)
index 0000000..b950aa1
Binary files /dev/null and b/src/cursor/cursorImages/green/9.png differ
diff --git a/src/cursor/cursorImages/green/cursorGreen.svg b/src/cursor/cursorImages/green/cursorGreen.svg
new file mode 100644 (file)
index 0000000..34e8bc6
--- /dev/null
@@ -0,0 +1,99 @@
+<?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"
+   width="48px"
+   height="48px"
+   id="svg2397"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="cursorGreen.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor00.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs2399">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       id="perspective2405" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7"
+     inkscape:cx="-31.071429"
+     inkscape:cy="24.45"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1125"
+     inkscape:window-x="1680"
+     inkscape:window-y="25"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <sodipodi:guide
+       orientation="0,1"
+       position="-4.4285714,24"
+       id="guide3229" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="24,-18.571429"
+       id="guide3231" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata2402">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#00ff00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="path2413"
+       sodipodi:cx="37.57143"
+       sodipodi:cy="36.57143"
+       sodipodi:rx="21.571428"
+       sodipodi:ry="21.571428"
+       d="M 59.142859,36.57143 A 21.571428,21.571428 0 1 1 16.000002,36.57143 A 21.571428,21.571428 0 1 1 59.142859,36.57143 z"
+       transform="matrix(1.0873786,0,0,1.0873787,-16.782941,-15.838419)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:0;stroke:#00ff00;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3227"
+       sodipodi:cx="10.928572"
+       sodipodi:cy="13.928572"
+       sodipodi:rx="13.785714"
+       sodipodi:ry="13.785714"
+       d="M 24.714286,13.928572 A 13.785714,13.785714 0 1 1 -2.8571424,13.928572 A 13.785714,13.785714 0 1 1 24.714286,13.928572 z"
+       transform="matrix(1.5759606,0,0,1.567424,6.8484285,2.1680224)"
+       inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor100.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+  </g>
+</svg>
diff --git a/src/cursor/cursorImages/red/0.png b/src/cursor/cursorImages/red/0.png
new file mode 100644 (file)
index 0000000..839bc89
Binary files /dev/null and b/src/cursor/cursorImages/red/0.png differ
diff --git a/src/cursor/cursorImages/red/1.png b/src/cursor/cursorImages/red/1.png
new file mode 100644 (file)
index 0000000..7124ee3
Binary files /dev/null and b/src/cursor/cursorImages/red/1.png differ
diff --git a/src/cursor/cursorImages/red/10.png b/src/cursor/cursorImages/red/10.png
new file mode 100644 (file)
index 0000000..e4323c2
Binary files /dev/null and b/src/cursor/cursorImages/red/10.png differ
diff --git a/src/cursor/cursorImages/red/2.png b/src/cursor/cursorImages/red/2.png
new file mode 100644 (file)
index 0000000..7df10de
Binary files /dev/null and b/src/cursor/cursorImages/red/2.png differ
diff --git a/src/cursor/cursorImages/red/3.png b/src/cursor/cursorImages/red/3.png
new file mode 100644 (file)
index 0000000..3088b82
Binary files /dev/null and b/src/cursor/cursorImages/red/3.png differ
diff --git a/src/cursor/cursorImages/red/4.png b/src/cursor/cursorImages/red/4.png
new file mode 100644 (file)
index 0000000..8e7549d
Binary files /dev/null and b/src/cursor/cursorImages/red/4.png differ
diff --git a/src/cursor/cursorImages/red/5.png b/src/cursor/cursorImages/red/5.png
new file mode 100644 (file)
index 0000000..a18a1a4
Binary files /dev/null and b/src/cursor/cursorImages/red/5.png differ
diff --git a/src/cursor/cursorImages/red/6.png b/src/cursor/cursorImages/red/6.png
new file mode 100644 (file)
index 0000000..6a6c2af
Binary files /dev/null and b/src/cursor/cursorImages/red/6.png differ
diff --git a/src/cursor/cursorImages/red/7.png b/src/cursor/cursorImages/red/7.png
new file mode 100644 (file)
index 0000000..865f580
Binary files /dev/null and b/src/cursor/cursorImages/red/7.png differ
diff --git a/src/cursor/cursorImages/red/8.png b/src/cursor/cursorImages/red/8.png
new file mode 100644 (file)
index 0000000..785a4b1
Binary files /dev/null and b/src/cursor/cursorImages/red/8.png differ
diff --git a/src/cursor/cursorImages/red/9.png b/src/cursor/cursorImages/red/9.png
new file mode 100644 (file)
index 0000000..e4c9758
Binary files /dev/null and b/src/cursor/cursorImages/red/9.png differ
diff --git a/src/cursor/cursorImages/red/cursorRed.svg b/src/cursor/cursorImages/red/cursorRed.svg
new file mode 100644 (file)
index 0000000..54f5fc0
--- /dev/null
@@ -0,0 +1,99 @@
+<?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"
+   width="48px"
+   height="48px"
+   id="svg2397"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="cursorRed.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor00.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs2399">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 24 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="48 : 24 : 1"
+       inkscape:persp3d-origin="24 : 16 : 1"
+       id="perspective2405" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7"
+     inkscape:cx="-31.071429"
+     inkscape:cy="22.307143"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1125"
+     inkscape:window-x="1680"
+     inkscape:window-y="25"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <sodipodi:guide
+       orientation="0,1"
+       position="-4.4285714,24"
+       id="guide3229" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="24,-18.571429"
+       id="guide3231" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata2402">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="path2413"
+       sodipodi:cx="37.57143"
+       sodipodi:cy="36.57143"
+       sodipodi:rx="21.571428"
+       sodipodi:ry="21.571428"
+       d="M 59.142859,36.57143 A 21.571428,21.571428 0 1 1 16.000002,36.57143 A 21.571428,21.571428 0 1 1 59.142859,36.57143 z"
+       transform="matrix(1.0873786,0,0,1.0873787,-16.782941,-15.838419)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#ffffff;fill-opacity:0;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path3227"
+       sodipodi:cx="10.928572"
+       sodipodi:cy="13.928572"
+       sodipodi:rx="13.785714"
+       sodipodi:ry="13.785714"
+       d="M 24.714286,13.928572 A 13.785714,13.785714 0 1 1 -2.8571424,13.928572 A 13.785714,13.785714 0 1 1 24.714286,13.928572 z"
+       transform="matrix(1.5759606,0,0,1.567424,6.9912861,2.264626)"
+       inkscape:export-filename="/home/sbenven/Desktop/cursorGifs/cursor100.png"
+       inkscape:export-xdpi="90"
+       inkscape:export-ydpi="90" />
+  </g>
+</svg>
diff --git a/src/data/themes/default/Vera.ttf b/src/data/themes/default/Vera.ttf
new file mode 100644 (file)
index 0000000..58cd6b5
Binary files /dev/null and b/src/data/themes/default/Vera.ttf differ
diff --git a/src/data/themes/default/box.down.png b/src/data/themes/default/box.down.png
new file mode 100644 (file)
index 0000000..b9e965d
Binary files /dev/null and b/src/data/themes/default/box.down.png differ
diff --git a/src/data/themes/default/box.hover.png b/src/data/themes/default/box.hover.png
new file mode 100644 (file)
index 0000000..ef3c225
Binary files /dev/null and b/src/data/themes/default/box.hover.png differ
diff --git a/src/data/themes/default/box.normal.png b/src/data/themes/default/box.normal.png
new file mode 100644 (file)
index 0000000..90f8d40
Binary files /dev/null and b/src/data/themes/default/box.normal.png differ
diff --git a/src/data/themes/default/box.xcf b/src/data/themes/default/box.xcf
new file mode 100644 (file)
index 0000000..960ca8d
Binary files /dev/null and b/src/data/themes/default/box.xcf differ
diff --git a/src/data/themes/default/button.down.tga b/src/data/themes/default/button.down.tga
new file mode 100644 (file)
index 0000000..64873c1
Binary files /dev/null and b/src/data/themes/default/button.down.tga differ
diff --git a/src/data/themes/default/button.hover.tga b/src/data/themes/default/button.hover.tga
new file mode 100644 (file)
index 0000000..5e5c53a
Binary files /dev/null and b/src/data/themes/default/button.hover.tga differ
diff --git a/src/data/themes/default/button.normal.tga b/src/data/themes/default/button.normal.tga
new file mode 100644 (file)
index 0000000..e9371c7
Binary files /dev/null and b/src/data/themes/default/button.normal.tga differ
diff --git a/src/data/themes/default/check.png b/src/data/themes/default/check.png
new file mode 100644 (file)
index 0000000..4ef58a3
Binary files /dev/null and b/src/data/themes/default/check.png differ
diff --git a/src/data/themes/default/checkbox.off.hover.tga b/src/data/themes/default/checkbox.off.hover.tga
new file mode 100644 (file)
index 0000000..9a4d8a8
Binary files /dev/null and b/src/data/themes/default/checkbox.off.hover.tga differ
diff --git a/src/data/themes/default/checkbox.off.normal.tga b/src/data/themes/default/checkbox.off.normal.tga
new file mode 100644 (file)
index 0000000..de59f19
Binary files /dev/null and b/src/data/themes/default/checkbox.off.normal.tga differ
diff --git a/src/data/themes/default/checkbox.on.hover.tga b/src/data/themes/default/checkbox.on.hover.tga
new file mode 100644 (file)
index 0000000..4940c26
Binary files /dev/null and b/src/data/themes/default/checkbox.on.hover.tga differ
diff --git a/src/data/themes/default/checkbox.on.normal.tga b/src/data/themes/default/checkbox.on.normal.tga
new file mode 100644 (file)
index 0000000..9cf658e
Binary files /dev/null and b/src/data/themes/default/checkbox.on.normal.tga differ
diff --git a/src/data/themes/default/config.txt b/src/data/themes/default/config.txt
new file mode 100644 (file)
index 0000000..e4ef48b
--- /dev/null
@@ -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/src/data/themes/default/console.input.focus.png b/src/data/themes/default/console.input.focus.png
new file mode 100644 (file)
index 0000000..819d835
Binary files /dev/null and b/src/data/themes/default/console.input.focus.png differ
diff --git a/src/data/themes/default/console.input.normal.png b/src/data/themes/default/console.input.normal.png
new file mode 100644 (file)
index 0000000..a14e329
Binary files /dev/null and b/src/data/themes/default/console.input.normal.png differ
diff --git a/src/data/themes/default/console.png b/src/data/themes/default/console.png
new file mode 100644 (file)
index 0000000..a14e329
Binary files /dev/null and b/src/data/themes/default/console.png differ
diff --git a/src/data/themes/default/desktop.png b/src/data/themes/default/desktop.png
new file mode 100644 (file)
index 0000000..c83f5cd
Binary files /dev/null and b/src/data/themes/default/desktop.png differ
diff --git a/src/data/themes/default/desktop.xcf b/src/data/themes/default/desktop.xcf
new file mode 100644 (file)
index 0000000..2c504ab
Binary files /dev/null and b/src/data/themes/default/desktop.xcf differ
diff --git a/src/data/themes/default/dialog.bar.png b/src/data/themes/default/dialog.bar.png
new file mode 100644 (file)
index 0000000..e014e04
Binary files /dev/null and b/src/data/themes/default/dialog.bar.png differ
diff --git a/src/data/themes/default/dialog.close.down.tga b/src/data/themes/default/dialog.close.down.tga
new file mode 100644 (file)
index 0000000..ade4813
Binary files /dev/null and b/src/data/themes/default/dialog.close.down.tga differ
diff --git a/src/data/themes/default/dialog.close.hover.tga b/src/data/themes/default/dialog.close.hover.tga
new file mode 100644 (file)
index 0000000..9f36bb7
Binary files /dev/null and b/src/data/themes/default/dialog.close.hover.tga differ
diff --git a/src/data/themes/default/dialog.close.normal.tga b/src/data/themes/default/dialog.close.normal.tga
new file mode 100644 (file)
index 0000000..ee3a5d4
Binary files /dev/null and b/src/data/themes/default/dialog.close.normal.tga differ
diff --git a/src/data/themes/default/dialog.png b/src/data/themes/default/dialog.png
new file mode 100644 (file)
index 0000000..26ae2a6
Binary files /dev/null and b/src/data/themes/default/dialog.png differ
diff --git a/src/data/themes/default/dot.down.png b/src/data/themes/default/dot.down.png
new file mode 100644 (file)
index 0000000..ab117a7
Binary files /dev/null and b/src/data/themes/default/dot.down.png differ
diff --git a/src/data/themes/default/dot.hover.png b/src/data/themes/default/dot.hover.png
new file mode 100644 (file)
index 0000000..090f07d
Binary files /dev/null and b/src/data/themes/default/dot.hover.png differ
diff --git a/src/data/themes/default/dot.normal.png b/src/data/themes/default/dot.normal.png
new file mode 100644 (file)
index 0000000..55bd736
Binary files /dev/null and b/src/data/themes/default/dot.normal.png differ
diff --git a/src/data/themes/default/dot.xcf b/src/data/themes/default/dot.xcf
new file mode 100644 (file)
index 0000000..3100750
Binary files /dev/null and b/src/data/themes/default/dot.xcf differ
diff --git a/src/data/themes/default/down.png b/src/data/themes/default/down.png
new file mode 100644 (file)
index 0000000..7532249
Binary files /dev/null and b/src/data/themes/default/down.png differ
diff --git a/src/data/themes/default/filebrowser.folder.png b/src/data/themes/default/filebrowser.folder.png
new file mode 100644 (file)
index 0000000..4a3bd2c
Binary files /dev/null and b/src/data/themes/default/filebrowser.folder.png differ
diff --git a/src/data/themes/default/generate.py b/src/data/themes/default/generate.py
new file mode 100644 (file)
index 0000000..a161556
--- /dev/null
@@ -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/src/data/themes/default/hslider.bar.hover.tga b/src/data/themes/default/hslider.bar.hover.tga
new file mode 100644 (file)
index 0000000..5e5c53a
Binary files /dev/null and b/src/data/themes/default/hslider.bar.hover.tga differ
diff --git a/src/data/themes/default/hslider.bar.normal.tga b/src/data/themes/default/hslider.bar.normal.tga
new file mode 100644 (file)
index 0000000..e9371c7
Binary files /dev/null and b/src/data/themes/default/hslider.bar.normal.tga differ
diff --git a/src/data/themes/default/hslider.left.tga b/src/data/themes/default/hslider.left.tga
new file mode 100644 (file)
index 0000000..2fe406c
Binary files /dev/null and b/src/data/themes/default/hslider.left.tga differ
diff --git a/src/data/themes/default/hslider.right.tga b/src/data/themes/default/hslider.right.tga
new file mode 100644 (file)
index 0000000..86a9ca5
Binary files /dev/null and b/src/data/themes/default/hslider.right.tga differ
diff --git a/src/data/themes/default/hslider.tga b/src/data/themes/default/hslider.tga
new file mode 100644 (file)
index 0000000..ff3b4b2
Binary files /dev/null and b/src/data/themes/default/hslider.tga differ
diff --git a/src/data/themes/default/idot.normal.png b/src/data/themes/default/idot.normal.png
new file mode 100644 (file)
index 0000000..4e22195
Binary files /dev/null and b/src/data/themes/default/idot.normal.png differ
diff --git a/src/data/themes/default/input.focus.png b/src/data/themes/default/input.focus.png
new file mode 100644 (file)
index 0000000..477a826
Binary files /dev/null and b/src/data/themes/default/input.focus.png differ
diff --git a/src/data/themes/default/input.normal.png b/src/data/themes/default/input.normal.png
new file mode 100644 (file)
index 0000000..8519a98
Binary files /dev/null and b/src/data/themes/default/input.normal.png differ
diff --git a/src/data/themes/default/left.png b/src/data/themes/default/left.png
new file mode 100644 (file)
index 0000000..b965666
Binary files /dev/null and b/src/data/themes/default/left.png differ
diff --git a/src/data/themes/default/list.item.down.png b/src/data/themes/default/list.item.down.png
new file mode 100644 (file)
index 0000000..fd9dc21
Binary files /dev/null and b/src/data/themes/default/list.item.down.png differ
diff --git a/src/data/themes/default/list.item.hover.png b/src/data/themes/default/list.item.hover.png
new file mode 100644 (file)
index 0000000..627790d
Binary files /dev/null and b/src/data/themes/default/list.item.hover.png differ
diff --git a/src/data/themes/default/list.item.normal.png b/src/data/themes/default/list.item.normal.png
new file mode 100644 (file)
index 0000000..627790d
Binary files /dev/null and b/src/data/themes/default/list.item.normal.png differ
diff --git a/src/data/themes/default/list.png b/src/data/themes/default/list.png
new file mode 100644 (file)
index 0000000..99ad5bc
Binary files /dev/null and b/src/data/themes/default/list.png differ
diff --git a/src/data/themes/default/listitem.down.tga b/src/data/themes/default/listitem.down.tga
new file mode 100644 (file)
index 0000000..13e2e57
Binary files /dev/null and b/src/data/themes/default/listitem.down.tga differ
diff --git a/src/data/themes/default/listitem.hover.tga b/src/data/themes/default/listitem.hover.tga
new file mode 100644 (file)
index 0000000..8bdf60a
Binary files /dev/null and b/src/data/themes/default/listitem.hover.tga differ
diff --git a/src/data/themes/default/listitem.normal.tga b/src/data/themes/default/listitem.normal.tga
new file mode 100644 (file)
index 0000000..a2994aa
Binary files /dev/null and b/src/data/themes/default/listitem.normal.tga differ
diff --git a/src/data/themes/default/menu.down.tga b/src/data/themes/default/menu.down.tga
new file mode 100644 (file)
index 0000000..f89d4b4
Binary files /dev/null and b/src/data/themes/default/menu.down.tga differ
diff --git a/src/data/themes/default/menu.hover.tga b/src/data/themes/default/menu.hover.tga
new file mode 100644 (file)
index 0000000..b304b87
Binary files /dev/null and b/src/data/themes/default/menu.hover.tga differ
diff --git a/src/data/themes/default/menu.normal.tga b/src/data/themes/default/menu.normal.tga
new file mode 100644 (file)
index 0000000..d3eb2d0
Binary files /dev/null and b/src/data/themes/default/menu.normal.tga differ
diff --git a/src/data/themes/default/notes.txt b/src/data/themes/default/notes.txt
new file mode 100644 (file)
index 0000000..f6541e4
--- /dev/null
@@ -0,0 +1,8 @@
+dot and box.xcf:
+
+color -170
+
+.down
+.hover +64 brightness
+.normal, grayscale +127 brightness, +48 contrast
+
diff --git a/src/data/themes/default/out.tga b/src/data/themes/default/out.tga
new file mode 100644 (file)
index 0000000..7ed46cc
Binary files /dev/null and b/src/data/themes/default/out.tga differ
diff --git a/src/data/themes/default/progressbar.bar.tga b/src/data/themes/default/progressbar.bar.tga
new file mode 100644 (file)
index 0000000..184ae9c
Binary files /dev/null and b/src/data/themes/default/progressbar.bar.tga differ
diff --git a/src/data/themes/default/progressbar.tga b/src/data/themes/default/progressbar.tga
new file mode 100644 (file)
index 0000000..d459763
Binary files /dev/null and b/src/data/themes/default/progressbar.tga differ
diff --git a/src/data/themes/default/radio.off.hover.tga b/src/data/themes/default/radio.off.hover.tga
new file mode 100644 (file)
index 0000000..6b0f737
Binary files /dev/null and b/src/data/themes/default/radio.off.hover.tga differ
diff --git a/src/data/themes/default/radio.off.normal.tga b/src/data/themes/default/radio.off.normal.tga
new file mode 100644 (file)
index 0000000..3da51d8
Binary files /dev/null and b/src/data/themes/default/radio.off.normal.tga differ
diff --git a/src/data/themes/default/radio.on.hover.tga b/src/data/themes/default/radio.on.hover.tga
new file mode 100644 (file)
index 0000000..d26764b
Binary files /dev/null and b/src/data/themes/default/radio.on.hover.tga differ
diff --git a/src/data/themes/default/radio.on.normal.tga b/src/data/themes/default/radio.on.normal.tga
new file mode 100644 (file)
index 0000000..42515fe
Binary files /dev/null and b/src/data/themes/default/radio.on.normal.tga differ
diff --git a/src/data/themes/default/radio.png b/src/data/themes/default/radio.png
new file mode 100644 (file)
index 0000000..7596f48
Binary files /dev/null and b/src/data/themes/default/radio.png differ
diff --git a/src/data/themes/default/rdot.down.png b/src/data/themes/default/rdot.down.png
new file mode 100644 (file)
index 0000000..35cd4fe
Binary files /dev/null and b/src/data/themes/default/rdot.down.png differ
diff --git a/src/data/themes/default/rdot.hover.png b/src/data/themes/default/rdot.hover.png
new file mode 100644 (file)
index 0000000..5cd77a2
Binary files /dev/null and b/src/data/themes/default/rdot.hover.png differ
diff --git a/src/data/themes/default/rdot.normal.png b/src/data/themes/default/rdot.normal.png
new file mode 100644 (file)
index 0000000..636a207
Binary files /dev/null and b/src/data/themes/default/rdot.normal.png differ
diff --git a/src/data/themes/default/right.png b/src/data/themes/default/right.png
new file mode 100644 (file)
index 0000000..613779e
Binary files /dev/null and b/src/data/themes/default/right.png differ
diff --git a/src/data/themes/default/sbox.normal.png b/src/data/themes/default/sbox.normal.png
new file mode 100644 (file)
index 0000000..00be882
Binary files /dev/null and b/src/data/themes/default/sbox.normal.png differ
diff --git a/src/data/themes/default/scroller.slide.bar.hover.tga b/src/data/themes/default/scroller.slide.bar.hover.tga
new file mode 100644 (file)
index 0000000..d0b85a9
Binary files /dev/null and b/src/data/themes/default/scroller.slide.bar.hover.tga differ
diff --git a/src/data/themes/default/scroller.slide.bar.normal.tga b/src/data/themes/default/scroller.slide.bar.normal.tga
new file mode 100644 (file)
index 0000000..84ff6bb
Binary files /dev/null and b/src/data/themes/default/scroller.slide.bar.normal.tga differ
diff --git a/src/data/themes/default/scroller.slide.h.tga b/src/data/themes/default/scroller.slide.h.tga
new file mode 100644 (file)
index 0000000..0281567
Binary files /dev/null and b/src/data/themes/default/scroller.slide.h.tga differ
diff --git a/src/data/themes/default/scroller.slide.v.tga b/src/data/themes/default/scroller.slide.v.tga
new file mode 100644 (file)
index 0000000..cbaa875
Binary files /dev/null and b/src/data/themes/default/scroller.slide.v.tga differ
diff --git a/src/data/themes/default/select.arrow.down.tga b/src/data/themes/default/select.arrow.down.tga
new file mode 100644 (file)
index 0000000..d721002
Binary files /dev/null and b/src/data/themes/default/select.arrow.down.tga differ
diff --git a/src/data/themes/default/select.arrow.hover.tga b/src/data/themes/default/select.arrow.hover.tga
new file mode 100644 (file)
index 0000000..162d8e7
Binary files /dev/null and b/src/data/themes/default/select.arrow.hover.tga differ
diff --git a/src/data/themes/default/select.arrow.normal.tga b/src/data/themes/default/select.arrow.normal.tga
new file mode 100644 (file)
index 0000000..162d8e7
Binary files /dev/null and b/src/data/themes/default/select.arrow.normal.tga differ
diff --git a/src/data/themes/default/select.arrow.png b/src/data/themes/default/select.arrow.png
new file mode 100644 (file)
index 0000000..19de760
Binary files /dev/null and b/src/data/themes/default/select.arrow.png differ
diff --git a/src/data/themes/default/select.option.hover.png b/src/data/themes/default/select.option.hover.png
new file mode 100644 (file)
index 0000000..fd9dc21
Binary files /dev/null and b/src/data/themes/default/select.option.hover.png differ
diff --git a/src/data/themes/default/select.option.normal.png b/src/data/themes/default/select.option.normal.png
new file mode 100644 (file)
index 0000000..627790d
Binary files /dev/null and b/src/data/themes/default/select.option.normal.png differ
diff --git a/src/data/themes/default/select.options.png b/src/data/themes/default/select.options.png
new file mode 100644 (file)
index 0000000..477a826
Binary files /dev/null and b/src/data/themes/default/select.options.png differ
diff --git a/src/data/themes/default/select.selected.down.tga b/src/data/themes/default/select.selected.down.tga
new file mode 100644 (file)
index 0000000..7d952a0
Binary files /dev/null and b/src/data/themes/default/select.selected.down.tga differ
diff --git a/src/data/themes/default/select.selected.hover.tga b/src/data/themes/default/select.selected.hover.tga
new file mode 100644 (file)
index 0000000..91dd794
Binary files /dev/null and b/src/data/themes/default/select.selected.hover.tga differ
diff --git a/src/data/themes/default/select.selected.normal.tga b/src/data/themes/default/select.selected.normal.tga
new file mode 100644 (file)
index 0000000..54b8927
Binary files /dev/null and b/src/data/themes/default/select.selected.normal.tga differ
diff --git a/src/data/themes/default/slider.bar.hover.tga b/src/data/themes/default/slider.bar.hover.tga
new file mode 100644 (file)
index 0000000..5e5c53a
Binary files /dev/null and b/src/data/themes/default/slider.bar.hover.tga differ
diff --git a/src/data/themes/default/slider.bar.normal.tga b/src/data/themes/default/slider.bar.normal.tga
new file mode 100644 (file)
index 0000000..e9371c7
Binary files /dev/null and b/src/data/themes/default/slider.bar.normal.tga differ
diff --git a/src/data/themes/default/slider.tga b/src/data/themes/default/slider.tga
new file mode 100644 (file)
index 0000000..ff3b4b2
Binary files /dev/null and b/src/data/themes/default/slider.tga differ
diff --git a/src/data/themes/default/tool.down.tga b/src/data/themes/default/tool.down.tga
new file mode 100644 (file)
index 0000000..f89d4b4
Binary files /dev/null and b/src/data/themes/default/tool.down.tga differ
diff --git a/src/data/themes/default/tool.hover.tga b/src/data/themes/default/tool.hover.tga
new file mode 100644 (file)
index 0000000..184ae9c
Binary files /dev/null and b/src/data/themes/default/tool.hover.tga differ
diff --git a/src/data/themes/default/tool.normal.tga b/src/data/themes/default/tool.normal.tga
new file mode 100644 (file)
index 0000000..b304b87
Binary files /dev/null and b/src/data/themes/default/tool.normal.tga differ
diff --git a/src/data/themes/default/up.png b/src/data/themes/default/up.png
new file mode 100644 (file)
index 0000000..d42c324
Binary files /dev/null and b/src/data/themes/default/up.png differ
diff --git a/src/data/themes/default/vbox.normal.png b/src/data/themes/default/vbox.normal.png
new file mode 100644 (file)
index 0000000..9229c87
Binary files /dev/null and b/src/data/themes/default/vbox.normal.png differ
diff --git a/src/data/themes/default/vdot.down.png b/src/data/themes/default/vdot.down.png
new file mode 100644 (file)
index 0000000..e9e781e
Binary files /dev/null and b/src/data/themes/default/vdot.down.png differ
diff --git a/src/data/themes/default/vdot.hover.png b/src/data/themes/default/vdot.hover.png
new file mode 100644 (file)
index 0000000..74e043b
Binary files /dev/null and b/src/data/themes/default/vdot.hover.png differ
diff --git a/src/data/themes/default/vdot.normal.png b/src/data/themes/default/vdot.normal.png
new file mode 100644 (file)
index 0000000..f64089b
Binary files /dev/null and b/src/data/themes/default/vdot.normal.png differ
diff --git a/src/data/themes/default/vsbox.normal.png b/src/data/themes/default/vsbox.normal.png
new file mode 100644 (file)
index 0000000..2deca17
Binary files /dev/null and b/src/data/themes/default/vsbox.normal.png differ
diff --git a/src/data/themes/default/vslider.bar.hover.tga b/src/data/themes/default/vslider.bar.hover.tga
new file mode 100644 (file)
index 0000000..0a3f70a
Binary files /dev/null and b/src/data/themes/default/vslider.bar.hover.tga differ
diff --git a/src/data/themes/default/vslider.bar.normal.tga b/src/data/themes/default/vslider.bar.normal.tga
new file mode 100644 (file)
index 0000000..07ee06e
Binary files /dev/null and b/src/data/themes/default/vslider.bar.normal.tga differ
diff --git a/src/data/themes/default/vslider.down.tga b/src/data/themes/default/vslider.down.tga
new file mode 100644 (file)
index 0000000..61c75a6
Binary files /dev/null and b/src/data/themes/default/vslider.down.tga differ
diff --git a/src/data/themes/default/vslider.tga b/src/data/themes/default/vslider.tga
new file mode 100644 (file)
index 0000000..ff3b4b2
Binary files /dev/null and b/src/data/themes/default/vslider.tga differ
diff --git a/src/data/themes/default/vslider.up.tga b/src/data/themes/default/vslider.up.tga
new file mode 100644 (file)
index 0000000..ce73c30
Binary files /dev/null and b/src/data/themes/default/vslider.up.tga differ
diff --git a/src/data/themes/default/x.png b/src/data/themes/default/x.png
new file mode 100644 (file)
index 0000000..d00f36b
Binary files /dev/null and b/src/data/themes/default/x.png differ
diff --git a/src/data/themes/gray/Vera.ttf b/src/data/themes/gray/Vera.ttf
new file mode 100644 (file)
index 0000000..58cd6b5
Binary files /dev/null and b/src/data/themes/gray/Vera.ttf differ
diff --git a/src/data/themes/gray/box.down.png b/src/data/themes/gray/box.down.png
new file mode 100644 (file)
index 0000000..0009fe7
Binary files /dev/null and b/src/data/themes/gray/box.down.png differ
diff --git a/src/data/themes/gray/box.normal.png b/src/data/themes/gray/box.normal.png
new file mode 100644 (file)
index 0000000..e4599d9
Binary files /dev/null and b/src/data/themes/gray/box.normal.png differ
diff --git a/src/data/themes/gray/button.down.png b/src/data/themes/gray/button.down.png
new file mode 100644 (file)
index 0000000..efb67bc
Binary files /dev/null and b/src/data/themes/gray/button.down.png differ
diff --git a/src/data/themes/gray/button.normal.png b/src/data/themes/gray/button.normal.png
new file mode 100644 (file)
index 0000000..7393150
Binary files /dev/null and b/src/data/themes/gray/button.normal.png differ
diff --git a/src/data/themes/gray/checkbox.off.down.png b/src/data/themes/gray/checkbox.off.down.png
new file mode 100644 (file)
index 0000000..656f779
Binary files /dev/null and b/src/data/themes/gray/checkbox.off.down.png differ
diff --git a/src/data/themes/gray/checkbox.off.normal.png b/src/data/themes/gray/checkbox.off.normal.png
new file mode 100644 (file)
index 0000000..70943f1
Binary files /dev/null and b/src/data/themes/gray/checkbox.off.normal.png differ
diff --git a/src/data/themes/gray/checkbox.on.down.png b/src/data/themes/gray/checkbox.on.down.png
new file mode 100644 (file)
index 0000000..fa61a2b
Binary files /dev/null and b/src/data/themes/gray/checkbox.on.down.png differ
diff --git a/src/data/themes/gray/checkbox.on.normal.png b/src/data/themes/gray/checkbox.on.normal.png
new file mode 100644 (file)
index 0000000..5ee17b1
Binary files /dev/null and b/src/data/themes/gray/checkbox.on.normal.png differ
diff --git a/src/data/themes/gray/config.txt b/src/data/themes/gray/config.txt
new file mode 100644 (file)
index 0000000..0ea2006
--- /dev/null
@@ -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/src/data/themes/gray/console.input.focus.png b/src/data/themes/gray/console.input.focus.png
new file mode 100644 (file)
index 0000000..819d835
Binary files /dev/null and b/src/data/themes/gray/console.input.focus.png differ
diff --git a/src/data/themes/gray/console.input.normal.png b/src/data/themes/gray/console.input.normal.png
new file mode 100644 (file)
index 0000000..a14e329
Binary files /dev/null and b/src/data/themes/gray/console.input.normal.png differ
diff --git a/src/data/themes/gray/console.png b/src/data/themes/gray/console.png
new file mode 100644 (file)
index 0000000..a14e329
Binary files /dev/null and b/src/data/themes/gray/console.png differ
diff --git a/src/data/themes/gray/desktop.png b/src/data/themes/gray/desktop.png
new file mode 100644 (file)
index 0000000..73ac803
Binary files /dev/null and b/src/data/themes/gray/desktop.png differ
diff --git a/src/data/themes/gray/dialog.bar.png b/src/data/themes/gray/dialog.bar.png
new file mode 100644 (file)
index 0000000..ffac15d
Binary files /dev/null and b/src/data/themes/gray/dialog.bar.png differ
diff --git a/src/data/themes/gray/dialog.close.down.png b/src/data/themes/gray/dialog.close.down.png
new file mode 100644 (file)
index 0000000..cde6e96
Binary files /dev/null and b/src/data/themes/gray/dialog.close.down.png differ
diff --git a/src/data/themes/gray/dialog.close.normal.png b/src/data/themes/gray/dialog.close.normal.png
new file mode 100644 (file)
index 0000000..73dc9e4
Binary files /dev/null and b/src/data/themes/gray/dialog.close.normal.png differ
diff --git a/src/data/themes/gray/dialog.png b/src/data/themes/gray/dialog.png
new file mode 100644 (file)
index 0000000..1308b9c
Binary files /dev/null and b/src/data/themes/gray/dialog.png differ
diff --git a/src/data/themes/gray/filebrowser.folder.png b/src/data/themes/gray/filebrowser.folder.png
new file mode 100644 (file)
index 0000000..4a3bd2c
Binary files /dev/null and b/src/data/themes/gray/filebrowser.folder.png differ
diff --git a/src/data/themes/gray/input.focus.png b/src/data/themes/gray/input.focus.png
new file mode 100644 (file)
index 0000000..477a826
Binary files /dev/null and b/src/data/themes/gray/input.focus.png differ
diff --git a/src/data/themes/gray/input.normal.png b/src/data/themes/gray/input.normal.png
new file mode 100644 (file)
index 0000000..8519a98
Binary files /dev/null and b/src/data/themes/gray/input.normal.png differ
diff --git a/src/data/themes/gray/list.item.normal.png b/src/data/themes/gray/list.item.normal.png
new file mode 100644 (file)
index 0000000..627790d
Binary files /dev/null and b/src/data/themes/gray/list.item.normal.png differ
diff --git a/src/data/themes/gray/list.png b/src/data/themes/gray/list.png
new file mode 100644 (file)
index 0000000..99ad5bc
Binary files /dev/null and b/src/data/themes/gray/list.png differ
diff --git a/src/data/themes/gray/menu.down.png b/src/data/themes/gray/menu.down.png
new file mode 100644 (file)
index 0000000..3555053
Binary files /dev/null and b/src/data/themes/gray/menu.down.png differ
diff --git a/src/data/themes/gray/menu.hover.png b/src/data/themes/gray/menu.hover.png
new file mode 100644 (file)
index 0000000..f4b2a6a
Binary files /dev/null and b/src/data/themes/gray/menu.hover.png differ
diff --git a/src/data/themes/gray/menu.normal.png b/src/data/themes/gray/menu.normal.png
new file mode 100644 (file)
index 0000000..9a7aca6
Binary files /dev/null and b/src/data/themes/gray/menu.normal.png differ
diff --git a/src/data/themes/gray/menu.option.hover.png b/src/data/themes/gray/menu.option.hover.png
new file mode 100644 (file)
index 0000000..8ae05f3
Binary files /dev/null and b/src/data/themes/gray/menu.option.hover.png differ
diff --git a/src/data/themes/gray/menu.option.normal.png b/src/data/themes/gray/menu.option.normal.png
new file mode 100644 (file)
index 0000000..394200b
Binary files /dev/null and b/src/data/themes/gray/menu.option.normal.png differ
diff --git a/src/data/themes/gray/radio.off.down.png b/src/data/themes/gray/radio.off.down.png
new file mode 100644 (file)
index 0000000..5a6e9a3
Binary files /dev/null and b/src/data/themes/gray/radio.off.down.png differ
diff --git a/src/data/themes/gray/radio.off.normal.png b/src/data/themes/gray/radio.off.normal.png
new file mode 100644 (file)
index 0000000..4a57f1f
Binary files /dev/null and b/src/data/themes/gray/radio.off.normal.png differ
diff --git a/src/data/themes/gray/radio.on.down.png b/src/data/themes/gray/radio.on.down.png
new file mode 100644 (file)
index 0000000..483bd66
Binary files /dev/null and b/src/data/themes/gray/radio.on.down.png differ
diff --git a/src/data/themes/gray/radio.on.normal.png b/src/data/themes/gray/radio.on.normal.png
new file mode 100644 (file)
index 0000000..43b380b
Binary files /dev/null and b/src/data/themes/gray/radio.on.normal.png differ
diff --git a/src/data/themes/gray/select.arrow.down.png b/src/data/themes/gray/select.arrow.down.png
new file mode 100644 (file)
index 0000000..9ef850e
Binary files /dev/null and b/src/data/themes/gray/select.arrow.down.png differ
diff --git a/src/data/themes/gray/select.arrow.normal.png b/src/data/themes/gray/select.arrow.normal.png
new file mode 100644 (file)
index 0000000..fde6e42
Binary files /dev/null and b/src/data/themes/gray/select.arrow.normal.png differ
diff --git a/src/data/themes/gray/select.arrow.png b/src/data/themes/gray/select.arrow.png
new file mode 100644 (file)
index 0000000..19de760
Binary files /dev/null and b/src/data/themes/gray/select.arrow.png differ
diff --git a/src/data/themes/gray/select.option.normal.png b/src/data/themes/gray/select.option.normal.png
new file mode 100644 (file)
index 0000000..627790d
Binary files /dev/null and b/src/data/themes/gray/select.option.normal.png differ
diff --git a/src/data/themes/gray/select.options.png b/src/data/themes/gray/select.options.png
new file mode 100644 (file)
index 0000000..477a826
Binary files /dev/null and b/src/data/themes/gray/select.options.png differ
diff --git a/src/data/themes/gray/select.selected.normal.png b/src/data/themes/gray/select.selected.normal.png
new file mode 100644 (file)
index 0000000..e1463f8
Binary files /dev/null and b/src/data/themes/gray/select.selected.normal.png differ
diff --git a/src/data/themes/gray/slider.bar.normal.png b/src/data/themes/gray/slider.bar.normal.png
new file mode 100644 (file)
index 0000000..b335bda
Binary files /dev/null and b/src/data/themes/gray/slider.bar.normal.png differ
diff --git a/src/data/themes/gray/slider.png b/src/data/themes/gray/slider.png
new file mode 100644 (file)
index 0000000..0ed9619
Binary files /dev/null and b/src/data/themes/gray/slider.png differ
diff --git a/src/data/themes/gray/tool.down.png b/src/data/themes/gray/tool.down.png
new file mode 100644 (file)
index 0000000..760e666
Binary files /dev/null and b/src/data/themes/gray/tool.down.png differ
diff --git a/src/data/themes/gray/tool.normal.png b/src/data/themes/gray/tool.normal.png
new file mode 100644 (file)
index 0000000..17b344d
Binary files /dev/null and b/src/data/themes/gray/tool.normal.png differ
diff --git a/src/data/themes/tools/config.txt b/src/data/themes/tools/config.txt
new file mode 100644 (file)
index 0000000..b663eb0
--- /dev/null
@@ -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/src/data/themes/tools/icons48.bkgr.tga b/src/data/themes/tools/icons48.bkgr.tga
new file mode 100644 (file)
index 0000000..67a614b
Binary files /dev/null and b/src/data/themes/tools/icons48.bkgr.tga differ
diff --git a/src/data/themes/tools/icons48.code.tga b/src/data/themes/tools/icons48.code.tga
new file mode 100644 (file)
index 0000000..bfe9615
Binary files /dev/null and b/src/data/themes/tools/icons48.code.tga differ
diff --git a/src/data/themes/tools/icons48.draw.tga b/src/data/themes/tools/icons48.draw.tga
new file mode 100644 (file)
index 0000000..0eec5ff
Binary files /dev/null and b/src/data/themes/tools/icons48.draw.tga differ
diff --git a/src/data/themes/tools/icons48.eraser.tga b/src/data/themes/tools/icons48.eraser.tga
new file mode 100644 (file)
index 0000000..a7f4d42
Binary files /dev/null and b/src/data/themes/tools/icons48.eraser.tga differ
diff --git a/src/data/themes/tools/icons48.fill.tga b/src/data/themes/tools/icons48.fill.tga
new file mode 100644 (file)
index 0000000..cb7be71
Binary files /dev/null and b/src/data/themes/tools/icons48.fill.tga differ
diff --git a/src/data/themes/tools/icons48.line.tga b/src/data/themes/tools/icons48.line.tga
new file mode 100644 (file)
index 0000000..19f9f9c
Binary files /dev/null and b/src/data/themes/tools/icons48.line.tga differ
diff --git a/src/data/themes/tools/icons48.pixel.tga b/src/data/themes/tools/icons48.pixel.tga
new file mode 100644 (file)
index 0000000..976b66a
Binary files /dev/null and b/src/data/themes/tools/icons48.pixel.tga differ
diff --git a/src/data/themes/tools/icons48.select.tga b/src/data/themes/tools/icons48.select.tga
new file mode 100644 (file)
index 0000000..09ee631
Binary files /dev/null and b/src/data/themes/tools/icons48.select.tga differ
diff --git a/src/data/themes/tools/icons48.tile.tga b/src/data/themes/tools/icons48.tile.tga
new file mode 100644 (file)
index 0000000..8ca8bae
Binary files /dev/null and b/src/data/themes/tools/icons48.tile.tga differ
diff --git a/src/dataTools/__init__.py b/src/dataTools/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/dataTools/__init__.pyc b/src/dataTools/__init__.pyc
new file mode 100644 (file)
index 0000000..759cc27
Binary files /dev/null and b/src/dataTools/__init__.pyc differ
diff --git a/src/dataTools/odict.py b/src/dataTools/odict.py
new file mode 100644 (file)
index 0000000..2c8391d
--- /dev/null
@@ -0,0 +1,1399 @@
+# odict.py
+# An Ordered Dictionary object
+# Copyright (C) 2005 Nicola Larosa, Michael Foord
+# E-mail: nico AT tekNico DOT net, fuzzyman AT voidspace DOT org DOT uk
+
+# This software is licensed under the terms of the BSD license.
+# http://www.voidspace.org.uk/python/license.shtml
+# Basically you're free to copy, modify, distribute and relicense it,
+# So long as you keep a copy of the license with it.
+
+# Documentation at http://www.voidspace.org.uk/python/odict.html
+# For information about bugfixes, updates and support, please join the
+# Pythonutils mailing list:
+# http://groups.google.com/group/pythonutils/
+# Comments, suggestions and bug reports welcome.
+
+"""A dict that keeps keys in insertion order"""
+from __future__ import generators
+
+__author__ = ('Nicola Larosa <nico-NoSp@m-tekNico.net>,'
+    'Michael Foord <fuzzyman AT voidspace DOT org DOT uk>')
+
+__docformat__ = "restructuredtext en"
+
+__revision__ = '$Id: odict.py 129 2005-09-12 18:15:28Z teknico $'
+
+__version__ = '0.2.2'
+
+__all__ = ['OrderedDict', 'SequenceOrderedDict']
+
+import sys
+INTP_VER = sys.version_info[:2]
+if INTP_VER < (2, 2):
+    raise RuntimeError("Python v.2.2 or later required")
+
+import types, warnings
+
+class OrderedDict(dict):
+    """
+    A class of dictionary that keeps the insertion order of keys.
+    
+    All appropriate methods return keys, items, or values in an ordered way.
+    
+    All normal dictionary methods are available. Update and comparison is
+    restricted to other OrderedDict objects.
+    
+    Various sequence methods are available, including the ability to explicitly
+    mutate the key ordering.
+    
+    __contains__ tests:
+    
+    >>> d = OrderedDict(((1, 3),))
+    >>> 1 in d
+    1
+    >>> 4 in d
+    0
+    
+    __getitem__ tests:
+    
+    >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2]
+    1
+    >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4]
+    Traceback (most recent call last):
+    KeyError: 4
+    
+    __len__ tests:
+    
+    >>> len(OrderedDict())
+    0
+    >>> len(OrderedDict(((1, 3), (3, 2), (2, 1))))
+    3
+    
+    get tests:
+    
+    >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+    >>> d.get(1)
+    3
+    >>> d.get(4) is None
+    1
+    >>> d.get(4, 5)
+    5
+    >>> d
+    OrderedDict([(1, 3), (3, 2), (2, 1)])
+    
+    has_key tests:
+    
+    >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+    >>> d.has_key(1)
+    1
+    >>> d.has_key(4)
+    0
+    """
+
+    def __init__(self, init_val=(), strict=False):
+        """
+        Create a new ordered dictionary. Cannot init from a normal dict,
+        nor from kwargs, since items order is undefined in those cases.
+        
+        If the ``strict`` keyword argument is ``True`` (``False`` is the
+        default) then when doing slice assignment - the ``OrderedDict`` you are
+        assigning from *must not* contain any keys in the remaining dict.
+        
+        >>> OrderedDict()
+        OrderedDict([])
+        >>> OrderedDict({1: 1})
+        Traceback (most recent call last):
+        TypeError: undefined order, cannot get items from dict
+        >>> OrderedDict({1: 1}.items())
+        OrderedDict([(1, 1)])
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d
+        OrderedDict([(1, 3), (3, 2), (2, 1)])
+        >>> OrderedDict(d)
+        OrderedDict([(1, 3), (3, 2), (2, 1)])
+        """
+        self.strict = strict
+        dict.__init__(self)
+        if isinstance(init_val, OrderedDict):
+            self._sequence = init_val.keys()
+            dict.update(self, init_val)
+        elif isinstance(init_val, dict):
+            # we lose compatibility with other ordered dict types this way
+            raise TypeError('undefined order, cannot get items from dict')
+        else:
+            self._sequence = []
+            self.update(init_val)
+
+### Special methods ###
+
+    def __delitem__(self, key):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> del d[3]
+        >>> d
+        OrderedDict([(1, 3), (2, 1)])
+        >>> del d[3]
+        Traceback (most recent call last):
+        KeyError: 3
+        >>> d[3] = 2
+        >>> d
+        OrderedDict([(1, 3), (2, 1), (3, 2)])
+        >>> del d[0:1]
+        >>> d
+        OrderedDict([(2, 1), (3, 2)])
+        """
+        if isinstance(key, types.SliceType):
+            # FIXME: efficiency?
+            keys = self._sequence[key]
+            for entry in keys:
+                dict.__delitem__(self, entry)
+            del self._sequence[key]
+        else:
+            # do the dict.__delitem__ *first* as it raises
+            # the more appropriate error
+            dict.__delitem__(self, key)
+            self._sequence.remove(key)
+
+    def __eq__(self, other):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d == OrderedDict(d)
+        True
+        >>> d == OrderedDict(((1, 3), (2, 1), (3, 2)))
+        False
+        >>> d == OrderedDict(((1, 0), (3, 2), (2, 1)))
+        False
+        >>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
+        False
+        >>> d == dict(d)
+        False
+        >>> d == False
+        False
+        """
+        if isinstance(other, OrderedDict):
+            # FIXME: efficiency?
+            #   Generate both item lists for each compare
+            return (self.items() == other.items())
+        else:
+            return False
+
+    def __lt__(self, other):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+        >>> c < d
+        True
+        >>> d < c
+        False
+        >>> d < dict(c)
+        Traceback (most recent call last):
+        TypeError: Can only compare with other OrderedDicts
+        """
+        if not isinstance(other, OrderedDict):
+            raise TypeError('Can only compare with other OrderedDicts')
+        # FIXME: efficiency?
+        #   Generate both item lists for each compare
+        return (self.items() < other.items())
+
+    def __le__(self, other):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+        >>> e = OrderedDict(d)
+        >>> c <= d
+        True
+        >>> d <= c
+        False
+        >>> d <= dict(c)
+        Traceback (most recent call last):
+        TypeError: Can only compare with other OrderedDicts
+        >>> d <= e
+        True
+        """
+        if not isinstance(other, OrderedDict):
+            raise TypeError('Can only compare with other OrderedDicts')
+        # FIXME: efficiency?
+        #   Generate both item lists for each compare
+        return (self.items() <= other.items())
+
+    def __ne__(self, other):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d != OrderedDict(d)
+        False
+        >>> d != OrderedDict(((1, 3), (2, 1), (3, 2)))
+        True
+        >>> d != OrderedDict(((1, 0), (3, 2), (2, 1)))
+        True
+        >>> d == OrderedDict(((0, 3), (3, 2), (2, 1)))
+        False
+        >>> d != dict(d)
+        True
+        >>> d != False
+        True
+        """
+        if isinstance(other, OrderedDict):
+            # FIXME: efficiency?
+            #   Generate both item lists for each compare
+            return not (self.items() == other.items())
+        else:
+            return True
+
+    def __gt__(self, other):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+        >>> d > c
+        True
+        >>> c > d
+        False
+        >>> d > dict(c)
+        Traceback (most recent call last):
+        TypeError: Can only compare with other OrderedDicts
+        """
+        if not isinstance(other, OrderedDict):
+            raise TypeError('Can only compare with other OrderedDicts')
+        # FIXME: efficiency?
+        #   Generate both item lists for each compare
+        return (self.items() > other.items())
+
+    def __ge__(self, other):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> c = OrderedDict(((0, 3), (3, 2), (2, 1)))
+        >>> e = OrderedDict(d)
+        >>> c >= d
+        False
+        >>> d >= c
+        True
+        >>> d >= dict(c)
+        Traceback (most recent call last):
+        TypeError: Can only compare with other OrderedDicts
+        >>> e >= d
+        True
+        """
+        if not isinstance(other, OrderedDict):
+            raise TypeError('Can only compare with other OrderedDicts')
+        # FIXME: efficiency?
+        #   Generate both item lists for each compare
+        return (self.items() >= other.items())
+
+    def __repr__(self):
+        """
+        Used for __repr__ and __str__
+        
+        >>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
+        >>> r1
+        "OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])"
+        >>> r2 = repr(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
+        >>> r2
+        "OrderedDict([('a', 'b'), ('e', 'f'), ('c', 'd')])"
+        >>> r1 == str(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f'))))
+        True
+        >>> r2 == str(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd'))))
+        True
+        """
+        return '%s([%s])' % (self.__class__.__name__, ', '.join(
+            ['(%r, %r)' % (key, self[key]) for key in self._sequence]))
+
+    def __setitem__(self, key, val):
+        """
+        Allows slice assignment, so long as the slice is an OrderedDict
+        >>> d = OrderedDict()
+        >>> d['a'] = 'b'
+        >>> d['b'] = 'a'
+        >>> d[3] = 12
+        >>> d
+        OrderedDict([('a', 'b'), ('b', 'a'), (3, 12)])
+        >>> d[:] = OrderedDict(((1, 2), (2, 3), (3, 4)))
+        >>> d
+        OrderedDict([(1, 2), (2, 3), (3, 4)])
+        >>> d[::2] = OrderedDict(((7, 8), (9, 10)))
+        >>> d
+        OrderedDict([(7, 8), (2, 3), (9, 10)])
+        >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)))
+        >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
+        >>> d
+        OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
+        >>> d = OrderedDict(((0, 1), (1, 2), (2, 3), (3, 4)), strict=True)
+        >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8)))
+        >>> d
+        OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)])
+        
+        >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True)
+        >>> a[3] = 4
+        >>> a
+        OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a
+        OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)])
+        Traceback (most recent call last):
+        ValueError: slice assignment must be from unique keys
+        >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)))
+        >>> a[3] = 4
+        >>> a
+        OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a[::1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a
+        OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a[:2] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a
+        OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> a
+        OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)])
+        
+        >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> d[:1] = 3
+        Traceback (most recent call last):
+        TypeError: slice assignment requires an OrderedDict
+        
+        >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)])
+        >>> d[:1] = OrderedDict([(9, 8)])
+        >>> d
+        OrderedDict([(9, 8), (1, 2), (2, 3), (3, 4)])
+        """
+        if isinstance(key, types.SliceType):
+            if not isinstance(val, OrderedDict):
+                # FIXME: allow a list of tuples?
+                raise TypeError('slice assignment requires an OrderedDict')
+            keys = self._sequence[key]
+            # NOTE: Could use ``range(*key.indices(len(self._sequence)))``
+            indexes = range(len(self._sequence))[key]
+            if key.step is None:
+                # NOTE: new slice may not be the same size as the one being
+                #   overwritten !
+                # NOTE: What is the algorithm for an impossible slice?
+                #   e.g. d[5:3]
+                pos = key.start or 0
+                del self[key]
+                newkeys = val.keys()
+                for k in newkeys:
+                    if k in self:
+                        if self.strict:
+                            raise ValueError('slice assignment must be from '
+                                'unique keys')
+                        else:
+                            # NOTE: This removes duplicate keys *first*
+                            #   so start position might have changed?
+                            del self[k]
+                self._sequence = (self._sequence[:pos] + newkeys +
+                    self._sequence[pos:])
+                dict.update(self, val)
+            else:
+                # extended slice - length of new slice must be the same
+                # as the one being replaced
+                if len(keys) != len(val):
+                    raise ValueError('attempt to assign sequence of size %s '
+                        'to extended slice of size %s' % (len(val), len(keys)))
+                # FIXME: efficiency?
+                del self[key]
+                item_list = zip(indexes, val.items())
+                # smallest indexes first - higher indexes not guaranteed to
+                # exist
+                item_list.sort()
+                for pos, (newkey, newval) in item_list:
+                    if self.strict and newkey in self:
+                        raise ValueError('slice assignment must be from unique'
+                            ' keys')
+                    self.insert(pos, newkey, newval)
+        else:
+            if key not in self:
+                self._sequence.append(key)
+            dict.__setitem__(self, key, val)
+
+    def __getitem__(self, key):
+        """
+        Allows slicing. Returns an OrderedDict if you slice.
+        >>> b = OrderedDict([(7, 0), (6, 1), (5, 2), (4, 3), (3, 4), (2, 5), (1, 6)])
+        >>> b[::-1]
+        OrderedDict([(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1), (7, 0)])
+        >>> b[2:5]
+        OrderedDict([(5, 2), (4, 3), (3, 4)])
+        >>> type(b[2:4])
+        <class '__main__.OrderedDict'>
+        """
+        if isinstance(key, types.SliceType):
+            # FIXME: does this raise the error we want?
+            keys = self._sequence[key]
+            # FIXME: efficiency?
+            return OrderedDict([(entry, self[entry]) for entry in keys])
+        else:
+            return dict.__getitem__(self, key)
+
+    __str__ = __repr__
+
+    def __setattr__(self, name, value):
+        """
+        Implemented so that accesses to ``sequence`` raise a warning and are
+        diverted to the new ``setkeys`` method.
+        """
+        if name == 'sequence':
+            warnings.warn('Use of the sequence attribute is deprecated.'
+                ' Use the keys method instead.', DeprecationWarning)
+            # NOTE: doesn't return anything
+            self.setkeys(value)
+        else:
+            # FIXME: do we want to allow arbitrary setting of attributes?
+            #   Or do we want to manage it?
+            object.__setattr__(self, name, value)
+
+    def __getattr__(self, name):
+        """
+        Implemented so that access to ``sequence`` raises a warning.
+        
+        >>> d = OrderedDict()
+        >>> d.sequence
+        []
+        """
+        if name == 'sequence':
+            warnings.warn('Use of the sequence attribute is deprecated.'
+                ' Use the keys method instead.', DeprecationWarning)
+            # NOTE: Still (currently) returns a direct reference. Need to
+            #   because code that uses sequence will expect to be able to
+            #   mutate it in place.
+            return self._sequence
+        else:
+            # raise the appropriate error
+            raise AttributeError("OrderedDict has no '%s' attribute" % name)
+
+    def __deepcopy__(self, memo):
+        """
+        To allow deepcopy to work with OrderedDict.
+        
+        >>> from copy import deepcopy
+        >>> a = OrderedDict([(1, 1), (2, 2), (3, 3)])
+        >>> a['test'] = {}
+        >>> b = deepcopy(a)
+        >>> b == a
+        True
+        >>> b is a
+        False
+        >>> a['test'] is b['test']
+        False
+        """
+        from copy import deepcopy
+        return self.__class__(deepcopy(self.items(), memo), self.strict)
+
+
+### Read-only methods ###
+
+    def copy(self):
+        """
+        >>> OrderedDict(((1, 3), (3, 2), (2, 1))).copy()
+        OrderedDict([(1, 3), (3, 2), (2, 1)])
+        """
+        return OrderedDict(self)
+
+    def items(self):
+        """
+        ``items`` returns a list of tuples representing all the 
+        ``(key, value)`` pairs in the dictionary.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.items()
+        [(1, 3), (3, 2), (2, 1)]
+        >>> d.clear()
+        >>> d.items()
+        []
+        """
+        return zip(self._sequence, self.values())
+
+    def keys(self):
+        """
+        Return a list of keys in the ``OrderedDict``.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.keys()
+        [1, 3, 2]
+        """
+        return self._sequence[:]
+
+    def values(self, values=None):
+        """
+        Return a list of all the values in the OrderedDict.
+        
+        Optionally you can pass in a list of values, which will replace the
+        current list. The value list must be the same len as the OrderedDict.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.values()
+        [3, 2, 1]
+        """
+        return [self[key] for key in self._sequence]
+
+    def iteritems(self):
+        """
+        >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iteritems()
+        >>> ii.next()
+        (1, 3)
+        >>> ii.next()
+        (3, 2)
+        >>> ii.next()
+        (2, 1)
+        >>> ii.next()
+        Traceback (most recent call last):
+        StopIteration
+        """
+        def make_iter(self=self):
+            keys = self.iterkeys()
+            while True:
+                key = keys.next()
+                yield (key, self[key])
+        return make_iter()
+
+    def iterkeys(self):
+        """
+        >>> ii = OrderedDict(((1, 3), (3, 2), (2, 1))).iterkeys()
+        >>> ii.next()
+        1
+        >>> ii.next()
+        3
+        >>> ii.next()
+        2
+        >>> ii.next()
+        Traceback (most recent call last):
+        StopIteration
+        """
+        return iter(self._sequence)
+
+    __iter__ = iterkeys
+
+    def itervalues(self):
+        """
+        >>> iv = OrderedDict(((1, 3), (3, 2), (2, 1))).itervalues()
+        >>> iv.next()
+        3
+        >>> iv.next()
+        2
+        >>> iv.next()
+        1
+        >>> iv.next()
+        Traceback (most recent call last):
+        StopIteration
+        """
+        def make_iter(self=self):
+            keys = self.iterkeys()
+            while True:
+                yield self[keys.next()]
+        return make_iter()
+
+### Read-write methods ###
+
+    def clear(self):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.clear()
+        >>> d
+        OrderedDict([])
+        """
+        dict.clear(self)
+        self._sequence = []
+
+    def pop(self, key, *args):
+        """
+        No dict.pop in Python 2.2, gotta reimplement it
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.pop(3)
+        2
+        >>> d
+        OrderedDict([(1, 3), (2, 1)])
+        >>> d.pop(4)
+        Traceback (most recent call last):
+        KeyError: 4
+        >>> d.pop(4, 0)
+        0
+        >>> d.pop(4, 0, 1)
+        Traceback (most recent call last):
+        TypeError: pop expected at most 2 arguments, got 3
+        """
+        if len(args) > 1:
+            raise TypeError, ('pop expected at most 2 arguments, got %s' %
+                (len(args) + 1))
+        if key in self:
+            val = self[key]
+            del self[key]
+        else:
+            try:
+                val = args[0]
+            except IndexError:
+                raise KeyError(key)
+        return val
+
+    def popitem(self, i=-1):
+        """
+        Delete and return an item specified by index, not a random one as in
+        dict. The index is -1 by default (the last item).
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.popitem()
+        (2, 1)
+        >>> d
+        OrderedDict([(1, 3), (3, 2)])
+        >>> d.popitem(0)
+        (1, 3)
+        >>> OrderedDict().popitem()
+        Traceback (most recent call last):
+        KeyError: 'popitem(): dictionary is empty'
+        >>> d.popitem(2)
+        Traceback (most recent call last):
+        IndexError: popitem(): index 2 not valid
+        """
+        if not self._sequence:
+            raise KeyError('popitem(): dictionary is empty')
+        try:
+            key = self._sequence[i]
+        except IndexError:
+            raise IndexError('popitem(): index %s not valid' % i)
+        return (key, self.pop(key))
+
+    def setdefault(self, key, defval = None):
+        """
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.setdefault(1)
+        3
+        >>> d.setdefault(4) is None
+        True
+        >>> d
+        OrderedDict([(1, 3), (3, 2), (2, 1), (4, None)])
+        >>> d.setdefault(5, 0)
+        0
+        >>> d
+        OrderedDict([(1, 3), (3, 2), (2, 1), (4, None), (5, 0)])
+        """
+        if key in self:
+            return self[key]
+        else:
+            self[key] = defval
+            return defval
+
+    def update(self, from_od):
+        """
+        Update from another OrderedDict or sequence of (key, value) pairs
+        
+        >>> d = OrderedDict(((1, 0), (0, 1)))
+        >>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1))))
+        >>> d
+        OrderedDict([(1, 3), (0, 1), (3, 2), (2, 1)])
+        >>> d.update({4: 4})
+        Traceback (most recent call last):
+        TypeError: undefined order, cannot get items from dict
+        >>> d.update((4, 4))
+        Traceback (most recent call last):
+        TypeError: cannot convert dictionary update sequence element "4" to a 2-item sequence
+        """
+        if isinstance(from_od, OrderedDict):
+            for key, val in from_od.items():
+                self[key] = val
+        elif isinstance(from_od, dict):
+            # we lose compatibility with other ordered dict types this way
+            raise TypeError('undefined order, cannot get items from dict')
+        else:
+            # FIXME: efficiency?
+            # sequence of 2-item sequences, or error
+            for item in from_od:
+                try:
+                    key, val = item
+                except TypeError:
+                    raise TypeError('cannot convert dictionary update'
+                        ' sequence element "%s" to a 2-item sequence' % item)
+                self[key] = val
+
+    def rename(self, old_key, new_key):
+        """
+        Rename the key for a given value, without modifying sequence order.
+        
+        For the case where new_key already exists this raise an exception,
+        since if new_key exists, it is ambiguous as to what happens to the
+        associated values, and the position of new_key in the sequence.
+        
+        >>> od = OrderedDict()
+        >>> od['a'] = 1
+        >>> od['b'] = 2
+        >>> od.items()
+        [('a', 1), ('b', 2)]
+        >>> od.rename('b', 'c')
+        >>> od.items()
+        [('a', 1), ('c', 2)]
+        >>> od.rename('c', 'a')
+        Traceback (most recent call last):
+        ValueError: New key already exists: 'a'
+        >>> od.rename('d', 'b')
+        Traceback (most recent call last):
+        KeyError: 'd'
+        """
+        if new_key == old_key:
+            # no-op
+            return
+        if new_key in self:
+            raise ValueError("New key already exists: %r" % new_key)
+        # rename sequence entry
+        value = self[old_key] 
+        old_idx = self._sequence.index(old_key)
+        self._sequence[old_idx] = new_key
+        # rename internal dict entry
+        dict.__delitem__(self, old_key)
+        dict.__setitem__(self, new_key, value)
+
+    def setitems(self, items):
+        """
+        This method allows you to set the items in the dict.
+        
+        It takes a list of tuples - of the same sort returned by the ``items``
+        method.
+        
+        >>> d = OrderedDict()
+        >>> d.setitems(((3, 1), (2, 3), (1, 2)))
+        >>> d
+        OrderedDict([(3, 1), (2, 3), (1, 2)])
+        """
+        self.clear()
+        # FIXME: this allows you to pass in an OrderedDict as well :-)
+        self.update(items)
+
+    def setkeys(self, keys):
+        """
+        ``setkeys`` all ows you to pass in a new list of keys which will
+        replace the current set. This must contain the same set of keys, but
+        need not be in the same order.
+        
+        If you pass in new keys that don't match, a ``KeyError`` will be
+        raised.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.keys()
+        [1, 3, 2]
+        >>> d.setkeys((1, 2, 3))
+        >>> d
+        OrderedDict([(1, 3), (2, 1), (3, 2)])
+        >>> d.setkeys(['a', 'b', 'c'])
+        Traceback (most recent call last):
+        KeyError: 'Keylist is not the same as current keylist.'
+        """
+        # FIXME: Efficiency? (use set for Python 2.4 :-)
+        # NOTE: list(keys) rather than keys[:] because keys[:] returns
+        #   a tuple, if keys is a tuple.
+        kcopy = list(keys)
+        kcopy.sort()
+        self._sequence.sort()
+        if kcopy != self._sequence:
+            raise KeyError('Keylist is not the same as current keylist.')
+        # NOTE: This makes the _sequence attribute a new object, instead
+        #       of changing it in place.
+        # FIXME: efficiency?
+        self._sequence = list(keys)
+
+    def setvalues(self, values):
+        """
+        You can pass in a list of values, which will replace the
+        current list. The value list must be the same len as the OrderedDict.
+        
+        (Or a ``ValueError`` is raised.)
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.setvalues((1, 2, 3))
+        >>> d
+        OrderedDict([(1, 1), (3, 2), (2, 3)])
+        >>> d.setvalues([6])
+        Traceback (most recent call last):
+        ValueError: Value list is not the same length as the OrderedDict.
+        """
+        if len(values) != len(self):
+            # FIXME: correct error to raise?
+            raise ValueError('Value list is not the same length as the '
+                'OrderedDict.')
+        self.update(zip(self, values))
+
+### Sequence Methods ###
+
+    def index(self, key):
+        """
+        Return the position of the specified key in the OrderedDict.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.index(3)
+        1
+        >>> d.index(4)
+        Traceback (most recent call last):
+        ValueError: list.index(x): x not in list
+        """
+        return self._sequence.index(key)
+
+    def insert(self, index, key, value):
+        """
+        Takes ``index``, ``key``, and ``value`` as arguments.
+        
+        Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in
+        the OrderedDict.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.insert(0, 4, 0)
+        >>> d
+        OrderedDict([(4, 0), (1, 3), (3, 2), (2, 1)])
+        >>> d.insert(0, 2, 1)
+        >>> d
+        OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2)])
+        >>> d.insert(8, 8, 1)
+        >>> d
+        OrderedDict([(2, 1), (4, 0), (1, 3), (3, 2), (8, 1)])
+        """
+        if key in self:
+            # FIXME: efficiency?
+            del self[key]
+        self._sequence.insert(index, key)
+        dict.__setitem__(self, key, value)
+
+    def reverse(self):
+        """
+        Reverse the order of the OrderedDict.
+        
+        >>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
+        >>> d.reverse()
+        >>> d
+        OrderedDict([(2, 1), (3, 2), (1, 3)])
+        """
+        self._sequence.reverse()
+
+    def sort(self, *args, **kwargs):
+        """
+        Sort the key order in the OrderedDict.
+        
+        This method takes the same arguments as the ``list.sort`` method on
+        your version of Python.
+        
+        >>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4)))
+        >>> d.sort()
+        >>> d
+        OrderedDict([(1, 4), (2, 2), (3, 3), (4, 1)])
+        """
+        self._sequence.sort(*args, **kwargs)
+
+class Keys(object):
+    # FIXME: should this object be a subclass of list?
+    """
+    Custom object for accessing the keys of an OrderedDict.
+    
+    Can be called like the normal ``OrderedDict.keys`` method, but also
+    supports indexing and sequence methods.
+    """
+
+    def __init__(self, main):
+        self._main = main
+
+    def __call__(self):
+        """Pretend to be the keys method."""
+        return self._main._keys()
+
+    def __getitem__(self, index):
+        """Fetch the key at position i."""
+        # NOTE: this automatically supports slicing :-)
+        return self._main._sequence[index]
+
+    def __setitem__(self, index, name):
+        """
+        You cannot assign to keys, but you can do slice assignment to re-order
+        them.
+        
+        You can only do slice assignment if the new set of keys is a reordering
+        of the original set.
+        """
+        if isinstance(index, types.SliceType):
+            # FIXME: efficiency?
+            # check length is the same
+            indexes = range(len(self._main._sequence))[index]
+            if len(indexes) != len(name):
+                raise ValueError('attempt to assign sequence of size %s '
+                    'to slice of size %s' % (len(name), len(indexes)))
+            # check they are the same keys
+            # FIXME: Use set
+            old_keys = self._main._sequence[index]
+            new_keys = list(name)
+            old_keys.sort()
+            new_keys.sort()
+            if old_keys != new_keys:
+                raise KeyError('Keylist is not the same as current keylist.')
+            orig_vals = [self._main[k] for k in name]
+            del self._main[index]
+            vals = zip(indexes, name, orig_vals)
+            vals.sort()
+            for i, k, v in vals:
+                if self._main.strict and k in self._main:
+                    raise ValueError('slice assignment must be from '
+                        'unique keys')
+                self._main.insert(i, k, v)
+        else:
+            raise ValueError('Cannot assign to keys')
+
+    ### following methods pinched from UserList and adapted ###
+    def __repr__(self): return repr(self._main._sequence)
+
+    # FIXME: do we need to check if we are comparing with another ``Keys``
+    #   object? (like the __cast method of UserList)
+    def __lt__(self, other): return self._main._sequence <  other
+    def __le__(self, other): return self._main._sequence <= other
+    def __eq__(self, other): return self._main._sequence == other
+    def __ne__(self, other): return self._main._sequence != other
+    def __gt__(self, other): return self._main._sequence >  other
+    def __ge__(self, other): return self._main._sequence >= other
+    # FIXME: do we need __cmp__ as well as rich comparisons?
+    def __cmp__(self, other): return cmp(self._main._sequence, other)
+
+    def __contains__(self, item): return item in self._main._sequence
+    def __len__(self): return len(self._main._sequence)
+    def __iter__(self): return self._main.iterkeys()
+    def count(self, item): return self._main._sequence.count(item)
+    def index(self, item, *args): return self._main._sequence.index(item, *args)
+    def reverse(self): self._main._sequence.reverse()
+    def sort(self, *args, **kwds): self._main._sequence.sort(*args, **kwds)
+    def __mul__(self, n): return self._main._sequence*n
+    __rmul__ = __mul__
+    def __add__(self, other): return self._main._sequence + other
+    def __radd__(self, other): return other + self._main._sequence
+
+    ## following methods not implemented for keys ##
+    def __delitem__(self, i): raise TypeError('Can\'t delete items from keys')
+    def __iadd__(self, other): raise TypeError('Can\'t add in place to keys')
+    def __imul__(self, n): raise TypeError('Can\'t multiply keys in place')
+    def append(self, item): raise TypeError('Can\'t append items to keys')
+    def insert(self, i, item): raise TypeError('Can\'t insert items into keys')
+    def pop(self, i=-1): raise TypeError('Can\'t pop items from keys')
+    def remove(self, item): raise TypeError('Can\'t remove items from keys')
+    def extend(self, other): raise TypeError('Can\'t extend keys')
+
+class Items(object):
+    """
+    Custom object for accessing the items of an OrderedDict.
+    
+    Can be called like the normal ``OrderedDict.items`` method, but also
+    supports indexing and sequence methods.
+    """
+
+    def __init__(self, main):
+        self._main = main
+
+    def __call__(self):
+        """Pretend to be the items method."""
+        return self._main._items()
+
+    def __getitem__(self, index):
+        """Fetch the item at position i."""
+        if isinstance(index, types.SliceType):
+            # fetching a slice returns an OrderedDict
+            return self._main[index].items()
+        key = self._main._sequence[index]
+        return (key, self._main[key])
+
+    def __setitem__(self, index, item):
+        """Set item at position i to item."""
+        if isinstance(index, types.SliceType):
+            # NOTE: item must be an iterable (list of tuples)
+            self._main[index] = OrderedDict(item)
+        else:
+            # FIXME: Does this raise a sensible error?
+            orig = self._main.keys[index]
+            key, value = item
+            if self._main.strict and key in self and (key != orig):
+                raise ValueError('slice assignment must be from '
+                        'unique keys')
+            # delete the current one
+            del self._main[self._main._sequence[index]]
+            self._main.insert(index, key, value)
+
+    def __delitem__(self, i):
+        """Delete the item at position i."""
+        key = self._main._sequence[i]
+        if isinstance(i, types.SliceType):
+            for k in key:
+                # FIXME: efficiency?
+                del self._main[k]
+        else:
+            del self._main[key]
+
+    ### following methods pinched from UserList and adapted ###
+    def __repr__(self): return repr(self._main.items())
+
+    # FIXME: do we need to check if we are comparing with another ``Items``
+    #   object? (like the __cast method of UserList)
+    def __lt__(self, other): return self._main.items() <  other
+    def __le__(self, other): return self._main.items() <= other
+    def __eq__(self, other): return self._main.items() == other
+    def __ne__(self, other): return self._main.items() != other
+    def __gt__(self, other): return self._main.items() >  other
+    def __ge__(self, other): return self._main.items() >= other
+    def __cmp__(self, other): return cmp(self._main.items(), other)
+
+    def __contains__(self, item): return item in self._main.items()
+    def __len__(self): return len(self._main._sequence) # easier :-)
+    def __iter__(self): return self._main.iteritems()
+    def count(self, item): return self._main.items().count(item)
+    def index(self, item, *args): return self._main.items().index(item, *args)
+    def reverse(self): self._main.reverse()
+    def sort(self, *args, **kwds): self._main.sort(*args, **kwds)
+    def __mul__(self, n): return self._main.items()*n
+    __rmul__ = __mul__
+    def __add__(self, other): return self._main.items() + other
+    def __radd__(self, other): return other + self._main.items()
+
+    def append(self, item):
+        """Add an item to the end."""
+        # FIXME: this is only append if the key isn't already present
+        key, value = item
+        self._main[key] = value
+
+    def insert(self, i, item):
+        key, value = item
+        self._main.insert(i, key, value)
+
+    def pop(self, i=-1):
+        key = self._main._sequence[i]
+        return (key, self._main.pop(key))
+
+    def remove(self, item):
+        key, value = item
+        try:
+            assert value == self._main[key]
+        except (KeyError, AssertionError):
+            raise ValueError('ValueError: list.remove(x): x not in list')
+        else:
+            del self._main[key]
+
+    def extend(self, other):
+        # FIXME: is only a true extend if none of the keys already present
+        for item in other:
+            key, value = item
+            self._main[key] = value
+
+    def __iadd__(self, other):
+        self.extend(other)
+
+    ## following methods not implemented for items ##
+
+    def __imul__(self, n): raise TypeError('Can\'t multiply items in place')
+
+class Values(object):
+    """
+    Custom object for accessing the values of an OrderedDict.
+    
+    Can be called like the normal ``OrderedDict.values`` method, but also
+    supports indexing and sequence methods.
+    """
+
+    def __init__(self, main):
+        self._main = main
+
+    def __call__(self):
+        """Pretend to be the values method."""
+        return self._main._values()
+
+    def __getitem__(self, index):
+        """Fetch the value at position i."""
+        if isinstance(index, types.SliceType):
+            return [self._main[key] for key in self._main._sequence[index]]
+        else:
+            return self._main[self._main._sequence[index]]
+
+    def __setitem__(self, index, value):
+        """
+        Set the value at position i to value.
+        
+        You can only do slice assignment to values if you supply a sequence of
+        equal length to the slice you are replacing.
+        """
+        if isinstance(index, types.SliceType):
+            keys = self._main._sequence[index]
+            if len(keys) != len(value):
+                raise ValueError('attempt to assign sequence of size %s '
+                    'to slice of size %s' % (len(name), len(keys)))
+            # FIXME: efficiency?  Would be better to calculate the indexes
+            #   directly from the slice object
+            # NOTE: the new keys can collide with existing keys (or even
+            #   contain duplicates) - these will overwrite
+            for key, val in zip(keys, value):
+                self._main[key] = val
+        else:
+            self._main[self._main._sequence[index]] = value
+
+    ### following methods pinched from UserList and adapted ###
+    def __repr__(self): return repr(self._main.values())
+
+    # FIXME: do we need to check if we are comparing with another ``Values``
+    #   object? (like the __cast method of UserList)
+    def __lt__(self, other): return self._main.values() <  other
+    def __le__(self, other): return self._main.values() <= other
+    def __eq__(self, other): return self._main.values() == other
+    def __ne__(self, other): return self._main.values() != other
+    def __gt__(self, other): return self._main.values() >  other
+    def __ge__(self, other): return self._main.values() >= other
+    def __cmp__(self, other): return cmp(self._main.values(), other)
+
+    def __contains__(self, item): return item in self._main.values()
+    def __len__(self): return len(self._main._sequence) # easier :-)
+    def __iter__(self): return self._main.itervalues()
+    def count(self, item): return self._main.values().count(item)
+    def index(self, item, *args): return self._main.values().index(item, *args)
+
+    def reverse(self):
+        """Reverse the values"""
+        vals = self._main.values()
+        vals.reverse()
+        # FIXME: efficiency
+        self[:] = vals
+
+    def sort(self, *args, **kwds):
+        """Sort the values."""
+        vals = self._main.values()
+        vals.sort(*args, **kwds)
+        self[:] = vals
+
+    def __mul__(self, n): return self._main.values()*n
+    __rmul__ = __mul__
+    def __add__(self, other): return self._main.values() + other
+    def __radd__(self, other): return other + self._main.values()
+
+    ## following methods not implemented for values ##
+    def __delitem__(self, i): raise TypeError('Can\'t delete items from values')
+    def __iadd__(self, other): raise TypeError('Can\'t add in place to values')
+    def __imul__(self, n): raise TypeError('Can\'t multiply values in place')
+    def append(self, item): raise TypeError('Can\'t append items to values')
+    def insert(self, i, item): raise TypeError('Can\'t insert items into values')
+    def pop(self, i=-1): raise TypeError('Can\'t pop items from values')
+    def remove(self, item): raise TypeError('Can\'t remove items from values')
+    def extend(self, other): raise TypeError('Can\'t extend values')
+
+class SequenceOrderedDict(OrderedDict):
+    """
+    Experimental version of OrderedDict that has a custom object for ``keys``,
+    ``values``, and ``items``.
+    
+    These are callable sequence objects that work as methods, or can be
+    manipulated directly as sequences.
+    
+    Test for ``keys``, ``items`` and ``values``.
+    
+    >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
+    >>> d
+    SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+    >>> d.keys
+    [1, 2, 3]
+    >>> d.keys()
+    [1, 2, 3]
+    >>> d.setkeys((3, 2, 1))
+    >>> d
+    SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
+    >>> d.setkeys((1, 2, 3))
+    >>> d.keys[0]
+    1
+    >>> d.keys[:]
+    [1, 2, 3]
+    >>> d.keys[-1]
+    3
+    >>> d.keys[-2]
+    2
+    >>> d.keys[0:2] = [2, 1]
+    >>> d
+    SequenceOrderedDict([(2, 3), (1, 2), (3, 4)])
+    >>> d.keys.reverse()
+    >>> d.keys
+    [3, 1, 2]
+    >>> d.keys = [1, 2, 3]
+    >>> d
+    SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+    >>> d.keys = [3, 1, 2]
+    >>> d
+    SequenceOrderedDict([(3, 4), (1, 2), (2, 3)])
+    >>> a = SequenceOrderedDict()
+    >>> b = SequenceOrderedDict()
+    >>> a.keys == b.keys
+    1
+    >>> a['a'] = 3
+    >>> a.keys == b.keys
+    0
+    >>> b['a'] = 3
+    >>> a.keys == b.keys
+    1
+    >>> b['b'] = 3
+    >>> a.keys == b.keys
+    0
+    >>> a.keys > b.keys
+    0
+    >>> a.keys < b.keys
+    1
+    >>> 'a' in a.keys
+    1
+    >>> len(b.keys)
+    2
+    >>> 'c' in d.keys
+    0
+    >>> 1 in d.keys
+    1
+    >>> [v for v in d.keys]
+    [3, 1, 2]
+    >>> d.keys.sort()
+    >>> d.keys
+    [1, 2, 3]
+    >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)), strict=True)
+    >>> d.keys[::-1] = [1, 2, 3]
+    >>> d
+    SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
+    >>> d.keys[:2]
+    [3, 2]
+    >>> d.keys[:2] = [1, 3]
+    Traceback (most recent call last):
+    KeyError: 'Keylist is not the same as current keylist.'
+
+    >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
+    >>> d
+    SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+    >>> d.values
+    [2, 3, 4]
+    >>> d.values()
+    [2, 3, 4]
+    >>> d.setvalues((4, 3, 2))
+    >>> d
+    SequenceOrderedDict([(1, 4), (2, 3), (3, 2)])
+    >>> d.values[::-1]
+    [2, 3, 4]
+    >>> d.values[0]
+    4
+    >>> d.values[-2]
+    3
+    >>> del d.values[0]
+    Traceback (most recent call last):
+    TypeError: Can't delete items from values
+    >>> d.values[::2] = [2, 4]
+    >>> d
+    SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+    >>> 7 in d.values
+    0
+    >>> len(d.values)
+    3
+    >>> [val for val in d.values]
+    [2, 3, 4]
+    >>> d.values[-1] = 2
+    >>> d.values.count(2)
+    2
+    >>> d.values.index(2)
+    0
+    >>> d.values[-1] = 7
+    >>> d.values
+    [2, 3, 7]
+    >>> d.values.reverse()
+    >>> d.values
+    [7, 3, 2]
+    >>> d.values.sort()
+    >>> d.values
+    [2, 3, 7]
+    >>> d.values.append('anything')
+    Traceback (most recent call last):
+    TypeError: Can't append items to values
+    >>> d.values = (1, 2, 3)
+    >>> d
+    SequenceOrderedDict([(1, 1), (2, 2), (3, 3)])
+    
+    >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4)))
+    >>> d
+    SequenceOrderedDict([(1, 2), (2, 3), (3, 4)])
+    >>> d.items()
+    [(1, 2), (2, 3), (3, 4)]
+    >>> d.setitems([(3, 4), (2 ,3), (1, 2)])
+    >>> d
+    SequenceOrderedDict([(3, 4), (2, 3), (1, 2)])
+    >>> d.items[0]
+    (3, 4)
+    >>> d.items[:-1]
+    [(3, 4), (2, 3)]
+    >>> d.items[1] = (6, 3)
+    >>> d.items
+    [(3, 4), (6, 3), (1, 2)]
+    >>> d.items[1:2] = [(9, 9)]
+    >>> d
+    SequenceOrderedDict([(3, 4), (9, 9), (1, 2)])
+    >>> del d.items[1:2]
+    >>> d
+    SequenceOrderedDict([(3, 4), (1, 2)])
+    >>> (3, 4) in d.items
+    1
+    >>> (4, 3) in d.items
+    0
+    >>> len(d.items)
+    2
+    >>> [v for v in d.items]
+    [(3, 4), (1, 2)]
+    >>> d.items.count((3, 4))
+    1
+    >>> d.items.index((1, 2))
+    1
+    >>> d.items.index((2, 1))
+    Traceback (most recent call last):
+    ValueError: list.index(x): x not in list
+    >>> d.items.reverse()
+    >>> d.items
+    [(1, 2), (3, 4)]
+    >>> d.items.reverse()
+    >>> d.items.sort()
+    >>> d.items
+    [(1, 2), (3, 4)]
+    >>> d.items.append((5, 6))
+    >>> d.items
+    [(1, 2), (3, 4), (5, 6)]
+    >>> d.items.insert(0, (0, 0))
+    >>> d.items
+    [(0, 0), (1, 2), (3, 4), (5, 6)]
+    >>> d.items.insert(-1, (7, 8))
+    >>> d.items
+    [(0, 0), (1, 2), (3, 4), (7, 8), (5, 6)]
+    >>> d.items.pop()
+    (5, 6)
+    >>> d.items
+    [(0, 0), (1, 2), (3, 4), (7, 8)]
+    >>> d.items.remove((1, 2))
+    >>> d.items
+    [(0, 0), (3, 4), (7, 8)]
+    >>> d.items.extend([(1, 2), (5, 6)])
+    >>> d.items
+    [(0, 0), (3, 4), (7, 8), (1, 2), (5, 6)]
+    """
+
+    def __init__(self, init_val=(), strict=True):
+        OrderedDict.__init__(self, init_val, strict=strict)
+        self._keys = self.keys
+        self._values = self.values
+        self._items = self.items
+        self.keys = Keys(self)
+        self.values = Values(self)
+        self.items = Items(self)
+        self._att_dict = {
+            'keys': self.setkeys,
+            'items': self.setitems,
+            'values': self.setvalues,
+        }
+
+    def __setattr__(self, name, value):
+        """Protect keys, items, and values."""
+        if not '_att_dict' in self.__dict__:
+            object.__setattr__(self, name, value)
+        else:
+            try:
+                fun = self._att_dict[name]
+            except KeyError:
+                OrderedDict.__setattr__(self, name, value)
+            else:
+                fun(value)
+
+if __name__ == '__main__':
+    if INTP_VER < (2, 3):
+        raise RuntimeError("Tests require Python v.2.3 or later")
+    # turn off warnings for tests
+    warnings.filterwarnings('ignore')
+    # run the code tests in doctest format
+    import doctest
+    m = sys.modules.get('__main__')
+    globs = m.__dict__.copy()
+    globs.update({
+        'INTP_VER': INTP_VER,
+    })
+    doctest.testmod(m, globs=globs)
+
diff --git a/src/dataTools/odict.pyc b/src/dataTools/odict.pyc
new file mode 100644 (file)
index 0000000..b9e213b
Binary files /dev/null and b/src/dataTools/odict.pyc differ
diff --git a/src/gradients/__init__.py b/src/gradients/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/gradients/gradients.py b/src/gradients/gradients.py
new file mode 100755 (executable)
index 0000000..cac1f2e
--- /dev/null
@@ -0,0 +1,576 @@
+#Copyright 2006 DR0ID <dr0id@bluewin.ch> http://mypage.bluewin.ch/DR0ID
+#
+#
+#
+"""
+Allow to draw some gradients relatively easy.
+"""
+
+__author__ = "$Author: DR0ID $"
+__version__= "$Revision: 109 $"
+__date__   = "$Date: 2007-08-09 20:33:32 +0200 (Do, 09 Aug 2007) $"
+
+import pygame
+import math
+
+BLEND_MODES_AVAILABLE = False
+vernum = pygame.vernum
+if vernum[0]>=1 and vernum[1]>=8:
+    BLEND_MODES_AVAILABLE = True
+    
+
+class ColorInterpolator(object):
+    '''
+    ColorInterpolator(distance, color1, color2, rfunc, gfunc, bfunc, afunc)
+    
+    interpolates a color over the distance using different functions for r,g,b,a
+    separately (a= alpha).
+    '''
+    def __init__(self, distance, color1, color2, rfunc, gfunc, bfunc, afunc):
+        object.__init__(self)
+        
+        self.rInterpolator = FunctionInterpolator(color1[0], color2[0], distance, rfunc)
+        self.gInterpolator = FunctionInterpolator(color1[1], color2[1], distance, gfunc)
+        self.bInterpolator = FunctionInterpolator(color1[2], color2[2], distance, bfunc)
+        if len(color1)==4 and len(color2)==4:
+            self.aInterpolator = FunctionInterpolator(color1[3], color2[3], distance, afunc)
+        else:
+            self.aInterpolator = FunctionInterpolator(255, 255, distance, afunc)
+            
+    def eval(self, x):
+        '''
+        eval(x) -> color
+        
+        returns the color at the position 0<=x<=d (actually not bound to this interval).
+        '''
+##        print "colorInterp x", x, self.rInterpolator.eval(x), self.gInterpolator.eval(x), self.bInterpolator.eval(x)
+        return [self.rInterpolator.eval(x), 
+                self.gInterpolator.eval(x), 
+                self.bInterpolator.eval(x), 
+                self.aInterpolator.eval(x)]
+            
+
+
+class FunctionInterpolator(object):
+    '''
+    FunctionINterpolator(startvalue, endvalue, trange, func)
+    
+    interpolates a function y=f(x) in the range trange with
+    startvalue = f(0)
+    endvalue   = f(trange)
+    using the function func
+    '''
+    def __init__(self, startvalue, endvalue, trange, func):
+        object.__init__(self)
+        # function
+        self.func = func
+        # y-scaling
+        self.a = endvalue-startvalue
+        if self.a == 0:
+            self.a = 1.
+        # x-scaling
+        if trange!=0:
+            self.b = 1./abs(trange)
+        else:
+            self.b = 1.
+        # x-displacement
+        self.c = 0
+        # y-displacement
+        self.d = min(max(startvalue,0),255)
+        
+    def eval(self, x):
+        ''' 
+        eval(x)->float
+        
+        return value at position x
+        '''
+        # make sure that the returned value is in [0,255]
+##        return int(round(min(max(self.a*self.func(self.b*(x+self.c))+self.d, 0), 255)))
+        return int(min(max(self.a*self.func(self.b*(x+self.c))+self.d, 0), 255))
+
+
+
+##def gradient(surface, 
+##                startpoint, 
+##                endpoint, 
+##                startcolor, 
+##                endcolor,
+##                Rfunc = (lambda x:x), 
+##                Gfunc = (lambda x:x), 
+##                Bfunc = (lambda x:x), 
+##                Afunc = (lambda x:1), 
+##                type  = "line", 
+##                mode  = None ):
+##    '''
+##    surface   : surface to draw on
+##    startpoint: (x,y) point on surface
+##    endpoint  : (x,y) point on surface
+##    startcolor: (r,g,b,a) color at startpoint
+##    endcolor  : (r,g,b,a) color at endpoint
+##    Rfunc     : function y = f(x) with  startcolor =f(0) and endcolor = f(1) where 0 is at startpoint and 1 at endpoint
+##    Gfunc     :  ---  "  ---
+##    Bfunc     :  ---  "  ---
+##    Afunc     :  ---  "  ---
+##                these functions are evaluated in the range 0 <= x <= 1 and 0<= y=f(x) <= 1
+##    type      : "line", "circle" or "rect"
+##    mode      : "+", "-", "*", None (how the pixels are drawen)
+##    
+##    returns   : surface with the color characteristics w,h = (d, 256) and d = length of endpoint-startpoint
+##    
+##    '''
+##    dx = endpoint[0]-startpoint[0]
+##    dy = endpoint[1]-startpoint[1]
+##    d = int(round(math.hypot(dx, dy)))
+##    angle = math.degrees( math.atan2(dy, dx) )
+##    
+##    color = ColorInterpolator(d, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+##    
+##    if type=="line":
+##        h = int(2.*math.hypot(*surface.get_size()))
+###        bigSurf = pygame.Surface((d, h)).convert_alpha()
+##        bigSurf = pygame.Surface((d, h), pygame.SRCALPHA)#.convert_alpha()
+###        bigSurf = pygame.Surface((d, 1), pygame.SRCALPHA)#.convert_alpha()
+##        bigSurf.lock()
+##        bigSurf.fill((0,0,0,0))
+##        bigSurf.set_colorkey((0,0,0,0))
+##        for x in range(d):
+##            pygame.draw.line(bigSurf, color.eval(x), (x,0), (x,h), 1)
+###        for x in range(d):
+###            bigSurf.set_at((x, 0), color.eval(x))
+###        bigSurf = pygame.transform.scale(bigSurf, (d, h))
+##            
+##        bigSurf = pygame.transform.rotate(bigSurf, -angle) #rotozoom(bigSurf, -angle, 1)
+##        bigSurf.set_colorkey((0,0,0, 0))
+##        rect = bigSurf.get_rect()
+##        srect = pygame.Rect(rect)
+##        dx = d/2. * math.cos(math.radians(angle))
+##        dy = d/2. * math.sin(math.radians(angle))
+##        rect.center = startpoint
+##        rect.move_ip(dx, dy)
+##        bigSurf.unlock()
+##        
+##    elif type=="circle":
+##        bigSurf = pygame.Surface((2*d, 2*d)).convert_alpha()
+##        bigSurf.fill((0,0,0,0))
+##        bigSurf.lock()
+##        for x in range(d, 0, -1):
+##            pygame.draw.circle(bigSurf, color.eval(x), (d,d), x)
+##        bigSurf.unlock()
+##        rect = bigSurf.get_rect()
+##        srect = pygame.Rect(rect)
+##        rect.center = (startpoint[0], startpoint[1])
+##        
+##    elif type=="rect":
+##        bigSurf = pygame.Surface((2*d, 2*d)).convert_alpha()
+##        bigSurf.fill((0,0,0,0))
+##        c = bigSurf.get_rect().center
+##        bigSurf.lock()
+##        for x in range(d,-1,-1):
+##            r = pygame.Rect(0,0,2*x,2*x)
+##            r.center = c
+##            pygame.draw.rect(bigSurf, color.eval(x), r)
+##        bigSurf.unlock()
+##        bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1)
+##        bigSurf.set_colorkey((0,0,0, 0))
+##        
+##        rect = bigSurf.get_rect()
+##        srect = pygame.Rect(rect)
+##        rect.center = startpoint
+##    else:
+##        raise NameError("type must be one of \"line\",\"circle\" or \"rect\"")
+##    
+##    if mode is None:
+##        surface.blit(bigSurf, rect, srect)
+##    else:
+##        if mode=="+":
+##            cf = pygame.color.add
+##        elif mode=="*":
+##            cf = pygame.color.multiply
+##        elif mode=="-":
+##            cf = pygame.color.subtract
+##        else:
+##            raise NameError("type must be one of \"+\", \"*\", \"-\" or None")
+##        irect = surface.get_clip().clip(rect)
+##        surface.lock()
+##        for x in range(irect.left, irect.left+irect.width):
+##            for y in range(irect.top, irect.top+irect.height):
+##                surface.set_at((x,y), cf(surface.get_at((x,y)), bigSurf.get_at((x-rect.left, y-rect.top)) ) )
+##        surface.unlock()
+##    
+##    del bigSurf   
+##    char = pygame.Surface((d+1, 257))
+###    char.fill((0,0,0))
+###    ox = 0
+###    oldcol = color.eval(0)
+###    for x in range(d):
+###        col = color.eval(x)
+###        pygame.draw.line(char, (255,0,0), (x, 256-col[0]), (ox, 256-oldcol[0]))
+###        pygame.draw.line(char, (0,255,0), (x, 256-col[1]), (ox, 256-oldcol[1]))
+###        pygame.draw.line(char, (0,0,255), (x, 256-col[2]), (ox, 256-oldcol[2]))
+###        pygame.draw.line(char, (255,255,255), (x, 256-col[3]), (ox, 256-oldcol[3]))
+###        ox = x
+###        oldcol = col
+###     
+##    return char
+        
+    
+    
+
+def vertical(size, startcolor, endcolor):
+    """
+    Draws a vertical linear gradient filling the entire surface. Returns a
+    surface filled with the gradient (numeric is only 2-3 times faster).
+    """
+    height = size[1]
+    bigSurf = pygame.Surface((1,height)).convert_alpha()
+    dd = 1.0/height
+    sr, sg, sb, sa = startcolor
+    er, eg, eb, ea = endcolor
+    rm = (er-sr)*dd
+    gm = (eg-sg)*dd
+    bm = (eb-sb)*dd
+    am = (ea-sa)*dd
+    for y in range(height):
+        bigSurf.set_at((0,y),
+                        (int(sr + rm*y),
+                         int(sg + gm*y),
+                         int(sb + bm*y),
+                         int(sa + am*y))
+                      )
+    return pygame.transform.scale(bigSurf, size)
+
+
+def horizontal(size, startcolor, endcolor):
+    """
+    Draws a horizontal linear gradient filling the entire surface. Returns a
+    surface filled with the gradient (numeric is only 2-3 times faster).
+    """
+    width = size[0]
+    bigSurf = pygame.Surface((width, 1)).convert_alpha()
+    dd = 1.0/width
+    sr, sg, sb, sa = startcolor
+    er, eg, eb, ea = endcolor
+    rm = (er-sr)*dd
+    gm = (eg-sg)*dd
+    bm = (eb-sb)*dd
+    am = (ea-sa)*dd
+    for y in range(width):
+        bigSurf.set_at((y,0),
+                        (int(sr + rm*y),
+                         int(sg + gm*y),
+                         int(sb + bm*y),
+                         int(sa + am*y))
+                      )
+    return pygame.transform.scale(bigSurf, size)
+
+
+def radial(radius, startcolor, endcolor):
+    """
+    Draws a linear raidal gradient on a square sized surface and returns
+    that surface.
+    """
+    bigSurf = pygame.Surface((2*radius, 2*radius)).convert_alpha()
+    bigSurf.fill((0,0,0,0))
+    dd = -1.0/radius
+    sr, sg, sb, sa = endcolor
+    er, eg, eb, ea = startcolor
+    rm = (er-sr)*dd
+    gm = (eg-sg)*dd
+    bm = (eb-sb)*dd
+    am = (ea-sa)*dd
+    
+    draw_circle = pygame.draw.circle
+    for rad in range(radius, 0, -1):
+        draw_circle(bigSurf, (er + int(rm*rad),
+                              eg + int(gm*rad),
+                              eb + int(bm*rad),
+                              ea + int(am*rad)), (radius, radius), rad)
+    return bigSurf
+
+def squared(width, startcolor, endcolor):
+    """
+    Draws a linear sqared gradient on a square sized surface and returns
+    that surface.
+    """
+    bigSurf = pygame.Surface((width, width)).convert_alpha()
+    bigSurf.fill((0,0,0,0))
+    dd = -1.0/(width/2)
+    sr, sg, sb, sa = endcolor
+    er, eg, eb, ea = startcolor
+    rm = (er-sr)*dd
+    gm = (eg-sg)*dd
+    bm = (eb-sb)*dd
+    am = (ea-sa)*dd
+    
+    draw_rect = pygame.draw.rect
+    for currentw in range((width/2), 0, -1):
+        pos = (width/2)-currentw
+        draw_rect(bigSurf, (er + int(rm*currentw),
+                            eg + int(gm*currentw),
+                            eb + int(bm*currentw),
+                            ea + int(am*currentw)), pygame.Rect(pos, pos, 2*currentw, 2*currentw ))
+    return bigSurf
+
+
+def vertical_func(size, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1)):
+    """
+    Draws a vertical linear gradient filling the entire surface. Returns a
+    surface filled with the gradient (numeric is only 2x faster).
+    Rfunc, Gfunc, Bfunc and Afunc are function like y = f(x). They define
+    how the color changes.
+    """
+    height = size[1]
+    bigSurf = pygame.Surface((1,height)).convert_alpha()
+    color = ColorInterpolator(height, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    for y in range(0, height):
+        bigSurf.set_at((0,y), color.eval(y+0.1))
+    return pygame.transform.scale(bigSurf, size)
+
+
+def horizontal_func(size, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1)):
+    """
+    Draws a horizontal linear gradient filling the entire surface. Returns a
+    surface filled with the gradient (numeric is only 2x faster).
+    Rfunc, Gfunc, Bfunc and Afunc are function like y = f(x). They define
+    how the color changes.
+    """
+    width = size[0]
+    bigSurf = pygame.Surface((width, 1)).convert_alpha()
+    color = ColorInterpolator(width, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    for y in range(0, width):
+        bigSurf.set_at((y, 0), color.eval(y+0.1))
+    return pygame.transform.scale(bigSurf, size)
+
+def radial_func(radius, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), colorkey=(0,0,0,0)):
+    """
+    Draws a linear raidal gradient on a square sized surface and returns
+    that surface.
+    """
+    bigSurf = pygame.Surface((2*radius, 2*radius)).convert_alpha()
+    if len(colorkey)==3:
+        colorkey += (0,)
+    bigSurf.fill(colorkey)
+    color = ColorInterpolator(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    draw_circle = pygame.draw.circle
+    for rad in range(radius, 0, -1):
+        draw_circle(bigSurf, color.eval(rad), (radius, radius), rad)
+    return bigSurf
+
+def radial_func_offset(radius, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), colorkey=(0,0,0,0), offset=(0,0)):
+    """
+    Draws a linear raidal gradient on a square sized surface and returns
+    that surface.
+    offset is the amount the center of the gradient is displaced of the center of the image.
+    Unfotunately this function ignores alpha.
+    """
+    bigSurf = pygame.Surface((2*radius, 2*radius))#.convert_alpha()
+    
+    mask = pygame.Surface((2*radius, 2*radius), pygame.SRCALPHA)#.convert_alpha()
+    mask.fill(colorkey)
+    mask.set_colorkey((255,0,255))
+    pygame.draw.circle(mask, (255,0,255), (radius, radius), radius)
+    
+    if len(colorkey)==3:
+        colorkey += (0,)
+    bigSurf.fill(colorkey)
+    
+    color = ColorInterpolator(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    draw_circle = pygame.draw.circle
+    radi = radius + int(math.hypot(offset[0], offset[1])+1)
+    for rad in range(radi, 0, -1):
+        draw_circle(bigSurf, color.eval(rad), (radius+offset[0], radius+offset[1]), rad)
+        
+    bigSurf.blit(mask, (0,0))
+    bigSurf.set_colorkey(colorkey)
+    return bigSurf
+
+
+def squared_func(width, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), offset=(0,0)):
+    """
+    Draws a linear sqared gradient on a square sized surface and returns
+    that surface.
+    """
+    bigSurf = pygame.Surface((width, width)).convert_alpha()
+    bigSurf.fill((0,0,0,0))
+    color = ColorInterpolator(width/2, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    draw_rect = pygame.draw.rect
+    widthh = width+2*int(max(abs(offset[0]),abs(offset[1])))
+    for currentw in range((widthh/2), 0, -1):
+##        pos = (width/2)-currentw
+        rect = pygame.Rect(0, 0, 2*currentw, 2*currentw )
+        rect.center = (width/2+offset[0], width/2+offset[1])
+        draw_rect(bigSurf, color.eval(currentw), rect)
+    return bigSurf
+
+def draw_gradient(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0):
+    """
+    Instead of returning an Surface, this function draw it directy onto the 
+    given Surface and returns the rect.
+    """
+    dx = endpoint[0]-startpoint[0]
+    dy = endpoint[1]-startpoint[1]
+    d = int(round(math.hypot(dx, dy)))
+    angle = math.degrees( math.atan2(dy, dx) )
+    
+    h = int(2.*math.hypot(*surface.get_size()))
+    
+    bigSurf = horizontal_func((d,h), startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    
+##    bigSurf = pygame.transform.rotate(bigSurf, -angle) #rotozoom(bigSurf, -angle, 1)
+    bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1)
+##    bigSurf.set_colorkey((0,0,0, 0))
+    rect = bigSurf.get_rect()
+    srect = pygame.Rect(rect)
+    dx = d/2. * math.cos(math.radians(angle))
+    dy = d/2. * math.sin(math.radians(angle))
+    rect.center = startpoint
+    rect.move_ip(dx, dy)
+    if BLEND_MODES_AVAILABLE:
+        return surface.blit(bigSurf, rect, None, mode)
+    else:
+        return surface.blit(bigSurf, rect)
+
+
+def draw_circle(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0):
+    """
+    Instead of returning an Surface, this function draw it directy onto the 
+    given Surface and returns the rect.
+    """
+    dx = endpoint[0]-startpoint[0]
+    dy = endpoint[1]-startpoint[1]
+    radius = int(round(math.hypot(dx, dy)))
+    pos = (startpoint[0]-radius, startpoint[1]-radius)
+    if BLEND_MODES_AVAILABLE:
+        return surface.blit(radial_func(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc), pos, None, mode)
+    else:
+        return surface.blit(radial_func(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc), pos)
+
+def draw_squared(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0):
+    """
+    Instead of returning an Surface, this function draw it directy onto the 
+    given Surface and returns the rect.
+    """
+    dx = endpoint[0]-startpoint[0]
+    dy = endpoint[1]-startpoint[1]
+    angle = math.degrees( math.atan2(dy, dx) )
+    width = 2*int(round(math.hypot(dx, dy)))
+    
+    bigSurf = squared_func(width, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    
+    bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1)
+##    bigSurf.set_colorkey((0,0,0, 0))
+    rect = bigSurf.get_rect()
+    rect.center = startpoint
+    if BLEND_MODES_AVAILABLE:
+        return surface.blit(bigSurf, rect, None, mode)
+    else:
+        return surface.blit(bigSurf, rect)
+    
+    
+def chart(startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), scale=None):
+    """
+    This returns a Surface where the change of the colors over the distance 
+    (the width of the image) is showen as a line.
+    scale: a float, 1 is not scaling
+    """
+    dx = endpoint[0]-startpoint[0]
+    dy = endpoint[1]-startpoint[1]
+    distance = int(round(math.hypot(dx, dy)))
+    color = ColorInterpolator(distance, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
+    bigSurf = pygame.Surface((distance, 256))
+    bigSurf.fill((0,)*3)
+    oldcol = color.eval(0)
+    for x in range(distance):
+        r, g, b, a = color.eval(x)
+        pygame.draw.line(bigSurf, (255, 0, 0, 128), (x-1, oldcol[0]), (x, r))
+        pygame.draw.line(bigSurf, (0, 255, 0, 128), (x-1, oldcol[1]), (x, g))
+        pygame.draw.line(bigSurf, (0, 0, 255, 128), (x-1, oldcol[2]), (x, b))
+        pygame.draw.line(bigSurf, (255, 255, 255, 128), (x-1, oldcol[3]), (x, a))
+        oldcol = (r,g,b,a)
+    if scale:
+##        return pygame.transform.scale(bigSurf, size)
+        return pygame.transform.rotozoom(bigSurf, 0, scale)
+    return pygame.transform.flip(bigSurf, 0, 1)
+#------------------------------------------------------------------------------
+
+
+    
+
+def genericFxyGradient(surf, clip, color1, color2, func, intx, yint, zint=None):
+    """
+    genericFxyGradient(size, color1, color2,func, intx, yint, zint=None)
+    
+    some sort of highfield drawer :-)
+    
+    surf   : surface to draw
+    clip   : rect on surf to draw in
+    color1 : start color
+    color2 : end color
+    func   : function z = func(x,y)
+    xint   : interval in x direction where the function is evaluated
+    yint   : interval in y direction where the function is evaluated
+    zint   : if not none same as yint or xint, if None then the max and min value
+             of func is taken as z-interval
+    
+    color = a*func(b*(x+c), d*(y+e))+f
+    """
+    # make shure that x1<x2 and y1<y2 and z1<z2
+    w,h = clip.size
+    x1 = min(intx)
+    x2 = max(intx)
+    y1 = min(yint)
+    y2 = max(yint)
+    if zint: # if user give us z intervall, then use it
+        z1 = min(zint)
+        z2 = max(zint)
+    else: # look for extrema of function (not best algorithme)
+        z1 = func(x1,y1)
+        z2 = z1
+        for i in range(w):
+            for j in range(h):
+                r = func(i,j)
+                z1 = min(z1, r)
+                z2 = max(z2, r)
+                
+    x1 = float(x1)
+    x2 = float(x2)
+    y1 = float(y1)
+    y2 = float(y2)
+    z1 = float(z1)
+    z2 = float(z2)
+    if len(color1)==3:
+        color1 = list(color1)
+        color1.append(255)
+    if len(color2)==3:
+        color2 = list(color2)
+        color2.append(255)
+    
+    # calculate streching and displacement variables
+    a = ((color2[0]-color1[0])/(z2-z1), \
+         (color2[1]-color1[1])/(z2-z1), \
+         (color2[2]-color1[2])/(z2-z1), \
+         (color2[3]-color1[3])/(z2-z1) ) # streching in z direction
+    b = (x2-x1)/float(w) # streching in x direction
+    d = (y2-y1)/float(h) # streching in y direction
+    f = ( color1[0]-a[0]*z1, \
+          color1[1]-a[1]*z1, \
+          color1[2]-a[2]*z1, \
+          color1[3]-a[3]*z1 )# z displacement
+    c = x1/b
+    e = y1/d
+    
+    surff = pygame.surface.Surface((w,h)).convert_alpha()
+    # generate values
+    for i in range(h):
+        for j in range(w):
+            val = func(b*(j+c), d*(i+e))
+            #clip color
+            color = (   max(min(a[0]*val+f[0],255),0), \
+                        max(min(a[1]*val+f[1],255),0), \
+                        max(min(a[2]*val+f[2],255),0), \
+                        max(min(a[3]*val+f[3],255),0) )
+            surff.set_at( (j,i), color )
+    surf.blit(surff, clip)
+
+
+
diff --git a/src/gui/DefaultFamiliarizer.py b/src/gui/DefaultFamiliarizer.py
new file mode 100644 (file)
index 0000000..ca9d35e
--- /dev/null
@@ -0,0 +1,27 @@
+'''\r
+Created on 21 janv. 2010\r
+\r
+@author: samsam\r
+'''\r
+import pygame\r
+\r
+from gui.FamiliarizerPGUConfiguration import FamiliarizerPGUConfiguration\r
+\r
+class DefaultFamiliarizer(FamiliarizerPGUConfiguration):\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,window):\r
+        '''\r
+        Constructor\r
+        '''\r
+        FamiliarizerPGUConfiguration.__init__(self, window,defaultParams = True)\r
+\r
+if __name__ == "__main__" :\r
+    pygame.init()\r
+    modeResolution = (1024,768)\r
+    window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+    familiarizer = DefaultFamiliarizer(window)\r
+    pygame.quit()
\ No newline at end of file
diff --git a/src/gui/DummyInstrumentChoice.py b/src/gui/DummyInstrumentChoice.py
new file mode 100644 (file)
index 0000000..84b4003
--- /dev/null
@@ -0,0 +1,57 @@
+'''\r
+Created on 21 janv. 2010\r
+\r
+@author: samsam\r
+'''\r
+\r
+import pygame\r
+import pygame.midi\r
+import sys\r
+import time\r
+import pickle\r
+\r
+from numpy import array\r
+from numpy.linalg import norm\r
+\r
+from math import floor\r
+\r
+from gui.constants import *\r
+from PlayingScreen import PlayingScreen\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from logging.EventLog import EventLog\r
+from logging.PickleableEvent import PickleableEvent \r
+from InstrumentChoice import InstrumentChoice\r
+\r
+class DummyInstrumentChoice():\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,wiimotes, window, screen, clock, joys, portOffset, activeWiimotes, eventLog=None, replay = False, logFilePath = None, scaleFactor = 1):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.wiimotes =wiimotes\r
+        self.activeWiimotes =activeWiimotes\r
+        self.window = window\r
+        self.screen = screen\r
+        self.scaleFactor = scaleFactor\r
+        self.width = int(floor(screen.get_width()*self.scaleFactor))\r
+        self.height = int(floor(screen.get_height()*self.scaleFactor))\r
+        self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2)\r
+        self.clock = clock        \r
+        self.savedScreen = pygame.Surface(self.screen.get_size())\r
+        self.savedScreen.fill((255, 255, 255))\r
+        self.playerScreen = pygame.Surface(self.savedScreen.get_size())\r
+        self.playerScreen.blit(self.savedScreen, (0, 0))\r
+        self.cursorPositions = []\r
+        for i in range(len(self.wiimotes)):\r
+            self.wiimotes[i].cursor.screen = self.playerScreen\r
+            self.cursorPositions.append(self.wiimotes[i].cursor.centerPosition)\r
+        self.joys = joys\r
+        self.portOffset = portOffset\r
+        self.eventLog = eventLog\r
+        self.replay = replay
\ No newline at end of file
diff --git a/src/gui/Familiarizer.py b/src/gui/Familiarizer.py
new file mode 100644 (file)
index 0000000..8e62b5e
--- /dev/null
@@ -0,0 +1,328 @@
+'''\r
+Created on 15 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+import pygame\r
+import pygame.midi\r
+import sys\r
+import time\r
+import pickle\r
+import random\r
+\r
+from numpy import array\r
+from numpy.linalg import norm\r
+\r
+from math import floor\r
+\r
+from gui.constants import *\r
+from PlayingScreen import PlayingScreen\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from logging.EventLog import EventLog\r
+from logging.PickleableEvent import PickleableEvent \r
+\r
+joyNames = ["PPJoy Virtual joystick 1", "PPJoy Virtual joystick 2", "PPJoy Virtual joystick 3"]\r
+portNames = ["Out To MIDI Yoke:  1"]\r
+majorScale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]\r
+minorScale = [55, 56, 58, 60, 62, 63, 65, 67, 68, 70, 72]\r
+myxolydianScale = [55, 57, 58, 60, 62, 64, 65, 67, 69, 70, 72]\r
+dorianScale = [55, 57, 58, 60, 62, 63, 65, 67, 69, 70, 72]\r
+instrumentImagePathList = ["piano", "guitare", "accordeon", "violon", "flute", "tuba", "orgue", "violoncelle", "celesta"]\r
+octaves = [0, -1, 0, 1, 1, -2, 0, -1, 0]\r
+\r
+class Familiarizer:\r
+    '''\r
+    The screen for choosing instruments\r
+    \r
+        instruments: \r
+                The available instruments            \r
+        wiimotes: \r
+                The wiimotes used in this session\r
+        window:\r
+            The main display window\r
+        screen:\r
+            The main display surface\r
+        clock:\r
+            The clock used to animate the screen\r
+        savedScreen:\r
+            The background that is painted every time\r
+        playerScreen:\r
+            The buffer for painting everything before bliting\r
+        width:\r
+            The width of the window in pixels\r
+        height:\r
+            The height of the window in pixels\r
+        done:\r
+            Goes to True when all instruments have been selected\r
+        cursorPositions:\r
+            The positions of the cursors on the screen, in pixels\r
+        imageRects:\r
+            The rectangles where the images of the instruments are located\r
+        focus:\r
+            The numbers of the instruments currently in focus\r
+    '''\r
+    \r
+    def __init__(self, instruments, wiimotes, window, screen, clock, joys, portOffset, eventLog=None, replay = False, logFilePath = None, scaleFactor = 1, level = 1):\r
+        '''\r
+        Constructor\r
+        \r
+            instruments: \r
+                The instruments for this session           \r
+            wiimotes: \r
+                The wiimotes used in this session\r
+        '''\r
+        \r
+        \r
+        self.scaleFactor = scaleFactor\r
+        \r
+        self.instruments = instruments\r
+        self.wiimotes = wiimotes\r
+        self.window = window\r
+        self.screen = screen\r
+        self.clock = clock\r
+        self.width = int(floor(screen.get_width()*self.scaleFactor))\r
+        self.height = int(floor(screen.get_height()*self.scaleFactor))\r
+        self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2)        \r
+        self.joys = joys\r
+        self.portOffset = portOffset\r
+        \r
+        self.currentWiimote = 0\r
+        self.done = False        \r
+        \r
+        self.cursorPositions = []\r
+        self.imageRects = []\r
+        self.displayedInstruments = []\r
+        self.savedImageRects = []\r
+        self.focus = []\r
+        self.level = level\r
+        \r
+        if eventLog == None:\r
+            self.eventLog = EventLog()\r
+            self.replay = False\r
+        else:\r
+            self.eventLog = eventLog\r
+            self.replay = replay\r
+                        \r
+        self.savedScreen = pygame.Surface(self.screen.get_size())        \r
+        \r
+        self.displayedInstruments.append(0)\r
+        if level == 1 :\r
+            self.displayedInstruments.append(2)\r
+        \r
+        self.savedScreen.fill((255, 255, 255))\r
+        for i in range(len(self.displayedInstruments)):\r
+            self.imageRects.append(self.drawInstrument(self.displayedInstruments[i]))\r
+                    \r
+        self.savedImageRects = self.imageRects[:]\r
+        \r
+        #Draw the initial cursor on the buffer\r
+        self.playerScreen = pygame.Surface(self.savedScreen.get_size())\r
+        self.playerScreen.blit(self.savedScreen, (0, 0))\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            #Create the list of instrument focus (one focus per wiimote)\r
+            self.focus.append(None)\r
+            #Set the screen for the cursors (it can't be set before)\r
+            self.wiimotes[i].cursor.screen = self.playerScreen\r
+            self.cursorPositions.append(self.wiimotes[i].cursor.centerPosition)\r
+            \r
+        self.wiimotes[self.currentWiimote].cursor.blit(self.playerScreen)\r
+        \r
+        #The main loop\r
+        while self.done == False :\r
+            \r
+            #Clear the cursors from the screen\r
+            self.drawBackground()\r
+            self.playerScreen.blit(self.savedScreen, (0, 0))\r
+            \r
+            # Limit frame speed to 50 FPS\r
+            #\r
+            timePassed = self.clock.tick(50)\r
+            \r
+            if self.replay:\r
+                self.eventLog.update(timePassed)\r
+                pickledEventsToPost = self.eventLog.getPickledEvents() \r
+                for pickledEvent in pickledEventsToPost:\r
+                    pygame.event.post(pickledEvent.event)\r
+            \r
+            events = pygame.event.get()\r
+            \r
+            if not self.replay:\r
+                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events if self.eventFilter(event)]\r
+                if pickledEvents != [] :\r
+                    self.eventLog.appendEventGroup(pickledEvents)\r
+            \r
+            for event in events:\r
+                self.input(event)\r
+            \r
+            for i in range(len(self.wiimotes)):\r
+                self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])\r
+                \r
+            self.wiimotes[self.currentWiimote].cursor.blit(self.playerScreen)  \r
+            \r
+            self.focus[self.currentWiimote] = None\r
+                          \r
+            for i in range(len(self.imageRects)) :\r
+                if self.imageRects[i].collidepoint(self.cursorPositions[self.currentWiimote]):\r
+                    self.focus[self.currentWiimote] = i\r
+                                \r
+            self.screen.blit(self.playerScreen, (0, 0))\r
+            \r
+            pygame.display.flip()\r
+            \r
+    def input(self, event):\r
+        if event.type == pygame.QUIT:\r
+            pygame.midi.quit()\r
+            sys.exit()\r
+        if event.type == pygame.JOYAXISMOTION:\r
+            self.updateCursorPositionFromJoy(event)\r
+        if event.type == pygame.JOYBUTTONDOWN :\r
+            self.joyClicked(event)\r
+        if event.type == pygame.MOUSEBUTTONDOWN:\r
+            self.mouseClicked(event)\r
+        if event.type == pygame.MOUSEMOTION:\r
+            self.updateCursorPositionFromMouse(event)\r
+        if event.type == pygame.KEYDOWN:\r
+            if event.key == pygame.K_q:\r
+                self.done = True                            \r
+                     \r
+    def updateCursorPositionFromJoy(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = joyNames.index(joyName) \r
+        if correctedJoyId < len(self.cursorPositions):\r
+            if joyEvent.axis == 0 :\r
+                self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])\r
+            if joyEvent.axis == 1 :\r
+                self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))                  \r
+    \r
+    def assignInstrumentToWiimote(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = joyNames.index(joyName)\r
+        if self.zoomed[correctedJoyId] == self.focus[correctedJoyId]:\r
+            self.wiimotes[correctedJoyId].instrument = self.instruments[self.focus[correctedJoyId]]\r
+            self.zoomed[correctedJoyId] = None\r
+            self.imageRects = self.savedImageRects[:]\r
+            if self.currentWiimote<len(self.wiimotes)-1:\r
+                self.currentWiimote = self.currentWiimote+1\r
+        else:\r
+            self.zoomed[correctedJoyId] = self.focus[correctedJoyId]\r
+        if self.hasFinished():\r
+            self.done = True\r
+    \r
+    def updateCursorPositionFromMouse(self, mouseEvent):\r
+        self.cursorPositions[0] = mouseEvent.pos\r
+    \r
+    def assignInstrumentToMouse(self, mouseEvent):\r
+        self.wiimotes[0].instrument = self.instruments[self.focus[0]]\r
+        if self.hasFinished():\r
+            self.done = True\r
+    \r
+    def hasFinished(self):\r
+        finished = True\r
+        for wiimote in self.wiimotes:\r
+            if wiimote.instrument == None:\r
+                finished = False\r
+        return(finished)\r
+    \r
+    def eventFilter(self, event):\r
+        c = event.type\r
+        if c == 17:\r
+            return False\r
+        elif c == pygame.MOUSEMOTION or pygame.MOUSEBUTTONDOWN or pygame.MOUSEBUTTONUP or pygame.JOYAXISMOTION or pygame.JOYBUTTONDOWN or pygame.JOYBUTTONUP or pygame.KEYDOWN:\r
+            return True\r
+        else:\r
+            return False\r
+        \r
+    def drawInstrument(self,instrumentNumber,drawPos = None):\r
+        if not drawPos :\r
+            drawPos = array([random.randint(0,self.width/2),random.randint(0,self.height/2)])\r
+        curImage = pygame.image.load(self.instruments[instrumentNumber].image).convert_alpha()\r
+        scaledImage = pygame.transform.smoothscale(curImage, (self.width / 2, self.height / 2))\r
+        imageRect = self.savedScreen.blit(scaledImage, drawPos + self.blitOrigin)\r
+        pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), imageRect, 5)\r
+        return imageRect           \r
+    \r
+    def drawBackground(self):\r
+        self.savedScreen.fill((255, 255, 255))\r
+        for i in range(len(self.displayedInstruments)):\r
+            self.drawInstrument(self.displayedInstruments[i], self.imageRects[i].topleft)\r
+            if i in self.focus:\r
+                pygame.draw.rect(self.savedScreen, pygame.Color(0, 255, 0, 255), self.imageRects[i], 10)\r
+    \r
+    def mouseClicked(self,mouseEvent):\r
+        correctedJoyId = 0\r
+        self.wiimotes[correctedJoyId].cursor.flash(400)\r
+        if self.focus[correctedJoyId] != None :\r
+            self.imageRects.pop(self.focus[correctedJoyId])\r
+            instrumentNumber = self.displayedInstruments.pop(self.focus[correctedJoyId])\r
+            self.drawBackground()\r
+            self.imageRects.append(self.drawInstrument(instrumentNumber))\r
+            self.displayedInstruments.append(instrumentNumber)\r
+            self.wiimotes[correctedJoyId].instrument = self.instruments[instrumentNumber]\r
+            octave = self.wiimotes[correctedJoyId].instrument.octave\r
+            noteOnHexCode = self.wiimotes[correctedJoyId].getNoteOnHexCode()\r
+            baseTime = pygame.midi.time()\r
+            self.wiimotes[correctedJoyId].port.write([[[noteOnHexCode,60+12*octave,127],baseTime],[[noteOnHexCode,60+12*octave,0],baseTime+500],[[noteOnHexCode,65+12*octave,100],baseTime+510],[[noteOnHexCode,65+12*octave,0],baseTime+1000]])\r
+            self.wiimotes[correctedJoyId].instrument = None\r
+            \r
+    def joyClicked(self,joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = joyNames.index(joyName)\r
+        self.wiimotes[correctedJoyId].cursor.flash(400)\r
+        if self.focus[correctedJoyId] != None :\r
+            self.imageRects.pop(self.focus[correctedJoyId])\r
+            instrumentNumber = self.displayedInstruments.pop(self.focus[correctedJoyId])\r
+            self.drawBackground()\r
+            self.imageRects.append(self.drawInstrument(instrumentNumber))\r
+            self.displayedInstruments.append(instrumentNumber)\r
+            self.wiimotes[correctedJoyId].instrument = self.instruments[instrumentNumber]\r
+            octave = self.wiimotes[correctedJoyId].instrument.octave\r
+            noteOnHexCode = self.wiimotes[correctedJoyId].getNoteOnHexCode()\r
+            baseTime = pygame.midi.time()\r
+            self.wiimotes[correctedJoyId].port.write([[[noteOnHexCode,60+12*octave,127],baseTime],[[noteOnHexCode,60+12*octave,0],baseTime+500],[[noteOnHexCode,65+12*octave,100],baseTime+510],[[noteOnHexCode,65+12*octave,0],baseTime+1000]])\r
+            self.wiimotes[correctedJoyId].instrument = None\r
+                                    \r
+\r
+def zoomRect(rect, ratio):\r
+    zoomedRect = rect.inflate(int(floor((ratio - 1) * rect.width)), int(floor((ratio - 1) * rect.height)))\r
+    return(zoomedRect)        \r
+\r
+if __name__ == "__main__":\r
+    pygame.init()\r
+    #pygame.event.set_blocked([pygame.MOUSEBUTTONDOWN,pygame.MOUSEBUTTONUP,pygame.MOUSEMOTION])\r
+    \r
+    pygame.midi.init()\r
+    instruments = [Instrument(majorScale, i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)]\r
+    \r
+    joys = [pygame.joystick.Joystick(id).get_name() for id in range(pygame.joystick.get_count())]\r
+    joyOffset = joys.index(joyNames[0])\r
+    pygame.joystick.Joystick(joyOffset).init()\r
+    print(joyOffset)  \r
+    \r
+    ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+    portOffset = ports.index(portNames[0])\r
+    print(portOffset)\r
+    \r
+    window = pygame.display.set_mode((1280, 1024),pygame.FULLSCREEN)\r
+    screen = pygame.display.get_surface()\r
+    clock = pygame.time.Clock()        \r
+    cursorImages = createImageListFromPath('../cursor/cursorImages/black', 11)\r
+    durations = [75 for i in range(len(cursorImages))]\r
+    \r
+    extsc = False\r
+    casc = False\r
+    \r
+    jadbt = [3, 4, 5, 3, 4, 4, 5, 6, 6, 5, 3, 3, 4, 5, 3, 4, 4, 5, 6, 7, 3]\r
+    song = None\r
+    \r
+    cursors = [WarpingCursor(None, cursorImages, durations, (300 * i, 300 * i),'../cursor/cursorImages/black/flash.png') for i in range(1)]\r
+    wiimotes = [Wiimote(i, i + portOffset, pygame.midi.Output(i+portOffset,latency = 20), None, cursors[i]) for i in range(1)]\r
+    familiarize = Familiarizer(instruments, wiimotes, window, screen, clock, joyOffset, portOffset)\r
+    for wiimote in wiimotes:\r
+        del wiimote.port            \r
+    \r
+    pygame.midi.quit()\r
+    sys.exit()\r
diff --git a/src/gui/FamiliarizerPGUConfiguration.py b/src/gui/FamiliarizerPGUConfiguration.py
new file mode 100644 (file)
index 0000000..a7b21f7
--- /dev/null
@@ -0,0 +1,237 @@
+'''\r
+Created on 12 nov. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+from logging.FamiliarizerLog import FamiliarizerLog\r
+\r
+import os\r
+import sys\r
+import subprocess\r
+\r
+import pygame\r
+import pygame.midi\r
+import pickle\r
+\r
+from pygame.locals import *\r
+\r
+from pgu import gui\r
+\r
+from StaticFamiliarizer import StaticFamiliarizer\r
+from SongFamiliarizer import SongFamiliarizer\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from logging.FamiliarizerLog import FamiliarizerLog\r
+from songs.Song import Song\r
+from constants import *\r
+from SongPlayingScreen import SongPlayingScreen\r
+from DummyInstrumentChoice import DummyInstrumentChoice\r
+\r
+class FamiliarizerPGUConfiguration(gui.Desktop):\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,window,defaultParams = False):\r
+        '''\r
+        Constructor\r
+        '''\r
+        gui.Desktop.__init__(self)\r
+        self.done = False\r
+        self.level = 0\r
+        self.scale = scaleDict["majorScale"]\r
+        self.fileName = fileName\r
+        self.log = None\r
+        self.activeWiimotes = [False for i in range(4)]    \r
+        \r
+        pygame.font.init()\r
+\r
+        self.titleFont = pygame.font.Font(None,100)\r
+        self.font = pygame.font.Font(None,70)\r
+        self.spaceSize = (100,100)\r
+        \r
+        self.goButton = gui.Button(self.createLabel("Go"))\r
+        self.goButton.connect(gui.CLICK,self.goButtonClicked,None)\r
+        \r
+        self.quitButton = gui.Button(self.createLabel("Fin"))\r
+        self.quitButton.connect(gui.CLICK,self.quitButtonClicked,None)\r
+        \r
+        self.levelSelect = gui.Select()\r
+        for i in range(3) :\r
+            self.levelSelect.add(self.createLabel(str(i+1)),i)\r
+        self.levelSelect.connect(gui.CHANGE,self.levelSelectChanged,None)\r
+        \r
+        self.activeWiimoteSwitches = [gui.Switch(False) for i in range(4)]\r
+        for i in range(len(self.activeWiimoteSwitches)) :\r
+            self.activeWiimoteSwitches[i].connect(gui.CHANGE,self.activeWiimoteSwitchesChanged,i)   \r
+        \r
+        self.connect(gui.QUIT,self.quit,None)\r
+        \r
+        self.window = window\r
+        \r
+        ##The table code is entered much like HTML.\r
+        ##::\r
+        mainContainer = gui.Table() \r
+        \r
+        c = gui.Table()\r
+                \r
+#        c.tr()\r
+#        c.td(self.createLabel("MINWii",self.titleFont),colspan = 4)\r
+        \r
+        c.tr()\r
+        c.td(gui.Spacer(*self.spaceSize))\r
+        \r
+        c.tr()\r
+        c.td(self.createLabel("Niveau :"))\r
+        c.td(self.levelSelect,colspan=3)\r
+        \r
+        c.tr()\r
+        c.td(gui.Spacer(*self.spaceSize))\r
+        \r
+        c.tr()\r
+        c.td(self.createLabel("Joueurs :", self.font))\r
+        playerTable = gui.Table()\r
+        for i in range(len(self.activeWiimoteSwitches)):\r
+            playerTable.td(self.createLabel(" " + str(i+1)+" ", self.font))\r
+            playerTable.td(self.activeWiimoteSwitches[i])\r
+        c.td(playerTable,colspan = 3)\r
+            \r
+        c.tr()\r
+        c.td(gui.Spacer(*self.spaceSize))\r
+        \r
+        c.tr()\r
+        c.td(self.goButton)\r
+        c.td(self.quitButton,colspan=3)        \r
+        \r
+        c.tr()\r
+        c.td(gui.Spacer(500,500))\r
+        \r
+        mainContainer.add(c,0,0)\r
+        \r
+        if defaultParams :\r
+            self.goButtonClicked()\r
+        else :\r
+            self.run(mainContainer)\r
+        \r
+    def open_file_browser(self,data=None):\r
+        d = gui.FileDialog()\r
+        d.connect(gui.CHANGE, self.handle_file_browser_closed, d)\r
+        d.open()\r
+    \r
+\r
+    def handle_file_browser_closed(self,dlg):\r
+        if dlg.value:\r
+            self.file = dlg.value\r
+            \r
+    def createLabel(self,text,font = None):\r
+        if font == None :\r
+            font = self.font\r
+        w,h = self.font.size(text)\r
+        label = gui.Label(text,width=w,height=h,font = font)\r
+        return(label)\r
+    \r
+    def levelSelectChanged(self,data=None):\r
+        self.level = self.levelSelect.value\r
+    \r
+    def quitButtonClicked(self,data = None):\r
+        self.done = True\r
+        self.quit()\r
+        \r
+    def activeWiimoteSwitchesChanged(self,data = None):\r
+        if self.activeWiimoteSwitches[data].value :\r
+            for i in range(len(self.activeWiimoteSwitches)) :\r
+                if self.activeWiimoteSwitches[i].value and data != i :\r
+                    self.activeWiimoteSwitches[i].click()                \r
+        for i in range(len(self.activeWiimoteSwitches)) :\r
+            self.activeWiimotes[i] = self.activeWiimoteSwitches[i].value\r
+    \r
+    def hasActiveWiimote(self):\r
+        hasActive = False\r
+        for active in self.activeWiimotes :\r
+            if active :\r
+                hasActive = True\r
+                break\r
+        return(hasActive)\r
+        \r
+\r
+                    \r
+        \r
+    def goButtonClicked(self,data = None):        \r
+        \r
+        pygame.midi.init()\r
+        instruments = [Instrument(self.scale, i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)]\r
+        \r
+        joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())]\r
+        for joy in joys:\r
+            if joy[1] in joyNames:\r
+                pygame.joystick.Joystick(joy[0]).init() \r
+        \r
+        ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+        portOffset = ports.index(portNames[0])\r
+        print(portOffset)\r
+        \r
+        screen = pygame.display.get_surface()\r
+        clock = pygame.time.Clock()        \r
+        cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']]\r
+        durations = [75 for i in range(len(cursorImages))]\r
+        \r
+        extsc = True\r
+        casc = False\r
+        easyMode = True\r
+        \r
+        song = Song(scaleDict["majorScale"],[3,9,6,4,1,8,5,7,2,10],True)\r
+\r
+        wiimoteCount = 4\r
+        \r
+        cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),'../cursor/cursorImages/black/flash.png') for i in range(wiimoteCount)]\r
+        wiimotes = [Wiimote(i, i + portOffset, None, instruments[0], cursors[i]) for i in range(wiimoteCount)]\r
+        dummyInstrumentChoice = DummyInstrumentChoice(wiimotes, self.window, screen, clock, joys, portOffset, self.activeWiimotes)\r
+        if not self.hasActiveWiimote():\r
+            self.activeWiimotes[0] = True\r
+        if self.level < 2 :\r
+            familiarize = StaticFamiliarizer(wiimotes, self.window, screen, clock, joys, portOffset,self.activeWiimotes,level = self.level)\r
+        elif familiarize.nextLevel == 2 :\r
+            familiarize = SongFamiliarizer(wiimotes, self.window, screen, clock, joys, portOffset,song,self.activeWiimotes,casc,extsc,easyMode)\r
+        else :\r
+            familiarize = SongPlayingScreen(dummyInstrumentChoice,songDict["clairdelalune"],easyMode = True)\r
+                        \r
+        while familiarize.nextLevel != None :\r
+            if familiarize.nextLevel < 2 :\r
+                familiarize = StaticFamiliarizer(wiimotes, self.window, screen, clock, joys, portOffset,self.activeWiimotes,level = familiarize.nextLevel,eventLog = familiarize.eventLog)\r
+            elif familiarize.nextLevel == 2 :\r
+                familiarize = SongFamiliarizer(wiimotes, self.window, screen, clock, joys, portOffset,song,self.activeWiimotes,casc,extsc,easyMode,eventLog = familiarize.eventLog)\r
+            else :\r
+                familiarize = SongPlayingScreen(dummyInstrumentChoice,songDict["clairdelalune"],easyMode = True,eventLog = familiarize.eventLog)\r
+            \r
+        for wiimote in wiimotes:\r
+            del wiimote.port            \r
+        \r
+        pygame.midi.quit()\r
+        \r
+        i = 1        \r
+        filePath = "".join([self.fileName,str(i),".fmwi"])        \r
+        while os.path.exists(filePath):\r
+            i += 1\r
+            filePath = "".join([self.fileName,str(i),".fmwi"])\r
+        \r
+        f = file(filePath, 'w')\r
+        self.log = FamiliarizerLog(familiarize.eventLog,self.level,self.activeWiimotes)\r
+        pickler = pickle.Pickler(f)\r
+        pickler.dump(self.log)\r
+        \r
+        f.close()\r
+        \r
+        self.repaint()\r
+        \r
+if __name__ == "__main__" :\r
+    pygame.init()\r
+    modeResolution = (1024,768)\r
+    window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+    familiarizer = FamiliarizerPGUConfiguration(window)\r
+    pygame.quit()   \r
+        \r
+        \r
+        \r
+        
\ No newline at end of file
diff --git a/src/gui/InstrumentChoice.py b/src/gui/InstrumentChoice.py
new file mode 100755 (executable)
index 0000000..d5f073e
--- /dev/null
@@ -0,0 +1,315 @@
+'''\r
+Created on 15 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+import pygame\r
+import pygame.midi\r
+import sys\r
+import time\r
+import pickle\r
+\r
+from numpy import array\r
+from numpy.linalg import norm\r
+\r
+from math import floor\r
+\r
+from gui.constants import *\r
+from PlayingScreen import PlayingScreen\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from logging.EventLog import EventLog\r
+from logging.PickleableEvent import PickleableEvent \r
+\r
+class InstrumentChoice:\r
+    '''\r
+    The screen for choosing instruments\r
+    \r
+        instruments: \r
+                The available instruments            \r
+        wiimotes: \r
+                The wiimotes used in this session\r
+        window:\r
+            The main display window\r
+        screen:\r
+            The main display surface\r
+        clock:\r
+            The clock used to animate the screen\r
+        savedScreen:\r
+            The background that is painted every time\r
+        playerScreen:\r
+            The buffer for painting everything before bliting\r
+        width:\r
+            The width of the window in pixels\r
+        height:\r
+            The height of the window in pixels\r
+        done:\r
+            Goes to True when all instruments have been selected\r
+        cursorPositions:\r
+            The positions of the cursors on the screen, in pixels\r
+        imageRects:\r
+            The rectangles where the images of the instruments are located\r
+        focus:\r
+            The numbers of the instruments currently in focus\r
+    '''\r
+    \r
+    def __init__(self, instruments, wiimotes, window, screen, clock, joys, portOffset, activeWiimotes, eventLog=None, replay = False, logFilePath = None, scaleFactor = 1):\r
+        '''\r
+        Constructor\r
+        \r
+            instruments: \r
+                The instruments for this session           \r
+            wiimotes: \r
+                The wiimotes used in this session\r
+        '''\r
+        self.scaleFactor = scaleFactor\r
+        \r
+        self.instruments = instruments\r
+        self.wiimotes = wiimotes\r
+        self.window = window\r
+        self.screen = screen\r
+        self.clock = clock\r
+        self.width = int(floor(screen.get_width()*self.scaleFactor))\r
+        self.height = int(floor(screen.get_height()*self.scaleFactor))\r
+        self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2)        \r
+        self.joys = joys\r
+        self.portOffset = portOffset\r
+        \r
+        self.activeWiimotes = activeWiimotes\r
+        \r
+        self.currentWiimote = 0\r
+        while not self.activeWiimotes[self.currentWiimote] :\r
+            self.currentWiimote += 1\r
+        self.done = False        \r
+        \r
+        self.cursorPositions = []\r
+        self.imageRects = []\r
+        self.savedImageRects = []\r
+        self.focus = []\r
+        self.zoomed = []\r
+        \r
+        if eventLog == None:\r
+            self.eventLog = EventLog()\r
+            self.replay = False\r
+        else:\r
+            self.eventLog = eventLog\r
+            self.replay = replay\r
+            print self.replay\r
+                \r
+        #There are 3 instruments per row, up to 9 instruments\r
+        #Draw their images on the screen\r
+        self.savedScreen = pygame.Surface(self.screen.get_size())\r
+        self.savedScreen.fill((255, 255, 255))\r
+        for i in range(len(self.instruments)) :\r
+            drawPos = array([(self.width / 3) * (i % 3), (self.height / 3) * (i / 3)])\r
+            curImage = pygame.image.load(self.instruments[i].image).convert_alpha()\r
+            scaledImage = pygame.transform.smoothscale(curImage, (self.width / 3, self.height / 3))\r
+            self.imageRects.append(self.savedScreen.blit(scaledImage, drawPos + self.blitOrigin))\r
+            self.savedImageRects = self.imageRects[:]\r
+        #Draw the initial cursor on the buffer\r
+        self.playerScreen = pygame.Surface(self.savedScreen.get_size())\r
+        self.playerScreen.blit(self.savedScreen, (0, 0))\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            #Create the list of instrument focus (one focus per wiimote)\r
+            self.focus.append(0)\r
+            self.zoomed.append(None)\r
+            #Set the screen for the cursors (it can't be set before)\r
+            self.wiimotes[i].cursor.screen = self.playerScreen\r
+            self.cursorPositions.append(self.wiimotes[i].cursor.centerPosition)\r
+            \r
+        self.wiimotes[self.currentWiimote].cursor.blit(self.playerScreen)\r
+        \r
+        #The main loop\r
+        while self.done == False :\r
+            \r
+            #Clear the cursors from the screen\r
+            self.playerScreen.blit(self.savedScreen, (0, 0))\r
+            \r
+            # Limit frame speed to 50 FPS\r
+            #\r
+            timePassed = self.clock.tick(50)\r
+            \r
+            if self.replay:\r
+                self.eventLog.update(timePassed)\r
+                pickledEventsToPost = self.eventLog.getPickledEvents() \r
+                for pickledEvent in pickledEventsToPost:\r
+                    pygame.event.post(pickledEvent.event)\r
+            \r
+            events = pygame.event.get()\r
+            \r
+            if not self.replay:\r
+                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events if self.eventFilter(event)]\r
+                if pickledEvents != [] :\r
+                    self.eventLog.appendEventGroup(pickledEvents)\r
+            \r
+            for event in events:\r
+                self.input(event)\r
+                            \r
+        \r
+            if self.zoomed[self.currentWiimote] != None :\r
+                self.imageRects = self.savedImageRects[:]\r
+                #inflate the chosen rect\r
+                zoomedRectNumber = self.zoomed[self.currentWiimote]\r
+                newRect = zoomRect(self.imageRects[zoomedRectNumber], 1.3)\r
+                self.imageRects[zoomedRectNumber] = newRect\r
+                curImage = pygame.image.load(self.instruments[zoomedRectNumber].image).convert_alpha()\r
+                self.scaledImage = pygame.transform.smoothscale(curImage, newRect.size)\r
+                self.playerScreen.blit(self.scaledImage, newRect.topleft)\r
+            \r
+            for i in range(len(self.wiimotes)):\r
+                self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])\r
+                \r
+            self.wiimotes[self.currentWiimote].cursor.blit(self.playerScreen)                \r
+            \r
+            if self.zoomed[self.currentWiimote] != None and self.imageRects[self.zoomed[self.currentWiimote]].collidepoint(self.cursorPositions[self.currentWiimote]):\r
+                self.focus[self.currentWiimote] = self.zoomed[self.currentWiimote]\r
+                pygame.draw.rect(self.playerScreen, pygame.Color(0, 255, 0, 255), self.imageRects[self.zoomed[self.currentWiimote]], 10)\r
+            else:\r
+                for i in range(len(self.imageRects)) :\r
+                    if self.imageRects[i].collidepoint(self.cursorPositions[self.currentWiimote]):\r
+                        self.focus[self.currentWiimote] = i\r
+                        pygame.draw.rect(self.playerScreen, pygame.Color(0, 255, 0, 255), self.imageRects[i], 10)\r
+                        if self.zoomed[self.currentWiimote] != None:\r
+                            self.playerScreen.blit(self.scaledImage, self.imageRects[self.zoomed[self.currentWiimote]].topleft)\r
+            \r
+            self.screen.blit(self.playerScreen, (0, 0))\r
+            \r
+            pygame.display.flip()\r
+            \r
+    def input(self, event):\r
+        if event.type == pygame.QUIT:\r
+            pygame.midi.quit()\r
+            sys.exit()\r
+        if event.type == pygame.JOYAXISMOTION:\r
+            self.updateCursorPositionFromJoy(event)\r
+        if event.type == pygame.JOYBUTTONDOWN :\r
+            self.assignInstrumentToWiimote(event)\r
+        if event.type == pygame.MOUSEBUTTONDOWN:\r
+            if self.zoomed[self.currentWiimote] == self.focus[self.currentWiimote]:\r
+                self.assignInstrumentToMouse(event)\r
+            else:\r
+                self.zoomed[self.currentWiimote] = self.focus[self.currentWiimote]\r
+        if event.type == pygame.MOUSEMOTION:\r
+            self.updateCursorPositionFromMouse(event)            \r
+                     \r
+    def updateCursorPositionFromJoy(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        print joyName\r
+        correctedJoyId = joyNames.index(joyName)\r
+        if self.activeWiimotes[correctedJoyId]: \r
+            if correctedJoyId < len(self.cursorPositions):\r
+                if joyEvent.axis == 0 :\r
+                    self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])\r
+                if joyEvent.axis == 1 :\r
+                    self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))                  \r
+    \r
+    def assignInstrumentToWiimote(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = joyNames.index(joyName)\r
+        if self.activeWiimotes[correctedJoyId]:\r
+            if self.zoomed[correctedJoyId] == self.focus[correctedJoyId]:\r
+                self.wiimotes[correctedJoyId].instrument = self.instruments[self.focus[correctedJoyId]]\r
+                self.zoomed[correctedJoyId] = None\r
+                self.imageRects = self.savedImageRects[:]\r
+                if self.currentWiimote<len(self.wiimotes)-1:\r
+                    self.currentWiimote = self.currentWiimote+1\r
+            else:\r
+                self.zoomed[correctedJoyId] = self.focus[correctedJoyId]\r
+            if self.hasFinished():\r
+                self.done = True\r
+    \r
+    def updateCursorPositionFromMouse(self, mouseEvent):\r
+        correctedJoyId = 0\r
+        while not self.activeWiimotes[correctedJoyId] :\r
+            correctedJoyId += 1\r
+        self.cursorPositions[correctedJoyId] = mouseEvent.pos\r
+    \r
+    def assignInstrumentToMouse(self, mouseEvent):\r
+        correctedJoyId = 0\r
+        while not self.activeWiimotes[correctedJoyId] :\r
+            correctedJoyId += 1\r
+        self.wiimotes[correctedJoyId].instrument = self.instruments[self.focus[correctedJoyId]]\r
+        if self.hasFinished():\r
+            self.done = True\r
+    \r
+    def hasFinished(self):\r
+        finished = True\r
+        for i in range(len(self.wiimotes)):\r
+            if self.wiimotes[i].instrument == None and self.activeWiimotes[i]:\r
+                finished = False\r
+        return(finished)\r
+    \r
+    def eventFilter(self, event):\r
+        c = event.type\r
+        if c == 17:\r
+            return False\r
+        elif c == pygame.MOUSEMOTION or pygame.MOUSEBUTTONDOWN or pygame.MOUSEBUTTONUP or pygame.JOYAXISMOTION or pygame.JOYBUTTONDOWN or pygame.JOYBUTTONUP or pygame.KEYDOWN:\r
+            return True\r
+        else:\r
+            return False\r
+\r
+def zoomRect(rect, ratio):\r
+    zoomedRect = rect.inflate(int(floor((ratio - 1) * rect.width)), int(floor((ratio - 1) * rect.height)))\r
+    return(zoomedRect)        \r
+\r
+if __name__ == "__main__":\r
+    pygame.init()\r
+    #pygame.event.set_blocked([pygame.MOUSEBUTTONDOWN,pygame.MOUSEBUTTONUP,pygame.MOUSEMOTION])\r
+    \r
+    pygame.midi.init()\r
+    instruments = [Instrument(majorScale, i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)]\r
+    \r
+    joys = [pygame.joystick.Joystick(id).get_name() for id in range(pygame.joystick.get_count())]\r
+    joyOffset = joys.index(joyNames[0])\r
+    pygame.joystick.Joystick(joyOffset).init()\r
+    print(joyOffset)  \r
+    \r
+    ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+    portOffset = ports.index(portNames[0])\r
+    print(portOffset)\r
+    \r
+    window = pygame.display.set_mode((1280, 1024),pygame.FULLSCREEN)\r
+    screen = pygame.display.get_surface()\r
+    clock = pygame.time.Clock()        \r
+    cursorImages = createImageListFromPath('../cursor/cursorImages/black', 11)\r
+    durations = [75 for i in range(len(cursorImages))]\r
+    \r
+    extsc = False\r
+    casc = False\r
+    \r
+    jadbt = [3, 4, 5, 3, 4, 4, 5, 6, 6, 5, 3, 3, 4, 5, 3, 4, 4, 5, 6, 7, 3]\r
+    song = None\r
+    \r
+    cursors = [WarpingCursor(None, cursorImages, durations, (300 * i, 300 * i)) for i in range(1)]\r
+    wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(1)]\r
+    choice = InstrumentChoice(instruments, wiimotes, window, screen, clock, joyOffset, portOffset)\r
+    play = PlayingScreen(choice, song, casc, extsc)\r
+    for wiimote in wiimotes:\r
+        del wiimote.port            \r
+        \r
+    f = file('temp.pkl', 'w')\r
+    pickler = pickle.Pickler(f)\r
+    pickler.dump(play.eventLog.eventGroups)\r
+    pickler.dump(play.eventLog.times)\r
+    f.close()\r
+    \r
+    f = file('temp.pkl', 'r')\r
+    unpickler = pickle.Unpickler(f)\r
+    eventGroups = unpickler.load()\r
+    times = unpickler.load()\r
+    f.close()\r
+    eventLog = EventLog(eventGroups,times)\r
+    \r
+    cursors = [WarpingCursor(None, cursorImages, durations, (300 * i, 300 * i)) for i in range(1)]\r
+    wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(1)]\r
+    choice2 = InstrumentChoice(instruments, wiimotes, window, screen, clock, joyOffset, portOffset, eventLog)\r
+    play = PlayingScreen(choice2, song, casc, extsc)\r
+    \r
+    for wiimote in wiimotes:\r
+        del wiimote.port\r
+    \r
+    pygame.midi.quit()\r
+    sys.exit()\r
diff --git a/src/gui/MINWiiDialog.py b/src/gui/MINWiiDialog.py
new file mode 100644 (file)
index 0000000..51e51af
--- /dev/null
@@ -0,0 +1,59 @@
+'''\r
+Created on 17 dec. 2009\r
+\r
+@author: samsam\r
+'''\r
+\r
+import pgu.gui as pguGui\r
+import os\r
+import constants\r
+\r
+class MINWiiDialog(pguGui.FileDialog):\r
+    '''\r
+    classdocs\r
+    '''\r
+    \r
+    def __init__(self, font = None, width = 800, height = 600,path=None):\r
+        pguGui.FileDialog.__init__(self,title_txt = "Choisir une chanson", customFont = font,customWidth = width, customHeight = height,folderText = "", fileText = "",path =path,showCurDir = False)\r
+    \r
+    def _list_dir_(self):\r
+        self.input_dir.value = self.curdir\r
+        self.input_dir.pos = len(self.curdir)\r
+        self.input_dir.vpos = 0\r
+        dirs = []\r
+        files = []\r
+        try:\r
+            for i in os.listdir(self.curdir):\r
+                if os.path.isdir(os.path.join(self.curdir, i)): dirs.append(i)\r
+                else: files.append(i)\r
+        except:\r
+            self.input_file.value = "Opps! no access"\r
+        #if '..' not in dirs: dirs.append('..')\r
+        dirs.sort()\r
+        dirs = ['..'] + dirs\r
+        \r
+        files.sort()\r
+        for i in dirs:\r
+            #item = ListItem(image=self.dir_img, text=i, value=i)\r
+            if self.customFont == None :\r
+                self.list.add(i,image=self.dir_img,value=i)\r
+            else :\r
+                if i == ".." or i[0] != ".":\r
+                    label = pguGui.basic.Label(i,font = self.customFont)\r
+                    self.list.add(label,image=self.dir_img,value=i)\r
+        for i in files:\r
+            #item = ListItem(image=None, text=i, value=i)\r
+            if self.customFont == None :\r
+                self.list.add(i,value=i)\r
+            else:\r
+                if i.endswith(".smwi"):\r
+                    key = i[:-5]\r
+                    if key in constants.reversedReadabilityDict :                     \r
+                        label = pguGui.basic.Label(constants.reversedReadabilityDict[key],font = self.customFont)\r
+                    else :\r
+                        label = pguGui.basic.Label(key,font = self.customFont)\r
+                    self.list.add(label,value=i)\r
+        #self.list.resize()\r
+        self.list.set_vertical_scroll(0)\r
+        #self.list.repaintall()\r
+        
\ No newline at end of file
diff --git a/src/gui/PGUConfiguration.py b/src/gui/PGUConfiguration.py
new file mode 100644 (file)
index 0000000..631932c
--- /dev/null
@@ -0,0 +1,306 @@
+'''\r
+Created on 12 nov. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+import os\r
+import sys\r
+import subprocess\r
+\r
+import pygame\r
+import pygame.midi\r
+import pickle\r
+\r
+from pygame.locals import *\r
+\r
+from pgu import gui as pguGui\r
+\r
+from PlayingScreen import PlayingScreen\r
+from SongPlayingScreen import SongPlayingScreen\r
+from InstrumentChoice import InstrumentChoice\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from logging.Log import Log\r
+from songs.Song import Song,loadSong\r
+from constants import *\r
+from MINWiiDialog import MINWiiDialog\r
+\r
+class PGUConfiguration(pguGui.Desktop):\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,window):\r
+        '''\r
+        Constructor\r
+        '''\r
+        pguGui.Desktop.__init__(self)\r
+        self.extendedScale = False\r
+        self.cascade = False\r
+        self.song = None\r
+        self.scale = scaleDict["majorScale"]\r
+        self.log = None\r
+        self.done = False\r
+        self.easyMode = True\r
+        self.mode = 0\r
+        self.activeWiimotes = [False for i in range(4)]\r
+        self.alwaysDown = False\r
+        \r
+        self.file = None\r
+        #fileName is the path for the log file\r
+        self.fileName = fileName\r
+        self.titleFont = pygame.font.Font(None,100)\r
+        self.font = pygame.font.Font(None,70)\r
+        self.spaceSize = (100,100)\r
+        \r
+        self.browseButton = pguGui.Button(self.createLabel("Choisir..."))\r
+        self.browseButton.connect(pguGui.CLICK, self.open_file_browser, None)\r
+        \r
+        self.songSwitch = pguGui.Switch(False)\r
+        \r
+        self.modeSelect = pguGui.Select()\r
+        for key in modeDict.keys() :\r
+            self.modeSelect.add(self.createLabel(reversedReadabilityDict[key]),key)\r
+        self.modeSelect.connect(pguGui.CHANGE,self.modeSelectChanged,None)\r
+        \r
+        self.activeWiimoteSwitches = [pguGui.Switch(False) for i in range(4)]\r
+        for i in range(len(self.activeWiimoteSwitches)) :\r
+            self.activeWiimoteSwitches[i].connect(pguGui.CHANGE,self.activeWiimoteSwitchesChanged,i)\r
+        \r
+        self.goButton = pguGui.Button(self.createLabel("Go"))\r
+        self.goButton.connect(pguGui.CLICK,self.goButtonClicked,None)\r
+        \r
+        self.quitButton = pguGui.Button(self.createLabel("Fin"))\r
+        self.quitButton.connect(pguGui.CLICK,self.quitButtonClicked,None)\r
+        \r
+        self.connect(pguGui.QUIT,self.quit,None)\r
+        \r
+        self.window = window\r
+        \r
+        ##The table code is entered much like HTML.\r
+        ##::\r
+        \r
+        self.mainTable = pguGui.Table()\r
+        \r
+        self.fillMainTable()\r
+                \r
+#        self.mainTable.tr()\r
+#        self.mainTable.td(self.createLabel("MINWii",self.titleFont),colspan = 4)\r
+        \r
+        self.run(self.mainTable)\r
+        \r
+    def open_file_browser(self,data=None):\r
+        d = MINWiiDialog(font = self.font,width = 800, height = 600,path = "../songs/smwis")\r
+        d.connect(pguGui.CHANGE, self.handle_file_browser_closed, d)\r
+        d.open()\r
+    \r
+\r
+    def handle_file_browser_closed(self,dlg):\r
+        if dlg.value:\r
+            if os.path.isfile(dlg.value):\r
+                self.file = dlg.value\r
+                self.song = loadSong(self.file)\r
+                key = os.path.basename(self.file)[:-5]\r
+                if key in reversedReadabilityDict :                     \r
+                    label = self.createLabel(reversedReadabilityDict[key])\r
+                else :\r
+                    label = self.createLabel(key)\r
+                self.browseButton = pguGui.Button(label)\r
+                self.browseButton.connect(pguGui.CLICK, self.open_file_browser, None)\r
+                if not self.songSwitch.value :\r
+                    self.songSwitch.click()\r
+                self.mainTable.clear()\r
+                self.fillMainTable()\r
+                \r
+    def fillMainTable(self):\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(pguGui.Spacer(*self.spaceSize))\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(self.createLabel("Chanson :"))\r
+        self.mainTable.td(self.browseButton,colspan=2)\r
+        self.mainTable.td(self.songSwitch)\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(pguGui.Spacer(*self.spaceSize))\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(self.createLabel("Niveau :"))\r
+        self.mainTable.td(self.modeSelect,colspan=3)\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(pguGui.Spacer(*self.spaceSize))\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(self.createLabel("Joueurs :", self.font))\r
+        playerTable = pguGui.Table()\r
+        for i in range(len(self.activeWiimoteSwitches)):\r
+            playerTable.td(self.createLabel(" " + str(i+1)+" ", self.font))\r
+            playerTable.td(self.activeWiimoteSwitches[i])\r
+        self.mainTable.td(playerTable,colspan = 3)\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(pguGui.Spacer(*self.spaceSize))\r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(self.goButton)\r
+        self.mainTable.td(self.quitButton,colspan=3)        \r
+        \r
+        self.mainTable.tr()\r
+        self.mainTable.td(pguGui.Spacer(500,500))        \r
+                           \r
+    def createLabel(self,text,font = None):\r
+        if font == None :\r
+            font = self.font\r
+        w,h = self.font.size(text)\r
+        label = pguGui.Label(text,width=w,height=h,font = font)\r
+        return(label)\r
+    \r
+    def songSelectChanged(self,data=None):\r
+        self.song = songDict[self.songSelect.value]\r
+        \r
+    def activeWiimoteSwitchesChanged(self,data = None):\r
+        if self.activeWiimoteSwitches[data].value :\r
+            for i in range(len(self.activeWiimoteSwitches)) :\r
+                if self.activeWiimoteSwitches[i].value and data != i :\r
+                    self.activeWiimoteSwitches[i].click()                \r
+        for i in range(len(self.activeWiimoteSwitches)) :\r
+            self.activeWiimotes[i] = self.activeWiimoteSwitches[i].value\r
+        \r
+    def modeSelectChanged(self,data = None):\r
+        self.mode = modeDict[self.modeSelect.value]\r
+        \r
+    def hasActiveWiimote(self):\r
+        hasActive = False\r
+        for i in self.activeWiimotes:\r
+            if i :\r
+                hasActive = True\r
+        return(hasActive)\r
+    \r
+    def quitButtonClicked(self,data = None):\r
+        self.done = True\r
+        print 'puti'\r
+        for isActive in self.activeWiimotes :\r
+            print isActive\r
+        self.quit()\r
+    \r
+    def goButtonClicked(self,data = None):\r
+        pygame.font.init()\r
+        \r
+        if not self.hasActiveWiimote():\r
+            self.activeWiimotes[0] = True\r
+        pygame.midi.init()\r
+        instruments = [Instrument(self.scale, i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)]\r
+        \r
+        joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())]\r
+        for joy in joys:\r
+            print joy[1]\r
+            if joy[1] in joyNames:\r
+                print "On"\r
+                pygame.joystick.Joystick(joy[0]).init()\r
+            else :\r
+                print "off" \r
+        \r
+        ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+        portOffset = ports.index(portNames[0])\r
+        print(portOffset)\r
+        \r
+        screen = pygame.display.get_surface()\r
+        clock = pygame.time.Clock()        \r
+        cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']]\r
+        durations = [75 for i in range(len(cursorImages[0]))]\r
+        \r
+        wiimoteCount = 4\r
+        cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),flashImage = '../cursor/cursorImages/black/flash.png' ) for i in range(wiimoteCount)]\r
+        wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(wiimoteCount)]\r
+        \r
+        if self.song != None and self.songSwitch.value :\r
+            \r
+            if self.mode == 0 :\r
+                self.extendedScale = self.song.requiresExtendedScale\r
+                self.cascade = True\r
+                self.easyMode = True\r
+                self.alwaysDown = True\r
+            elif self.mode == 1 :\r
+                self.extendedScale = self.song.requiresExtendedScale\r
+                self.cascade = True\r
+                self.easyMode = True\r
+            elif self.mode == 2:\r
+                self.extendedScale = self.song.requiresExtendedScale\r
+                self.cascade = False\r
+                self.easyMode = True\r
+            elif self.mode == 3:\r
+                self.extendedScale = True\r
+                self.cascade = False\r
+                self.easyMode = False\r
+                \r
+            choice = InstrumentChoice(instruments, wiimotes, self.window, screen, clock, joys, portOffset,self.activeWiimotes, scaleFactor = songScaleFactor)\r
+            play = SongPlayingScreen(choice, self.song,self.cascade, self.extendedScale,self.easyMode,self.alwaysDown)\r
+        \r
+        else:\r
+            \r
+            if self.mode == 0 :\r
+                self.extendedScale = False\r
+                self.cascade = False\r
+            elif self.mode == 1 :\r
+                self.extendedScale = True\r
+                self.cascade = False\r
+            elif self.mode == 2:\r
+                self.extendedScale = False\r
+                self.cascade = True\r
+            elif self.mode == 3:\r
+                self.extendedScale = True\r
+                self.cascade = True\r
+                \r
+            choice = InstrumentChoice(instruments, wiimotes, self.window, screen, clock, joys, portOffset,self.activeWiimotes)\r
+            play = PlayingScreen(choice, None,self.cascade, self.extendedScale)            \r
+                \r
+        while play.backToInstrumentChoice == True :\r
+            \r
+            for wiimote in wiimotes:\r
+                del wiimote.port\r
+                \r
+            wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(wiimoteCount)]\r
+            previousEventLog = choice.eventLog\r
+\r
+            if self.song != None :\r
+                choice = InstrumentChoice(instruments, wiimotes, self.window, screen, clock, joys, portOffset, self.activeWiimotes,eventLog = previousEventLog, replay = False, scaleFactor = songScaleFactor)\r
+                play = SongPlayingScreen(choice, self.song, False, self.extendedScale,self.easyMode)\r
+            else:\r
+                choice = InstrumentChoice(instruments, wiimotes, self.window, screen, clock, joys, portOffset,self.activeWiimotes, eventLog = previousEventLog, replay = False)\r
+                play = PlayingScreen(choice, None, self.cascade, self.extendedScale)\r
+                               \r
+        for wiimote in wiimotes:\r
+                del wiimote.port\r
+        \r
+        i = 1        \r
+        filePath = "".join([self.fileName,str(i),".mwi"])        \r
+        while os.path.exists(filePath):\r
+            i += 1\r
+            filePath = "".join([self.fileName,str(i),".mwi"])            \r
+        \r
+        f = file(filePath, 'w')\r
+        self.log = Log(play.eventLog,self.scale,self.extendedScale,self.cascade,self.song,self.mode,self.activeWiimotes)\r
+        pickler = pickle.Pickler(f)\r
+        pickler.dump(self.log)\r
+        \r
+        f.close()\r
+        \r
+        pygame.midi.quit()\r
+        \r
+        self.repaint()\r
+        \r
+if __name__ == "__main__" :\r
+    pygame.init()\r
+    modeResolution = (1024,768)\r
+    window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+    pgu = PGUConfiguration(window)\r
+    pygame.quit()   \r
+        \r
+        \r
+        \r
+        
\ No newline at end of file
diff --git a/src/gui/PlayingScreen.py b/src/gui/PlayingScreen.py
new file mode 100755 (executable)
index 0000000..0b1b272
--- /dev/null
@@ -0,0 +1,439 @@
+'''\r
+Created on 23 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+from math import floor, ceil\r
+import pygame\r
+import sys\r
+import colorsys\r
+import constants\r
+from gradients import gradients\r
+from logging.PickleableEvent import PickleableEvent\r
+\r
+\r
+class PlayingScreen:\r
+    '''\r
+    The screen on which the game is played\r
+    \r
+        wiimotes: \r
+                The wiimotes used in this session\r
+        window:\r
+            The main display window\r
+        screen:\r
+            The main display surface\r
+        clock:\r
+            The clock used to animate the screen\r
+        savedScreen:\r
+            The background that is painted every time\r
+        playerScreen:\r
+            The buffer for painting everything before bliting\r
+        width:\r
+            The width of the window in pixels\r
+        height:\r
+            The height of the window in pixels\r
+        extendScale :\r
+            True if the scale is G to C instead of C to C\r
+        cascade:\r
+            True if crossing from note to note with a button pressed triggers a new note\r
+        scaleSize:\r
+            The size of the scale used\r
+        cursorPositions:\r
+            The positions of the cursors on the screen, in pixels\r
+    '''\r
+    \r
+    \r
+    \r
+    def __init__(self, instrumentChoice=None, song = None, cascade=False, extendedScale=False, defaultInstrumentChannel = 16, defaultNote = 60):\r
+        '''\r
+        Constructor\r
+        '''        \r
+        self.blinkLength = 200\r
+        self.minimalVelocity = 64\r
+        self.shortScaleSize = 8\r
+        self.longScaleSize = 11\r
+        if not extendedScale:\r
+            self.offset = self.longScaleSize - self.shortScaleSize\r
+        else:\r
+            self.offset = 0\r
+        self.borderSize = 5\r
+        self.savedHighlightedNote = 0\r
+        \r
+        self.wiimotes = instrumentChoice.wiimotes\r
+        self.activeWiimotes = instrumentChoice.activeWiimotes\r
+        self.window = instrumentChoice.window\r
+        self.screen = instrumentChoice.screen\r
+        self.blitOrigin = instrumentChoice.blitOrigin\r
+        self.clock = instrumentChoice.clock\r
+        self.width = instrumentChoice.width\r
+        self.height = instrumentChoice.height\r
+        self.cursorPositions = instrumentChoice.cursorPositions\r
+        self.savedScreen = instrumentChoice.savedScreen\r
+        self.playerScreen = instrumentChoice.playerScreen\r
+        self.extendedScale = extendedScale\r
+        self.cascade = cascade\r
+        self.joys = instrumentChoice.joys\r
+        self.portOffset = instrumentChoice.portOffset\r
+        self.eventLog = instrumentChoice.eventLog\r
+        self.cursorPositions = instrumentChoice.cursorPositions\r
+        self.song = song\r
+        self.songIterator = self.moveToNextNote()\r
+        self.replay = instrumentChoice.replay\r
+        \r
+        self.defaultInstrumentChannel = defaultInstrumentChannel\r
+        self.defaultNote = defaultNote\r
+        \r
+        self.done = False\r
+        self.backToInstrumentChoice = False\r
+        self.easyMode = False\r
+        \r
+        self.highlightedNote = self.songIterator.next()\r
+        \r
+        self.blinkOn = False\r
+        self.savedBlinkOn = False\r
+        ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two\r
+        ##i.e. it guarantees that there will be an attack between two identical consecutive notes\r
+        self.highlightIsFree = True\r
+        \r
+        self.noteRects = []\r
+        self.boundingRect = None\r
+        self.notes = []\r
+        self.buttonDown = []\r
+        self.velocityLock = []\r
+        \r
+        self._blinkOffset = 0\r
+        \r
+        self.font = pygame.font.Font(None,50)\r
+        self.firstWiimote = 0\r
+        while not self.activeWiimotes[self.firstWiimote] :\r
+            self.firstWiimote += 1\r
+        self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.wiimotes[self.firstWiimote].instrument.notes[self.offset:]]\r
+        \r
+        self.drawBackground()\r
+        self.initializeWiimotes()\r
+        \r
+        #The main loop\r
+        while not self.done :\r
+            \r
+            #Clear the cursors from the screen\r
+            if self.hasChanged():\r
+                self.drawBackground()\r
+            self.playerScreen.blit(self.savedScreen, (0, 0))\r
+            \r
+            # Limit frame speed to 50 FPS\r
+            #\r
+            timePassed = self.clock.tick(50)\r
+            \r
+            self._blinkOffset += timePassed\r
+            if self._blinkOffset > self.blinkLength:\r
+                self._blinkOffset -= self.blinkLength\r
+                self.blinkOn = not self.blinkOn\r
+                \r
+            if self.replay:\r
+                self.eventLog.update(timePassed)\r
+                pickledEventsToPost = self.eventLog.getPickledEvents() \r
+                for pickledEvent in pickledEventsToPost:\r
+                    pygame.event.post(pickledEvent.event)\r
+            \r
+            events = pygame.event.get()\r
+            \r
+            if not self.replay:\r
+                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events]\r
+                if pickledEvents != [] :\r
+                    self.eventLog.appendEventGroup(pickledEvents)\r
+            \r
+            for event in events:\r
+                self.input(event)\r
+                            \r
+            for i in range(len(self.wiimotes)):\r
+                if self.activeWiimotes[i]:\r
+                    self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])\r
+                    if self.buttonDown[i] :\r
+                        self.wiimotes[i].cursor.flash()\r
+                    self.wiimotes[i].cursor.blit(self.playerScreen)\r
+            \r
+            self.screen.blit(self.playerScreen, (0,0))\r
+            \r
+            pygame.display.flip()\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            if self.activeWiimotes[i]:\r
+                self.wiimotes[i].stopNote(self.notes[i])            \r
+            \r
+    def drawBackground(self):\r
+        self.savedScreen.fill((255,255,255))\r
+        \r
+        if self.extendedScale :\r
+            self.scaleSize = self.longScaleSize\r
+        else:\r
+            self.scaleSize = self.shortScaleSize\r
+        \r
+        self.noteRects = [pygame.Rect(i * self.width / self.scaleSize+self.blitOrigin[0], self.blitOrigin[1], self.width / self.scaleSize + 1, self.height+1) for i in range(self.scaleSize)]\r
+        #inflate last noteRect to cover the far right pixels\r
+        self.noteRects[-1].width = self.noteRects[-1].width + 1\r
+        \r
+        #create bounding rect\r
+        self.boundingRect = self.noteRects[0].unionall(self.noteRects)\r
+        \r
+        #fill the rectangles with a color gradient\r
+        #We start with blue\r
+        startingHue = 0.66666666666666663\r
+        \r
+        for rectNumber in range(self.scaleSize):\r
+            colorRatio = float(rectNumber) / (self.scaleSize - 1)\r
+            #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up\r
+            hue = startingHue * (1 - colorRatio)\r
+            if self.song != None:\r
+                if rectNumber + self.offset == self.highlightedNote:\r
+                    #The color of the bottom of the rectangle in hls coordinates\r
+                    bottomColorHls = (hue, 0.6, 1)\r
+                    #The color of the top of the rectangle in hls coordinates\r
+                    topColorHls = (hue, 0.9, 1)\r
+                else:\r
+                    #The color of the bottom of the rectangle in hls coordinates\r
+                    bottomColorHls = (hue, 0.2, 1)\r
+                    #The color of the top of the rectangle in hls coordinates\r
+                    topColorHls = (hue, 0.4, 1)\r
+            else:\r
+                #The color of the bottom of the rectangle in hls coordinates\r
+                bottomColorHls = (hue, 0.6, 1)\r
+                #The color of the top of the rectangle in hls coordinates\r
+                topColorHls = (hue, 0.9, 1)\r
+            \r
+            #convert to rgb ranging from 0 to 255\r
+            bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]\r
+            topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]\r
+            #add transparency\r
+            bottomColorRgb.append(255)\r
+            topColorRgb.append(255)\r
+            #convert to tuple\r
+            bottomColorRgb = tuple(bottomColorRgb)\r
+            topColorRgb = tuple(topColorRgb)            \r
+            \r
+            self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber])\r
+            \r
+            textBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber].get_width())/2,\r
+                             self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber].get_height())\r
+            \r
+            self.savedScreen.blit(self.renderedNoteNames[rectNumber], textBlitPoint)\r
+            \r
+            pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2)\r
+            \r
+            \r
+            if self.song != None and self.blinkOn:\r
+                borderSize = self.borderSize\r
+                pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize)\r
+        \r
+    def initializeWiimotes(self):\r
+        for loop in self.wiimotes:\r
+            if loop.port == None :\r
+                loop.port = pygame.midi.Output(loop.portNumber)\r
+            self.notes.append(0)\r
+            self.buttonDown.append(False)\r
+            self.velocityLock.append(False)\r
+    \r
+    def updateCursorPositionFromJoy(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = constants.joyNames.index(joyName)\r
+        if correctedJoyId < len(self.cursorPositions):\r
+            if joyEvent.axis == 0 :\r
+                self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])\r
+            if joyEvent.axis == 1 :\r
+                self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))\r
+    \r
+    def heightToVelocity(self, pos, controllerNumber):\r
+        if self.song != None:\r
+            if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]):\r
+                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+            else :\r
+                if self.easyMode:\r
+                    velocity = None\r
+                else:\r
+                    velocity = self.minimalVelocity/3\r
+        else:\r
+            if self.boundingRect.collidepoint(pos):\r
+                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+            else :\r
+                velocity = self.minimalVelocity\r
+        return(velocity)\r
+    \r
+    def widthToNote(self, pos):\r
+        nn = 0\r
+        try :\r
+            while self.noteRects[nn].collidepoint(pos) == False:\r
+                nn = nn + 1\r
+            return(nn + self.offset)\r
+        except(IndexError):\r
+            return(None)\r
+    \r
+    def input(self, event): \r
+        \r
+        if event.type == pygame.QUIT:\r
+            for loop in self.wiimotes:\r
+                del loop.port\r
+            pygame.midi.quit()\r
+            sys.exit(0) \r
+        \r
+        if event.type == pygame.KEYDOWN:\r
+            if event.key == pygame.K_q:\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_i:\r
+                self.backToInstrumentChoice = True\r
+                self.done = True \r
+        \r
+        if event.type == pygame.JOYAXISMOTION:\r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.updateCursorPositionFromJoy(event)            \r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+    \r
+                if self.buttonDown[correctedJoyId]:\r
+                    if self.notes[correctedJoyId] != None:\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        CCHexCode = wiimote.getCCHexCode()\r
+                        wiimote.port.write_short(CCHexCode, 07, velocity)\r
+                    if self.cascade:\r
+                        n = self.widthToNote(pos)\r
+                        if n != self.notes[correctedJoyId]:\r
+                            wiimote.stopNote(self.notes[correctedJoyId])\r
+                            self.notes[correctedJoyId] = n\r
+                            \r
+                            if self.song != None :\r
+                                if self.highlightedNote == self.notes[correctedJoyId]:\r
+                                    self.highlightedNote = self.songIterator.next()\r
+                                    self.velocityLock[correctedJoyId] = True\r
+                                else:\r
+                                    self.velocityLock[correctedJoyId] = False\r
+                            \r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                            \r
+                            wiimote.playNote(self.notes[correctedJoyId],velocity)\r
+        \r
+        if event.type == pygame.JOYBUTTONDOWN :\r
+            \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+    \r
+                if not self.buttonDown[correctedJoyId]:\r
+                    savedNote = self.notes[correctedJoyId]\r
+                    self.notes[correctedJoyId] = self.widthToNote(pos)\r
+                    \r
+                    if self.song != None :\r
+                        if self.highlightedNote == self.notes[correctedJoyId]:\r
+                            self.highlightedNote = self.songIterator.next()\r
+                            self.velocityLock[correctedJoyId] = True\r
+                    \r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    \r
+                    if velocity != None :                            \r
+                        if self.easyMode :\r
+                            wiimote.stopNote(savedNote)\r
+                        wiimote.playNote(self.notes[correctedJoyId],velocity)\r
+                    self.buttonDown[correctedJoyId] = True\r
+        \r
+        if event.type == pygame.JOYBUTTONUP:\r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.buttonDown[correctedJoyId] = False\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                if not self.easyMode:\r
+                    wiimote.stopNote(self.notes[correctedJoyId])\r
+                    self.velocityLock[correctedJoyId] = False\r
+            \r
+        if event.type == pygame.MOUSEMOTION:\r
+            \r
+            self.updateCursorPositionFromMouse(event)\r
+\r
+            correctedJoyId = 0\r
+            while not self.activeWiimotes[correctedJoyId] :\r
+                correctedJoyId += 1\r
+            wiimote = self.wiimotes[correctedJoyId]\r
+            pos = self.cursorPositions[correctedJoyId]\r
+\r
+            if self.buttonDown[correctedJoyId]:\r
+                if self.notes[correctedJoyId] != None:\r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    CCHexCode = wiimote.getCCHexCode()\r
+                    wiimote.port.write_short(CCHexCode, 07, velocity)\r
+                if self.cascade:\r
+                    n = self.widthToNote(pos)\r
+                    if n != self.notes[correctedJoyId]:\r
+                        wiimote.stopNote(self.notes[correctedJoyId])\r
+                        self.notes[correctedJoyId] = n\r
+                        \r
+                        if self.song != None :\r
+                            if self.highlightedNote == self.notes[correctedJoyId]:\r
+                                self.highlightedNote = self.songIterator.next()\r
+                                self.velocityLock[correctedJoyId] = True\r
+                            else:\r
+                                self.velocityLock[correctedJoyId] = False\r
+                        \r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                            \r
+                        wiimote.playNote(self.notes[correctedJoyId],velocity)                            \r
+        \r
+        if event.type == pygame.MOUSEBUTTONDOWN:\r
+            \r
+            if event.button == 1:\r
+                correctedJoyId = 0\r
+                while not self.activeWiimotes[correctedJoyId] :\r
+                    correctedJoyId += 1\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+    \r
+                if not self.buttonDown[correctedJoyId]:\r
+                    self.notes[correctedJoyId] = self.widthToNote(pos)\r
+                    \r
+                    if self.song != None :\r
+                        if self.highlightedNote == self.notes[correctedJoyId]:\r
+                            self.highlightedNote = self.songIterator.next()\r
+                            self.velocityLock[correctedJoyId] = True\r
+                    \r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                                \r
+                    wiimote.playNote(self.notes[correctedJoyId],velocity)\r
+                    self.buttonDown[correctedJoyId] = True\r
+            \r
+            if event.button == 2:\r
+                \r
+                self.done = True\r
+        \r
+        if event.type == pygame.MOUSEBUTTONUP:\r
+            \r
+            correctedJoyId = 0\r
+            while not self.activeWiimotes[correctedJoyId] :\r
+                correctedJoyId += 1\r
+            wiimote = self.wiimotes[correctedJoyId]\r
+            wiimote.stopNote(self.notes[correctedJoyId])\r
+            self.buttonDown[correctedJoyId] = False\r
+            self.velocityLock[correctedJoyId] = False\r
+        \r
+    def hasChanged(self):\r
+        changed = False\r
+        if self.song != None:\r
+            if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote:\r
+                self.savedBlinkOn = self.blinkOn\r
+                self.savedHighlightedNote = self.highlightedNote\r
+                changed = True\r
+        return(changed)\r
+    \r
+    def updateCursorPositionFromMouse(self, mouseEvent):\r
+        correctedJoyId = 0\r
+        while not self.activeWiimotes[correctedJoyId] :\r
+            correctedJoyId += 1\r
+        self.cursorPositions[correctedJoyId] = mouseEvent.pos\r
+        \r
+    def moveToNextNote(self):\r
+        while True:\r
+            if self.song == None:\r
+                yield(None)\r
+            else:\r
+                for note in self.song:\r
+                    yield note\r
diff --git a/src/gui/SongFamiliarizer.py b/src/gui/SongFamiliarizer.py
new file mode 100644 (file)
index 0000000..69ab03c
--- /dev/null
@@ -0,0 +1,583 @@
+'''\r
+Created on 23 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+from math import floor, ceil\r
+import pygame\r
+import sys\r
+import colorsys\r
+import constants\r
+from gradients import gradients\r
+from logging.PickleableEvent import PickleableEvent\r
+from logging.EventLog import EventLog\r
+\r
+\r
+class SongFamiliarizer:\r
+    '''\r
+    The screen on which the game is played\r
+    \r
+        wiimotes: \r
+                The wiimotes used in this session\r
+        window:\r
+            The main display window\r
+        screen:\r
+            The main display surface\r
+        clock:\r
+            The clock used to animate the screen\r
+        savedScreen:\r
+            The background that is painted every time\r
+        playerScreen:\r
+            The buffer for painting everything before bliting\r
+        width:\r
+            The width of the window in pixels\r
+        height:\r
+            The height of the window in pixels\r
+        extendScale :\r
+            True if the scale is G to C instead of C to C\r
+        cascade:\r
+            True if crossing from note to note with a button pressed triggers a new note\r
+        scaleSize:\r
+            The size of the scale used\r
+        cursorPositions:\r
+            The positions of the cursors on the screen, in pixels\r
+    '''\r
+    \r
+    \r
+    \r
+    def __init__(self, wiimotes, window, screen, clock, joys, portOffset, song,  activeWiimotes, cascade=False, extendedScale=False, easyMode = False, replay = False, eventLog = None, defaultInstrumentChannel = 16, defaultNote = 60):\r
+        '''\r
+        Constructor\r
+        ''' \r
+        self.firstClickTime = None\r
+        self.firstClickInTime = None\r
+        self.duration = None\r
+        self.clicks = 0\r
+        self.clicksIn = 0\r
+        \r
+        pygame.font.init()\r
+        self.font = pygame.font.Font(None,60)\r
+        self.congratulations = ["Bien !","Tres Bien !","Bravo !","Excellent !","Felicitations !"]\r
+        self.renderedCongratulations = [self.font.render(congratulation,False,(0,0,0)) for congratulation in self.congratulations]\r
+        self.congratulationCount = None\r
+        self.isCongratulating = False\r
+        self.congratulationTimer = 0\r
+        self.congratulationLength = 2000\r
+        self.congratulationPos = None\r
+               \r
+        self.blinkLength = 200\r
+        self.minimalVelocity = 90\r
+        self.shortScaleSize = 8\r
+        self.longScaleSize = 11\r
+        if not extendedScale:\r
+            self.offset = self.longScaleSize - self.shortScaleSize\r
+        else:\r
+            self.offset = 0\r
+        self.borderSize = 5\r
+        self.highlightedNote = 0\r
+        self.highlightedNoteNumber = 0\r
+        self.syllabus = None\r
+        self.savedHighlightedNote = 0\r
+        self.scaleFactor = 1\r
+        self.level = 3\r
+        \r
+        self.wiimotes = wiimotes\r
+        self.activeWiimotes = activeWiimotes\r
+        self.window = window\r
+        self.screen = screen\r
+        self.width = int(floor(screen.get_width()*self.scaleFactor))\r
+        self.height = int(floor(screen.get_height()*self.scaleFactor))\r
+        self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2)        \r
+        self.joys = joys\r
+        self.clock = clock\r
+        self.cursorPositions = []\r
+        self.savedScreen = pygame.Surface(self.screen.get_size())\r
+        self.savedScreen.fill((255,255,255))\r
+        self.playerScreen = pygame.Surface(self.savedScreen.get_size())\r
+        self.playerScreen.blit(self.savedScreen, (0, 0))\r
+        self.extendedScale = extendedScale\r
+        self.cascade = cascade\r
+        self.portOffset =portOffset\r
+        self.eventLog = eventLog\r
+        self.song = song\r
+        self.songIterator = self.song.getSongIterator()\r
+        self.midiNoteNumbers = self.song.scale\r
+        self.replay = replay\r
+        self.quarterNoteLength = 800\r
+        self.cascadeLockLengthMultiplier = 1\r
+        self.cascadeLockLength = self.quarterNoteLength * self.cascadeLockLengthMultiplier\r
+        \r
+        self.defaultInstrumentChannel = defaultInstrumentChannel\r
+        self.defaultNote = defaultNote\r
+        \r
+        self.done = False\r
+        self.backToInstrumentChoice = False\r
+        self.easyMode = easyMode\r
+        \r
+        if eventLog == None:\r
+            self.eventLog = EventLog()\r
+            self.replay = False\r
+        else:\r
+            self.eventLog = eventLog\r
+            self.replay = replay\r
+        \r
+        #Initializes the highlightedNote and highlightedNoteNumber etc...\r
+        self.moveToNextNote()\r
+        \r
+        self.blinkOn = False\r
+        self.savedBlinkOn = False\r
+        ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two\r
+        ##i.e. it guarantees that there will be an attack between two identical consecutive notes\r
+        self.highlightIsFree = True\r
+        \r
+        self.noteRects = []\r
+        self.boundingRect = None\r
+        self.notes = []\r
+        \r
+        self.buttonDown = []\r
+        self.velocityLock = []\r
+        \r
+        self._blinkOffset = 0\r
+        self._cascadeLockTimer = 0\r
+        self.cascadeIsFree = True\r
+        \r
+        self.font = pygame.font.Font(None,50)\r
+        self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers]\r
+        \r
+        self.drawBackground()\r
+        self.initializeWiimotes()\r
+        \r
+        events = pygame.event.get()\r
+        \r
+        #The main loop\r
+        while not self.done :\r
+            \r
+            #Clear the cursors from the screen\r
+            if self.hasChanged():\r
+                self.drawBackground()\r
+            self.playerScreen.blit(self.savedScreen, (0, 0))\r
+            \r
+            # Limit frame speed to 50 FPS\r
+            #\r
+            timePassed = self.clock.tick(10000)\r
+            \r
+            self._blinkOffset += timePassed\r
+            if self.buttonDown and not self.cascadeIsFree :\r
+                self._cascadeLockTimer += timePassed\r
+                if self._cascadeLockTimer > self.cascadeLockLength :\r
+                    self.cascadeIsFree = True\r
+            \r
+            \r
+            if self._blinkOffset > self.blinkLength:\r
+                self._blinkOffset -= self.blinkLength\r
+                self.blinkOn = not self.blinkOn\r
+                \r
+            if self.replay:\r
+                self.eventLog.update(timePassed)\r
+                pickledEventsToPost = self.eventLog.getPickledEvents() \r
+                for pickledEvent in pickledEventsToPost:\r
+                    pygame.event.post(pickledEvent.event)\r
+            \r
+            events = pygame.event.get()\r
+            \r
+            if not self.replay:\r
+                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events]\r
+                if pickledEvents != [] :\r
+                    self.eventLog.appendEventGroup(pickledEvents)\r
+            \r
+            for event in events:\r
+                self.input(event)\r
+            \r
+            if self.isCongratulating :\r
+                self.congratulationTimer += timePassed\r
+                if self.congratulationTimer < self.congratulationLength :\r
+                    self.blitCongratulation()\r
+                else :\r
+                    self.isCongratulating = False\r
+                            \r
+            for i in range(len(self.wiimotes)):\r
+                if self.activeWiimotes[i]:\r
+                    self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])\r
+                    if self.buttonDown[i] :\r
+                        self.wiimotes[i].cursor.flash()\r
+                    self.wiimotes[i].cursor.blit(self.playerScreen)\r
+            \r
+            self.screen.blit(self.playerScreen, (0,0))\r
+            \r
+            pygame.display.flip()\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            if self.activeWiimotes[i]:\r
+                self.wiimotes[i].stopNoteByNoteNumber(self.midiNoteNumbers[self.notes[i]])            \r
+        if self.replay :\r
+            self.duration = self.eventLog.getCurrentTime()\r
+        \r
+    def drawBackground(self):\r
+        self.savedScreen.fill((255,255,255))\r
+        \r
+        if self.extendedScale :\r
+            self.scaleSize = self.longScaleSize\r
+        else:\r
+            self.scaleSize = self.shortScaleSize\r
+        \r
+        self.noteRects = [pygame.Rect(i * self.width / self.scaleSize+self.blitOrigin[0], self.blitOrigin[1], self.width / self.scaleSize + 1, self.height+1) for i in range(self.scaleSize)]\r
+        #inflate last noteRect to cover the far right pixels\r
+        self.noteRects[-1].width = self.noteRects[-1].width + 1\r
+        \r
+        self.noteRects[self.highlightedNote-self.offset].inflate_ip(self.noteRects[self.highlightedNote-self.offset].width*2,0)\r
+        \r
+        #create bounding rect\r
+        self.boundingRect = self.noteRects[0].unionall(self.noteRects)\r
+        \r
+        self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers]\r
+        \r
+        #fill the rectangles with a color gradient\r
+        #We start with blue\r
+        startingHue = 0.66666666666666663\r
+        \r
+#        for rectNumber in range(self.scaleSize):\r
+#            colorRatio = float(rectNumber) / (self.scaleSize - 1)\r
+#            #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up\r
+#            hue = startingHue * (1 - colorRatio)\r
+#            if rectNumber + self.offset != self.highlightedNote:\r
+#                #The color of the bottom of the rectangle in hls coordinates\r
+#                bottomColorHls = (hue, 0.1, 1)\r
+#                #The color of the top of the rectangle in hls coordinates\r
+#                topColorHls = (hue, 0.1, 1)\r
+#            \r
+#                #convert to rgb ranging from 0 to 255\r
+#                bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]\r
+#                topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]\r
+#                #add transparency\r
+#                bottomColorRgb.append(255)\r
+#                topColorRgb.append(255)\r
+#                #convert to tuple\r
+#                bottomColorRgb = tuple(bottomColorRgb)\r
+#                topColorRgb = tuple(topColorRgb)            \r
+#                \r
+#                self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber])\r
+#                \r
+#                noteNameBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber].get_width())/2,\r
+#                                 self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber].get_height())\r
+#                \r
+#                self.savedScreen.blit(self.renderedNoteNames[rectNumber], noteNameBlitPoint)\r
+#                \r
+#                pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2)\r
+        \r
+        colorRatio = float(self.highlightedNote-self.offset) / (self.scaleSize - 1)\r
+        #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up\r
+        hue = startingHue * (1 - colorRatio)\r
+        #The color of the bottom of the rectangle in hls coordinates\r
+        bottomColorHls = (hue, 0.6, 1)\r
+        #The color of the top of the rectangle in hls coordinates\r
+        topColorHls = (hue, 0.9, 1)\r
+        \r
+        #convert to rgb ranging from 0 to 255\r
+        bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]\r
+        topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]\r
+        #add transparency\r
+        bottomColorRgb.append(255)\r
+        topColorRgb.append(255)\r
+        #convert to tuple\r
+        bottomColorRgb = tuple(bottomColorRgb)\r
+        topColorRgb = tuple(topColorRgb)            \r
+        \r
+        self.savedScreen.blit(gradients.vertical(self.noteRects[self.highlightedNote-self.offset].size, topColorRgb, bottomColorRgb), self.noteRects[self.highlightedNote-self.offset])\r
+        \r
+#        noteNameBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-self.renderedNoteNames[self.highlightedNote-self.offset].get_width())/2,\r
+#                         self.noteRects[self.highlightedNote-self.offset].bottom-self.renderedNoteNames[self.highlightedNote-self.offset].get_height())\r
+#        \r
+#        self.savedScreen.blit(self.renderedNoteNames[self.highlightedNote-self.offset], noteNameBlitPoint)\r
+#        \r
+#        if self.syllabus :\r
+#            renderedSyllabus = self.font.render(self.syllabus,False,(0,0,0))\r
+#        \r
+#            syllabusBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-renderedSyllabus.get_width())/2,\r
+#                             self.noteRects[self.highlightedNote-self.offset].centery-renderedSyllabus.get_height()/2)\r
+#            \r
+#            self.savedScreen.blit(renderedSyllabus, syllabusBlitPoint)\r
+        \r
+        pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[self.highlightedNote-self.offset], 2)    \r
+            \r
+#        if self.song != None and self.blinkOn:\r
+#            borderSize = self.borderSize\r
+#            pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize)\r
+        \r
+    def initializeWiimotes(self):\r
+        for loop in self.wiimotes:\r
+            if loop.port == None :\r
+                loop.port = pygame.midi.Output(loop.portNumber)\r
+            self.notes.append(0)\r
+            self.cursorPositions.append(loop.cursor.centerPosition)\r
+            self.buttonDown.append(False)\r
+            self.velocityLock.append(False)\r
+    \r
+    def updateCursorPositionFromJoy(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = constants.joyNames.index(joyName)\r
+        if correctedJoyId < len(self.cursorPositions):\r
+            if joyEvent.axis == 0 :\r
+                self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])\r
+            if joyEvent.axis == 1 :\r
+                self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))\r
+    \r
+    def heightToVelocity(self, pos, controllerNumber):\r
+        if self.song != None:\r
+            if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]):\r
+                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+            else :\r
+                if self.easyMode:\r
+                    velocity = None\r
+                else:\r
+                    velocity = 60\r
+        else:\r
+            if self.boundingRect.collidepoint(pos):\r
+                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+            else :\r
+                velocity = self.minimalVelocity\r
+        return(velocity)\r
+    \r
+    def widthToNote(self, pos):\r
+        nn = 0\r
+        try :\r
+            if self.noteRects[self.highlightedNote-self.offset].collidepoint(pos) :\r
+                return self.highlightedNote\r
+            else :\r
+                while self.noteRects[nn].collidepoint(pos) == False:\r
+                    nn = nn + 1\r
+                return(nn + self.offset)\r
+        except(IndexError):\r
+            return(None)\r
+    \r
+    def congratulate(self,targetRect,posy):\r
+        if self.congratulationCount != None :\r
+            if self.congratulationCount < len(self.congratulations)-1:\r
+                self.congratulationCount += 1\r
+        else :\r
+            self.congratulationCount = 0\r
+        self.congratulationTimer = 0\r
+        self.congratulationPos = (targetRect.left+(targetRect.width-self.renderedCongratulations[self.congratulationCount].get_width())/2,posy)\r
+        self.isCongratulating = True\r
+        \r
+    def resetCongratulation(self):\r
+        self.congratulationCount = None\r
+        self.congratulationPos = None\r
+        self.isCongratulating = False\r
+        \r
+    def blitCongratulation(self):\r
+        self.playerScreen.blit(self.renderedCongratulations[self.congratulationCount],self.congratulationPos)\r
+    \r
+    def input(self, event): \r
+        \r
+        if event.type == pygame.QUIT:\r
+            for loop in self.wiimotes:\r
+                del loop.port\r
+            pygame.midi.quit()\r
+            sys.exit(0) \r
+        \r
+        if event.type == pygame.KEYDOWN:\r
+            if event.key == pygame.K_q:\r
+                self.nextLevel = None\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_w:\r
+                self.nextLevel = 0\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_e:\r
+                self.nextLevel = 1\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_r:\r
+                self.nextLevel = 2\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_t:\r
+                self.nextLevel = 3\r
+                self.done = True\r
+        \r
+        if event.type == pygame.JOYAXISMOTION:\r
+            \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.updateCursorPositionFromJoy(event)\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+    \r
+                if self.buttonDown[correctedJoyId]:\r
+                    if self.notes[correctedJoyId] != None:\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        if velocity != None :\r
+                            CCHexCode = wiimote.getCCHexCode()\r
+                            wiimote.port.write_short(CCHexCode, 07, velocity)\r
+                    if self.cascade and self.cascadeIsFree :\r
+                        n = self.widthToNote(pos)\r
+                        if self.highlightedNote == n:\r
+                            wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                            self.notes[correctedJoyId] = n\r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                            self.velocityLock[correctedJoyId] = True\r
+                            wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                            self.moveToNextNote()\r
+                            self._cascadeLockTimer = 0\r
+                            self.cascadeIsFree = False\r
+        \r
+        if event.type == pygame.JOYBUTTONDOWN :\r
+            \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+                self.wiimotes[correctedJoyId].cursor.flash()\r
+                if self.replay:\r
+                    self.clicks += 1\r
+                    if self.firstClickTime == None :\r
+                        self.firstClickTime = self.eventLog.getCurrentTime()\r
+                        \r
+                if not self.buttonDown[correctedJoyId]:\r
+                    n = self.widthToNote(pos)\r
+                    if self.highlightedNote == n:\r
+                        self._cascadeLockTimer = 0\r
+                        self.cascadeIsFree = False\r
+                        if self.easyMode:\r
+                            wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                        self.notes[correctedJoyId] = n\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        self.velocityLock[correctedJoyId] = True\r
+                        wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                        self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1])\r
+                        if self.replay:\r
+                            self.clicksIn += 1\r
+                            if self.firstClickInTime == None :\r
+                                self.firstClickInTime = self.eventLog.getCurrentTime()\r
+                 \r
+                        self.moveToNextNote()\r
+                    else :\r
+                        self.resetCongratulation()\r
+                        if not self.easyMode :\r
+                            self._cascadeLockTimer = 0\r
+                            self.cascadeIsFree = False\r
+                            self.notes[correctedJoyId] = n\r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)                    \r
+                            if velocity != None :\r
+                                wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)                                      \r
+                    self.buttonDown[correctedJoyId] = True\r
+            \r
+        if event.type == pygame.JOYBUTTONUP:\r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.buttonDown[correctedJoyId] = False\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                if not self.easyMode:\r
+                    wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                self.velocityLock[correctedJoyId] = False\r
+            \r
+        if event.type == pygame.MOUSEMOTION:\r
+            \r
+            self.updateCursorPositionFromMouse(event)\r
+            \r
+            correctedJoyId = 0\r
+            while not self.activeWiimotes[correctedJoyId] :\r
+                correctedJoyId += 1\r
+            wiimote = self.wiimotes[correctedJoyId]\r
+            pos = self.cursorPositions[correctedJoyId]\r
+\r
+            if self.buttonDown[correctedJoyId]:\r
+                self.wiimotes[correctedJoyId].cursor.flash()\r
+                if self.notes[correctedJoyId] != None:\r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    if velocity != None :\r
+                        CCHexCode = wiimote.getCCHexCode()\r
+                        wiimote.port.write_short(CCHexCode, 07, velocity)\r
+                if self.cascade and self.cascadeIsFree :\r
+                    n = self.widthToNote(pos)\r
+                    if self.highlightedNote == n:\r
+                        wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                        self.notes[correctedJoyId] = n\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        self.velocityLock[correctedJoyId] = True\r
+                        wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                        self.moveToNextNote()\r
+                        self._cascadeLockTimer = 0\r
+                        self.cascadeIsFree = False             \r
+        \r
+        if event.type == pygame.MOUSEBUTTONDOWN:\r
+            \r
+            if event.button == 1:\r
+                correctedJoyId = 0\r
+                while not self.activeWiimotes[correctedJoyId] :\r
+                    correctedJoyId += 1\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+                self.wiimotes[correctedJoyId].cursor.flash()\r
+                if self.replay:\r
+                    self.clicks += 1\r
+                    if self.firstClickTime == None :\r
+                        self.firstClickTime = self.eventLog.getCurrentTime()\r
+                        \r
+                if not self.buttonDown[correctedJoyId]:\r
+                    n = self.widthToNote(pos)\r
+                    if self.highlightedNote == n:\r
+                        self._cascadeLockTimer = 0\r
+                        self.cascadeIsFree = False\r
+                        if self.easyMode:\r
+                            wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                        self.notes[correctedJoyId] = n\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        self.velocityLock[correctedJoyId] = True\r
+                        wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                        self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1])\r
+                        if self.replay:\r
+                            self.clicksIn += 1\r
+                            if self.firstClickInTime == None :\r
+                                self.firstClickInTime = self.eventLog.getCurrentTime()\r
+                 \r
+                        self.moveToNextNote()\r
+                    else :\r
+                        self.resetCongratulation()\r
+                        if not self.easyMode :\r
+                            self._cascadeLockTimer = 0\r
+                            self.cascadeIsFree = False\r
+                            self.notes[correctedJoyId] = n\r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)                    \r
+                            if velocity != None :\r
+                                wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)                                      \r
+                    self.buttonDown[correctedJoyId] = True                    \r
+            \r
+            if event.button == 2:\r
+                \r
+                self.done = True\r
+        \r
+        if event.type == pygame.MOUSEBUTTONUP:\r
+            if event.button == 1 :\r
+                correctedJoyId = 0\r
+                while not self.activeWiimotes[correctedJoyId] :\r
+                    correctedJoyId += 1\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                self.buttonDown[correctedJoyId] = False\r
+                if not self.easyMode:\r
+                    wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                self.velocityLock[correctedJoyId] = False\r
+        \r
+    def hasChanged(self):\r
+        changed = False\r
+        if self.song != None:\r
+            if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote:\r
+                self.savedBlinkOn = self.blinkOn\r
+                self.savedHighlightedNote = self.highlightedNote\r
+                changed = True\r
+        return(changed)\r
+    \r
+    def updateCursorPositionFromMouse(self, mouseEvent):\r
+        correctedJoyId = 0\r
+        while not self.activeWiimotes[correctedJoyId] :\r
+            correctedJoyId += 1\r
+        self.cursorPositions[correctedJoyId] = mouseEvent.pos\r
+        \r
+    def moveToNextNote(self):\r
+        self.savedMidiNoteNumbers = self.midiNoteNumbers[:]\r
+        self.highlightedNote, self.highlightedNoteNumber, self.syllabus, self.cascadeLockLengthMultiplier = self.songIterator.next()\r
+        self.midiNoteNumbers[self.highlightedNote] = self.highlightedNoteNumber\r
diff --git a/src/gui/SongPlayingScreen.py b/src/gui/SongPlayingScreen.py
new file mode 100644 (file)
index 0000000..a06e831
--- /dev/null
@@ -0,0 +1,564 @@
+'''\r
+Created on 23 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+from math import floor, ceil\r
+import pygame\r
+import sys\r
+import colorsys\r
+import constants\r
+from gradients import gradients\r
+from logging.PickleableEvent import PickleableEvent\r
+\r
+\r
+class SongPlayingScreen:\r
+    '''\r
+    The screen on which the game is played\r
+    \r
+        wiimotes: \r
+                The wiimotes used in this session\r
+        window:\r
+            The main display window\r
+        screen:\r
+            The main display surface\r
+        clock:\r
+            The clock used to animate the screen\r
+        savedScreen:\r
+            The background that is painted every time\r
+        playerScreen:\r
+            The buffer for painting everything before bliting\r
+        width:\r
+            The width of the window in pixels\r
+        height:\r
+            The height of the window in pixels\r
+        extendScale :\r
+            True if the scale is G to C instead of C to C\r
+        cascade:\r
+            True if crossing from note to note with a button pressed triggers a new note\r
+        scaleSize:\r
+            The size of the scale used\r
+        cursorPositions:\r
+            The positions of the cursors on the screen, in pixels\r
+    '''\r
+    \r
+    \r
+    \r
+    def __init__(self, instrumentChoice, song, cascade=False, extendedScale=False, easyMode = False, alwaysDown = False, eventLog = None, replay = None, defaultInstrumentChannel = 16, defaultNote = 60):\r
+        '''\r
+        Constructor\r
+        '''        \r
+        self.songDurations = []\r
+        self.totalDuration = None\r
+        self.clicks = [0]\r
+        self.clicksIn = [0]\r
+        self.clicksPerMinute = [0]\r
+        self.clicksInPerMinute = [0]\r
+        self.meanTimeBetweenNotes = []\r
+        self.firstClick = None\r
+        self.firstClickIn = None\r
+        \r
+        self.blinkLength = 200\r
+        self.minimalVelocity = 90\r
+        self.shortScaleSize = 8\r
+        self.longScaleSize = 11\r
+        if not extendedScale:\r
+            self.offset = self.longScaleSize - self.shortScaleSize\r
+        else:\r
+            self.offset = 0\r
+        self.borderSize = 5\r
+        self.highlightedNote = 0\r
+        self.highlightedNoteNumber = 0\r
+        self.syllabus = None\r
+        self.savedHighlightedNote = 0\r
+        self.alwaysDown = alwaysDown\r
+        self.nextLevel = None\r
+        \r
+        self.wiimotes = instrumentChoice.wiimotes\r
+        self.activeWiimotes = instrumentChoice.activeWiimotes\r
+        self.window = instrumentChoice.window\r
+        self.screen = instrumentChoice.screen\r
+        self.blitOrigin = instrumentChoice.blitOrigin\r
+        self.clock = instrumentChoice.clock\r
+        self.width = instrumentChoice.width\r
+        self.height = instrumentChoice.height\r
+        self.cursorPositions = instrumentChoice.cursorPositions\r
+        self.savedScreen = instrumentChoice.savedScreen\r
+        self.playerScreen = instrumentChoice.playerScreen\r
+        self.extendedScale = extendedScale\r
+        self.cascade = cascade\r
+        self.joys = instrumentChoice.joys\r
+        self.portOffset = instrumentChoice.portOffset\r
+        if eventLog == None :\r
+            self.eventLog = instrumentChoice.eventLog\r
+        else :\r
+            self.eventLog = eventLog\r
+        self.cursorPositions = instrumentChoice.cursorPositions\r
+        self.song = song\r
+        self.songIterator = self.song.getSongIterator()\r
+        self.midiNoteNumbers = self.song.scale\r
+        if replay == None :\r
+            self.replay = instrumentChoice.replay\r
+        else :\r
+            self.replay = replay\r
+        self.quarterNoteLength = song.quarterNoteLength\r
+        self.cascadeLockLengthMultiplier = 1\r
+        self.nextCascadeLockLengthMultiplier = 1\r
+        self.cascadeLockLength = self.quarterNoteLength * self.cascadeLockLengthMultiplier\r
+        \r
+        self.defaultInstrumentChannel = defaultInstrumentChannel\r
+        self.defaultNote = defaultNote\r
+        \r
+        self.done = False\r
+        self.backToInstrumentChoice = False\r
+        self.easyMode = easyMode\r
+        \r
+        #Initializes the highlightedNote and highlightedNoteNumber etc...\r
+        self.moveToNextNote()\r
+        self.cascadeLockLengthMultiplier = self.nextCascadeLockLengthMultiplier\r
+        \r
+        self.blinkOn = False\r
+        self.savedBlinkOn = False\r
+        ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two\r
+        ##i.e. it guarantees that there will be an attack between two identical consecutive notes\r
+        self.highlightIsFree = True\r
+        \r
+        self.noteRects = []\r
+        self.boundingRect = None\r
+        self.notes = []\r
+        \r
+        self.buttonDown = []\r
+        self.velocityLock = []\r
+        \r
+        self._blinkOffset = 0\r
+        self._cascadeLockTimer = 0\r
+        self.cascadeIsFree = True\r
+        \r
+        self.font = pygame.font.Font(None,80)\r
+        self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers]\r
+        \r
+        self.drawBackground()\r
+        self.initializeWiimotes()\r
+        \r
+        self.songStartTime = self.eventLog.getCurrentTime()\r
+        \r
+        #The main loop\r
+        while not self.done :\r
+            \r
+            #Clear the cursors from the screen\r
+            if self.hasChanged():\r
+                self.drawBackground()\r
+            self.playerScreen.blit(self.savedScreen, (0, 0))\r
+            \r
+            # Limit frame speed to 50 FPS\r
+            #\r
+            timePassed = self.clock.tick(10000)\r
+            \r
+            self._blinkOffset += timePassed\r
+            if (self.buttonDown or self.alwaysDown) and not self.cascadeIsFree :\r
+                self._cascadeLockTimer += timePassed\r
+                if self._cascadeLockTimer > self.cascadeLockLengthMultiplier*self.quarterNoteLength :\r
+                    self.cascadeIsFree = True\r
+                    self.cascadeLockLengthMultiplier = self.nextCascadeLockLengthMultiplier\r
+            \r
+            \r
+            if self._blinkOffset > self.blinkLength:\r
+                self._blinkOffset -= self.blinkLength\r
+                self.blinkOn = not self.blinkOn\r
+                \r
+            if self.replay:\r
+                self.eventLog.update(timePassed)\r
+                pickledEventsToPost = self.eventLog.getPickledEvents() \r
+                for pickledEvent in pickledEventsToPost:\r
+                    pygame.event.post(pickledEvent.event)\r
+            \r
+            events = pygame.event.get()\r
+            \r
+            if not self.replay:\r
+                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events]\r
+                if pickledEvents != [] :\r
+                    self.eventLog.appendEventGroup(pickledEvents)\r
+            \r
+            for event in events:\r
+                self.input(event)\r
+                            \r
+            for i in range(len(self.wiimotes)):\r
+                if self.activeWiimotes[i]:\r
+                    self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])\r
+                    if self.buttonDown[i] or self.alwaysDown:\r
+                        self.wiimotes[i].cursor.flash()\r
+                    self.wiimotes[i].cursor.blit(self.playerScreen)\r
+            \r
+            self.screen.blit(self.playerScreen, (0,0))\r
+            \r
+            pygame.display.flip()\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            if self.activeWiimotes[i]:\r
+                self.wiimotes[i].stopNoteByNoteNumber(self.midiNoteNumbers[self.notes[i]])\r
+        if self.replay :        \r
+            self.totalDuration = self.eventLog.getCurrentTime()            \r
+        \r
+    def drawBackground(self):\r
+        self.savedScreen.fill((255,255,255))\r
+        \r
+        if self.extendedScale :\r
+            self.scaleSize = self.longScaleSize\r
+        else:\r
+            self.scaleSize = self.shortScaleSize\r
+        \r
+        self.noteRects = [pygame.Rect(i * self.width / self.scaleSize+self.blitOrigin[0], self.blitOrigin[1], self.width / self.scaleSize + 1, self.height+1) for i in range(self.scaleSize)]\r
+        #inflate last noteRect to cover the far right pixels\r
+        self.noteRects[-1].width = self.noteRects[-1].width + 1\r
+        \r
+        self.noteRects[self.highlightedNote-self.offset].inflate_ip(self.noteRects[self.highlightedNote-self.offset].width*2,0)\r
+        \r
+        #create bounding rect\r
+        self.boundingRect = self.noteRects[0].unionall(self.noteRects)\r
+        \r
+        self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers]\r
+        \r
+        #fill the rectangles with a color gradient\r
+        #We start with blue\r
+        startingHue = 0.66666666666666663\r
+        \r
+        for rectNumber in range(self.scaleSize):\r
+            colorRatio = float(rectNumber) / (self.scaleSize - 1)\r
+            #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up\r
+            hue = startingHue * (1 - colorRatio)\r
+            if rectNumber + self.offset != self.highlightedNote:\r
+                #The color of the bottom of the rectangle in hls coordinates\r
+                bottomColorHls = (hue, 0.1, 1)\r
+                #The color of the top of the rectangle in hls coordinates\r
+                topColorHls = (hue, 0.1, 1)\r
+            \r
+                #convert to rgb ranging from 0 to 255\r
+                bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]\r
+                topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]\r
+                #add transparency\r
+                bottomColorRgb.append(255)\r
+                topColorRgb.append(255)\r
+                #convert to tuple\r
+                bottomColorRgb = tuple(bottomColorRgb)\r
+                topColorRgb = tuple(topColorRgb)            \r
+                \r
+                self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber])\r
+                \r
+                noteNameBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber+self.offset].get_width())/2,\r
+                                 self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber+self.offset].get_height())\r
+                \r
+                self.savedScreen.blit(self.renderedNoteNames[rectNumber+self.offset], noteNameBlitPoint)\r
+                \r
+                pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2)\r
+        \r
+        colorRatio = float(self.highlightedNote-self.offset) / (self.scaleSize - 1)\r
+        #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up\r
+        hue = startingHue * (1 - colorRatio)\r
+        #The color of the bottom of the rectangle in hls coordinates\r
+        bottomColorHls = (hue, 0.6, 1)\r
+        #The color of the top of the rectangle in hls coordinates\r
+        topColorHls = (hue, 0.9, 1)\r
+        \r
+        #convert to rgb ranging from 0 to 255\r
+        bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]\r
+        topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]\r
+        #add transparency\r
+        bottomColorRgb.append(255)\r
+        topColorRgb.append(255)\r
+        #convert to tuple\r
+        bottomColorRgb = tuple(bottomColorRgb)\r
+        topColorRgb = tuple(topColorRgb)            \r
+        \r
+        self.savedScreen.blit(gradients.vertical(self.noteRects[self.highlightedNote-self.offset].size, topColorRgb, bottomColorRgb), self.noteRects[self.highlightedNote-self.offset])\r
+        \r
+        noteNameBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-self.renderedNoteNames[self.highlightedNote].get_width())/2,\r
+                         self.noteRects[self.highlightedNote-self.offset].bottom-self.renderedNoteNames[self.highlightedNote].get_height())\r
+        \r
+        self.savedScreen.blit(self.renderedNoteNames[self.highlightedNote], noteNameBlitPoint)\r
+        \r
+        if self.syllabus :\r
+            renderedSyllabus = self.font.render(self.syllabus,False,(0,0,0))\r
+        \r
+            syllabusBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-renderedSyllabus.get_width())/2,\r
+                             self.noteRects[self.highlightedNote-self.offset].centery-renderedSyllabus.get_height()/2)\r
+            \r
+            self.savedScreen.blit(renderedSyllabus, syllabusBlitPoint)\r
+        \r
+        pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[self.highlightedNote-self.offset], 2)    \r
+            \r
+        if self.song != None and self.blinkOn:\r
+            borderSize = self.borderSize\r
+            pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize)\r
+        \r
+    def initializeWiimotes(self):\r
+        for loop in self.wiimotes:\r
+            if loop.port == None :\r
+                loop.port = pygame.midi.Output(loop.portNumber)\r
+            self.notes.append(0)\r
+            self.buttonDown.append(False)\r
+            self.velocityLock.append(False)\r
+    \r
+    def updateCursorPositionFromJoy(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = constants.joyNames.index(joyName)\r
+        if correctedJoyId < len(self.cursorPositions):\r
+            if joyEvent.axis == 0 :\r
+                self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])\r
+            if joyEvent.axis == 1 :\r
+                self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))\r
+    \r
+    def heightToVelocity(self, pos, controllerNumber):\r
+        if self.song != None:\r
+            if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]):\r
+                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+            else :\r
+                if self.easyMode:\r
+                    velocity = None\r
+                else:\r
+                    velocity = 60\r
+        else:\r
+            if self.boundingRect.collidepoint(pos):\r
+                velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+            else :\r
+                velocity = self.minimalVelocity\r
+        return(velocity)\r
+    \r
+    def widthToNote(self, pos):\r
+        nn = 0\r
+        try :\r
+            if self.noteRects[self.highlightedNote-self.offset].collidepoint(pos) :\r
+                return self.highlightedNote\r
+            else :\r
+                while self.noteRects[nn].collidepoint(pos) == False:\r
+                    nn = nn + 1\r
+                return(nn + self.offset)\r
+        except(IndexError):\r
+            return(None)\r
+    \r
+    def logClick(self):\r
+        self.clicks[-1] += 1\r
+        if self.firstClick == None :\r
+            self.firstClick = self.eventLog.getCurrentTime()\r
+        minute = int(floor((self.eventLog.getCurrentTime()-self.songStartTime)/60000))\r
+        if minute > len(self.clicksPerMinute)-1:\r
+            self.clicksPerMinute.append(0)\r
+        self.clicksPerMinute[-1] += 1\r
+    \r
+    def logClickIn(self):\r
+        self.clicksIn[-1] += 1\r
+        if self.clicksIn[-1] > len(self.song.notes)-1 :\r
+            self.clicksIn.append(0)\r
+            self.clicks.append(0)\r
+            self.songDurations.append(self.eventLog.getCurrentTime())\r
+        if self.firstClickIn == None :\r
+            self.firstClickIn = self.eventLog.getCurrentTime()\r
+        minute = int(floor((self.eventLog.getCurrentTime()-self.songStartTime)/60000))\r
+        if minute > len(self.clicksInPerMinute)-1:\r
+            self.clicksInPerMinute.append(0)\r
+        self.clicksInPerMinute[-1]+=1\r
+    \r
+    def input(self, event): \r
+        \r
+        if event.type == pygame.QUIT:\r
+            for loop in self.wiimotes:\r
+                del loop.port\r
+            pygame.midi.quit()\r
+            sys.exit(0) \r
+        \r
+        if event.type == pygame.KEYDOWN:\r
+            if event.key == pygame.K_q:\r
+                self.nextLevel = None\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_i:\r
+                self.backToInstrumentChoice = True\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_w:\r
+                self.nextLevel = 0\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_e:\r
+                self.nextLevel = 1\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_r:\r
+                self.nextLevel = 2\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_t:\r
+                self.nextLevel = 3\r
+                self.done = True \r
+        \r
+        if event.type == pygame.JOYAXISMOTION:\r
+            \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.updateCursorPositionFromJoy(event)\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+    \r
+                if (self.buttonDown[correctedJoyId] or self.alwaysDown):\r
+                    if self.notes[correctedJoyId] != None:\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        if velocity != None :\r
+                            CCHexCode = wiimote.getCCHexCode()\r
+                            wiimote.port.write_short(CCHexCode, 07, velocity)\r
+                    if self.cascade and self.cascadeIsFree :\r
+                        n = self.widthToNote(pos)\r
+                        if self.highlightedNote == n:\r
+                            wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                            self.notes[correctedJoyId] = n\r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                            self.velocityLock[correctedJoyId] = True\r
+                            wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                            self.moveToNextNote()\r
+                            self._cascadeLockTimer = 0\r
+                            self.cascadeIsFree = False\r
+        \r
+        if event.type == pygame.JOYBUTTONDOWN :\r
+            \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+                self.wiimotes[correctedJoyId].cursor.flash()\r
+                if self.replay:\r
+                    self.logClick()\r
+                    \r
+                if not (self.buttonDown[correctedJoyId] or self.alwaysDown):\r
+                    n = self.widthToNote(pos)\r
+                    if self.highlightedNote == n:\r
+                        self._cascadeLockTimer = 0\r
+                        self.cascadeIsFree = False\r
+                        if self.easyMode:\r
+                            wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                        self.notes[correctedJoyId] = n\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        self.velocityLock[correctedJoyId] = True\r
+                        wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                        if self.replay :\r
+                            self.logClickIn()\r
+                        self.moveToNextNote()\r
+                    else :\r
+                        if not self.easyMode :\r
+                            self._cascadeLockTimer = 0\r
+                            self.cascadeIsFree = False\r
+                            self.notes[correctedJoyId] = n\r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)                    \r
+                            if velocity != None and self.notes[correctedJoyId] != None :\r
+                                wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)                                      \r
+                    self.buttonDown[correctedJoyId] = True\r
+            \r
+        if event.type == pygame.JOYBUTTONUP:\r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.buttonDown[correctedJoyId] = False\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                if not self.easyMode:\r
+                    wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                self.velocityLock[correctedJoyId] = False\r
+            \r
+        if event.type == pygame.MOUSEMOTION:\r
+            \r
+            self.updateCursorPositionFromMouse(event)\r
+            \r
+            correctedJoyId = 0\r
+            while not self.activeWiimotes[correctedJoyId] :\r
+                correctedJoyId += 1\r
+            wiimote = self.wiimotes[correctedJoyId]\r
+            pos = self.cursorPositions[correctedJoyId]\r
+\r
+            if (self.buttonDown[correctedJoyId] or self.alwaysDown):\r
+                self.wiimotes[correctedJoyId].cursor.flash()\r
+                if self.notes[correctedJoyId] != None:\r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    if velocity != None :\r
+                        CCHexCode = wiimote.getCCHexCode()\r
+                        wiimote.port.write_short(CCHexCode, 07, velocity)\r
+                if self.cascade and self.cascadeIsFree :\r
+                    n = self.widthToNote(pos)\r
+                    if self.highlightedNote == n:\r
+                        wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                        self.notes[correctedJoyId] = n\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        self.velocityLock[correctedJoyId] = True\r
+                        wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                        self.moveToNextNote()\r
+                        self._cascadeLockTimer = 0\r
+                        self.cascadeIsFree = False             \r
+        \r
+        if event.type == pygame.MOUSEBUTTONDOWN:\r
+            \r
+            if event.button == 1:\r
+                correctedJoyId = 0\r
+                while not self.activeWiimotes[correctedJoyId] :\r
+                    correctedJoyId += 1\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+                self.wiimotes[correctedJoyId].cursor.flash()\r
+                if self.replay:\r
+                    self.logClick()\r
+                    \r
+                if not (self.buttonDown[correctedJoyId] or self.alwaysDown):\r
+                    n = self.widthToNote(pos)\r
+                    if self.highlightedNote == n:\r
+                        self._cascadeLockTimer = 0\r
+                        self.cascadeIsFree = False\r
+                        if self.easyMode:\r
+                            wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                        self.notes[correctedJoyId] = n\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        self.velocityLock[correctedJoyId] = True\r
+                        wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)\r
+                        if self.replay :\r
+                            self.logClickIn()\r
+                        self.moveToNextNote()\r
+                    else :\r
+                        if not self.easyMode :\r
+                            self._cascadeLockTimer = 0\r
+                            self.cascadeIsFree = False\r
+                            self.notes[correctedJoyId] = n\r
+                            velocity = self.heightToVelocity(pos, correctedJoyId)                    \r
+                            if velocity != None and self.notes[correctedJoyId] != None :\r
+                                wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity)                                      \r
+                    self.buttonDown[correctedJoyId] = True                    \r
+            \r
+            if event.button == 2:\r
+                \r
+                self.done = True\r
+        \r
+        if event.type == pygame.MOUSEBUTTONUP:\r
+            if event.button == 1 :\r
+                correctedJoyId = 0\r
+                while not self.activeWiimotes[correctedJoyId] :\r
+                    correctedJoyId += 1\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                self.buttonDown[correctedJoyId] = False\r
+                if not self.easyMode:\r
+                    if self.notes[correctedJoyId] != None :\r
+                        wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]])\r
+                self.velocityLock[correctedJoyId] = False\r
+        \r
+    def hasChanged(self):\r
+        changed = False\r
+        if self.song != None:\r
+            if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote:\r
+                self.savedBlinkOn = self.blinkOn\r
+                self.savedHighlightedNote = self.highlightedNote\r
+                changed = True\r
+        return(changed)\r
+    \r
+    def updateCursorPositionFromMouse(self, mouseEvent):\r
+        correctedJoyId = 0\r
+        while not self.activeWiimotes[correctedJoyId] :\r
+            correctedJoyId += 1\r
+        self.cursorPositions[correctedJoyId] = mouseEvent.pos\r
+        \r
+    def moveToNextNote(self):\r
+        self.savedMidiNoteNumbers = self.midiNoteNumbers[:]\r
+        self.highlightedNote, self.highlightedNoteNumber, self.syllabus, self.nextCascadeLockLengthMultiplier = self.songIterator.next()\r
+        self.midiNoteNumbers[self.highlightedNote] = self.highlightedNoteNumber\r
diff --git a/src/gui/StaticFamiliarizer.py b/src/gui/StaticFamiliarizer.py
new file mode 100644 (file)
index 0000000..0e095c8
--- /dev/null
@@ -0,0 +1,457 @@
+'''\r
+Created on 23 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+from math import floor, ceil\r
+import pygame\r
+import pygame.midi\r
+import sys\r
+import colorsys\r
+import constants\r
+from gradients import gradients\r
+from logging.PickleableEvent import PickleableEvent\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from logging.EventLog import EventLog\r
+\r
+\r
+class StaticFamiliarizer:\r
+    '''\r
+    The screen on which the game is played\r
+    \r
+        wiimotes: \r
+                The wiimotes used in this session\r
+        window:\r
+            The main display window\r
+        screen:\r
+            The main display surface\r
+        clock:\r
+            The clock used to animate the screen\r
+        savedScreen:\r
+            The background that is painted every time\r
+        playerScreen:\r
+            The buffer for painting everything before bliting\r
+        width:\r
+            The width of the window in pixels\r
+        height:\r
+            The height of the window in pixels\r
+        extendScale :\r
+            True if the scale is G to C instead of C to C\r
+        cascade:\r
+            True if crossing from note to note with a button pressed triggers a new note\r
+        scaleSize:\r
+            The size of the scale used\r
+        cursorPositions:\r
+            The positions of the cursors on the screen, in pixels\r
+    '''\r
+    \r
+    \r
+    \r
+    def __init__(self, wiimotes, window, screen, clock, joys, portOffset,activeWiimotes,replay = False, level = 0, defaultInstrumentChannel = 16, defaultNote = 60, eventLog = None):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.firstClickTime = None\r
+        self.firstClickInTime = None\r
+        self.duration = None\r
+        self.clicks = 0\r
+        self.clicksIn = 0\r
+        \r
+        pygame.font.init()\r
+        self.font = pygame.font.Font(None,60)\r
+        self.congratulations = ["Bien !","Tres Bien !","Bravo !","Excellent !","Felicitations !"]\r
+        self.renderedCongratulations = [self.font.render(congratulation,False,(0,0,0)) for congratulation in self.congratulations]\r
+        self.congratulationCount = None\r
+        self.isCongratulating = False\r
+        self.congratulationTimer = 0\r
+        self.congratulationLength = 2000\r
+        self.congratulationPos = None\r
+                \r
+        self.blinkLength = 200\r
+        self.minimalVelocity = 64\r
+        self.shortScaleSize = 8\r
+        self.longScaleSize = 11\r
+        self.borderSize = 5\r
+        self.savedHighlightedNote = 0\r
+        self.scaleFactor = 1\r
+        self.wiimotes = wiimotes\r
+        self.window = window\r
+        self.screen = screen\r
+        self.clock = clock\r
+        self.width = int(floor(screen.get_width()*self.scaleFactor))\r
+        self.height = int(floor(screen.get_height()*self.scaleFactor))\r
+        self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2)        \r
+        self.joys = joys\r
+        self.portOffset = portOffset\r
+        self.savedScreen = pygame.Surface(self.screen.get_size())\r
+        self.savedScreen.fill((255,255,255))\r
+        self.playerScreen = pygame.Surface(self.savedScreen.get_size())\r
+        self.playerScreen.blit(self.savedScreen, (0, 0))\r
+        self.cursorPositions = []\r
+        self.level = level\r
+        self.nextLevel = None\r
+        self.activeWiimotes = activeWiimotes\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            #Set the screen for the cursors (it can't be set before)\r
+            self.wiimotes[i].cursor.screen = self.playerScreen\r
+            self.cursorPositions.append(self.wiimotes[i].cursor.centerPosition)\r
+        \r
+        if eventLog == None:\r
+            self.eventLog = EventLog()\r
+            self.replay = False\r
+        else:\r
+            self.eventLog = eventLog\r
+            self.replay = replay\r
+        \r
+        self.defaultInstrumentChannel = defaultInstrumentChannel\r
+        self.defaultNote = defaultNote\r
+        \r
+        self.done = False\r
+        self.backToInstrumentChoice = False\r
+        self.easyMode = False\r
+\r
+        self.noteRects = []\r
+        self.boundingRect = None\r
+        self.notes = []\r
+        self.buttonDown = []\r
+        self.velocityLock = []\r
+        \r
+        self.drawBackground()\r
+        self.initializeWiimotes()\r
+        events = pygame.event.get()\r
+        \r
+        #The main loop\r
+        while not self.done :\r
+            \r
+            self.playerScreen.blit(self.savedScreen, (0, 0))\r
+            \r
+            # Limit frame speed to 50 FPS\r
+            #\r
+            timePassed = self.clock.tick(10000)\r
+                \r
+            if self.replay:\r
+                self.eventLog.update(timePassed)\r
+                pickledEventsToPost = self.eventLog.getPickledEvents() \r
+                for pickledEvent in pickledEventsToPost:\r
+                    pygame.event.post(pickledEvent.event)\r
+            \r
+            events = pygame.event.get()\r
+            \r
+            if not self.replay:\r
+                pickledEvents = [PickleableEvent(event.type,event.dict) for event in events]\r
+                if pickledEvents != [] :\r
+                    self.eventLog.appendEventGroup(pickledEvents)\r
+            \r
+            for event in events:\r
+                self.input(event)\r
+            \r
+            if self.isCongratulating :\r
+                self.congratulationTimer += timePassed\r
+                if self.congratulationTimer < self.congratulationLength :\r
+                    self.blitCongratulation()\r
+                else :\r
+                    self.isCongratulating = False\r
+                            \r
+            for i in range(len(self.wiimotes)):\r
+                if self.activeWiimotes[i]:\r
+                    self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i])\r
+                    if self.buttonDown[i] :\r
+                        self.wiimotes[i].cursor.flash()\r
+                    self.wiimotes[i].cursor.blit(self.playerScreen)\r
+            \r
+            self.screen.blit(self.playerScreen, (0,0))\r
+            \r
+            pygame.display.flip()\r
+        \r
+        for i in range(len(self.wiimotes)):\r
+            if self.activeWiimotes[i]:\r
+                if self.notes[i] != None :\r
+                    self.wiimotes[i].stopNote(self.notes[i])\r
+        if self.replay :\r
+            self.duration = self.eventLog.getCurrentTime()            \r
+        \r
+    def drawBackground(self):\r
+        self.savedScreen.fill((255,255,255))\r
+        if self.level == 0 :\r
+            A = [4]    \r
+        else :\r
+            A = [1,7]\r
+        \r
+        self.noteRects = [pygame.Rect(i * self.width / 11+self.blitOrigin[0], self.blitOrigin[1], (self.width / 11 + 1)*3, self.height+1) for i in A]\r
+        \r
+        #create bounding rect\r
+        self.boundingRect = self.noteRects[0].unionall(self.noteRects)\r
+        \r
+        #fill the rectangles with a color gradient\r
+        #We start with blue\r
+        startingHue = 0.66666666666666663\r
+        \r
+        for rectNumber in range(len(self.noteRects)) :\r
+            colorRatio = float(A[rectNumber]) / (11 - 1)\r
+            #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up\r
+            hue = startingHue * (1 - colorRatio)\r
+            #The color of the bottom of the rectangle in hls coordinates\r
+            bottomColorHls = (hue, 0.6, 1)\r
+            #The color of the top of the rectangle in hls coordinates\r
+            topColorHls = (hue, 0.9, 1)\r
+            \r
+            #convert to rgb ranging from 0 to 255\r
+            bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)]\r
+            topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)]\r
+            #add transparency\r
+            bottomColorRgb.append(255)\r
+            topColorRgb.append(255)\r
+            #convert to tuple\r
+            bottomColorRgb = tuple(bottomColorRgb)\r
+            topColorRgb = tuple(topColorRgb)            \r
+            \r
+            self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber])\r
+            \r
+            pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2)\r
+            \r
+    def initializeWiimotes(self):\r
+        for loop in self.wiimotes:\r
+            if loop.port == None :\r
+                loop.port = pygame.midi.Output(loop.portNumber)\r
+            self.notes.append(0)\r
+            self.buttonDown.append(False)\r
+            self.velocityLock.append(False)\r
+    \r
+    def updateCursorPositionFromJoy(self, joyEvent):\r
+        joyName = pygame.joystick.Joystick(joyEvent.joy).get_name()\r
+        correctedJoyId = constants.joyNames.index(joyName)\r
+        if correctedJoyId < len(self.cursorPositions):\r
+            if joyEvent.axis == 0 :\r
+                self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1])\r
+            if joyEvent.axis == 1 :\r
+                self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height()))\r
+    \r
+    def heightToVelocity(self, pos, controllerNumber):\r
+        velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity)\r
+        return(velocity)\r
+    \r
+    def widthToNote(self, pos):\r
+        nn = 0\r
+        try :\r
+            while self.noteRects[nn].collidepoint(pos) == False:\r
+                nn = nn + 1\r
+            return(nn)\r
+        except(IndexError):\r
+            return(None)\r
+        \r
+    def congratulate(self,targetRect,posy):\r
+        if self.congratulationCount != None :\r
+            if self.congratulationCount < len(self.congratulations)-1:\r
+                self.congratulationCount += 1\r
+        else :\r
+            self.congratulationCount = 0\r
+        self.congratulationTimer = 0\r
+        self.congratulationPos = (targetRect.left+(targetRect.width-self.renderedCongratulations[self.congratulationCount].get_width())/2,posy)\r
+        self.isCongratulating = True\r
+        \r
+    def resetCongratulation(self):\r
+        self.congratulationCount = None\r
+        self.congratulationPos = None\r
+        self.isCongratulating = False\r
+            \r
+    def blitCongratulation(self):\r
+        self.playerScreen.blit(self.renderedCongratulations[self.congratulationCount],self.congratulationPos)        \r
+    \r
+    def input(self, event): \r
+        \r
+        print event\r
+        \r
+        if event.type == pygame.QUIT:\r
+            for loop in self.wiimotes:\r
+                del loop.port\r
+            pygame.midi.quit()\r
+            sys.exit(0) \r
+        \r
+        if event.type == pygame.KEYDOWN:\r
+            if event.key == pygame.K_q:\r
+                self.nextLevel = None\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_w:\r
+                self.nextLevel = 0\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_e:\r
+                self.nextLevel = 1\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_r:\r
+                self.nextLevel = 2\r
+                self.done = True\r
+                \r
+            if event.key == pygame.K_t:\r
+                self.nextLevel = 3\r
+                self.done = True \r
+        \r
+        if event.type == pygame.JOYAXISMOTION:\r
+            \r
+           \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                self.updateCursorPositionFromJoy(event)\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+    \r
+                if self.buttonDown[correctedJoyId]:\r
+                    wiimote.cursor.flash()\r
+                    if self.notes[correctedJoyId] != None:\r
+                        velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                        CCHexCode = wiimote.getCCHexCode()\r
+                        wiimote.port.write_short(CCHexCode, 07, velocity)\r
+        \r
+        if event.type == pygame.JOYBUTTONDOWN :\r
+            \r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+                wiimote.cursor.flash()\r
+                if self.replay :\r
+                    self.clicks += 1\r
+                    if self.firstClickTime == None :\r
+                        self.firstClickTime = self.eventLog.getCurrentTime()\r
+    \r
+                if not self.buttonDown[correctedJoyId]:\r
+                    self.notes[correctedJoyId] = self.widthToNote(pos)\r
+                    \r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    \r
+                    if self.notes[correctedJoyId] != None :            \r
+                        wiimote.playNote(self.notes[correctedJoyId],velocity)\r
+                        self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1])\r
+                        if self.replay :\r
+                            self.clicksIn += 1\r
+                            if self.firstClickInTime == None :\r
+                                self.firstClickInTime = self.eventLog.getCurrentTime()\r
+                    else :\r
+                        self.resetCongratulation()\r
+                        \r
+                    self.buttonDown[correctedJoyId] = True\r
+        \r
+        if event.type == pygame.JOYBUTTONUP:\r
+            joyName = pygame.joystick.Joystick(event.joy).get_name()\r
+            correctedJoyId = constants.joyNames.index(joyName)\r
+            if self.activeWiimotes[correctedJoyId]:\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                wiimote.stopNote(self.notes[correctedJoyId])\r
+                self.buttonDown[correctedJoyId] = False\r
+                self.velocityLock[correctedJoyId] = False\r
+            \r
+        if event.type == pygame.MOUSEMOTION:\r
+            \r
+            self.updateCursorPositionFromMouse(event)\r
+            \r
+            correctedJoyId = 0\r
+            while not self.activeWiimotes[correctedJoyId] :\r
+                correctedJoyId += 1\r
+            wiimote = self.wiimotes[correctedJoyId]\r
+            pos = self.cursorPositions[correctedJoyId]\r
+\r
+            if self.buttonDown[correctedJoyId]:\r
+                wiimote.cursor.flash()\r
+                if self.notes[correctedJoyId] != None:\r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    CCHexCode = wiimote.getCCHexCode()\r
+                    wiimote.port.write_short(CCHexCode, 07, velocity)                            \r
+        \r
+        if event.type == pygame.MOUSEBUTTONDOWN:\r
+            \r
+            if event.button == 1:\r
+                correctedJoyId = 0\r
+                while not self.activeWiimotes[correctedJoyId] :\r
+                    correctedJoyId += 1\r
+                wiimote = self.wiimotes[correctedJoyId]\r
+                pos = self.cursorPositions[correctedJoyId]\r
+                wiimote.cursor.flash()\r
+                if self.replay :\r
+                    self.clicks += 1\r
+                    if self.firstClickTime == None :\r
+                        self.firstClickTime = self.eventLog.getCurrentTime()\r
+    \r
+                if not self.buttonDown[correctedJoyId]:\r
+                    self.notes[correctedJoyId] = self.widthToNote(pos)\r
+                    \r
+                    velocity = self.heightToVelocity(pos, correctedJoyId)\r
+                    \r
+                    if self.notes[correctedJoyId] != None :            \r
+                        wiimote.playNote(self.notes[correctedJoyId],velocity)\r
+                        self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1])\r
+                        if self.replay :\r
+                            self.clicksIn += 1\r
+                            if self.firstClickInTime == None :\r
+                                self.firstClickInTime = self.eventLog.getCurrentTime()\r
+                    else :\r
+                        self.resetCongratulation()\r
+                        \r
+                    self.buttonDown[correctedJoyId] = True\r
+            \r
+            if event.button == 2:\r
+                \r
+                self.done = True\r
+        \r
+        if event.type == pygame.MOUSEBUTTONUP:\r
+            \r
+            correctedJoyId = 0\r
+            while not self.activeWiimotes[correctedJoyId] :\r
+                correctedJoyId += 1\r
+            wiimote = self.wiimotes[correctedJoyId]\r
+            wiimote.stopNote(self.notes[correctedJoyId])\r
+            self.buttonDown[correctedJoyId] = False\r
+            self.velocityLock[correctedJoyId] = False\r
+        \r
+    def hasChanged(self):\r
+        return(True)\r
+    \r
+    def updateCursorPositionFromMouse(self, mouseEvent):\r
+        correctedJoyId = 0\r
+        while not self.activeWiimotes[correctedJoyId] :\r
+            correctedJoyId += 1\r
+        self.cursorPositions[correctedJoyId] = mouseEvent.pos\r
+                    \r
+if __name__ == "__main__" :\r
+    pygame.init()\r
+    modeResolution = (1024,768)\r
+    window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+    pygame.font.init()\r
+        \r
+    pygame.midi.init()\r
+    instruments = [Instrument(constants.scaleDict["majorScale"], i + 1, "".join(["../instruments/instrumentImages/", constants.instrumentImagePathList[i], ".jpg"]), constants.octaves[i]) for i in range(9)]\r
+    \r
+    joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())]\r
+    for joy in joys:\r
+        if joy[1] in constants.joyNames:\r
+            pygame.joystick.Joystick(joy[0]).init() \r
+    \r
+    ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+    portOffset = ports.index(constants.portNames[0])\r
+    print(portOffset)\r
+    \r
+    events = pygame.event.get()\r
+    \r
+    screen = pygame.display.get_surface()\r
+    clock = pygame.time.Clock()        \r
+    cursorImages = [createImageListFromPath('../cursor/cursorImages/black', 11),createImageListFromPath('../cursor/cursorImages/red', 11)]\r
+    durations = [75 for i in range(len(cursorImages[0]))]\r
+    \r
+    wiimoteCount = 1\r
+    cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),flashImage = '../cursor/cursorImages/black/flash.png' ) for i in range(wiimoteCount)]\r
+    wiimotes = [Wiimote(i, i + portOffset, None, instruments[i], cursors[i]) for i in range(wiimoteCount)]\r
+    \r
+    fam = StaticFamiliarizer(instruments, wiimotes, window, screen, clock, joys, portOffset)\r
+    \r
+    for loop in fam.wiimotes:\r
+                del loop.port\r
+    \r
+    pygame.midi.quit()\r
+    \r
+    pygame.quit()  \r
diff --git a/src/gui/__init__.py b/src/gui/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/gui/constants.py b/src/gui/constants.py
new file mode 100755 (executable)
index 0000000..df13903
--- /dev/null
@@ -0,0 +1,130 @@
+from songs.Song import Song,loadSongFromMidi\r
+from dataTools.odict import OrderedDict\r
+\r
+joyNames = ["PPJoy Virtual joystick 1", "PPJoy Virtual joystick 2", "PPJoy Virtual joystick 3", "PPJoy Virtual joystick 4"]\r
+portNames = ["Out To MIDI Yoke:  1","Out To MIDI Yoke:  2","Out To MIDI Yoke:  3","Out To MIDI Yoke:  4"]\r
+\r
+readabilityDict = OrderedDict([("majeure" , "majorScale"),\r
+                               ("mineure naturelle" , "minorScale"),\r
+                               ("majeure myxolydienne" , "myxolydianScale"),\r
+                               ("mineure dorienne" , "dorianScale"),\r
+                               ("phrygienne espagnole" , "spanishPhrygianScale"),\r
+                               ("lydienne" , "lydianScale"),\r
+                               ("phrygienne" , "phrygianScale"),\r
+                               ("J'ai du bon tabac" , "jadbt"),\r
+                               ("L'eau vive" , "eauvive"),\r
+                               ("Le penitencier" , "penitencier"),\r
+                               ("La foule" , "foule"),\r
+                               ("Petit papa noel" , "papanoel"),\r
+                               ("La marseillaise" , "marseillaise"),\r
+                               ("A la claire fontaine" , "clairefontaine"),\r
+                               ("Au clair de la lune" , "clairdelalune"),\r
+                               ("Frere jacques" , "frerejacques"),\r
+                               ("Le petit vin blanc" , "vinblanc"),\r
+                               ("La vie en rose","vierose"),\r
+                               ("Les feuilles mortes","feuillesmortes"),\r
+                               ("Il pleut bergere","bergere"),\r
+                               ("Le temps des cerises","cerises"),\r
+                               ("La boheme","boheme"),\r
+                               ("Chanson Test","test"),\r
+                               ("Improvisation" , "none"),\r
+                               ("Do/Do","C/C"),\r
+                               ("Sol/Do","G/C"),\r
+                               ("Oui","Yes"),\r
+                               ("Non","No"),\r
+                               ("Tres facile","veryEasy"),\r
+                               ("Facile","easy"),\r
+                               ("Normal","normal"),\r
+                               ("Expert","expert")])\r
+\r
+reversedReadabilityDict = dict(zip(readabilityDict.values(),readabilityDict.keys()))\r
+\r
+rangeDict = {"C/C":False,"G/C":True}\r
+\r
+cascadeDict = {"Yes":True,"No":False}\r
+\r
+modeDict = OrderedDict([("veryEasy",0),("easy",1),("normal",2),("expert",3)])\r
+print modeDict['veryEasy']\r
+\r
+scaleDict = OrderedDict ([("majorScale" , [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]),\r
+                         ("minorScale" , [55, 56, 58, 60, 62, 63, 65, 67, 68, 70, 72]),\r
+                         ("myxolydianScale" , [55, 57, 58, 60, 62, 64, 65, 67, 69, 70, 72]),\r
+                         ("dorianScale" , [55, 57, 58, 60, 62, 63, 65, 67, 69, 70, 72]),\r
+                         ("spanishPhrygianScale" , [55,57,58,60,62,63,66,67,69,70,72]),\r
+                         ("lydianScale" , [55, 57, 59, 60, 62, 64, 66, 67, 69, 71, 72]),\r
+                         ("phrygianScale" , [55, 56, 58, 60, 61, 63, 65, 67, 68, 70, 72])])\r
+\r
+songDict = OrderedDict([("jadbt" , Song(scaleDict["majorScale"],[3, 4, 5, 3, 4, 4, 5, 6, 6, 5, 5, 3, 4, 5, 3, 4, 4, 5, 6, 7, 3,7,7,6,5,4,5,6,7,4,7,7,6,5,4,5,6,7,4],False,\r
+                                        lyrics = ["J'ai","du","bon","ta-","-bac","dans","ma","ta-","-ba-","-tie-","-re","J'ai","du","bon","ta-","-bac","tu","n'en","au-","-ras","pas","j'en","ai","du","fin","et","du","bien","ra-","-pe","mais","ce","n'est","pas","pour","ton","vi-","-lain","nez"],\r
+                                        noteLengths = [1,1,1,1,2,1,1,2,2,2,2,1,1,1,1,2,1,1,2,2,4,2,1,1,2,1,1,2,2,4,2,1,1,2,1,1,2,2,4],\r
+                                        quarterNoteLength = 400)),\r
+            ("eauvive" , Song(scaleDict["majorScale"],[5,3,5,3,5,3,4,4,5,6,4,5,3,4,5,3,5,3,5,3,4,4,5,6,4,5,3,4,7,8,7,6,6,4,6,5,3,5,4,3,4,5,6,5,4,3,4,3,2,3],\r
+                              True,\r
+                              lyrics = ["Ma","pe-","-tite","est","co-","-mme","l'eau","elle","est","co-","-mme","l'eau","vi-","-ve","e-","-lle","court","comme","un","rui-","-sseau","que","les","en-","-fants","pour-","-sui-","-vent","ve-","-nez","ve-","-nez","mes","a-","-gneaux","mes","a-","-gne-","-lets","ja-","-mais","ja-","-mais","vous","ne","la","ra-","-ttra-","-pe-","-rez"],\r
+                              noteLengths = [2,1,2,1,2,1,3,1,1,1,2,1,3,3,2,1,2,1,2,1,3,1,1,1,2,1,3,3,3,3,3,3,1,1,1,1,1,1,6,3,3,3,3,1,1,1,1,1,1,6],\r
+                              quarterNoteLength = 300)),\r
+            ("penitencier" , \r
+             Song(scaleDict["dorianScale"],[3,3,3,5,7,6,3,3,10,10,10,9,7,6,7,10,10,10,3,5,6,7,6,3,5,3,3,3,3,2,2,3],\r
+                  True,\r
+                  alterationIndexes = [-2,-3],\r
+                  alterations = [1,1],\r
+                  lyrics = ["Les","por-","-tes","du","pe-","-ni-","-ten-","-cier","bien-","-tot","vont","se","re-","-fer-","-mer","et","c'est","la","que","je","fi-","-ni-","-rai","ma","vie","comme","d'au-","-tres","gars","l'ont","fi-","-nie"],\r
+                  noteLengths =[1,5,1,5,1,1,6,4,1,5,1,4,1,1,10,1,1,5,1,4,1,1,5,1,5,1,1,1,4,5,1,12],\r
+                  quarterNoteLength = 250)),\r
+            ("papanoel" , Song(scaleDict["myxolydianScale"],[3,6,6,6,7,6,6,7,8,8,8,9,8,7,6,6,6,6,5,4,3,3,3,6,6,6,7,7,6],False,\r
+                               lyrics = ["pe-","-ti","Pa-","-pa","No-","-el","quand","tu","de-","-scen-","-dras","du","ciel","a-","-vec","tes","jou-","-ets","par","mi-","-lliers","n'ou-","-blie","pas","mes","pe-","-tits","sou-","-liers"],\r
+                               noteLengths = [1,1,1,1,1,3,0.5,0.5,1,1,1,1,3,1,1.5,0.5,0.5,0.5,0.5,0.5,3,0.5,0.5,1,0.5,0.5,0.5,0.5,3],\r
+                               quarterNoteLength = 500)),\r
+            ("foule" , Song(scaleDict["myxolydianScale"],[7,7,6,5,6,8,7,7,6,8,7,7,6,9,7,7,6,9,7,7,6,8,7,7,6,8,7,6,5,4,4,4,4,4,4,6,6,6,6,8,8,7,6,8,7,7,7,7,7,7,7,7,7,7,7,7,6,9,8,7,8,8,7,8,8,7,8,8,7,8,8,6,8,8,6,8,8,7],False,modulationIndexes = [28],modulationScales = [scaleDict["spanishPhrygianScale"]],\r
+                            lyrics = ["Em-","-por-","-tes","par","la","fou-","-le","qui","nous","trai-","-ne","nous","en-","-trai-","-ne","e-","-cra-","-ses","l'un","con-","-tre","l'au-","-tre","nous","ne","for-","-mons","qu'un","seul","corps","et","le","flot","sans","e-","-ffort","nous","pousse","en-","-chai-","-nes","l'un","et","l'au-","-tre","et","nous","lai-","-sse","tous","deux","e-","-pa-","-nouis-","en-","-i-","-vres","et","heu-","-reux","ta-","-dam","ta","ta-","-dam","ta","ta-","-dam","ta","ta-","-dam","ta","ta-","-dam","ta","ta-","-dam","ta"],\r
+                            noteLengths = [1,1.5,.5,.5,.5,1.5,.5,.5,.5,1.5,.5,.5,.5,1.5,.5,.5,.5,1.5,.5,.5,.5,1.5,.5,.5,.5,1.5,.5,.5,.5,3,.5,.5,.5,.5,.5,.5,.5,.5,.5,1,1,.5,.5,1.5,3,.5,.5,.5,.5,.5,.5,.5,.5,.5,.5,.5,.5,1,1,1.5,.5,1,1.5,.5,1,1.5,.5,1,1.5,.5,1,1.5,.5,1,1.5,.5,1,3],\r
+                            quarterNoteLength = 400)),\r
+            ("clairefontaine" , Song(scaleDict["majorScale"],\r
+                                     [3,3,5,5,4,5,4,3,3,5,5,4,5,5,5,4,3,5,7,5,7,7,5,3,5,4,3,3,5,5,5,4,5,5,5,5,3,5,4,3],\r
+                                     False,\r
+                                     lyrics = ["A","la","clai-","-re","fon-","-tai-","-ne","m'en","a-","-llant","pro-","-me-","-ner","j'ai","trou-","-ve","l'eau","si","be-","-lle","que","je","m'y","suis","bai-","-gne","il","y-a","long-","-temps","que","je","t'aime","ja-","-mais","je","ne","t'ou-","-blie-","-rai"],\r
+                                     noteLengths = [2,1,1,1,1,1,1,2,1,1,1,1,2,2,1,1,1,1,1,1,2,1,1,1,1,2,2,1,1,1,0.5,0.5,2,2,1,0.5,0.5,1,1,4],\r
+                                     quarterNoteLength = 400)),\r
+            ("clairdelalune", Song(scaleDict["lydianScale"], [7,7,7,8,9,8,7,9,8,8,7,7,7,7,8,9,8,7,9,8,8,7,8,8,8,8,5,5,8,7,6,5,4,7,7,7,8,9,8,7,9,8,8,7],False,\r
+                                   lyrics = ["Au","clair","de","la","lu-","-ne","mon","a-","-mi","Pie-","-rrot","pre-","-te","moi","ta","plu-","-me","pour","e-","-crire","un","mot","ma","chan-","-delle","est","mor-","-te","je","n'ai","plus","de","feu","ou-","-vre","moi","ta","po-","-rte","pour","l'a-","-mour","de","Dieu"],\r
+                                   noteLengths = [1,1,1,1,2,2,1,1,1,1,4,1,1,1,1,2,2,1,1,1,1,4,1,1,1,1,2,2,1,1,1,1,4,1,1,1,1,2,2,1,1,1,1,4],\r
+                                   quarterNoteLength = 500)),\r
+            ("frerejacques" , \r
+             Song(scaleDict["majorScale"],\r
+                  [3,4,5,3,3,4,5,3,5,6,7,5,6,7,7,8,7,6,5,3,7,8,7,6,5,3,3,0,3,3,0,3],\r
+                  True,\r
+                  lyrics = ["Fre-","-re","Ja-","-cques","fre-","-re","-Ja","-cques","dor-","-mez","vous","dor-","-mez","vous","so-","-nnez","les","ma-","-ti-","-nes","so-","-nnez","les","ma-","-ti-","-nes","ding","ding","dong","ding","ding","dong"],\r
+                  noteLengths = [1,1,1,1,1,1,1,1,1,1,2,1,1,2,1.5,0.5,1,1,2,2,1.5,0.5,1,1,2,2,1,1,2,1,1,2],\r
+                  quarterNoteLength = 600)),\r
+            ("marseillaise" , Song(scaleDict["majorScale"],[0, 0, 0, 3, 3, 4, 4, 7, 5, 3, 3, 5, 3, 1, 6, 4, 2, 3, 3, 4, 5, 5, 5, 6, 5, 5, 4, 4, 5, 6, 6, 6, 7, 6, 5, 7, 7, 7, 5, 3, 7, 5, 3, 0, 0, 0, 2, 4, 6, 4, 2, 4, 3, 2, 1, 3, 3, 3, 2, 3, 4, 4, 5, 5, 5, 5, 6, 7, 4, 5, 4, 3, 3, 3, 5, 4, 3, 3,2,7,7,7,5,3,4,7,7,7,5,3,4,0,3,4,5,6,7,8,4,8,7,5,6,4,3], True, modulationIndexes = [53,54,61,77], modulationScales = [scaleDict["dorianScale"],scaleDict["majorScale"],scaleDict["dorianScale"],scaleDict["majorScale"]],\r
+                                   lyrics = ["A-","-llons","en-","-fants","de","la","pa","tri-","-i-","-e","le","jour","de","gloire","est","a-","-rri-","-ve","con-","-tre","nous","de","la","ty-","-ra-","-nni-","-e","l'e-","-ten-","-dard","san-","-glant","est","le-","-ve","l'e-","-ten-","-dard","san-","-an-","-glant","est","le-","-ve","en-","-ten-","-dez","vous","dans","nos","cam-","-pa-","-gnes","mu-","-gir","ces","fe-","-ro-","-ces","sol-","-dats","qui","vie-","-nnent","ju-","-sque","dans","nos","bras","e-","-gor-","-ger","nos","fils","et","nos","com-","-pa-","-gnes","aux","ar-","-mes","ci-","-toy-","-yen","for-","-mez","vos","ba-","-ta-","-illons","mar-","-chons","mar-","-chons","qu'un","sang","im-","-pur","a-","-breu-","-ve","nos","si-","-illons","pon","pon","pon","pon"],\r
+                                   noteLengths = [1,2,1,3,3,3,3,5,1,2,1,2,1,3,6,2,1,9,2,1,3,3,3,2,1,3,6,2,1,3,3,3,2,1,6,2,1,3,2,1,3,2,1,6,1,2,1,6,3,2,1,3,3,6,3,2,1,3,2,1,6,3,5,1,2,1,2,1,6,2,1,5,1,2,1,2,1,6,5,1,7,1,2,1,8,1,7,1,2,1,8,3,9,3,9,6,3,3,9,3,8,1,2,1,2,1,2,1,8],\r
+                                   quarterNoteLength = 300)),\r
+            ("vinblanc" , \r
+             Song(scaleDict["phrygianScale"],[5, 5, 5, 3, 5, 6, 5, 5, 5, 3, 7, 6, 6, 6, 6, 4, 8, 7, 7, 7, 8, 9, 7, 5, 5, 5, 5, 3, 5, 6, 8, 9, 10, 9, 8, 10, 9, 8, 6, 6, 8, 7, 6, 8, 6, 5, 3, 5, 6, 3, 5, 6, 3, 5, 6, 3, 5, 6, 3, 5, 7, 6, 5, 7, 6, 5, 8],\r
+                  False,\r
+                  lyrics = ["Ah","le","pe-","-tit","vin","blanc","qu'on","boit","sous","les","to-","-nelles","quand","les","fi-","-lles","sont","belles","du","co-","-te","de","No-","-geant","et","puis","de","temps","en","temps","un","air","de","vie-","-lle","ro-","-man-","-ce","sem-","-ble","do-","-nner","la","ca-","-den-","-ce","pour","fau-","-ter","pour","fau-","-ter","dans","les","bois","dans","les","pres","du","co-","-te","du","co-","-te","de","No-","-geant"],\r
+                  noteLengths = [1,1,1,2,1,6,1,1,1,2,1,6,1,1,1,2,1,6,1,1,1,2,1,6,1,1,1,2,1,6,1,1,1,1,1,1,3,3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,6],\r
+                  quarterNoteLength = 300)),\r
+            ("none" , None)])\r
+\r
+instrumentImagePathList = ["piano", "guitare", "accordeon", "violon", "flute", "tuba", "orgue", "violoncelle", "celesta"]\r
+octaves = [0, -1, 0, 1, 1, -2, 0, -1, 0]\r
+\r
+defaultInstrumentChannel = 16\r
+defaultInstrumentNote = 60\r
+defaultCCHexCode = 0xB0+defaultInstrumentChannel - 1\r
+defaultNoteOnHexCode = 0x90+defaultInstrumentChannel - 1\r
+\r
+songScaleFactor = 0.99\r
+\r
+fileName = "../../../saves/22-01-2009-coll1-v65-"\r
+\r
+def noteNumberToName(noteNumber):\r
+    names = ["Do","Do#","R\xe9","Mib","Mi","Fa","Fa#","Sol","Sol#","La","Sib","Si"]\r
+    return(names[noteNumber%12])\r
+\r
+if __name__ == "__main__":\r
+    key = "papanoel"\r
+    if songDict[key] != None :\r
+        songDict[key].save("../songs/smwis/"+str(key)+".smwi")\r
diff --git a/src/instruments/Instrument.py b/src/instruments/Instrument.py
new file mode 100755 (executable)
index 0000000..fb54d3a
--- /dev/null
@@ -0,0 +1,46 @@
+'''\r
+Created on 15 juil. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+class Instrument:\r
+    '''\r
+    Object representing an instrument.\r
+    \r
+        notes:\r
+            The MIDI numbers of the notes played by this instrument (usually a scale)\r
+        channel:\r
+            The channel corresponding to the instrument in the synthesizer\r
+        image:\r
+            The image for the instrument\r
+    '''\r
+\r
+    def __init__(self, notes, channel, image, octave = 0):\r
+        '''\r
+        Constructor\r
+            \r
+        notes:\r
+            The MIDI numbers of the notes played by this instrument (usually a scale)\r
+        channel:\r
+            The channel corresponding to the instrument in the synthesizer\r
+        image:\r
+            The image for the instrument\r
+        '''\r
+        \r
+        self.notes = [loop+12*octave for loop in notes]\r
+        self.octave = octave\r
+        self.channel = channel\r
+        self.image = image\r
+        \r
+    def getNote(self,noteNumber):\r
+        if noteNumber == None :\r
+            return(None)\r
+        else :\r
+            return(self.notes[noteNumber])\r
+        \r
+    def getNoteByNoteNumber(self,baseMidiNoteNumber):\r
+        if baseMidiNoteNumber == None:\r
+            return(None)\r
+        else :\r
+            return(baseMidiNoteNumber+self.octave*12)
\ No newline at end of file
diff --git a/src/instruments/__init__.py b/src/instruments/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/instruments/instrumentImages/accordeon.jpg b/src/instruments/instrumentImages/accordeon.jpg
new file mode 100755 (executable)
index 0000000..015b1e0
Binary files /dev/null and b/src/instruments/instrumentImages/accordeon.jpg differ
diff --git a/src/instruments/instrumentImages/accordeonOld.jpg b/src/instruments/instrumentImages/accordeonOld.jpg
new file mode 100755 (executable)
index 0000000..68ee03b
Binary files /dev/null and b/src/instruments/instrumentImages/accordeonOld.jpg differ
diff --git a/src/instruments/instrumentImages/celesta.jpg b/src/instruments/instrumentImages/celesta.jpg
new file mode 100755 (executable)
index 0000000..2cb012b
Binary files /dev/null and b/src/instruments/instrumentImages/celesta.jpg differ
diff --git a/src/instruments/instrumentImages/celestaOld.jpg b/src/instruments/instrumentImages/celestaOld.jpg
new file mode 100755 (executable)
index 0000000..fdec154
Binary files /dev/null and b/src/instruments/instrumentImages/celestaOld.jpg differ
diff --git a/src/instruments/instrumentImages/flute.jpg b/src/instruments/instrumentImages/flute.jpg
new file mode 100755 (executable)
index 0000000..5dbab3d
Binary files /dev/null and b/src/instruments/instrumentImages/flute.jpg differ
diff --git a/src/instruments/instrumentImages/guitare.jpg b/src/instruments/instrumentImages/guitare.jpg
new file mode 100755 (executable)
index 0000000..33dcd50
Binary files /dev/null and b/src/instruments/instrumentImages/guitare.jpg differ
diff --git a/src/instruments/instrumentImages/orgue.jpg b/src/instruments/instrumentImages/orgue.jpg
new file mode 100755 (executable)
index 0000000..ea4e1b5
Binary files /dev/null and b/src/instruments/instrumentImages/orgue.jpg differ
diff --git a/src/instruments/instrumentImages/piano.jpg b/src/instruments/instrumentImages/piano.jpg
new file mode 100755 (executable)
index 0000000..ef109bc
Binary files /dev/null and b/src/instruments/instrumentImages/piano.jpg differ
diff --git a/src/instruments/instrumentImages/tuba.jpg b/src/instruments/instrumentImages/tuba.jpg
new file mode 100755 (executable)
index 0000000..453d3bd
Binary files /dev/null and b/src/instruments/instrumentImages/tuba.jpg differ
diff --git a/src/instruments/instrumentImages/violon.jpg b/src/instruments/instrumentImages/violon.jpg
new file mode 100755 (executable)
index 0000000..454e4f3
Binary files /dev/null and b/src/instruments/instrumentImages/violon.jpg differ
diff --git a/src/instruments/instrumentImages/violoncelle.jpg b/src/instruments/instrumentImages/violoncelle.jpg
new file mode 100755 (executable)
index 0000000..9bcc42c
Binary files /dev/null and b/src/instruments/instrumentImages/violoncelle.jpg differ
diff --git a/src/launcher/__init__.py b/src/launcher/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/logging/EventLog.py b/src/logging/EventLog.py
new file mode 100755 (executable)
index 0000000..3e11f25
--- /dev/null
@@ -0,0 +1,69 @@
+'''\r
+Created on 21 aout 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+import time\r
+\r
+class EventLog():\r
+    '''\r
+    classdocs\r
+    '''\r
+    \r
+    def __init__(self,eventGroups = [], times = []):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.eventGroups = eventGroups\r
+        self.times = times\r
+        self.rate = 1\r
+        self.yielder = self.eventGroupYielder()\r
+        self.yieldPointer = 0\r
+        \r
+        self._timeCounter = 0\r
+                \r
+    def __getstate__(self):\r
+        d = []\r
+        d.append(self.eventGroups)\r
+        d.append(self.times)\r
+        return d\r
+    \r
+    def __setstate__(self,d):\r
+        self.eventGroups = d[0]\r
+        self.times = d[1]\r
+        self.yielder = self.eventGroupYielder()\r
+        \r
+        self._timeCounter = 0\r
+    \r
+    def appendEventGroup(self, eventGroup):\r
+        t = time.clock()*1000\r
+        self.times.append(t)\r
+        self.eventGroups.append(eventGroup)\r
+        \r
+    def update(self,timePassed):\r
+        self._timeCounter += timePassed\r
+    \r
+    def setReplayRate(self,rate):\r
+        self.rate = rate\r
+        self.yielder = self.eventGroupYielder()\r
+    \r
+    def getPickledEvents(self):\r
+        return(self.yielder.next())\r
+    \r
+    def getCurrentTime(self):\r
+        return(self.times[self.yieldPointer])\r
+    \r
+    def eventGroupYielder(self):\r
+        '''\r
+        Will return the next event to post if enough time has passed and [] otherwise\r
+        '''\r
+        i = 0\r
+        while i in range(len(self.eventGroups)):\r
+            print "rate is " + str(self.rate)\r
+            if self._timeCounter*self.rate>self.times[i]:\r
+                print str(self._timeCounter*self.rate)+" > "+ str(self.times[i])\r
+                self.yieldPointer = i\r
+                yield self.eventGroups[i]\r
+                i += 1\r
+            else:\r
+                yield []\r
diff --git a/src/logging/FamiliarizerLog.py b/src/logging/FamiliarizerLog.py
new file mode 100644 (file)
index 0000000..e84d1c2
--- /dev/null
@@ -0,0 +1,100 @@
+'''\r
+Created on 28 aout 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+import os\r
+import sys\r
+import subprocess\r
+import re\r
+\r
+import pygame\r
+import pygame.midi\r
+import pickle\r
+\r
+from gui.constants import *\r
+\r
+from gui.PlayingScreen import PlayingScreen\r
+from gui.InstrumentChoice import InstrumentChoice\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from songs.Song import Song\r
+from gui.StaticFamiliarizer import StaticFamiliarizer\r
+from gui.SongFamiliarizer import SongFamiliarizer\r
+from gui.SongPlayingScreen import SongPlayingScreen\r
+from gui.DummyInstrumentChoice import DummyInstrumentChoice\r
+\r
+class FamiliarizerLog():\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+    def __init__(self,eventLog,level,activeWiimotes):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.eventLog = eventLog\r
+        self.level = level\r
+        self.activeWiimotes = activeWiimotes\r
+        self.scale = scaleDict["majorScale"]   \r
+        \r
+if __name__ == '__main__':\r
+        \r
+        f = file('../../../saves/19-01-2009-testcoll1-v65-1.fmwi', 'r')\r
+        unpickler = pickle.Unpickler(f)\r
+        log = unpickler.load()\r
+        f.close()\r
+        \r
+        pygame.init()\r
+        modeResolution = (1024,768)\r
+        window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+        pygame.midi.init()\r
+        instruments = [Instrument(log.scale, i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)]\r
+        \r
+        joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())]\r
+        for joy in joys:\r
+            if joy[1] in joyNames:\r
+                pygame.joystick.Joystick(joy[0]).init() \r
+        \r
+        ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+        portOffset = ports.index(portNames[0])\r
+        print(portOffset)\r
+        \r
+        screen = pygame.display.get_surface()\r
+        clock = pygame.time.Clock()        \r
+        cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']]\r
+        durations = [75 for i in range(len(cursorImages))]\r
+        \r
+        extsc = True\r
+        casc = False\r
+        easyMode = True\r
+        \r
+        song = Song(scaleDict["majorScale"],[3,9,6,4,1,8,5,7,2,10],True)\r
+\r
+        wiimoteCount = 4\r
+        \r
+        cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),'../cursor/cursorImages/black/flash.png') for i in range(wiimoteCount)]\r
+        wiimotes = [Wiimote(i, i + portOffset, None, instruments[0], cursors[i]) for i in range(wiimoteCount)]\r
+        dummyInstrumentChoice = DummyInstrumentChoice(wiimotes, window, screen, clock, joys, portOffset, log.activeWiimotes)\r
+        if log.level < 2 :\r
+            familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = log.level,eventLog = log.eventLog,replay = True)\r
+        elif log.level == 2 :\r
+            familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = log.eventLog,replay = True)\r
+        else :\r
+            familiarize = SongPlayingScreen(dummyInstrumentChoice,songDict["clairdelalune"],easyMode = True,eventLog = log.eventLog,replay = True)\r
+                        \r
+        while familiarize.nextLevel != None :\r
+            if familiarize.nextLevel < 2 :\r
+                familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = familiarize.nextLevel,eventLog = familiarize.eventLog,replay = True)\r
+            elif familiarize.nextLevel == 2 :\r
+                familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = familiarize.eventLog,replay = True)\r
+            else :\r
+                familiarize = SongPlayingScreen(dummyInstrumentChoice,songDict["clairdelalune"],easyMode = True,eventLog = familiarize.eventLog,replay = True)\r
+            \r
+        for wiimote in wiimotes:\r
+            del wiimote.port            \r
+        \r
+        pygame.midi.quit()\r
+        pygame.quit()
\ No newline at end of file
diff --git a/src/logging/Log.py b/src/logging/Log.py
new file mode 100755 (executable)
index 0000000..4aef88b
--- /dev/null
@@ -0,0 +1,135 @@
+'''\r
+Created on 28 aout 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+import os\r
+import sys\r
+import subprocess\r
+import re\r
+\r
+import pygame\r
+import pygame.midi\r
+import pickle\r
+\r
+from gui.constants import *\r
+\r
+from gui.PlayingScreen import PlayingScreen\r
+from gui.SongPlayingScreen import SongPlayingScreen\r
+from gui.InstrumentChoice import InstrumentChoice\r
+from instruments.Instrument import Instrument\r
+from cursor.WarpingCursor import *\r
+from controllers.Wiimote import Wiimote\r
+from songs.Song import Song\r
+\r
+\r
+class Log():\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,eventLog,scale,extendedScale,cascade,song,mode,activeWiimotes,easyMode = True):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.eventLog = eventLog\r
+        self.scale = scale\r
+        self.extendedScale = extendedScale\r
+        self.cascade = cascade\r
+        self.song = song\r
+        self.activeWiimotes = activeWiimotes\r
+        self.mode = mode    \r
+        \r
+if __name__ == '__main__':\r
+        \r
+        f = file('../../saves/4-12-2009-B1-v50-1.mwi', 'r')\r
+        unpickler = pickle.Unpickler(f)\r
+        log = unpickler.load()\r
+        f.close()\r
+        \r
+        pygame.init()\r
+        pygame.midi.init()\r
+        instruments = [Instrument(scaleDict["majorScale"], i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)]\r
+        \r
+        joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())]\r
+        for joy in joys:\r
+            if joy[1] in joyNames:\r
+                pygame.joystick.Joystick(joy[0]).init() \r
+        \r
+        ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+        portOffset = ports.index(portNames[0])\r
+        print(portOffset)\r
+        \r
+        modeResolution = (1024,768)\r
+        window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+        screen = pygame.display.get_surface()\r
+        clock = pygame.time.Clock()        \r
+        cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']]\r
+        durations = [75 for i in range(len(cursorImages[0]))]\r
+        \r
+        wiimoteCount = 4\r
+        cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),flashImage = '../cursor/cursorImages/black/flash.png' ) for i in range(wiimoteCount)]\r
+        wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(wiimoteCount)]\r
+        \r
+        if log.song != None :\r
+            \r
+            if log.mode == 0 :\r
+                log.extendedScale = log.song.requiresExtendedScale\r
+                log.cascade = True\r
+                log.easyMode = True\r
+            elif log.mode == 1 :\r
+                log.extendedScale = log.song.requiresExtendedScale\r
+                log.cascade = True\r
+                log.easyMode = True\r
+            elif log.mode == 2:\r
+                log.extendedScale = log.song.requiresExtendedScale\r
+                log.cascade = False\r
+                log.easyMode = True\r
+            elif log.mode == 3:\r
+                log.extendedScale = True\r
+                log.cascade = False\r
+                log.easyMode = False\r
+                \r
+            choice = InstrumentChoice(instruments, wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes, eventLog = log.eventLog,scaleFactor = songScaleFactor,replay = True)\r
+            play = SongPlayingScreen(choice, log.song,log.cascade, log.extendedScale,log.easyMode)\r
+        \r
+        else:\r
+            \r
+            if log.mode == 0 :\r
+                log.extendedScale = False\r
+                log.cascade = False\r
+            elif log.mode == 1 :\r
+                log.extendedScale = True\r
+                log.cascade = False\r
+            elif log.mode == 2:\r
+                log.extendedScale = False\r
+                log.cascade = True\r
+            elif log.mode == 3:\r
+                log.extendedScale = True\r
+                log.cascade = True\r
+                \r
+            choice = InstrumentChoice(instruments, wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,eventLog = log.eventLog,replay = True)\r
+            play = PlayingScreen(choice, None,log.cascade, log.extendedScale)            \r
+                \r
+        while play.backToInstrumentChoice == True :\r
+            \r
+            for wiimote in wiimotes:\r
+                del wiimote.port\r
+                \r
+            wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(wiimoteCount)]\r
+            previousEventLog = play.eventLog\r
+\r
+            if log.song != None :\r
+                choice = InstrumentChoice(instruments, wiimotes,window, screen, clock, joys, portOffset, log.activeWiimotes,eventLog = previousEventLog, replay = True, scaleFactor = songScaleFactor)\r
+                play = SongPlayingScreen(choice, log.song, False, log.extendedScale,log.easyMode)\r
+            else:\r
+                choice = InstrumentChoice(instruments, wiimotes, log.window, screen, clock, joys, portOffset,log.activeWiimotes, eventLog = previousEventLog, replay = True)\r
+                play = PlayingScreen(choice, None, log.cascade, log.extendedScale)\r
+                               \r
+        for wiimote in wiimotes:\r
+                del wiimote.port\r
+        \r
+        pygame.midi.quit()\r
+        pygame.quit()
\ No newline at end of file
diff --git a/src/logging/LogPGUAnalyzer.py b/src/logging/LogPGUAnalyzer.py
new file mode 100644 (file)
index 0000000..a3551fd
--- /dev/null
@@ -0,0 +1,42 @@
+'''\r
+Created on 25 janv. 2010\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+from LogPGUPlayer import LogPGUPlayer\r
+\r
+class LogPGUAnalyzer(LogPGUPlayer):\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self):\r
+        '''\r
+        Constructor\r
+        '''\r
+        \r
+        self.firstStepDurations = []\r
+        self.firstStepClicks = []\r
+        self.firstStepClicksIn = []\r
+        \r
+        self.secondStepDurations = []\r
+        self.secondStepClicks = []\r
+        self.secondStepClicksIn = []\r
+        \r
+        self.thirdStepDurations = []\r
+        self.thirdStepClicks = []\r
+        self.thirdStepClicksIn = []\r
+        \r
+        self.songDurations = []\r
+        self.songClicks = []\r
+        self.songClicksIn = []\r
+        self.songClicksPerMinute = []\r
+        self.songClicksInPerMinute = []\r
+        self.songTotalDurations = []\r
+        \r
+        self.meanTimeBetweenNotes = []\r
+        \r
+        LogPGUPlayer.__init__(self,20)\r
+        
\ No newline at end of file
diff --git a/src/logging/LogPGUPlayer.py b/src/logging/LogPGUPlayer.py
new file mode 100644 (file)
index 0000000..cf1a670
--- /dev/null
@@ -0,0 +1,237 @@
+'''\r
+Created on 25 janv. 2010\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+import pygame\r
+import pickle\r
+\r
+from pgu import gui as pguGui\r
+\r
+from gui import constants\r
+\r
+from instruments.Instrument import Instrument\r
+from songs.Song import Song\r
+from cursor.WarpingCursor import *\r
+from gui.StaticFamiliarizer import StaticFamiliarizer\r
+from gui.SongFamiliarizer import SongFamiliarizer\r
+from gui.SongPlayingScreen import SongPlayingScreen\r
+from gui.DummyInstrumentChoice import DummyInstrumentChoice\r
+from controllers.Wiimote import Wiimote\r
+\r
+class LogPGUPlayer(pguGui.Desktop):\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,rate):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.firstStepDurations = []\r
+        self.firstStepClicks = []\r
+        self.firstStepClicksIn = []\r
+        \r
+        self.secondStepDurations = []\r
+        self.secondStepClicks = []\r
+        self.secondStepClicksIn = []\r
+        \r
+        self.thirdStepDurations = []\r
+        self.thirdStepClicks = []\r
+        self.thirdStepClicksIn = []\r
+        \r
+        self.songDurations = []\r
+        self.songClicks = []\r
+        self.songClicksIn = []\r
+        self.songClicksPerMinute = []\r
+        self.songClicksInPerMinute = []\r
+        self.songTotalDurations = []\r
+        \r
+        self.meanTimeBetweenNotes = []\r
+        \r
+        pguGui.Desktop.__init__(self)\r
+        \r
+        self.replayRate = rate\r
+        #pguGui.theme.load('../data/themes/default')\r
+        \r
+        self.connect(pguGui.QUIT,self.quit,None)\r
+        \r
+        main = pguGui.Container(width=500, height=400) #, background=(220, 220, 220) )\r
+        \r
+        \r
+        main.add(pguGui.Label("File Dialog Example", cls="h1"), 20, 20)\r
+        \r
+        \r
+        td_style = {'padding_right': 10}\r
+        t = pguGui.Table()\r
+        t.tr()\r
+        t.td( pguGui.Label('File Name:') , style=td_style )\r
+        self.input_file = pguGui.Input()\r
+        t.td( self.input_file, style=td_style )\r
+        self.browseButton = pguGui.Button("Browse...")\r
+        t.td( self.browseButton, style=td_style )\r
+        self.browseButton.connect(pguGui.CLICK, self.open_file_browser, None)\r
+        \r
+        self.goButton = pguGui.Button("Go")\r
+        \r
+        self.goButton.connect(pguGui.CLICK, self.goButtonClicked,None)\r
+        \r
+        self.quitButton = pguGui.Button("Fin")\r
+        self.quitButton.connect(pguGui.CLICK,self.quit,None)\r
+        \r
+        t.td( self.browseButton, style=td_style )\r
+        t.td( self.goButton, style=td_style )\r
+        t.td( self.quitButton, style=td_style )\r
+        \r
+        main.add(t, 20, 100)\r
+        \r
+        self.run(main)\r
+        #import profile\r
+        #profile.run('app.run(main)')\r
+        \r
+    def open_file_browser(self,data=None):\r
+        d = pguGui.FileDialog(path = "../../../saves")\r
+        d.connect(pguGui.CHANGE, self.handle_file_browser_closed, d)\r
+        d.open()\r
+        \r
+    \r
+    def handle_file_browser_closed(self,dlg):\r
+        if dlg.value: self.input_file.value = dlg.value\r
+    \r
+    def goButtonClicked(self,data=None):\r
+        if self.input_file.value.endswith(".fmwi"):\r
+            f = file(self.input_file.value, 'r')\r
+            unpickler = pickle.Unpickler(f)\r
+            log = unpickler.load()\r
+            f.close()\r
+            \r
+            log.eventLog.setReplayRate(self.replayRate)\r
+        \r
+            pygame.midi.init()\r
+            instruments = [Instrument(log.scale, i + 1, "".join(["../instruments/instrumentImages/", constants.instrumentImagePathList[i], ".jpg"]), constants.octaves[i]) for i in range(9)]\r
+            \r
+            joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())]\r
+            for joy in joys:\r
+                if joy[1] in constants.joyNames:\r
+                    pygame.joystick.Joystick(joy[0]).init() \r
+            \r
+            ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())]\r
+            portOffset = ports.index(constants.portNames[0])\r
+            print(portOffset)\r
+            \r
+            screen = pygame.display.get_surface()\r
+            clock = pygame.time.Clock()        \r
+            cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']]\r
+            durations = [75 for i in range(len(cursorImages))]\r
+            \r
+            extsc = True\r
+            casc = False\r
+            easyMode = True\r
+            \r
+            song = Song(constants.scaleDict["majorScale"],[3,9,6,4,1,8,5,7,2,10],True)\r
+    \r
+            wiimoteCount = 4\r
+            \r
+            cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),'../cursor/cursorImages/black/flash.png') for i in range(wiimoteCount)]\r
+            wiimotes = [Wiimote(i, i + portOffset, None, instruments[0], cursors[i]) for i in range(wiimoteCount)]\r
+            dummyInstrumentChoice = DummyInstrumentChoice(wiimotes, window, screen, clock, joys, portOffset, log.activeWiimotes)\r
+            if log.level < 2 :\r
+                familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = log.level,eventLog = log.eventLog,replay = True)\r
+                self.fillStaticFamiliarizerStats(familiarize)\r
+            elif log.level == 2 :\r
+                familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = log.eventLog,replay = True)\r
+                self.fillSongFamiliarizerStats(familiarize)\r
+            else :\r
+                familiarize = SongPlayingScreen(dummyInstrumentChoice,constants.songDict["clairdelalune"],easyMode = True,eventLog = log.eventLog,replay = True)\r
+                self.fillSongStats(familiarize)            \r
+            \r
+            while familiarize.nextLevel != None :\r
+                if familiarize.nextLevel < 2 :\r
+                    familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = familiarize.nextLevel,eventLog = familiarize.eventLog,replay = True)\r
+                    self.fillStaticFamiliarizerStats(familiarize)\r
+                elif familiarize.nextLevel == 2 :\r
+                    familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = familiarize.eventLog,replay = True)\r
+                    self.fillSongFamiliarizerStats(familiarize)\r
+                else :\r
+                    familiarize = SongPlayingScreen(dummyInstrumentChoice,constants.songDict["clairdelalune"],easyMode = True,eventLog = familiarize.eventLog,replay = True)\r
+                    self.fillSongStats(familiarize)\r
+                    \r
+            for wiimote in wiimotes:\r
+                del wiimote.port            \r
+            \r
+            pygame.midi.quit()\r
+            self.printStatsToFile()\r
+            \r
+    def fillStaticFamiliarizerStats(self,familiarizer):\r
+        if familiarizer.level == 0 :\r
+            self.firstStepClicks.append(familiarizer.clicks)\r
+            self.firstStepClicksIn.append(familiarizer.clicksIn)\r
+            self.firstStepDurations.append(familiarizer.duration)\r
+            \r
+        if familiarizer.level == 1 :\r
+            self.secondStepClicks.append(familiarizer.clicks)\r
+            self.secondStepClicksIn.append(familiarizer.clicksIn)\r
+            self.secondStepDurations.append(familiarizer.duration)\r
+            \r
+    def fillSongFamiliarizerStats(self,familiarizer):\r
+        self.thirdStepClicks.append(familiarizer.clicks)\r
+        self.thirdStepClicksIn.append(familiarizer.clicksIn)\r
+        self.thirdStepDurations.append(familiarizer.duration)\r
+        \r
+    def fillSongStats(self,familiarizer):\r
+        self.songClicks.append(familiarizer.clicks)\r
+        self.songClicksIn.append(familiarizer.clicksIn)\r
+        self.songClicksPerMinute.append(familiarizer.clicksPerMinute)\r
+        self.songClicksInPerMinute.append(familiarizer.clicksInPerMinute)\r
+        self.songDurations.append(familiarizer.songDurations)\r
+        self.songTotalDurations.append(familiarizer.totalDuration)\r
+        \r
+    def statsToFormattedString(self):\r
+        return("First step durations :\n"+"\n"+\r
+                   str(self.firstStepDurations)+"\n"+"\n"+\r
+                   "First step clicks :\n"+"\n"+\r
+                   str(self.firstStepClicks)+"\n"+"\n"+\r
+                   "First step clicksIn :\n"+"\n"+\r
+                   str(self.firstStepClicksIn)+"\n"+"\n"+\r
+                   "Second step durations :\n"+"\n"+\r
+                   str(self.secondStepDurations)+"\n"+"\n"+\r
+                   "Second step clicks :\n"+"\n"+\r
+                   str(self.secondStepClicks)+"\n"+"\n"+\r
+                   "Second step clicksIn :\n"+"\n"+\r
+                   str(self.secondStepClicksIn)+"\n"+"\n"+\r
+                   "Third step durations :\n"+"\n"+\r
+                   str(self.thirdStepDurations)+"\n"+"\n"+\r
+                   "Third step clicks :\n"+"\n"+\r
+                   str(self.thirdStepClicks)+"\n"+"\n"+\r
+                   "Third step clicksIn :\n"+"\n"+\r
+                   str(self.thirdStepClicksIn)+"\n"+"\n"+\r
+                   "song durations :\n"+"\n"+\r
+                   str(self.songDurations)+"\n"+"\n"+\r
+                   "song clicks :\n"+"\n"+\r
+                   str(self.songClicks)+"\n"+"\n"+\r
+                   "song clicksIn :\n"+"\n"+\r
+                   str(self.songClicksIn)+"\n"+"\n"+\r
+                   "song clicks per minute:\n"+"\n"+\r
+                   str(self.songClicksPerMinute)+"\n"+"\n"+\r
+                   "song clicksIn per minute :\n"+"\n"+\r
+                   str(self.songClicksInPerMinute)+"\n"+"\n"+\r
+                   "song total durations :\n"+"\n"+\r
+                   str(self.songTotalDurations)+"\n"+"\n")\r
+        \r
+    def printStatsToFile(self,path=None):\r
+        if path == None :\r
+            path = self.input_file.value.replace(".fmwi",".txt")\r
+        file = open(path,"w")\r
+        file.write("Log ID : "+self.input_file.value+"\n"+"\n")\r
+        file.write(self.statsToFormattedString())\r
+        file.close()\r
+                    \r
+if __name__ == "__main__":\r
+    pygame.init()\r
+    modeResolution = (1024,768)\r
+    window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN)\r
+    logConfig = LogPGUPlayer(10000)\r
+    pygame.quit()\r
+    
\ No newline at end of file
diff --git a/src/logging/PickleableEvent.py b/src/logging/PickleableEvent.py
new file mode 100755 (executable)
index 0000000..ab218f8
--- /dev/null
@@ -0,0 +1,24 @@
+import pygame\r
+import copy\r
+import pickle\r
+\r
+from pygame.event import Event\r
+\r
+class PickleableEvent(object):\r
+    "A pygame.Event that can be serialized."\r
+    \r
+    def __init__(self,type,dict):\r
+        self.__dict__ = copy.copy(dict)\r
+        self.type = type\r
+        self.event = Event(self.type,dict)\r
+\r
+    def __getstate__(self):\r
+        d = []\r
+        d.append(self.type)\r
+        d.append(copy.copy(self.event.dict))\r
+        return d\r
+\r
+    def __setstate__(self, d):\r
+        self.__dict__ = copy.copy(d[1])\r
+        self.type = d[0]\r
+        self.event = Event(d[0],d[1])
\ No newline at end of file
diff --git a/src/logging/__init__.py b/src/logging/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/mxmMidi/DataTypeConverters.py b/src/mxmMidi/DataTypeConverters.py
new file mode 100644 (file)
index 0000000..25115de
--- /dev/null
@@ -0,0 +1,217 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+from struct import pack, unpack\r
+\r
+"""\r
+This module contains functions for reading and writing the special data types\r
+that a midi file contains.\r
+"""\r
+\r
+"""\r
+nibbles are four bits. A byte consists of two nibles.\r
+hiBits==0xF0, loBits==0x0F Especially used for setting\r
+channel and event in 1. byte of musical midi events\r
+"""\r
+\r
+\r
+\r
+def getNibbles(byte):\r
+    """\r
+    Returns hi and lo bits in a byte as a tuple\r
+    >>> getNibbles(142)\r
+    (8, 14)\r
+    \r
+    Asserts byte value in byte range\r
+    >>> getNibbles(256)\r
+    Traceback (most recent call last):\r
+        ...\r
+    ValueError: Byte value out of range 0-255: 256\r
+    """\r
+    if not 0 <= byte <= 255:\r
+        raise ValueError('Byte value out of range 0-255: %s' % byte)\r
+    return (byte >> 4 & 0xF, byte & 0xF)\r
+\r
+\r
+def setNibbles(hiNibble, loNibble):\r
+    """\r
+    Returns byte with value set according to hi and lo bits\r
+    Asserts hiNibble and loNibble in range(16)\r
+    >>> setNibbles(8, 14)\r
+    142\r
+    \r
+    >>> setNibbles(8, 16)\r
+    Traceback (most recent call last):\r
+        ...\r
+    ValueError: Nible value out of range 0-15: (8, 16)\r
+    """\r
+    if not (0 <= hiNibble <= 15) or not (0 <= loNibble <= 15):\r
+        raise ValueError('Nible value out of range 0-15: (%s, %s)' % (hiNibble, loNibble))\r
+    return (hiNibble << 4) + loNibble\r
+\r
+\r
+\r
+def readBew(value):\r
+    """\r
+    Reads string as big endian word, (asserts len(value) in [1,2,4])\r
+    >>> readBew('aáâã')\r
+    1642193635L\r
+    >>> readBew('aá')\r
+    25057\r
+    """\r
+    return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0]\r
+\r
+\r
+def writeBew(value, length):\r
+    """\r
+    Write int as big endian formatted string, (asserts length in [1,2,4])\r
+    Difficult to print the result in doctest, so I do a simple roundabout test.\r
+    >>> readBew(writeBew(25057, 2))\r
+    25057\r
+    >>> readBew(writeBew(1642193635L, 4))\r
+    1642193635L\r
+    """\r
+    return pack('>%s' % {1:'B', 2:'H', 4:'L'}[length], value)\r
+\r
+\r
+\r
+"""\r
+Variable Length Data (varlen) is a data format sprayed liberally throughout\r
+a midi file. It can be anywhere from 1 to 4 bytes long.\r
+If the 8'th bit is set in a byte another byte follows. The value is stored\r
+in the lowest 7 bits of each byte. So max value is 4x7 bits = 28 bits.\r
+"""\r
+\r
+\r
+def readVar(value):\r
+    """\r
+    Converts varlength format to integer. Just pass it 0 or more chars that\r
+    might be a varlen and it will only use the relevant chars.\r
+    use varLen(readVar(value)) to see how many bytes the integer value takes.\r
+    asserts len(value) >= 0\r
+    >>> readVar('\80@')\r
+    64\r
+    >>> readVar('áâãa')\r
+    205042145\r
+    """\r
+    sum = 0\r
+    for byte in unpack('%sB' % len(value), value):\r
+        sum = (sum << 7) + (byte & 0x7F)\r
+        if not 0x80 & byte: break # stop after last byte\r
+    return sum\r
+\r
+\r
+\r
+def varLen(value):\r
+    """\r
+    Returns the the number of bytes an integer will be when\r
+    converted to varlength\r
+    """\r
+    if value <= 127:\r
+        return 1\r
+    elif value <= 16383:\r
+        return 2\r
+    elif value <= 2097151:\r
+        return 3\r
+    else:\r
+        return 4\r
+\r
+\r
+def writeVar(value):\r
+    "Converts an integer to varlength format"\r
+    sevens = to_n_bits(value, varLen(value))\r
+    for i in range(len(sevens)-1):\r
+        sevens[i] = sevens[i] | 0x80\r
+    return fromBytes(sevens)\r
+\r
+\r
+def to_n_bits(value, length=1, nbits=7):\r
+    "returns the integer value as a sequence of nbits bytes"\r
+    bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)]\r
+    bytes.reverse()\r
+    return bytes\r
+\r
+\r
+def toBytes(value):\r
+    "Turns a string into a list of byte values"\r
+    return unpack('%sB' % len(value), value)\r
+\r
+\r
+def fromBytes(value):\r
+    "Turns a list of bytes into a string"\r
+    if not value:\r
+        return ''\r
+    return pack('%sB' % len(value), *value)\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+#    print to7bits(0, 3)\r
+#    print to7bits(127, 3)\r
+#    print to7bits(255, 3)\r
+#    print to7bits(65536, 3)\r
+\r
+    # simple test cases\r
+    \r
+#    print 'getHiLoHex', getNibbles(16)\r
+#    print 'setHiLoHex', setNibbles(1,0)\r
+#    \r
+#    print 'readBew', readBew('aáâã')\r
+#    print 'writeBew', writeBew(1642193635, 4)\r
+#\r
+#    print 'varLen', varLen(1)\r
+#\r
+    print 'readVar', readVar('\80@')\r
+    print 'writeVar', writeVar(8192)\r
+    \r
+    print 'readVar', readVar('áâãa')\r
+    print 'writeVar', writeVar(205058401)\r
+#    \r
+#    vartest = '\x82\xF7\x80\x00'\r
+#    print 'toBytes', toBytes(vartest)\r
+#    print 'fromBytes', fromBytes([48, 49, 50,])\r
+    \r
+    \r
+#    instr = '\xFF\xFF\xFF\x00'\r
+#    print 'readVar', readVar(instr)\r
+#    inst2 = 268435455\r
+#    print inst2\r
+#    print writeVar(inst2)\r
+#    print writeVar(readVar(instr))\r
+\r
+    s1 = 0x00000000\r
+    print '%08X -' % s1, '00',  writeVar(s1)\r
+    s2 = 0x00000040\r
+    print '%08X -' % s2, '40',  writeVar(s2)\r
+    s3 = 0x0000007F\r
+    print '%08X -' % s3, '7F',  writeVar(s3)\r
+    s4 = 0x00000080\r
+    print '%08X -' % s4, '81 00',  writeVar(s4)\r
+    s5 = 0x00002000\r
+    print '%08X -' % s5, 'C0 00',  writeVar(s5)\r
+    s6 = 0x00003FFF\r
+    print '%08X -' % s6, 'FF 7F',  writeVar(s6)\r
+    s7 = 0x00004000\r
+    print '%08X -' % s7, '81 80 00',  writeVar(s7)\r
+    s8 = 0x00100000\r
+    print '%08X -' % s8, 'C0 80 00',  writeVar(s8)\r
+    s9 = 0x001FFFFF\r
+    print '%08X -' % s9, 'FF FF 7F',  writeVar(s9)\r
+    s10 = 0x00200000\r
+    print '%08X -' % s10, '81 80 80 00', writeVar(s10)\r
+    s11 = 0x08000000\r
+    print '%08X -' % s11, 'C0 80 80 00', writeVar(s11)\r
+    s12 = 0x0FFFFFFF\r
+    print '%08X -' % s12, 'FF FF FF 7F', writeVar(s12)\r
+              \r
+              \r
+              \r
+             \r
+             \r
+             \r
+           \r
+           \r
+           \r
+          \r
+          \r
+          
\ No newline at end of file
diff --git a/src/mxmMidi/DataTypeConverters.pyc b/src/mxmMidi/DataTypeConverters.pyc
new file mode 100644 (file)
index 0000000..f5ef8c8
Binary files /dev/null and b/src/mxmMidi/DataTypeConverters.pyc differ
diff --git a/src/mxmMidi/EventDispatcher.py b/src/mxmMidi/EventDispatcher.py
new file mode 100644 (file)
index 0000000..3c07a33
--- /dev/null
@@ -0,0 +1,287 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+# std library\r
+from struct import unpack\r
+\r
+# custom\r
+from DataTypeConverters import readBew, readVar, varLen, toBytes\r
+\r
+# uhh I don't really like this, but there are so many constants to \r
+# import otherwise\r
+from constants import *\r
+\r
+\r
+class EventDispatcher:\r
+\r
+\r
+    def __init__(self, outstream):\r
+        \r
+        """\r
+        \r
+        The event dispatcher generates events on the outstream.\r
+        \r
+        """\r
+        \r
+        # internal values, don't mess with 'em directly\r
+        self.outstream = outstream\r
+        \r
+        # public flags\r
+\r
+        # A note_on with a velocity of 0x00 is actually the same as a \r
+        # note_off with a velocity of 0x40. When \r
+        # "convert_zero_velocity" is set, the zero velocity note_on's \r
+        # automatically gets converted into note_off's. This is a less \r
+        # suprising behaviour for those that are not into the intimate \r
+        # details of the midi spec.\r
+        self.convert_zero_velocity = 1\r
+        \r
+        # If dispatch_continuos_controllers is true, continuos \r
+        # controllers gets dispatched to their defined handlers. Else \r
+        # they just trigger the "continuous_controller" event handler.\r
+        self.dispatch_continuos_controllers = 1 # NOT IMPLEMENTED YET\r
+        \r
+        # If dispatch_meta_events is true, meta events get's dispatched \r
+        # to their defined events. Else they all they trigger the \r
+        # "meta_event" handler.\r
+        self.dispatch_meta_events = 1\r
+\r
+\r
+\r
+    def header(self, format, nTracks, division):\r
+        "Triggers the header event"\r
+        self.outstream.header(format, nTracks, division)\r
+\r
+\r
+    def start_of_track(self, current_track):\r
+        "Triggers the start of track event"\r
+        \r
+        # I do this twice so that users can overwrite the \r
+        # start_of_track event handler without worrying whether the \r
+        # track number is updated correctly.\r
+        self.outstream.set_current_track(current_track)\r
+        self.outstream.start_of_track(current_track)\r
+        \r
+    \r
+    def sysex_event(self, data):\r
+        "Dispatcher for sysex events"\r
+        self.outstream.sysex_event(data)\r
+    \r
+    \r
+    def eof(self):\r
+        "End of file!"\r
+        self.outstream.eof()\r
+\r
+\r
+    def update_time(self, new_time=0, relative=1):\r
+        "Updates relative/absolute time."\r
+        self.outstream.update_time(new_time, relative)\r
+        \r
+        \r
+    def reset_time(self):\r
+        "Updates relative/absolute time."\r
+        self.outstream.reset_time()\r
+        \r
+        \r
+    # Event dispatchers for similar types of events\r
+    \r
+    \r
+    def channel_messages(self, hi_nible, channel, data):\r
+    \r
+        "Dispatches channel messages"\r
+        \r
+        stream = self.outstream\r
+        data = toBytes(data)\r
+        \r
+        if (NOTE_ON & 0xF0) == hi_nible:\r
+            note, velocity = data\r
+            # note_on with velocity 0x00 are same as note \r
+            # off with velocity 0x40 according to spec!\r
+            if velocity==0 and self.convert_zero_velocity:\r
+                stream.note_off(channel, note, 0x40)\r
+            else:\r
+                stream.note_on(channel, note, velocity)\r
+        \r
+        elif (NOTE_OFF & 0xF0) == hi_nible:\r
+            note, velocity = data\r
+            stream.note_off(channel, note, velocity)\r
+        \r
+        elif (AFTERTOUCH & 0xF0) == hi_nible:\r
+            note, velocity = data\r
+            stream.aftertouch(channel, note, velocity)\r
+        \r
+        elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible:\r
+            controller, value = data\r
+            # A lot of the cc's are defined, so we trigger those directly\r
+            if self.dispatch_continuos_controllers:\r
+                self.continuous_controllers(channel, controller, value)\r
+            else:\r
+                stream.continuous_controller(channel, controller, value)\r
+            \r
+        elif (PATCH_CHANGE & 0xF0) == hi_nible:\r
+            program = data[0]\r
+            stream.patch_change(channel, program)\r
+            \r
+        elif (CHANNEL_PRESSURE & 0xF0) == hi_nible:\r
+            pressure = data[0]\r
+            stream.channel_pressure(channel, pressure)\r
+            \r
+        elif (PITCH_BEND & 0xF0) == hi_nible:\r
+            hibyte, lobyte = data\r
+            value = (hibyte<<7) + lobyte\r
+            stream.pitch_bend(channel, value)\r
+\r
+        else:\r
+\r
+            raise ValueError, 'Illegal channel message!'\r
+\r
+\r
+\r
+    def continuous_controllers(self, channel, controller, value):\r
+    \r
+        "Dispatches channel messages"\r
+\r
+        stream = self.outstream\r
+        \r
+        # I am not really shure if I ought to dispatch continuous controllers\r
+        # There's so many of them that it can clutter up the OutStream \r
+        # classes.\r
+        \r
+        # So I just trigger the default event handler\r
+        stream.continuous_controller(channel, controller, value)\r
+\r
+\r
+\r
+    def system_commons(self, common_type, common_data):\r
+    \r
+        "Dispatches system common messages"\r
+        \r
+        stream = self.outstream\r
+        \r
+        # MTC Midi time code Quarter value\r
+        if common_type == MTC:\r
+            data = readBew(common_data)\r
+            msg_type = (data & 0x07) >> 4\r
+            values = (data & 0x0F)\r
+            stream.midi_time_code(msg_type, values)\r
+        \r
+        elif common_type == SONG_POSITION_POINTER:\r
+            hibyte, lobyte = toBytes(common_data)\r
+            value = (hibyte<<7) + lobyte\r
+            stream.song_position_pointer(value)\r
+\r
+        elif common_type == SONG_SELECT:\r
+            data = readBew(common_data)\r
+            stream.song_select(data)\r
+\r
+        elif common_type == TUNING_REQUEST:\r
+            # no data then\r
+            stream.tuning_request(time=None)\r
+\r
+\r
+\r
+    def meta_event(self, meta_type, data):\r
+        \r
+        "Dispatches meta events"\r
+        \r
+        stream = self.outstream\r
+        \r
+        # SEQUENCE_NUMBER = 0x00 (00 02 ss ss (seq-number))\r
+        if meta_type == SEQUENCE_NUMBER:\r
+            number = readBew(data)\r
+            stream.sequence_number(number)\r
+        \r
+        # TEXT = 0x01 (01 len text...)\r
+        elif meta_type == TEXT:\r
+            stream.text(data)\r
+        \r
+        # COPYRIGHT = 0x02 (02 len text...)\r
+        elif meta_type == COPYRIGHT:\r
+            stream.copyright(data)\r
+        \r
+        # SEQUENCE_NAME = 0x03 (03 len text...)\r
+        elif meta_type == SEQUENCE_NAME:\r
+            stream.sequence_name(data)\r
+        \r
+        # INSTRUMENT_NAME = 0x04 (04 len text...)\r
+        elif meta_type == INSTRUMENT_NAME:\r
+            stream.instrument_name(data)\r
+        \r
+        # LYRIC = 0x05 (05 len text...)\r
+        elif meta_type == LYRIC:\r
+            stream.lyric(data)\r
+        \r
+        # MARKER = 0x06 (06 len text...)\r
+        elif meta_type == MARKER:\r
+            stream.marker(data)\r
+        \r
+        # CUEPOINT = 0x07 (07 len text...)\r
+        elif meta_type == CUEPOINT:\r
+            stream.cuepoint(data)\r
+        \r
+        # PROGRAM_NAME = 0x08 (05 len text...)\r
+        elif meta_type == PROGRAM_NAME:\r
+            stream.program_name(data)\r
+        \r
+        # DEVICE_NAME = 0x09 (09 len text...)\r
+        elif meta_type == DEVICE_NAME:\r
+            stream.device_name(data)\r
+        \r
+        # MIDI_CH_PREFIX = 0x20 (20 01 channel)\r
+        elif meta_type == MIDI_CH_PREFIX:\r
+            channel = readBew(data)\r
+            stream.midi_ch_prefix(channel)\r
+        \r
+        # MIDI_PORT  = 0x21 (21 01 port (legacy stuff))\r
+        elif meta_type == MIDI_PORT:\r
+            port = readBew(data)\r
+            stream.midi_port(port)\r
+        \r
+        # END_OFF_TRACK = 0x2F (2F 00)\r
+        elif meta_type == END_OF_TRACK:\r
+            stream.end_of_track()\r
+        \r
+        # TEMPO = 0x51 (51 03 tt tt tt (tempo in us/quarternote))\r
+        elif meta_type == TEMPO:\r
+            b1, b2, b3 = toBytes(data)\r
+            # uses 3 bytes to represent time between quarter \r
+            # notes in microseconds\r
+            stream.tempo((b1<<16) + (b2<<8) + b3)\r
+        \r
+        # SMTP_OFFSET = 0x54 (54 05 hh mm ss ff xx)\r
+        elif meta_type == SMTP_OFFSET:\r
+            hour, minute, second, frame, framePart = toBytes(data)\r
+            stream.smtp_offset(\r
+                    hour, minute, second, frame, framePart)\r
+        \r
+        # TIME_SIGNATURE = 0x58 (58 04 nn dd cc bb)\r
+        elif meta_type == TIME_SIGNATURE:\r
+            nn, dd, cc, bb = toBytes(data)\r
+            stream.time_signature(nn, dd, cc, bb)\r
+        \r
+        # KEY_SIGNATURE = 0x59 (59 02 sf mi)\r
+        elif meta_type == KEY_SIGNATURE:\r
+            sf, mi = toBytes(data)\r
+            stream.key_signature(sf, mi)\r
+        \r
+        # SPECIFIC = 0x7F (Sequencer specific event)\r
+        elif meta_type == SPECIFIC:\r
+            meta_data = toBytes(data)\r
+            stream.sequencer_specific(meta_data)\r
+        \r
+        # Handles any undefined meta events\r
+        else: # undefined meta type\r
+            meta_data = toBytes(data)\r
+            stream.meta_event(meta_type, meta_data)\r
+\r
+\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+\r
+    from MidiToText import MidiToText\r
+    \r
+    outstream = MidiToText()\r
+    dispatcher = EventDispatcher(outstream)\r
+    dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')
\ No newline at end of file
diff --git a/src/mxmMidi/EventDispatcher.pyc b/src/mxmMidi/EventDispatcher.pyc
new file mode 100644 (file)
index 0000000..2657b04
Binary files /dev/null and b/src/mxmMidi/EventDispatcher.pyc differ
diff --git a/src/mxmMidi/MidiFileParser.py b/src/mxmMidi/MidiFileParser.py
new file mode 100644 (file)
index 0000000..2b447bc
--- /dev/null
@@ -0,0 +1,192 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+# std library\r
+from struct import unpack\r
+\r
+# uhh I don't really like this, but there are so many constants to \r
+# import otherwise\r
+from constants import *\r
+\r
+from EventDispatcher import EventDispatcher\r
+\r
+class MidiFileParser:\r
+\r
+    """\r
+    \r
+    The MidiFileParser is the lowest level parser that see the data as \r
+    midi data. It generates events that gets triggered on the outstream.\r
+    \r
+    """\r
+\r
+    def __init__(self, raw_in, outstream):\r
+\r
+        """\r
+        raw_data is the raw content of a midi file as a string.\r
+        """\r
+\r
+        # internal values, don't mess with 'em directly\r
+        self.raw_in = raw_in\r
+        self.dispatch = EventDispatcher(outstream)\r
+\r
+        # Used to keep track of stuff\r
+        self._running_status = None\r
+\r
+\r
+\r
+\r
+    def parseMThdChunk(self):\r
+        \r
+        "Parses the header chunk"\r
+        \r
+        raw_in = self.raw_in\r
+\r
+        header_chunk_type = raw_in.nextSlice(4)\r
+        header_chunk_zise = raw_in.readBew(4)\r
+\r
+        # check if it is a proper midi file\r
+        if header_chunk_type != 'MThd':\r
+            raise TypeError, "It is not a valid midi file!"\r
+\r
+        # Header values are at fixed locations, so no reason to be clever\r
+        self.format = raw_in.readBew(2)\r
+        self.nTracks = raw_in.readBew(2)\r
+        self.division = raw_in.readBew(2)\r
+        \r
+        # Theoretically a header larger than 6 bytes can exist\r
+        # but no one has seen one in the wild\r
+        # But correctly ignore unknown data if it is though\r
+        if header_chunk_zise > 6:\r
+            raw_in.moveCursor(header_chunk_zise-6)\r
+\r
+        # call the header event handler on the stream\r
+        self.dispatch.header(self.format, self.nTracks, self.division)\r
+\r
+\r
+\r
+    def parseMTrkChunk(self):\r
+        \r
+        "Parses a track chunk. This is the most important part of the parser."\r
+        \r
+        # set time to 0 at start of a track\r
+        self.dispatch.reset_time()\r
+        \r
+        dispatch = self.dispatch\r
+        raw_in = self.raw_in\r
+        \r
+        # Trigger event at the start of a track\r
+        dispatch.start_of_track(self._current_track)\r
+        # position cursor after track header\r
+        raw_in.moveCursor(4)\r
+        # unsigned long is 4 bytes\r
+        tracklength = raw_in.readBew(4)\r
+        track_endposition = raw_in.getCursor() + tracklength # absolute position!\r
+\r
+        while raw_in.getCursor() < track_endposition:\r
+        \r
+            # find relative time of the event\r
+            time = raw_in.readVarLen()\r
+            dispatch.update_time(time)\r
+            \r
+            # be aware of running status!!!!\r
+            peak_ahead = raw_in.readBew(move_cursor=0)\r
+            if (peak_ahead & 0x80): \r
+                # the status byte has the high bit set, so it\r
+                # was not running data but proper status byte\r
+                status = self._running_status = raw_in.readBew()\r
+            else:\r
+                # use that darn running status\r
+                status = self._running_status\r
+                # could it be illegal data ?? Do we need to test for that?\r
+                # I need more example midi files to be shure.\r
+                \r
+                # Also, while I am almost certain that no realtime \r
+                # messages will pop up in a midi file, I might need to \r
+                # change my mind later.\r
+\r
+            # we need to look at nibbles here\r
+            hi_nible, lo_nible = status & 0xF0, status & 0x0F\r
+            \r
+            # match up with events\r
+\r
+            # Is it a meta_event ??\r
+            # these only exists in midi files, not in transmitted midi data\r
+            # In transmitted data META_EVENT (0xFF) is a system reset\r
+            if status == META_EVENT:\r
+                meta_type = raw_in.readBew()\r
+                meta_length = raw_in.readVarLen()\r
+                meta_data = raw_in.nextSlice(meta_length)\r
+                dispatch.meta_event(meta_type, meta_data)\r
+\r
+\r
+            # Is it a sysex_event ??\r
+            elif status == SYSTEM_EXCLUSIVE:\r
+                # ignore sysex events\r
+                sysex_length = raw_in.readVarLen()\r
+                # don't read sysex terminator\r
+                sysex_data = raw_in.nextSlice(sysex_length-1)\r
+                # only read last data byte if it is a sysex terminator\r
+                # It should allways be there, but better safe than sorry\r
+                if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:\r
+                    eo_sysex = raw_in.readBew()\r
+                dispatch.sysex_event(sysex_data)\r
+                # the sysex code has not been properly tested, and might be fishy!\r
+\r
+\r
+            # is it a system common event?\r
+            elif hi_nible == 0xF0: # Hi bits are set then\r
+                data_sizes = {\r
+                    MTC:1,\r
+                    SONG_POSITION_POINTER:2,\r
+                    SONG_SELECT:1,\r
+                }\r
+                data_size = data_sizes.get(hi_nible, 0)\r
+                common_data = raw_in.nextSlice(data_size)\r
+                common_type = lo_nible\r
+                dispatch.system_common(common_type, common_data)\r
+            \r
+\r
+            # Oh! Then it must be a midi event (channel voice message)\r
+            else:\r
+                data_sizes = {\r
+                    PATCH_CHANGE:1,\r
+                    CHANNEL_PRESSURE:1,\r
+                    NOTE_OFF:2,\r
+                    NOTE_ON:2,\r
+                    AFTERTOUCH:2,\r
+                    CONTINUOUS_CONTROLLER:2,\r
+                    PITCH_BEND:2,\r
+                }\r
+                data_size = data_sizes.get(hi_nible, 0)\r
+                channel_data = raw_in.nextSlice(data_size)\r
+                event_type, channel = hi_nible, lo_nible\r
+                dispatch.channel_messages(event_type, channel, channel_data)\r
+\r
+\r
+    def parseMTrkChunks(self):\r
+        "Parses all track chunks."\r
+        for t in range(self.nTracks):\r
+            self._current_track = t\r
+            self.parseMTrkChunk() # this is where it's at!\r
+        self.dispatch.eof()\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    # get data\r
+    test_file = 'test/midifiles/minimal.mid'\r
+    test_file = 'test/midifiles/cubase-minimal.mid'\r
+    test_file = 'test/midifiles/Lola.mid'\r
+#    f = open(test_file, 'rb')\r
+#    raw_data = f.read()\r
+#    f.close()\r
+#    \r
+#    \r
+#    # do parsing\r
+    from MidiToText import MidiToText\r
+    from RawInstreamFile import RawInstreamFile\r
+\r
+    midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText())\r
+    midi_in.parseMThdChunk()\r
+    midi_in.parseMTrkChunks()\r
+    
\ No newline at end of file
diff --git a/src/mxmMidi/MidiFileParser.pyc b/src/mxmMidi/MidiFileParser.pyc
new file mode 100644 (file)
index 0000000..f6a7165
Binary files /dev/null and b/src/mxmMidi/MidiFileParser.pyc differ
diff --git a/src/mxmMidi/MidiInFile.py b/src/mxmMidi/MidiInFile.py
new file mode 100644 (file)
index 0000000..57f4c7e
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+from RawInstreamFile import RawInstreamFile\r
+from MidiFileParser import MidiFileParser\r
+\r
+\r
+class MidiInFile:\r
+\r
+    """\r
+    \r
+    Parses a midi file, and triggers the midi events on the outStream \r
+    object.\r
+    \r
+    Get example data from a minimal midi file, generated with cubase.\r
+    >>> test_file = 'C:/Documents and Settings/maxm/Desktop/temp/midi/src/midi/tests/midifiles/minimal-cubase-type0.mid'\r
+    \r
+    Do parsing, and generate events with MidiToText,\r
+    so we can see what a minimal midi file contains\r
+    >>> from MidiToText import MidiToText\r
+    >>> midi_in = MidiInFile(MidiToText(), test_file)\r
+    >>> midi_in.read()\r
+    format: 0, nTracks: 1, division: 480\r
+    ----------------------------------\r
+    <BLANKLINE>\r
+    Start - track #0\r
+    sequence_name: Type 0\r
+    tempo: 500000\r
+    time_signature: 4 2 24 8\r
+    note_on  - ch:00,  note:48,  vel:64 time:0\r
+    note_off - ch:00,  note:48,  vel:40 time:480\r
+    End of track\r
+    <BLANKLINE>\r
+    End of file\r
+    \r
+    \r
+    """\r
+\r
+    def __init__(self, outStream, infile):\r
+        # these could also have been mixins, would that be better? Nah!\r
+        self.raw_in = RawInstreamFile(infile)\r
+        self.parser = MidiFileParser(self.raw_in, outStream)\r
+\r
+\r
+    def read(self):\r
+        "Start parsing the file"\r
+        p = self.parser\r
+        p.parseMThdChunk()\r
+        p.parseMTrkChunks()\r
+\r
+\r
+    def setData(self, data=''):\r
+        "Sets the data from a plain string"\r
+        self.raw_in.setData(data)\r
+    \r
+    \r
diff --git a/src/mxmMidi/MidiInFile.pyc b/src/mxmMidi/MidiInFile.pyc
new file mode 100644 (file)
index 0000000..182efaa
Binary files /dev/null and b/src/mxmMidi/MidiInFile.pyc differ
diff --git a/src/mxmMidi/MidiInStream.py b/src/mxmMidi/MidiInStream.py
new file mode 100644 (file)
index 0000000..22f7e09
--- /dev/null
@@ -0,0 +1,52 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+from MidiOutStream import MidiOutStream\r
+\r
+class MidiInStream:\r
+\r
+    """\r
+    Takes midi events from the midi input and calls the apropriate\r
+    method in the eventhandler object\r
+    """\r
+\r
+    def __init__(self, midiOutStream, device):\r
+\r
+        """\r
+\r
+        Sets a default output stream, and sets the device from where\r
+        the input comes\r
+\r
+        """\r
+\r
+        if midiOutStream is None:\r
+            self.midiOutStream = MidiOutStream()\r
+        else:\r
+            self.midiOutStream = midiOutStream\r
+\r
+\r
+    def close(self):\r
+\r
+        """\r
+        Stop the MidiInstream\r
+        """\r
+\r
+\r
+    def read(self, time=0):\r
+\r
+        """\r
+\r
+        Start the MidiInstream.\r
+\r
+        "time" sets timer to specific start value.\r
+\r
+        """\r
+\r
+\r
+    def resetTimer(self, time=0):\r
+        """\r
+\r
+        Resets the timer, probably a good idea if there is some kind\r
+        of looping going on\r
+\r
+        """\r
+\r
diff --git a/src/mxmMidi/MidiOutFile.py b/src/mxmMidi/MidiOutFile.py
new file mode 100644 (file)
index 0000000..a39bc9f
--- /dev/null
@@ -0,0 +1,448 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+from MidiOutStream import MidiOutStream\r
+from RawOutstreamFile import RawOutstreamFile\r
+\r
+from constants import *\r
+from DataTypeConverters import fromBytes, writeVar\r
+\r
+class MidiOutFile(MidiOutStream):\r
+\r
+\r
+    """\r
+    MidiOutFile is an eventhandler that subclasses MidiOutStream.\r
+    """\r
+\r
+\r
+    def __init__(self, raw_out=''):\r
+\r
+        self.raw_out = RawOutstreamFile(raw_out)\r
+        MidiOutStream.__init__(self)\r
+        \r
+    \r
+    def write(self):\r
+        self.raw_out.write()\r
+\r
+\r
+    def event_slice(self, slc):\r
+        """\r
+        Writes the slice of an event to the current track. Correctly \r
+        inserting a varlen timestamp too.\r
+        """\r
+        trk = self._current_track_buffer\r
+        trk.writeVarLen(self.rel_time())\r
+        trk.writeSlice(slc)\r
+        \r
+    \r
+    #####################\r
+    ## Midi events\r
+\r
+\r
+    def note_on(self, channel=0, note=0x40, velocity=0x40):\r
+\r
+        """\r
+        channel: 0-15\r
+        note, velocity: 0-127\r
+        """\r
+        slc = fromBytes([NOTE_ON + channel, note, velocity])\r
+        self.event_slice(slc)\r
+\r
+\r
+    def note_off(self, channel=0, note=0x40, velocity=0x40):\r
+\r
+        """\r
+        channel: 0-15\r
+        note, velocity: 0-127\r
+        """\r
+        slc = fromBytes([NOTE_OFF + channel, note, velocity])\r
+        self.event_slice(slc)\r
+\r
+\r
+    def aftertouch(self, channel=0, note=0x40, velocity=0x40):\r
+\r
+        """\r
+        channel: 0-15\r
+        note, velocity: 0-127\r
+        """\r
+        slc = fromBytes([AFTERTOUCH + channel, note, velocity])\r
+        self.event_slice(slc)\r
+\r
+\r
+    def continuous_controller(self, channel, controller, value):\r
+\r
+        """\r
+        channel: 0-15\r
+        controller, value: 0-127\r
+        """\r
+        slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])\r
+        self.event_slice(slc)\r
+        # These should probably be implemented\r
+        # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums\r
+\r
+\r
+    def patch_change(self, channel, patch):\r
+\r
+        """\r
+        channel: 0-15\r
+        patch: 0-127\r
+        """\r
+        slc = fromBytes([PATCH_CHANGE + channel, patch])\r
+        self.event_slice(slc)\r
+\r
+\r
+    def channel_pressure(self, channel, pressure):\r
+\r
+        """\r
+        channel: 0-15\r
+        pressure: 0-127\r
+        """\r
+        slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])\r
+        self.event_slice(slc)\r
+\r
+\r
+    def pitch_bend(self, channel, value):\r
+\r
+        """\r
+        channel: 0-15\r
+        value: 0-16383\r
+        """\r
+        msb = (value>>7) & 0xFF\r
+        lsb = value & 0xFF\r
+        slc = fromBytes([PITCH_BEND + channel, msb, lsb])\r
+        self.event_slice(slc)\r
+\r
+\r
+\r
+\r
+    #####################\r
+    ## System Exclusive\r
+\r
+#    def sysex_slice(sysex_type, data):\r
+#        ""\r
+#        sysex_len = writeVar(len(data)+1)\r
+#        self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE)\r
+#\r
+    def system_exclusive(self, data):\r
+\r
+        """\r
+        data: list of values in range(128)\r
+        """\r
+        sysex_len = writeVar(len(data)+1)\r
+        self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))\r
+\r
+\r
+    #####################\r
+    ## Common events\r
+\r
+    def midi_time_code(self, msg_type, values):\r
+        """\r
+        msg_type: 0-7\r
+        values: 0-15\r
+        """\r
+        value = (msg_type<<4) + values\r
+        self.event_slice(fromBytes([MIDI_TIME_CODE, value]))\r
+\r
+\r
+    def song_position_pointer(self, value):\r
+\r
+        """\r
+        value: 0-16383\r
+        """\r
+        lsb = (value & 0x7F)\r
+        msb = (value >> 7) & 0x7F\r
+        self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))\r
+\r
+\r
+    def song_select(self, songNumber):\r
+\r
+        """\r
+        songNumber: 0-127\r
+        """\r
+        self.event_slice(fromBytes([SONG_SELECT, songNumber]))\r
+\r
+\r
+    def tuning_request(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        self.event_slice(chr(TUNING_REQUEST))\r
+\r
+            \r
+    #########################\r
+    # header does not really belong here. But anyhoo!!!\r
+    \r
+    def header(self, format=0, nTracks=1, division=96):\r
+\r
+        """\r
+        format: type of midi file in [0,1,2]\r
+        nTracks: number of tracks. 1 track for type 0 file\r
+        division: timing division ie. 96 ppq.\r
+        \r
+        """        \r
+        raw = self.raw_out\r
+        raw.writeSlice('MThd')\r
+        bew = raw.writeBew\r
+        bew(6, 4) # header size\r
+        bew(format, 2)\r
+        bew(nTracks, 2)\r
+        bew(division, 2)\r
+\r
+\r
+    def eof(self):\r
+\r
+        """\r
+        End of file. No more events to be processed.\r
+        """\r
+        # just write the file then.\r
+        self.write()\r
+\r
+\r
+    #####################\r
+    ## meta events\r
+\r
+\r
+    def meta_slice(self, meta_type, data_slice):\r
+        "Writes a meta event"\r
+        slc = fromBytes([META_EVENT, meta_type]) + \\r
+                         writeVar(len(data_slice)) +  data_slice\r
+        self.event_slice(slc)\r
+\r
+\r
+    def meta_event(self, meta_type, data):\r
+        """\r
+        Handles any undefined meta events\r
+        """\r
+        self.meta_slice(meta_type, fromBytes(data))\r
+\r
+\r
+    def start_of_track(self, n_track=0):\r
+        """\r
+        n_track: number of track\r
+        """\r
+        self._current_track_buffer = RawOutstreamFile()\r
+        self.reset_time()\r
+        self._current_track += 1\r
+\r
+\r
+    def end_of_track(self):\r
+        """\r
+        Writes the track to the buffer.\r
+        """\r
+        raw = self.raw_out\r
+        raw.writeSlice(TRACK_HEADER)\r
+        track_data = self._current_track_buffer.getvalue()\r
+        # wee need to know size of track data.\r
+        eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])\r
+        raw.writeBew(len(track_data)+len(eot_slice), 4)\r
+        # then write\r
+        raw.writeSlice(track_data)\r
+        raw.writeSlice(eot_slice)\r
+        \r
+\r
+\r
+    def sequence_number(self, value):\r
+\r
+        """\r
+        value: 0-65535\r
+        """\r
+        self.meta_slice(meta_type, writeBew(value, 2))\r
+\r
+\r
+    def text(self, text):\r
+        """\r
+        Text event\r
+        text: string\r
+        """\r
+        self.meta_slice(TEXT, text)\r
+\r
+\r
+    def copyright(self, text):\r
+\r
+        """\r
+        Copyright notice\r
+        text: string\r
+        """\r
+        self.meta_slice(COPYRIGHT, text)\r
+\r
+\r
+    def sequence_name(self, text):\r
+        """\r
+        Sequence/track name\r
+        text: string\r
+        """\r
+        self.meta_slice(SEQUENCE_NAME, text)\r
+\r
+\r
+    def instrument_name(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        self.meta_slice(INSTRUMENT_NAME, text)\r
+\r
+\r
+    def lyric(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        self.meta_slice(LYRIC, text)\r
+\r
+\r
+    def marker(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        self.meta_slice(MARKER, text)\r
+\r
+\r
+    def cuepoint(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        self.meta_slice(CUEPOINT, text)\r
+\r
+\r
+    def midi_ch_prefix(self, channel):\r
+\r
+        """\r
+        channel: midi channel for subsequent data\r
+        (deprecated in the spec)\r
+        """\r
+        self.meta_slice(MIDI_CH_PREFIX, chr(channel))\r
+\r
+\r
+    def midi_port(self, value):\r
+\r
+        """\r
+        value: Midi port (deprecated in the spec)\r
+        """\r
+        self.meta_slice(MIDI_CH_PREFIX, chr(value))\r
+\r
+\r
+    def tempo(self, value):\r
+\r
+        """\r
+        value: 0-2097151\r
+        tempo in us/quarternote\r
+        (to calculate value from bpm: int(60,000,000.00 / BPM))\r
+        """\r
+        hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)\r
+        self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))\r
+\r
+\r
+    def smtp_offset(self, hour, minute, second, frame, framePart):\r
+\r
+        """\r
+        hour,\r
+        minute,\r
+        second: 3 bytes specifying the hour (0-23), minutes (0-59) and \r
+                seconds (0-59), respectively. The hour should be \r
+                encoded with the SMPTE format, just as it is in MIDI \r
+                Time Code.\r
+        frame: A byte specifying the number of frames per second (one \r
+               of : 24, 25, 29, 30).\r
+        framePart: A byte specifying the number of fractional frames, \r
+                   in 100ths of a frame (even in SMPTE-based tracks \r
+                   using a different frame subdivision, defined in the \r
+                   MThd chunk).\r
+        """\r
+        self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))\r
+\r
+\r
+\r
+    def time_signature(self, nn, dd, cc, bb):\r
+\r
+        """\r
+        nn: Numerator of the signature as notated on sheet music\r
+        dd: Denominator of the signature as notated on sheet music\r
+            The denominator is a negative power of 2: 2 = quarter \r
+            note, 3 = eighth, etc.\r
+        cc: The number of MIDI clocks in a metronome click\r
+        bb: The number of notated 32nd notes in a MIDI quarter note \r
+            (24 MIDI clocks)        \r
+        """\r
+        self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))\r
+\r
+\r
+\r
+\r
+    def key_signature(self, sf, mi):\r
+\r
+        """\r
+        sf: is a byte specifying the number of flats (-ve) or sharps \r
+            (+ve) that identifies the key signature (-7 = 7 flats, -1 \r
+            = 1 flat, 0 = key of C, 1 = 1 sharp, etc).\r
+        mi: is a byte specifying a major (0) or minor (1) key.\r
+        """\r
+        self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))\r
+\r
+\r
+\r
+    def sequencer_specific(self, data):\r
+\r
+        """\r
+        data: The data as byte values\r
+        """\r
+        self.meta_slice(SEQUENCER_SPECIFIC, data)\r
+\r
+\r
+\r
+\r
+\r
+#    #####################\r
+#    ## realtime events\r
+\r
+#    These are of no use in a midi file, so they are ignored!!!\r
+\r
+#    def timing_clock(self):\r
+#    def song_start(self):\r
+#    def song_stop(self):\r
+#    def song_continue(self):\r
+#    def active_sensing(self):\r
+#    def system_reset(self):\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    out_file = 'test/midifiles/midiout.mid'\r
+    midi = MidiOutFile(out_file)\r
+\r
+#format: 0, nTracks: 1, division: 480\r
+#----------------------------------\r
+#\r
+#Start - track #0\r
+#sequence_name: Type 0\r
+#tempo: 500000\r
+#time_signature: 4 2 24 8\r
+#note_on  - ch:00,  note:48,  vel:64 time:0\r
+#note_off - ch:00,  note:48,  vel:40 time:480\r
+#End of track\r
+#\r
+#End of file\r
+\r
+\r
+    midi.header(0, 1, 480)\r
+    \r
+    midi.start_of_track()\r
+    midi.sequence_name('Type 0')\r
+    midi.tempo(750000)\r
+    midi.time_signature(4, 2, 24, 8)\r
+    ch = 0\r
+    for i in range(127):\r
+        midi.note_on(ch, i, 0x64)\r
+        midi.update_time(96)\r
+        midi.note_off(ch, i, 0x40)\r
+        midi.update_time(0)\r
+    \r
+    midi.update_time(0)\r
+    midi.end_of_track()\r
+    \r
+    midi.eof() # currently optional, should it do the write instead of write??\r
+\r
+\r
+    midi.write()
\ No newline at end of file
diff --git a/src/mxmMidi/MidiOutStream.py b/src/mxmMidi/MidiOutStream.py
new file mode 100644 (file)
index 0000000..c128fa6
--- /dev/null
@@ -0,0 +1,471 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+class MidiOutStream:\r
+\r
+\r
+    """\r
+\r
+    MidiOutstream is Basically an eventhandler. It is the most central\r
+    class in the Midi library. You use it both for writing events to\r
+    an output stream, and as an event handler for an input stream.\r
+\r
+    This makes it extremely easy to take input from one stream and\r
+    send it to another. Ie. if you want to read a Midi file, do some\r
+    processing, and send it to a midiport.\r
+\r
+    All time values are in absolute values from the opening of a\r
+    stream. To calculate time values, please use the MidiTime and\r
+    MidiDeltaTime classes.\r
+\r
+    """\r
+\r
+    def __init__(self):\r
+        \r
+        # the time is rather global, so it needs to be stored \r
+        # here. Otherwise there would be no really simple way to \r
+        # calculate it. The alternative would be to have each event \r
+        # handler do it. That sucks even worse!\r
+        self._absolute_time = 0\r
+        self._relative_time = 0\r
+        self._current_track = 0\r
+        self._running_status = None\r
+\r
+    # time handling event handlers. They should be overwritten with care\r
+\r
+    def update_time(self, new_time=0, relative=1):\r
+        """\r
+        Updates the time, if relative is true, new_time is relative, \r
+        else it's absolute.\r
+        """\r
+        if relative:\r
+            self._relative_time = new_time\r
+            self._absolute_time += new_time\r
+        else:\r
+            self._relative_time = new_time - self._absolute_time\r
+            self._absolute_time = new_time\r
+\r
+    def reset_time(self):\r
+        """\r
+        reset time to 0\r
+        """\r
+        self._relative_time = 0\r
+        self._absolute_time = 0\r
+        \r
+    def rel_time(self):\r
+        "Returns the relative time"\r
+        return self._relative_time\r
+\r
+    def abs_time(self):\r
+        "Returns the absolute time"\r
+        return self._absolute_time\r
+\r
+    # running status methods\r
+    \r
+    def reset_run_stat(self):\r
+        "Invalidates the running status"\r
+        self._running_status = None\r
+\r
+    def set_run_stat(self, new_status):\r
+        "Set the new running status"\r
+        self._running_status = new_status\r
+\r
+    def get_run_stat(self):\r
+        "Set the new running status"\r
+        return self._running_status\r
+\r
+    # track handling event handlers\r
+    \r
+    def set_current_track(self, new_track):\r
+        "Sets the current track number"\r
+        self._current_track = new_track\r
+    \r
+    def get_current_track(self):\r
+        "Returns the current track number"\r
+        return self._current_track\r
+    \r
+    \r
+    #####################\r
+    ## Midi events\r
+\r
+\r
+    def channel_message(self, message_type, channel, data):\r
+        """The default event handler for channel messages"""\r
+        pass\r
+\r
+\r
+    def note_on(self, channel=0, note=0x40, velocity=0x40):\r
+\r
+        """\r
+        channel: 0-15\r
+        note, velocity: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def note_off(self, channel=0, note=0x40, velocity=0x40):\r
+\r
+        """\r
+        channel: 0-15\r
+        note, velocity: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def aftertouch(self, channel=0, note=0x40, velocity=0x40):\r
+\r
+        """\r
+        channel: 0-15\r
+        note, velocity: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def continuous_controller(self, channel, controller, value):\r
+\r
+        """\r
+        channel: 0-15\r
+        controller, value: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def patch_change(self, channel, patch):\r
+\r
+        """\r
+        channel: 0-15\r
+        patch: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def channel_pressure(self, channel, pressure):\r
+\r
+        """\r
+        channel: 0-15\r
+        pressure: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def pitch_bend(self, channel, value):\r
+\r
+        """\r
+        channel: 0-15\r
+        value: 0-16383\r
+\r
+        """\r
+        pass\r
+\r
+\r
+\r
+\r
+    #####################\r
+    ## System Exclusive\r
+\r
+    def system_exclusive(self, data):\r
+\r
+        """\r
+        data: list of values in range(128)\r
+        """\r
+        pass\r
+\r
+\r
+    #####################\r
+    ## Common events\r
+\r
+    def song_position_pointer(self, value):\r
+\r
+        """\r
+        value: 0-16383\r
+        """\r
+        pass\r
+\r
+\r
+    def song_select(self, songNumber):\r
+\r
+        """\r
+        songNumber: 0-127\r
+        """\r
+        pass\r
+\r
+\r
+    def tuning_request(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+            \r
+    def midi_time_code(self, msg_type, values):\r
+        """\r
+        msg_type: 0-7\r
+        values: 0-15\r
+        """\r
+        pass\r
+\r
+\r
+    #########################\r
+    # header does not really belong here. But anyhoo!!!\r
+    \r
+    def header(self, format=0, nTracks=1, division=96):\r
+\r
+        """\r
+        format: type of midi file in [1,2]\r
+        nTracks: number of tracks\r
+        division: timing division\r
+        """\r
+        pass\r
+\r
+\r
+    def eof(self):\r
+\r
+        """\r
+        End of file. No more events to be processed.\r
+        """\r
+        pass\r
+\r
+\r
+    #####################\r
+    ## meta events\r
+\r
+\r
+    def meta_event(self, meta_type, data):\r
+        \r
+        """\r
+        Handles any undefined meta events\r
+        """\r
+        pass\r
+\r
+\r
+    def start_of_track(self, n_track=0):\r
+\r
+        """\r
+        n_track: number of track\r
+        """\r
+        pass\r
+\r
+\r
+    def end_of_track(self):\r
+\r
+        """\r
+        n_track: number of track\r
+        """\r
+        pass\r
+\r
+\r
+    def sequence_number(self, value):\r
+\r
+        """\r
+        value: 0-16383\r
+        """\r
+        pass\r
+\r
+\r
+    def text(self, text):\r
+\r
+        """\r
+        Text event\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def copyright(self, text):\r
+\r
+        """\r
+        Copyright notice\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def sequence_name(self, text):\r
+\r
+        """\r
+        Sequence/track name\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def instrument_name(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def lyric(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def marker(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def cuepoint(self, text):\r
+\r
+        """\r
+        text: string\r
+        """\r
+        pass\r
+\r
+\r
+    def midi_ch_prefix(self, channel):\r
+\r
+        """\r
+        channel: midi channel for subsequent data (deprecated in the spec)\r
+        """\r
+        pass\r
+\r
+\r
+    def midi_port(self, value):\r
+\r
+        """\r
+        value: Midi port (deprecated in the spec)\r
+        """\r
+        pass\r
+\r
+\r
+    def tempo(self, value):\r
+\r
+        """\r
+        value: 0-2097151\r
+        tempo in us/quarternote\r
+        (to calculate value from bpm: int(60,000,000.00 / BPM))\r
+        """\r
+        pass\r
+\r
+\r
+    def smtp_offset(self, hour, minute, second, frame, framePart):\r
+\r
+        """\r
+        hour,\r
+        minute,\r
+        second: 3 bytes specifying the hour (0-23), minutes (0-59) and \r
+                seconds (0-59), respectively. The hour should be \r
+                encoded with the SMPTE format, just as it is in MIDI \r
+                Time Code.\r
+        frame: A byte specifying the number of frames per second (one \r
+               of : 24, 25, 29, 30).\r
+        framePart: A byte specifying the number of fractional frames, \r
+                   in 100ths of a frame (even in SMPTE-based tracks \r
+                   using a different frame subdivision, defined in the \r
+                   MThd chunk).\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def time_signature(self, nn, dd, cc, bb):\r
+\r
+        """\r
+        nn: Numerator of the signature as notated on sheet music\r
+        dd: Denominator of the signature as notated on sheet music\r
+            The denominator is a negative power of 2: 2 = quarter \r
+            note, 3 = eighth, etc.\r
+        cc: The number of MIDI clocks in a metronome click\r
+        bb: The number of notated 32nd notes in a MIDI quarter note \r
+            (24 MIDI clocks)        \r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def key_signature(self, sf, mi):\r
+\r
+        """\r
+        sf: is a byte specifying the number of flats (-ve) or sharps \r
+            (+ve) that identifies the key signature (-7 = 7 flats, -1 \r
+            = 1 flat, 0 = key of C, 1 = 1 sharp, etc).\r
+        mi: is a byte specifying a major (0) or minor (1) key.\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def sequencer_specific(self, data):\r
+\r
+        """\r
+        data: The data as byte values\r
+        """\r
+        pass\r
+\r
+\r
+\r
+\r
+    #####################\r
+    ## realtime events\r
+\r
+    def timing_clock(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def song_start(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def song_stop(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def song_continue(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def active_sensing(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+\r
+\r
+    def system_reset(self):\r
+\r
+        """\r
+        No values passed\r
+        """\r
+        pass\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    midiOut = MidiOutStream()\r
+    midiOut.update_time(0,0)\r
+    midiOut.note_on(0, 63, 127)\r
+    midiOut.note_off(0, 63, 127)\r
+\r
+    
\ No newline at end of file
diff --git a/src/mxmMidi/MidiOutStream.pyc b/src/mxmMidi/MidiOutStream.pyc
new file mode 100644 (file)
index 0000000..53ceb24
Binary files /dev/null and b/src/mxmMidi/MidiOutStream.pyc differ
diff --git a/src/mxmMidi/MidiToText.py b/src/mxmMidi/MidiToText.py
new file mode 100644 (file)
index 0000000..7a35ff3
--- /dev/null
@@ -0,0 +1,184 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+from MidiOutStream import MidiOutStream\r
+class MidiToText(MidiOutStream):\r
+\r
+\r
+    """\r
+    This class renders a midi file as text. It is mostly used for debugging\r
+    """\r
+\r
+    #############################\r
+    # channel events\r
+    \r
+    def channel_message(self, message_type, channel, data):\r
+        """The default event handler for channel messages"""\r
+        print 'message_type:%X, channel:%X, data size:%X' % (message_type, channel, len(data))\r
+\r
+\r
+    def note_on(self, channel=0, note=0x40, velocity=0x40):\r
+        print 'note_on  - ch:%02X,  note:%02X,  vel:%02X time:%s' % (channel, note, velocity, self.timeInMs())\r
+\r
+    def note_off(self, channel=0, note=0x40, velocity=0x40):\r
+        print 'note_off - ch:%02X,  note:%02X,  vel:%02X time:%s' % (channel, note, velocity, self.abs_time())\r
+\r
+    def aftertouch(self, channel=0, note=0x40, velocity=0x40):\r
+        print 'aftertouch', channel, note, velocity\r
+\r
+\r
+    def continuous_controller(self, channel, controller, value):\r
+        print 'controller - ch: %02X, cont: #%02X, value: %02X' % (channel, controller, value)\r
+\r
+\r
+    def patch_change(self, channel, patch):\r
+        print 'patch_change - ch:%02X, patch:%02X' % (channel, patch)\r
+\r
+\r
+    def channel_pressure(self, channel, pressure):\r
+        print 'channel_pressure', channel, pressure\r
+\r
+\r
+    def pitch_bend(self, channel, value):\r
+        print 'pitch_bend ch:%s, value:%s' % (channel, value)\r
+\r
+\r
+\r
+    #####################\r
+    ## Common events\r
+\r
+\r
+    def system_exclusive(self, data):\r
+        print 'system_exclusive - data size: %s' % len(data)\r
+\r
+\r
+    def song_position_pointer(self, value):\r
+        print 'song_position_pointer: %s' % value\r
+\r
+\r
+    def song_select(self, songNumber):\r
+        print 'song_select: %s' % songNumber\r
+\r
+\r
+    def tuning_request(self):\r
+        print 'tuning_request'\r
+\r
+\r
+    def midi_time_code(self, msg_type, values):\r
+        print 'midi_time_code - msg_type: %s, values: %s' % (msg_type, values)\r
+\r
+\r
+\r
+    #########################\r
+    # header does not really belong here. But anyhoo!!!\r
+\r
+    def header(self, format=0, nTracks=1, division=96):\r
+        print 'format: %s, nTracks: %s, division: %s' % (format, nTracks, division)\r
+        print '----------------------------------'\r
+        print ''\r
+        print division\r
+        self.division = division\r
+\r
+    def eof(self):\r
+        print 'End of file'\r
+\r
+\r
+    def start_of_track(self, n_track=0):\r
+        print 'Start - track #%s' % n_track\r
+\r
+\r
+    def end_of_track(self):\r
+        print 'End of track'\r
+        print ''\r
+\r
+\r
+\r
+    ###############\r
+    # sysex event\r
+\r
+    def sysex_event(self, data):\r
+        print 'sysex_event - datasize: %X' % len(data)\r
+\r
+\r
+    #####################\r
+    ## meta events\r
+\r
+    def meta_event(self, meta_type, data):\r
+        print 'undefined_meta_event:', meta_type, len(data)\r
+\r
+\r
+    def sequence_number(self, value):\r
+        print 'sequence_number', value\r
+\r
+\r
+    def text(self, text):\r
+        print 'text', text\r
+\r
+\r
+    def copyright(self, text):\r
+        print 'copyright', text\r
+\r
+\r
+    def sequence_name(self, text):\r
+        print 'sequence_name:', text\r
+\r
+\r
+    def instrument_name(self, text):\r
+        print 'instrument_name:', text\r
+\r
+\r
+    def lyric(self, text):\r
+        print 'lyric', text\r
+\r
+\r
+    def marker(self, text):\r
+        print 'marker', text\r
+\r
+\r
+    def cuepoint(self, text):\r
+        print 'cuepoint', text\r
+\r
+\r
+    def midi_ch_prefix(self, channel):\r
+        print 'midi_ch_prefix', channel\r
+\r
+\r
+    def midi_port(self, value):\r
+        print 'midi_port:', value\r
+\r
+\r
+    def tempo(self, value):\r
+        print 'tempo:', value\r
+        self.tempo = value\r
+\r
+\r
+    def smtp_offset(self, hour, minute, second, frame, framePart):\r
+        print 'smtp_offset', hour, minute, second, frame, framePart\r
+\r
+\r
+    def time_signature(self, nn, dd, cc, bb):\r
+        print 'time_signature:', nn, dd, cc, bb\r
+\r
+\r
+    def key_signature(self, sf, mi):\r
+        print 'key_signature', sf, mi\r
+\r
+\r
+    def sequencer_specific(self, data):\r
+        print 'sequencer_specific', len(data)\r
+        \r
+    def timeInMs(self):\r
+        return(long(self.abs_time())*1000000000/(long(self.tempo)*long(self.division)))\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    # get data\r
+    test_file = '../songs/midis/test.mid'\r
+    f = open(test_file, 'rb')\r
+    \r
+    # do parsing\r
+    from MidiInFile import MidiInFile\r
+    midiIn = MidiInFile(MidiToText(), f)\r
+    midiIn.read()\r
+    f.close()\r
diff --git a/src/mxmMidi/RawInstreamFile.py b/src/mxmMidi/RawInstreamFile.py
new file mode 100644 (file)
index 0000000..0c2eba6
--- /dev/null
@@ -0,0 +1,108 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+# standard library imports\r
+from types import StringType\r
+from struct import unpack\r
+\r
+# custom import\r
+from DataTypeConverters import readBew, readVar, varLen\r
+\r
+\r
+class RawInstreamFile:\r
+    \r
+    """\r
+    \r
+    It parses and reads data from an input file. It takes care of big \r
+    endianess, and keeps track of the cursor position. The midi parser \r
+    only reads from this object. Never directly from the file.\r
+    \r
+    """\r
+    \r
+    def __init__(self, infile=''):\r
+        """ \r
+        If 'file' is a string we assume it is a path and read from \r
+        that file.\r
+        If it is a file descriptor we read from the file, but we don't \r
+        close it.\r
+        Midi files are usually pretty small, so it should be safe to \r
+        copy them into memory.\r
+        """\r
+        if infile:\r
+            if isinstance(infile, StringType):\r
+                infile = open(infile, 'rb')\r
+                self.data = infile.read()\r
+                infile.close()\r
+            else:\r
+                # don't close the f\r
+                self.data = infile.read()\r
+        else:\r
+            self.data = ''\r
+        # start at beginning ;-)\r
+        self.cursor = 0\r
+\r
+\r
+    # setting up data manually\r
+    \r
+    def setData(self, data=''):\r
+        "Sets the data from a string."\r
+        self.data = data\r
+    \r
+    # cursor operations\r
+\r
+    def setCursor(self, position=0):\r
+        "Sets the absolute position if the cursor"\r
+        self.cursor = position\r
+\r
+\r
+    def getCursor(self):\r
+        "Returns the value of the cursor"\r
+        return self.cursor\r
+        \r
+        \r
+    def moveCursor(self, relative_position=0):\r
+        "Moves the cursor to a new relative position"\r
+        self.cursor += relative_position\r
+\r
+    # native data reading functions\r
+        \r
+    def nextSlice(self, length, move_cursor=1):\r
+        "Reads the next text slice from the raw data, with length"\r
+        c = self.cursor\r
+        slc = self.data[c:c+length]\r
+        if move_cursor:\r
+            self.moveCursor(length)\r
+        return slc\r
+        \r
+        \r
+    def readBew(self, n_bytes=1, move_cursor=1):\r
+        """\r
+        Reads n bytes of date from the current cursor position.\r
+        Moves cursor if move_cursor is true\r
+        """\r
+        return readBew(self.nextSlice(n_bytes, move_cursor))\r
+\r
+\r
+    def readVarLen(self):\r
+        """\r
+        Reads a variable length value from the current cursor position.\r
+        Moves cursor if move_cursor is true\r
+        """\r
+        MAX_VARLEN = 4 # Max value varlen can be\r
+        var = readVar(self.nextSlice(MAX_VARLEN, 0))\r
+        # only move cursor the actual bytes in varlen\r
+        self.moveCursor(varLen(var))\r
+        return var\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    test_file = 'test/midifiles/minimal.mid'\r
+    fis = RawInstreamFile(test_file)\r
+    print fis.nextSlice(len(fis.data))\r
+\r
+    test_file = 'test/midifiles/cubase-minimal.mid'\r
+    cubase_minimal = open(test_file, 'rb')\r
+    fis2 = RawInstreamFile(cubase_minimal)\r
+    print fis2.nextSlice(len(fis2.data))\r
+    cubase_minimal.close()\r
diff --git a/src/mxmMidi/RawInstreamFile.pyc b/src/mxmMidi/RawInstreamFile.pyc
new file mode 100644 (file)
index 0000000..9d05bf3
Binary files /dev/null and b/src/mxmMidi/RawInstreamFile.pyc differ
diff --git a/src/mxmMidi/RawOutstreamFile.py b/src/mxmMidi/RawOutstreamFile.py
new file mode 100644 (file)
index 0000000..73eed31
--- /dev/null
@@ -0,0 +1,69 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+# standard library imports\r
+import sys\r
+from types import StringType\r
+from struct import unpack\r
+from cStringIO import StringIO\r
+\r
+# custom import\r
+from DataTypeConverters import writeBew, writeVar, fromBytes\r
+\r
+class RawOutstreamFile:\r
+    \r
+    """\r
+    \r
+    Writes a midi file to disk.\r
+    \r
+    """\r
+\r
+    def __init__(self, outfile=''):\r
+        self.buffer = StringIO()\r
+        self.outfile = outfile\r
+\r
+\r
+    # native data reading functions\r
+\r
+\r
+    def writeSlice(self, str_slice):\r
+        "Writes the next text slice to the raw data"\r
+        self.buffer.write(str_slice)\r
+        \r
+        \r
+    def writeBew(self, value, length=1):\r
+        "Writes a value to the file as big endian word"\r
+        self.writeSlice(writeBew(value, length))\r
+\r
+\r
+    def writeVarLen(self, value):\r
+        "Writes a variable length word to the file"\r
+        var = self.writeSlice(writeVar(value))\r
+\r
+\r
+    def write(self):\r
+        "Writes to disc"\r
+        if self.outfile:\r
+            if isinstance(self.outfile, StringType):\r
+                outfile = open(self.outfile, 'wb')\r
+                outfile.write(self.getvalue())\r
+                outfile.close()\r
+            else:\r
+                self.outfile.write(self.getvalue())\r
+        else:\r
+            sys.stdout.write(self.getvalue())\r
+                \r
+    def getvalue(self):\r
+        return self.buffer.getvalue()\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    out_file = 'test/midifiles/midiout.mid'\r
+    out_file = ''\r
+    rawOut = RawOutstreamFile(out_file)\r
+    rawOut.writeSlice('MThd')\r
+    rawOut.writeBew(6, 4)\r
+    rawOut.writeBew(1, 2)\r
+    rawOut.writeBew(2, 2)\r
+    rawOut.writeBew(15360, 2)\r
+    rawOut.write()\r
diff --git a/src/mxmMidi/__init__.py b/src/mxmMidi/__init__.py
new file mode 100644 (file)
index 0000000..b2d2031
--- /dev/null
@@ -0,0 +1,6 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+#import MidiOutStream\r
+#import MidiInStream\r
+#import MidiInFile\r
+#import MidiToText
\ No newline at end of file
diff --git a/src/mxmMidi/changes.txt b/src/mxmMidi/changes.txt
new file mode 100644 (file)
index 0000000..8b6670a
--- /dev/null
@@ -0,0 +1,45 @@
+------------------------------------------------------------------------\r
+r409 | maxm | 2006-01-05 16:37:29 +0100 (to, 05 jan 2006) | 1 line\r
+\r
+Made RawOutstreamFile.py write to std out if no outfile is given.\r
+------------------------------------------------------------------------\r
+r403 | maxm | 2006-01-05 13:34:11 +0100 (to, 05 jan 2006) | 1 line\r
+\r
+\r
+------------------------------------------------------------------------\r
+r402 | maxm | 2006-01-05 13:33:56 +0100 (to, 05 jan 2006) | 1 line\r
+\r
+- Fixed minor bugs, added coding headers\r
+------------------------------------------------------------------------\r
+r401 | maxm | 2006-01-01 23:09:17 +0100 (s_, 01 jan 2006) | 1 line\r
+\r
+Fixed sysex dispathcer bug.\r
+------------------------------------------------------------------------\r
+r268 | maxm | 2005-02-04 12:26:59 +0100 (fr, 04 feb 2005) | 1 line\r
+\r
+\r
+------------------------------------------------------------------------\r
+r128 | maxm | 2004-12-18 14:05:27 +0100 (l_, 18 dec 2004) | 1 line\r
+\r
+Fixed bug when using relative time\r
+------------------------------------------------------------------------\r
+r15 | maxm | 2004-03-09 15:01:41 +0100 (ti, 09 mar 2004) | 1 line\r
+\r
+made a copy to meta folder\r
+------------------------------------------------------------------------\r
+r13 | maxm | 2004-03-09 09:17:23 +0100 (ti, 09 mar 2004) | 1 line\r
+\r
+Deleted .pyc files\r
+------------------------------------------------------------------------\r
+r12 | maxm | 2004-03-09 09:15:54 +0100 (ti, 09 mar 2004) | 1 line\r
+\r
+Removed file/folder\r
+------------------------------------------------------------------------\r
+r3 | maxm | 2004-03-08 23:16:25 +0100 (ma, 08 mar 2004) | 1 line\r
+\r
+Adde midi\r
+------------------------------------------------------------------------\r
+r1 | maxm | 2004-03-08 22:49:23 +0100 (ma, 08 mar 2004) | 1 line\r
+\r
+Initial Import\r
+------------------------------------------------------------------------\r
diff --git a/src/mxmMidi/constants.py b/src/mxmMidi/constants.py
new file mode 100644 (file)
index 0000000..81b91bc
--- /dev/null
@@ -0,0 +1,210 @@
+# -*- coding: ISO-8859-1 -*-\r
+\r
+###################################################\r
+## Definitions of the different midi events\r
+\r
+\r
+\r
+###################################################\r
+## Midi channel events (The most usual events)\r
+## also called "Channel Voice Messages"\r
+\r
+NOTE_OFF = 0x80\r
+# 1000cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)\r
+\r
+NOTE_ON = 0x90\r
+# 1001cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)\r
+\r
+AFTERTOUCH = 0xA0\r
+# 1010cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity)\r
+\r
+CONTINUOUS_CONTROLLER = 0xB0 # see Channel Mode Messages!!!\r
+# 1011cccc 0ccccccc 0vvvvvvv (channel, controller, value)\r
+\r
+PATCH_CHANGE = 0xC0\r
+# 1100cccc 0ppppppp (channel, program)\r
+\r
+CHANNEL_PRESSURE = 0xD0\r
+# 1101cccc 0ppppppp (channel, pressure)\r
+\r
+PITCH_BEND = 0xE0\r
+# 1110cccc 0vvvvvvv 0wwwwwww (channel, value-lo, value-hi)\r
+\r
+\r
+###################################################\r
+##  Channel Mode Messages (Continuous Controller)\r
+##  They share a status byte.\r
+##  The controller makes the difference here\r
+\r
+# High resolution continuous controllers (MSB)\r
+\r
+BANK_SELECT = 0x00\r
+MODULATION_WHEEL = 0x01\r
+BREATH_CONTROLLER = 0x02\r
+FOOT_CONTROLLER = 0x04\r
+PORTAMENTO_TIME = 0x05\r
+DATA_ENTRY = 0x06\r
+CHANNEL_VOLUME = 0x07\r
+BALANCE = 0x08\r
+PAN = 0x0A\r
+EXPRESSION_CONTROLLER = 0x0B\r
+EFFECT_CONTROL_1 = 0x0C\r
+EFFECT_CONTROL_2 = 0x0D\r
+GEN_PURPOSE_CONTROLLER_1 = 0x10\r
+GEN_PURPOSE_CONTROLLER_2 = 0x11\r
+GEN_PURPOSE_CONTROLLER_3 = 0x12\r
+GEN_PURPOSE_CONTROLLER_4 = 0x13\r
+\r
+# High resolution continuous controllers (LSB)\r
+\r
+BANK_SELECT = 0x20\r
+MODULATION_WHEEL = 0x21\r
+BREATH_CONTROLLER = 0x22\r
+FOOT_CONTROLLER = 0x24\r
+PORTAMENTO_TIME = 0x25\r
+DATA_ENTRY = 0x26\r
+CHANNEL_VOLUME = 0x27\r
+BALANCE = 0x28\r
+PAN = 0x2A\r
+EXPRESSION_CONTROLLER = 0x2B\r
+EFFECT_CONTROL_1 = 0x2C\r
+EFFECT_CONTROL_2 = 0x2D\r
+GENERAL_PURPOSE_CONTROLLER_1 = 0x30\r
+GENERAL_PURPOSE_CONTROLLER_2 = 0x31\r
+GENERAL_PURPOSE_CONTROLLER_3 = 0x32\r
+GENERAL_PURPOSE_CONTROLLER_4 = 0x33\r
+\r
+# Switches\r
+\r
+SUSTAIN_ONOFF = 0x40\r
+PORTAMENTO_ONOFF = 0x41\r
+SOSTENUTO_ONOFF = 0x42\r
+SOFT_PEDAL_ONOFF = 0x43\r
+LEGATO_ONOFF = 0x44\r
+HOLD_2_ONOFF = 0x45\r
+\r
+# Low resolution continuous controllers\r
+\r
+SOUND_CONTROLLER_1 = 0x46                  # (TG: Sound Variation;   FX: Exciter On/Off)\r
+SOUND_CONTROLLER_2 = 0x47                  # (TG: Harmonic Content;   FX: Compressor On/Off)\r
+SOUND_CONTROLLER_3 = 0x48                  # (TG: Release Time;   FX: Distortion On/Off)\r
+SOUND_CONTROLLER_4 = 0x49                  # (TG: Attack Time;   FX: EQ On/Off)\r
+SOUND_CONTROLLER_5 = 0x4A                  # (TG: Brightness;   FX: Expander On/Off)75 SOUND_CONTROLLER_6   (TG: Undefined;   FX: Reverb OnOff)\r
+SOUND_CONTROLLER_7 = 0x4C                  # (TG: Undefined;   FX: Delay OnOff)\r
+SOUND_CONTROLLER_8 = 0x4D                  # (TG: Undefined;   FX: Pitch Transpose OnOff)\r
+SOUND_CONTROLLER_9 = 0x4E                  # (TG: Undefined;   FX: Flange/Chorus OnOff)\r
+SOUND_CONTROLLER_10 = 0x4F                 # (TG: Undefined;   FX: Special Effects OnOff)\r
+GENERAL_PURPOSE_CONTROLLER_5 = 0x50\r
+GENERAL_PURPOSE_CONTROLLER_6 = 0x51\r
+GENERAL_PURPOSE_CONTROLLER_7 = 0x52\r
+GENERAL_PURPOSE_CONTROLLER_8 = 0x53\r
+PORTAMENTO_CONTROL = 0x54                  # (PTC)   (0vvvvvvv is the source Note number)   (Detail)\r
+EFFECTS_1 = 0x5B                           # (Ext. Effects Depth)\r
+EFFECTS_2 = 0x5C                           # (Tremelo Depth)\r
+EFFECTS_3 = 0x5D                           # (Chorus Depth)\r
+EFFECTS_4 = 0x5E                           # (Celeste Depth)\r
+EFFECTS_5 = 0x5F                           # (Phaser Depth)\r
+DATA_INCREMENT = 0x60                      # (0vvvvvvv is n/a; use 0)\r
+DATA_DECREMENT = 0x61                      # (0vvvvvvv is n/a; use 0)\r
+NON_REGISTERED_PARAMETER_NUMBER = 0x62     # (LSB)\r
+NON_REGISTERED_PARAMETER_NUMBER = 0x63     # (MSB)\r
+REGISTERED_PARAMETER_NUMBER = 0x64         # (LSB)\r
+REGISTERED_PARAMETER_NUMBER = 0x65         # (MSB)\r
+\r
+# Channel Mode messages - (Detail)\r
+\r
+ALL_SOUND_OFF = 0x78\r
+RESET_ALL_CONTROLLERS = 0x79\r
+LOCAL_CONTROL_ONOFF = 0x7A\r
+ALL_NOTES_OFF = 0x7B\r
+OMNI_MODE_OFF = 0x7C          # (also causes ANO)\r
+OMNI_MODE_ON = 0x7D           # (also causes ANO)\r
+MONO_MODE_ON = 0x7E           # (Poly Off; also causes ANO)\r
+POLY_MODE_ON = 0x7F           # (Mono Off; also causes ANO)\r
+\r
+\r
+\r
+###################################################\r
+## System Common Messages, for all channels\r
+\r
+SYSTEM_EXCLUSIVE = 0xF0\r
+# 11110000 0iiiiiii 0ddddddd ... 11110111\r
+\r
+MTC = 0xF1 # MIDI Time Code Quarter Frame\r
+# 11110001\r
+\r
+SONG_POSITION_POINTER = 0xF2\r
+# 11110010 0vvvvvvv 0wwwwwww (lo-position, hi-position)\r
+\r
+SONG_SELECT = 0xF3\r
+# 11110011 0sssssss (songnumber)\r
+\r
+#UNDEFINED = 0xF4\r
+## 11110100\r
+\r
+#UNDEFINED = 0xF5\r
+## 11110101\r
+\r
+TUNING_REQUEST = 0xF6\r
+# 11110110\r
+\r
+END_OFF_EXCLUSIVE = 0xF7 # terminator\r
+# 11110111 # End of system exclusive\r
+\r
+\r
+###################################################\r
+## Midifile meta-events\r
+\r
+SEQUENCE_NUMBER = 0x00      # 00 02 ss ss (seq-number)\r
+TEXT            = 0x01      # 01 len text...\r
+COPYRIGHT       = 0x02      # 02 len text...\r
+SEQUENCE_NAME   = 0x03      # 03 len text...\r
+INSTRUMENT_NAME = 0x04      # 04 len text...\r
+LYRIC           = 0x05      # 05 len text...\r
+MARKER          = 0x06      # 06 len text...\r
+CUEPOINT        = 0x07      # 07 len text...\r
+PROGRAM_NAME    = 0x08      # 08 len text...\r
+DEVICE_NAME     = 0x09      # 09 len text...\r
+\r
+MIDI_CH_PREFIX  = 0x20      # MIDI channel prefix assignment (unofficial)\r
+\r
+MIDI_PORT       = 0x21      # 21 01 port, legacy stuff but still used\r
+END_OF_TRACK    = 0x2F      # 2f 00\r
+TEMPO           = 0x51      # 51 03 tt tt tt (tempo in us/quarternote)\r
+SMTP_OFFSET     = 0x54      # 54 05 hh mm ss ff xx\r
+TIME_SIGNATURE  = 0x58      # 58 04 nn dd cc bb\r
+KEY_SIGNATURE   = 0x59      # ??? len text...\r
+SPECIFIC        = 0x7F      # Sequencer specific event\r
+\r
+FILE_HEADER     = 'MThd'\r
+TRACK_HEADER    = 'MTrk'\r
+\r
+###################################################\r
+## System Realtime messages\r
+## I don't supose these are to be found in midi files?!\r
+\r
+TIMING_CLOCK   = 0xF8\r
+# undefined    = 0xF9\r
+SONG_START     = 0xFA\r
+SONG_CONTINUE  = 0xFB\r
+SONG_STOP      = 0xFC\r
+# undefined    = 0xFD\r
+ACTIVE_SENSING = 0xFE\r
+SYSTEM_RESET   = 0xFF\r
+\r
+\r
+###################################################\r
+## META EVENT, it is used only in midi files.\r
+## In transmitted data it means system reset!!!\r
+\r
+META_EVENT     = 0xFF\r
+# 11111111\r
+\r
+\r
+###################################################\r
+## Helper functions\r
+\r
+def is_status(byte):\r
+    return (byte & 0x80) == 0x80 # 1000 0000\r
+\r
+\r
diff --git a/src/mxmMidi/constants.pyc b/src/mxmMidi/constants.pyc
new file mode 100644 (file)
index 0000000..0ce9fec
Binary files /dev/null and b/src/mxmMidi/constants.pyc differ
diff --git a/src/mxmMidi/example_mimimal_type0.py b/src/mxmMidi/example_mimimal_type0.py
new file mode 100644 (file)
index 0000000..d8d1842
--- /dev/null
@@ -0,0 +1,29 @@
+from MidiOutFile import MidiOutFile\r
+\r
+"""\r
+This is an example of the smallest possible type 0 midi file, where \r
+all the midi events are in the same track.\r
+"""\r
+\r
+out_file = 'midiout/minimal_type0.mid'\r
+midi = MidiOutFile(out_file)\r
+\r
+# non optional midi framework\r
+midi.header()\r
+midi.start_of_track() \r
+\r
+\r
+# musical events\r
+\r
+midi.update_time(0)\r
+midi.note_on(channel=0, note=0x40)\r
+\r
+midi.update_time(192)\r
+midi.note_off(channel=0, note=0x40)\r
+\r
+\r
+# non optional midi framework\r
+midi.update_time(0)\r
+midi.end_of_track()\r
+\r
+midi.eof()\r
diff --git a/src/mxmMidi/example_print_channel_0.py b/src/mxmMidi/example_print_channel_0.py
new file mode 100644 (file)
index 0000000..2dbe3e4
--- /dev/null
@@ -0,0 +1,23 @@
+from MidiOutStream import MidiOutStream\r
+from MidiInFile import MidiInFile\r
+\r
+"""\r
+This prints all note on events on midi channel 0\r
+"""\r
+\r
+\r
+class Transposer(MidiOutStream):\r
+    \r
+    "Transposes all notes by 1 octave"\r
+    \r
+    def note_on(self, channel=0, note=0x40, velocity=0x40):\r
+        if channel == 0:\r
+            print channel, note, velocity, self.rel_time()\r
+\r
+\r
+event_handler = Transposer()\r
+\r
+in_file = 'midiout/minimal_type0.mid'\r
+midi_in = MidiInFile(event_handler, in_file)\r
+midi_in.read()\r
+\r
diff --git a/src/mxmMidi/example_print_events.py b/src/mxmMidi/example_print_events.py
new file mode 100644 (file)
index 0000000..b1e27f9
--- /dev/null
@@ -0,0 +1,28 @@
+from MidiToText import MidiToText\r
+\r
+"""\r
+This is an example that uses the MidiToText eventhandler. When an \r
+event is triggered on it, it prints the event to the console.\r
+"""\r
+\r
+midi = MidiToText()\r
+\r
+# non optional midi framework\r
+midi.header()\r
+midi.start_of_track() \r
+\r
+\r
+# musical events\r
+\r
+midi.update_time(0)\r
+midi.note_on(channel=0, note=0x40)\r
+\r
+midi.update_time(192)\r
+midi.note_off(channel=0, note=0x40)\r
+\r
+\r
+# non optional midi framework\r
+midi.update_time(0)\r
+midi.end_of_track() # not optional!\r
+\r
+midi.eof()\r
diff --git a/src/mxmMidi/example_print_file.py b/src/mxmMidi/example_print_file.py
new file mode 100644 (file)
index 0000000..4fcd531
--- /dev/null
@@ -0,0 +1,19 @@
+"""\r
+This is an example that uses the MidiToText eventhandler. When an \r
+event is triggered on it, it prints the event to the console.\r
+\r
+It gets the events from the MidiInFile.\r
+\r
+So it prints all the events from the infile to the console. great for \r
+debugging :-s\r
+"""\r
+\r
+\r
+# get data\r
+test_file = 'test/midifiles/minimal-cubase-type0.mid'\r
+\r
+# do parsing\r
+from MidiInFile import MidiInFile\r
+from MidiToText import MidiToText # the event handler\r
+midiIn = MidiInFile(MidiToText(), test_file)\r
+midiIn.read()\r
diff --git a/src/mxmMidi/example_transpose_octave.py b/src/mxmMidi/example_transpose_octave.py
new file mode 100644 (file)
index 0000000..57dbbff
--- /dev/null
@@ -0,0 +1,40 @@
+from MidiOutFile import MidiOutFile\r
+from MidiInFile import MidiInFile\r
+\r
+"""\r
+This is an example of the smallest possible type 0 midi file, where \r
+all the midi events are in the same track.\r
+"""\r
+\r
+\r
+class Transposer(MidiOutFile):\r
+    \r
+    "Transposes all notes by 1 octave"\r
+    \r
+    def _transp(self, ch, note):\r
+        if ch != 9: # not the drums!\r
+            note += 12\r
+            if note > 127:\r
+                note = 127\r
+        return note\r
+\r
+\r
+    def note_on(self, channel=0, note=0x40, velocity=0x40):\r
+        note = self._transp(channel, note)\r
+        MidiOutFile.note_on(self, channel, note, velocity)\r
+        \r
+        \r
+    def note_off(self, channel=0, note=0x40, velocity=0x40):\r
+        note = self._transp(channel, note)\r
+        MidiOutFile.note_off(self, channel, note, velocity)\r
+\r
+\r
+out_file = 'midiout/transposed.mid'\r
+midi_out = Transposer(out_file)\r
+\r
+#in_file = 'midiout/minimal_type0.mid'\r
+#in_file = 'test/midifiles/Lola.mid'\r
+in_file = 'test/midifiles/tennessee_waltz.mid'\r
+midi_in = MidiInFile(midi_out, in_file)\r
+midi_in.read()\r
+\r
diff --git a/src/mxmMidi/experimental/EventDispatcherBase.py b/src/mxmMidi/experimental/EventDispatcherBase.py
new file mode 100644 (file)
index 0000000..71bde62
--- /dev/null
@@ -0,0 +1,76 @@
+class EventDispatcherBase:\r
+\r
+\r
+    def __init__(self, outstream):\r
+        """\r
+        The event dispatcher generates events on the outstream. This \r
+        is the base implementation. It is more like an interface for \r
+        how the EventDispatcher. It has the methods that are used by \r
+        the Midi Parser.\r
+        """\r
+        # internal values, don't mess with 'em directly\r
+        self.outstream = outstream\r
+\r
+\r
+    def eof(self):\r
+        "End of file!"\r
+        self.outstream.eof()\r
+\r
+\r
+    def update_time(self, new_time=0, relative=1):\r
+        "Updates relative/absolute time."\r
+        self.outstream.update_time(new_time, relative)\r
+\r
+    # 'official' midi events\r
+\r
+    def header(self, format, nTracks, division):\r
+        "Triggers the header event"\r
+        self.outstream.header(format, nTracks, division)\r
+\r
+\r
+    def start_of_track(self, current_track):\r
+        "Triggers the start of track event"\r
+        \r
+        # I do this twice so that users can overwrite the \r
+        # start_of_track event handler without worrying whether the \r
+        # track number is updated correctly.\r
+        self.outstream.set_current_track(current_track)\r
+        self.outstream.start_of_track(current_track)\r
+\r
+    # Event dispatchers for midi events\r
+\r
+    def channel_messages(self, hi_nible, channel, data):\r
+        "Dispatches channel messages"\r
+        self.outstream.channel_message(hi_nible, channel, data)\r
+\r
+\r
+    def continuous_controllers(self, channel, controller, value):\r
+        "Dispatches channel messages"\r
+        self.outstream.continuous_controller(channel, controller, value)\r
+    \r
+    \r
+    def system_commons(self, common_type, common_data):\r
+        "Dispatches system common messages"\r
+        self.outstream.system_common(common_type, common_data)\r
+\r
+\r
+    def meta_event(self, meta_type, data):\r
+        "Dispatches meta events"\r
+        self.outstream.meta_event(meta_type, data)\r
+\r
+\r
+    def sysex_events(self, data):\r
+        "Dispatcher for sysex events"\r
+        self.outstream.sysex_event(data)\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+\r
+    from MidiToText import MidiToText\r
+    from constants import NOTE_ON\r
+    \r
+    outstream = MidiToText()\r
+    dispatcher = EventDispatcherBase(outstream)\r
+    dispatcher.channel_messages(NOTE_ON, 0x00, '\x40\x40')
\ No newline at end of file
diff --git a/src/mxmMidi/experimental/MidiOutPassThrough.py b/src/mxmMidi/experimental/MidiOutPassThrough.py
new file mode 100644 (file)
index 0000000..25ceed4
--- /dev/null
@@ -0,0 +1,182 @@
+from MidiOutStream import MidiOutStream\r
+\r
+class MidiOutPassThrough(MidiOutStream):\r
+\r
+\r
+    """\r
+\r
+    This class i mainly used for testing the event dispatcher. The \r
+    methods just returns the passed parameters as a tupple.\r
+\r
+    """\r
+\r
+\r
+    #####################\r
+    ## Midi channel events\r
+\r
+\r
+    def note_on(self, channel, note, velocity, time=None):\r
+        return channel, note, velocity, time\r
+\r
+\r
+    def note_off(self, channel, note, velocity, time=None):\r
+        return channel, note, velocity, time\r
+\r
+\r
+    def aftertouch(self, channel, note, velocity, time=None):\r
+        return channel, note, velocity, time\r
+\r
+        \r
+    def continuous_controller(self, channel, controller, value, time=None):\r
+        return channel, controller, value, time\r
+\r
+\r
+    def patch_change(self, channel, patch, time=None):\r
+        return channel, patch, time\r
+\r
+\r
+    def channel_pressure(self, channel, pressure, time=None):\r
+        return channel, pressure, time\r
+\r
+\r
+    #####################\r
+    ## defined continuous controller events\r
+    \r
+#    def cc_\r
+\r
+    #####################\r
+    ## Common events\r
+\r
+    def system_exclusive(self, data, time=None):\r
+        return data, time\r
+\r
+\r
+    def song_position_pointer(self, hiPos, loPos, time=None):\r
+        return hiPos, loPos, time\r
+\r
+\r
+    def song_select(self, songNumber, time=None):\r
+        return songNumber, time\r
+\r
+\r
+    def tuning_request(self, time=None):\r
+        return time\r
+\r
+\r
+\r
+    #########################\r
+    # header does not really belong here. But anyhoo!!!\r
+    \r
+    def header(self, format, nTracks, division):\r
+        return format, nTracks, division\r
+\r
+\r
+    def eof(self):\r
+        return 'eof'\r
+\r
+\r
+    #####################\r
+    ## meta events\r
+\r
+    def start_of_track(self, n_track=0):\r
+        return n_track\r
+\r
+\r
+    def end_of_track(self, n_track=0, time=None):\r
+        return n_track, time\r
+\r
+\r
+    def sequence_number(self, hiVal, loVal, time=None):\r
+        return hiVal, loVal, time\r
+\r
+\r
+    def text(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def copyright(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def sequence_name(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def instrument_name(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def lyric(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def marker(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def cuepoint(self, text, time=None):\r
+        return text, time\r
+\r
+\r
+    def midi_port(self, value, time=None):\r
+        return value, time\r
+\r
+\r
+    def tempo(self, value, time=None):\r
+        return value, time\r
+\r
+    def smtp_offset(self, hour, minute, second, frame, framePart, time=None):\r
+        return hour, minute, second, frame, framePart, time\r
+\r
+\r
+    def time_signature(self, nn, dd, cc, bb, time=None):\r
+        return nn, dd, cc, bb, time\r
+\r
+\r
+    def key_signature(self, sf, mi, time=None):\r
+        return sf, mi, time\r
+\r
+\r
+    def sequencer_specific(self, data, time=None):\r
+        return data, time\r
+\r
+\r
+\r
+\r
+    #####################\r
+    ## realtime events\r
+\r
+    def timing_clock(self, time=None):\r
+        return time\r
+\r
+\r
+    def song_start(self, time=None):\r
+        return time\r
+\r
+\r
+    def song_stop(self, time=None):\r
+        return time\r
+\r
+\r
+    def song_continue(self, time=None):\r
+        return time\r
+\r
+\r
+    def active_sensing(self, time=None):\r
+        return time\r
+\r
+\r
+    def system_reset(self, time=None):\r
+        return time\r
+\r
+\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    midiOut = MidiOutStream()\r
+    midiOut.note_on(0, 63, 127, 0)\r
+    midiOut.note_off(0, 63, 127, 384)\r
+\r
+    
\ No newline at end of file
diff --git a/src/mxmMidi/experimental/MidiOutStreamBase.py b/src/mxmMidi/experimental/MidiOutStreamBase.py
new file mode 100644 (file)
index 0000000..1abada0
--- /dev/null
@@ -0,0 +1,135 @@
+class MidiOutStreamBase:\r
+\r
+\r
+    """\r
+\r
+    MidiOutStreamBase is Basically an eventhandler. It is the most central\r
+    class in the Midi library. You use it both for writing events to\r
+    an output stream, and as an event handler for an input stream.\r
+\r
+    This makes it extremely easy to take input from one stream and\r
+    send it to another. Ie. if you want to read a Midi file, do some\r
+    processing, and send it to a midiport.\r
+\r
+    All time values are in absolute values from the opening of a\r
+    stream. To calculate time values, please use the MidiTime and\r
+    MidiDeltaTime classes.\r
+\r
+    """\r
+\r
+    def __init__(self):\r
+        \r
+        # the time is rather global, so it needs to be stored \r
+        # here. Otherwise there would be no really simple way to \r
+        # calculate it. The alternative would be to have each event \r
+        # handler do it. That sucks even worse!\r
+        self._absolute_time = 0\r
+        self._relative_time = 0\r
+        self._current_track = 0\r
+\r
+    # time handling event handlers. They should overwritten with care\r
+\r
+    def update_time(self, new_time=0, relative=1):\r
+        """\r
+        Updates the time, if relative is true, new_time is relative, \r
+        else it's absolute.\r
+        """\r
+        if relative:\r
+            self._relative_time = new_time\r
+            self._absolute_time += new_time\r
+        else:\r
+            self._absolute_time = new_time\r
+            self._relative_time = new_time - self._absolute_time\r
+\r
+    def rel_time(self):\r
+        "Returns the relative time"\r
+        return self._relative_time\r
+\r
+    def abs_time(self):\r
+        "Returns the absolute time"\r
+        return self._absolute_time\r
+\r
+    # track handling event handlers\r
+    \r
+    def set_current_track(self, new_track):\r
+        "Sets the current track number"\r
+        self._current_track = new_track\r
+    \r
+    def get_current_track(self):\r
+        "Returns the current track number"\r
+        return self._current_track\r
+    \r
+    \r
+    #####################\r
+    ## Midi events\r
+\r
+\r
+    def channel_message(self, message_type, channel, data):\r
+        """The default event handler for channel messages"""\r
+        pass\r
+\r
+\r
+    #####################\r
+    ## Common events\r
+\r
+    def system_exclusive(self, data):\r
+\r
+        """The default event handler for system_exclusive messages"""\r
+        pass\r
+\r
+\r
+    def system_common(self, common_type, common_data):\r
+\r
+        """The default event handler for system common messages"""\r
+        pass\r
+\r
+\r
+    #########################\r
+    # header does not really belong here. But anyhoo!!!\r
+    \r
+    def header(self, format, nTracks, division):\r
+\r
+        """\r
+        format: type of midi file in [1,2]\r
+        nTracks: number of tracks\r
+        division: timing division\r
+        """\r
+        pass\r
+\r
+\r
+    def start_of_track(self, n_track=0):\r
+\r
+        """\r
+        n_track: number of track\r
+        """\r
+        pass\r
+\r
+\r
+    def eof(self):\r
+\r
+        """\r
+        End of file. No more events to be processed.\r
+        """\r
+        pass\r
+\r
+\r
+    #####################\r
+    ## meta events\r
+\r
+\r
+    def meta_event(self, meta_type, data, time):\r
+        \r
+        """The default event handler for meta_events"""\r
+        pass\r
+\r
+\r
+\r
+\r
+if __name__ == '__main__':\r
+\r
+    midiOut = MidiOutStreamBase()\r
+    midiOut.update_time(0,0)\r
+    midiOut.note_on(0, 63, 127)\r
+    midiOut.note_off(0, 63, 127)\r
+\r
+    
\ No newline at end of file
diff --git a/src/mxmMidi/experimental/readme.txt b/src/mxmMidi/experimental/readme.txt
new file mode 100644 (file)
index 0000000..4234118
--- /dev/null
@@ -0,0 +1 @@
+Stuff that I am just playing around with
\ No newline at end of file
diff --git a/src/mxmMidi/midiout/minimal_type0.mid b/src/mxmMidi/midiout/minimal_type0.mid
new file mode 100644 (file)
index 0000000..ffdfcda
Binary files /dev/null and b/src/mxmMidi/midiout/minimal_type0.mid differ
diff --git a/src/mxmMidi/midiout/transposed.mid b/src/mxmMidi/midiout/transposed.mid
new file mode 100644 (file)
index 0000000..a964548
Binary files /dev/null and b/src/mxmMidi/midiout/transposed.mid differ
diff --git a/src/mxmMidi/readme.txt b/src/mxmMidi/readme.txt
new file mode 100644 (file)
index 0000000..d3c7431
--- /dev/null
@@ -0,0 +1,50 @@
+This is the documentation for the midi package\r
+==============================================\r
+\r
+\r
+The modules follows the following naming convention:\r
+\r
+\r
+MidiIn<StreamType>.py\r
+---------------------\r
+\r
+The MidiIn modules reads midi content for a specific type of stream. Ie. a file or a midi port. It then generates events and triggers them on a MidiOutStream.\r
+\r
+\r
+MidiOut<StreamType>.py\r
+----------------------\r
+\r
+The MidiOut modules are event handlers, that reacts to events generated by a a Midi in module.\r
+\r
+\r
+MidiInBase.py\r
+---------------\r
+\r
+The base class for input streams.\r
+\r
+\r
+MidiOutBase.py\r
+----------------\r
+\r
+The base class for the output streams.\r
+\r
+\r
+\r
+\r
+\r
+\r
+Internal modules\r
+================\r
+\r
+\r
+DataTypeConverters.py\r
+---------------------\r
+\r
+A collection of functions that converts the special data types used in midi files to and from strings.\r
+\r
+\r
+constants.py\r
+------------\r
+\r
+A collection of constants from the midi spec.\r
+\r
diff --git a/src/mxmMidi/test/midifiles/midiout.mid b/src/mxmMidi/test/midifiles/midiout.mid
new file mode 100644 (file)
index 0000000..eff8ffa
Binary files /dev/null and b/src/mxmMidi/test/midifiles/midiout.mid differ
diff --git a/src/mxmMidi/test/midifiles/minimal-cubase-type0.mid b/src/mxmMidi/test/midifiles/minimal-cubase-type0.mid
new file mode 100644 (file)
index 0000000..dcea0cf
Binary files /dev/null and b/src/mxmMidi/test/midifiles/minimal-cubase-type0.mid differ
diff --git a/src/mxmMidi/test/midifiles/minimal-cubase-type1.mid b/src/mxmMidi/test/midifiles/minimal-cubase-type1.mid
new file mode 100644 (file)
index 0000000..94ad302
Binary files /dev/null and b/src/mxmMidi/test/midifiles/minimal-cubase-type1.mid differ
diff --git a/src/mxmMidi/test/midifiles/minimal.mid b/src/mxmMidi/test/midifiles/minimal.mid
new file mode 100644 (file)
index 0000000..c4567b2
Binary files /dev/null and b/src/mxmMidi/test/midifiles/minimal.mid differ
diff --git a/src/mxmMidi/test/midifiles/minimal.txt b/src/mxmMidi/test/midifiles/minimal.txt
new file mode 100644 (file)
index 0000000..dd9f61e
--- /dev/null
@@ -0,0 +1,26 @@
+MThd | Format=1 | # of Tracks=2 | Division=15360\r
+\r
+\r
+\r
+\r
+Track #0 ******************************************\r
+    Time       Event\r
+    \r
+   1: 1:  0 |Time Sig    |  4/4      | MIDI-clocks\click=24 | 32nds\quarter=8\r
+            |Tempo       | BPM=120   | micros\quarter=500000\r
+ 101: 1:  0 |End of track| \r
+\r
+\r
+\r
+\r
+\r
+Track #1 ******************************************\r
+    Time       Event\r
+    \r
+   1: 1:  0 |Track Name  | len=7     |\r
+            0x53 0x79 0x6E 0x74 0x68 0x20 0x31 <Synth 1>\r
+            |Instrument  | len=7     |\r
+            0x53 0x79 0x6E 0x74 0x68 0x20 0x31 <Synth 1>\r
+            |On Note     | chan= 1   | pitch=C 1         | vol=127\r
+   2: 1:  0 |Off Note    | chan= 1   | pitch=c 1         | vol=0\r
+ 101: 1:  0 |End of track| \r
diff --git a/src/mxmMidi/test/midifiles/minimal_analyse.txt b/src/mxmMidi/test/midifiles/minimal_analyse.txt
new file mode 100644 (file)
index 0000000..498dfe9
--- /dev/null
@@ -0,0 +1,54 @@
+4D 54 68 64         MThd\r
+00 00 00 06         len:    6\r
+00 01               Type:   1\r
+00 02               tracks: 2\r
+3C 00               tempo:  15360\r
+\r
+\r
+\r
+4D 54 72 6B         MTrk\r
+00 00 00 16         len: 22\r
+\r
+00                  time\r
+    FF 58 04        Time Signature\r
+    04 02 18 08     4/4 24 8\r
+    \r
+00                  time\r
+    FF 51 03        TEMPO:\r
+    07 A1 20        500.000 mySec\r
+\r
+82 F7 80 00         time  ## oh bugger, it's buggy.\r
+    FF 2F 00        End Of Track\r
+    \r
+\r
+\r
+4D 54 72 6B         MTrk\r
+00 00 00 2C         len: 44\r
+\r
+00                  time\r
+    FF 03           Sequence/Track Name\r
+    07              len: 7\r
+    53 79 6E 74\r
+    68 20 31        'Synth 1'\r
+\r
+00                  time\r
+    FF 04           Instrument\r
+    07              len: 7\r
+    53 79 6E 74\r
+    68 20 31        'Synth 1'\r
+\r
+00                  time\r
+    FF 21 01        Midi port\r
+    04              Port #5\r
+\r
+00                  time\r
+    90 24 7F        Note ON\r
+    83 E0 00        Note OFF\r
+    \r
+00                  time\r
+    80 24 00        Note OFF\r
+\r
+00 82 F3 A0        \r
+\r
+00\r
+    FF 2F 00        End Of Track
\ No newline at end of file
diff --git a/src/mxmMidi/test/midifiles/readme.txt b/src/mxmMidi/test/midifiles/readme.txt
new file mode 100644 (file)
index 0000000..f3f59cd
--- /dev/null
@@ -0,0 +1,7 @@
+These files are used for testing the midi package\r
+=================================================\r
+\r
+minimal.mid\r
+-----------\r
+\r
+A minimal working midifile. Plays a one bar "middle C" at 120 bpm. The absolute simplest file I could get to play in midi devices.
\ No newline at end of file
diff --git a/src/mxmMidi/test/readme.txt b/src/mxmMidi/test/readme.txt
new file mode 100644 (file)
index 0000000..c40adf3
--- /dev/null
@@ -0,0 +1,3 @@
+Embarrasingly empty.\r
+\r
+Why don't you write some tests?
\ No newline at end of file
diff --git a/src/mxmMidi/version.txt b/src/mxmMidi/version.txt
new file mode 100644 (file)
index 0000000..446ba66
--- /dev/null
@@ -0,0 +1 @@
+0.1.4
\ No newline at end of file
diff --git a/src/pgu/__init__.py b/src/pgu/__init__.py
new file mode 100644 (file)
index 0000000..5cc6d1c
--- /dev/null
@@ -0,0 +1,7 @@
+"""Phil's pyGame Utilities
+
+
+"""
+__version__ = '0.12.3'
+
+# vim: set filetype=python sts=4 sw=4 noet si :
diff --git a/src/pgu/algo.py b/src/pgu/algo.py
new file mode 100644 (file)
index 0000000..993eab0
--- /dev/null
@@ -0,0 +1,143 @@
+"""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>
+"""
+
+# The manhattan distance metric
+def manhattan_dist(a,b):
+    return abs(a[0]-b[0]) + abs(a[1]-b[1])
+    
+class node:
+    def __init__(self, prev, pos, dest, dist):
+        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=manhattan_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) - manhattan distance is used by default
+    </dl>
+    
+    <p>returns a list of positions from start to end</p>
+    """
+
+    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
+
+    if layer[start[1]][start[0]]:
+        return [] #start is blocked
+    if layer[end[1]][end[0]]:
+        return [] #end is blocked
+
+    opens = []
+    open = {}
+    closed = {}
+    cur = node(None, start, end, dist)
+    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)]:
+            pos = cur.pos[0]+dx,cur.pos[1]+dy
+            # Check if the point lies in the grid
+            if (pos[0] < 0 or pos[1] < 0 or 
+                pos[0] >= w or pos[1] >= h or
+                layer[pos[0]][pos[1]]):
+                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, dist)
+            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/src/pgu/ani.py b/src/pgu/ani.py
new file mode 100644 (file)
index 0000000..c33d380
--- /dev/null
@@ -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/src/pgu/engine.py b/src/pgu/engine.py
new file mode 100644 (file)
index 0000000..76be583
--- /dev/null
@@ -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/src/pgu/fonts.py b/src/pgu/fonts.py
new file mode 100644 (file)
index 0000000..ab6f73d
--- /dev/null
@@ -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/src/pgu/gui/__init__.py b/src/pgu/gui/__init__.py
new file mode 100644 (file)
index 0000000..256fb63
--- /dev/null
@@ -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/src/pgu/gui/app.py b/src/pgu/gui/app.py
new file mode 100644 (file)
index 0000000..857af98
--- /dev/null
@@ -0,0 +1,279 @@
+"""
+"""
+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
+
+    def open(self, w, pos=None):
+        w.container = self
+        
+        if (w.rect.w == 0 or w.rect.h == 0):
+            w.rect.size = w.resize()
+        
+        if (not pos): 
+            # Auto-center the window
+            w.rect.center = self.rect.center
+            #w.rect.topleft = ((self.rect.w - w.rect.w)/2,
+            #                  (self.rect.h - w.rect.h)/2)
+        else: 
+            # Show the window in a particular location
+            w.rect.topleft = pos
+        
+        self.windows.append(w)
+        self.mywindow = w
+        self.focus(w)
+        self.repaint(w)
+        w.send(OPEN)
+
+    def close(self, 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)
+
+
+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/src/pgu/gui/area.py b/src/pgu/gui/area.py
new file mode 100644 (file)
index 0000000..64074d6
--- /dev/null
@@ -0,0 +1,454 @@
+"""
+"""
+import os
+
+import pguglobals
+from const import *
+import surface
+import container, table
+import group
+import basic, button, slider
+from pygame.font import Font
+
+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)
+
+    def event(self, e):
+         #checking for event recipient
+         if (table.Table.event(self, e)):
+             return True
+
+         #mouse wheel scrolling
+         if self.vscrollbar:
+             if not hasattr(self.vscrollbar,'value'): 
+                 return False
+             if e.type == pygame.locals.MOUSEBUTTONDOWN:
+                 if e.button == 4: #wheel up
+                     self.vscrollbar._click(-1)
+                     return True
+                 elif e.button == 5: #wheel down
+                     self.vscrollbar._click(1)
+                     return True
+         return False
+
+        
+
+
+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/src/pgu/gui/basic.py b/src/pgu/gui/basic.py
new file mode 100644 (file)
index 0000000..3800440
--- /dev/null
@@ -0,0 +1,136 @@
+"""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 (is_color(desc)):
+        # 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)
+
+# Determines if the given object is a pygame-compatible color or not
+def is_color(col):
+    # In every version of pygame (up to 1.8.1 so far) will interpret
+    # a tuple as a color.
+    if (type(col) == tuple):
+        return col
+    if (hasattr(pygame, "Color") and type(pygame.Color) == type):
+        # This is a recent version of pygame that uses a proper type
+        # instance for colors.
+        return (isinstance(col, pygame.Color))
+    # Otherwise, this version of pygame only supports tuple colors
+    return False
+
+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/src/pgu/gui/button.py b/src/pgu/gui/button.py
new file mode 100644 (file)
index 0000000..90cdd1e
--- /dev/null
@@ -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/src/pgu/gui/const.py b/src/pgu/gui/const.py
new file mode 100644 (file)
index 0000000..c865cd8
--- /dev/null
@@ -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/src/pgu/gui/container.py b/src/pgu/gui/container.py
new file mode 100644 (file)
index 0000000..73a8e68
--- /dev/null
@@ -0,0 +1,414 @@
+"""
+"""
+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)
+                #if (hasattr(w, "_container_bkgr")):
+                #    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') or 
+#                    w._container_bkgr.get_size() != sub.get_size()):
+#                         #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:
+            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
+        
+        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):
+        if (not w):
+            w = self
+
+        if (x != None):
+            # The position is relative to this container
+            rect = self.get_abs_rect()
+            pos = (rect.x + x, rect.y + y)
+        else:
+            pos = None
+        # Have the application open the window
+        pguglobals.app.open(w, pos)
+        
+    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
+
+    # Returns the widget with the given name
+    def find(self, name):
+        for w in self.widgets:
+            if (w.name == name):
+                return w
+            elif (isinstance(w, Container)):
+                tmp = w.find(name)
+                if (tmp): return tmp
+        return None
+
diff --git a/src/pgu/gui/deprecated.py b/src/pgu/gui/deprecated.py
new file mode 100644 (file)
index 0000000..8d53515
--- /dev/null
@@ -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/src/pgu/gui/dialog.py b/src/pgu/gui/dialog.py
new file mode 100644 (file)
index 0000000..0d30b34
--- /dev/null
@@ -0,0 +1,168 @@
+"""
+"""
+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", folderText = "Folder", fileText = "File", path=None, customFont = None, showCurDir = True, customWidth = 350, customHeight = 150):
+        
+        self.customFont = customFont
+        self.showCurDir= showCurDir
+        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=customWidth, height=customHeight)
+        self.input_dir = input.Input(customFont = self.customFont)
+        self.input_file = input.Input(customFont = self.customFont)
+        self._list_dir_()
+        self.button_ok = button.Button(button_txt)
+        self.body.tr()
+        if self.showCurDir :
+            self.body.td(basic.Label(folderText), 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(fileText), 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)
+            if self.customFont == None :
+                self.list.add(i,image=self.dir_img,value=i)
+            else :
+                label = basic.Label(i,font = self.customFont)
+                self.list.add(label,image=self.dir_img,value=i)
+        for i in files:
+            #item = ListItem(image=None, text=i, value=i)
+            if self.customFont == None :
+                self.list.add(i,value=i)
+            else:
+                label = basic.Label(i,font = self.customFont)
+                self.list.add(label,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/src/pgu/gui/document.py b/src/pgu/gui/document.py
new file mode 100644 (file)
index 0000000..1bd96df
--- /dev/null
@@ -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/src/pgu/gui/form.py b/src/pgu/gui/form.py
new file mode 100644 (file)
index 0000000..9a874f9
--- /dev/null
@@ -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/src/pgu/gui/group.py b/src/pgu/gui/group.py
new file mode 100644 (file)
index 0000000..bcb231a
--- /dev/null
@@ -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/src/pgu/gui/input.py b/src/pgu/gui/input.py
new file mode 100644 (file)
index 0000000..3f3f653
--- /dev/null
@@ -0,0 +1,169 @@
+"""
+"""
+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,customFont = None,**params):
+        params.setdefault('cls','input')
+        widget.Widget.__init__(self,**params)
+        self.value = value
+        self.pos = len(str(value))
+        self.vpos = 0
+        if customFont != None:
+            self.font = customFont
+        else:
+            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/src/pgu/gui/keysym.py b/src/pgu/gui/keysym.py
new file mode 100644 (file)
index 0000000..cc24089
--- /dev/null
@@ -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/src/pgu/gui/layout.py b/src/pgu/gui/layout.py
new file mode 100644 (file)
index 0000000..01fe0cb
--- /dev/null
@@ -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/src/pgu/gui/menus.py b/src/pgu/gui/menus.py
new file mode 100644 (file)
index 0000000..b850b6c
--- /dev/null
@@ -0,0 +1,119 @@
+"""
+"""
+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._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/src/pgu/gui/misc.py b/src/pgu/gui/misc.py
new file mode 100644 (file)
index 0000000..afb10c5
--- /dev/null
@@ -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/src/pgu/gui/pguglobals.py b/src/pgu/gui/pguglobals.py
new file mode 100644 (file)
index 0000000..dc0e673
--- /dev/null
@@ -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/src/pgu/gui/select.py b/src/pgu/gui/select.py
new file mode 100644 (file)
index 0000000..0ee39e9
--- /dev/null
@@ -0,0 +1,180 @@
+"""
+"""
+
+import traceback
+
+from const import *
+from button import Button
+from basic import Label, Image
+from table import Table
+
+class Select(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>
+    
+    """
+
+    # The drop-down arrow button for the selection widget
+    top_arrow = None
+    # A button displaying the currently selected item
+    top_selection = None
+    # The first option added to the selector
+    firstOption = None
+    # The PGU table of options
+    options = None
+
+    def __init__(self,value=None,**params):
+        params.setdefault('cls','select')
+        Table.__init__(self,**params)
+        
+        label = Label(" ",cls=self.cls+".option.label")
+        self.top_selected = Button(label, cls=self.cls+".selected")
+        Table.add(self,self.top_selected) #,hexpand=1,vexpand=1)#,0,0)
+        
+        self.top_arrow = Button(Image(self.style.arrow), cls=self.cls+".arrow")
+        Table.add(self,self.top_arrow) #,hexpand=1,vexpand=1) #,1,0)
+        
+        self.options = Table(cls=self.cls+".options")
+        self.options.connect(BLUR,self._close,None)
+        self.options.name = "pulldown-table"
+        
+        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.resize(self,width,height)
+        
+        self.options.style.width = w
+        #HACK: sort of, but not a big one..
+        self.options.resize()
+        
+        return w,h
+        
+    def _open(self,value):
+        opts = self.options
+        
+        opts.rect.w, opts.rect.h = opts.resize()
+        
+#        y = self.rect.y
+#        c = self.container
+#        while hasattr(c, 'container'):
+#            y += c.rect.y
+#            if (not c.container): 
+#                break
+#            c = c.container
+            
+#        if y + self.rect.h + opts.rect.h <= c.rect.h: #down
+#            dy = self.rect.y + self.rect.h
+#        else: #up
+#            dy = self.rect.y - self.rect.h
+
+        opts.rect.w, opts.rect.h = opts.resize()
+
+        # TODO - make sure there is enough space to open down
+        # ...
+        yp = self.rect.bottom-1
+
+        self.container.open(opts, self.rect.x, yp)
+        self.firstOption.focus()
+
+        # TODO - this is a hack
+        for opt in self.options.widgets:
+            opt.repaint()
+
+    def _close(self,value):
+        self.options.close()
+        self.top_selected.focus()
+    
+    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 = 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 = Label(w,cls=self.cls+".option.label")
+        
+        w.style.align = -1
+        btn = Button(w,cls=self.cls+".option")
+        btn.connect(CLICK,self._setvalue,w)
+        
+        self.options.tr()
+        self.options.add(btn)
+        
+        if (not self.firstOption):
+            self.firstOption = btn
+        
+        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/src/pgu/gui/slider.py b/src/pgu/gui/slider.py
new file mode 100644 (file)
index 0000000..f4fa623
--- /dev/null
@@ -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/src/pgu/gui/style.py b/src/pgu/gui/style.py
new file mode 100644 (file)
index 0000000..3060928
--- /dev/null
@@ -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/src/pgu/gui/surface.py b/src/pgu/gui/surface.py
new file mode 100644 (file)
index 0000000..9bd064d
--- /dev/null
@@ -0,0 +1,143 @@
+"""
+"""
+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/src/pgu/gui/table.py b/src/pgu/gui/table.py
new file mode 100644 (file)
index 0000000..6ff6c74
--- /dev/null
@@ -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
+        #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/src/pgu/gui/textarea.py b/src/pgu/gui/textarea.py
new file mode 100644 (file)
index 0000000..667076a
--- /dev/null
@@ -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/src/pgu/gui/theme.py b/src/pgu/gui/theme.py
new file mode 100644 (file)
index 0000000..283c287
--- /dev/null
@@ -0,0 +1,485 @@
+# theme.py
+
+"""
+"""
+import os, re
+import pygame
+
+from const import *
+import widget
+import surface
+from basic import parse_color, is_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 is_color(box):
+            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)
+
+        
+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 is_color(v):
+            s.fill(v)
+        else: 
+            self.theme.render(s,v,r)
diff --git a/src/pgu/gui/widget.py b/src/pgu/gui/widget.py
new file mode 100644 (file)
index 0000000..f0f4b13
--- /dev/null
@@ -0,0 +1,377 @@
+"""
+"""
+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>
+    """
+
+    # The name of the widget (or None if not defined)
+    name = None
+    
+    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)
+        pguglobals.app.open(self)
+
+    def close(self, w=None):
+        """Close this Widget (if it is a modal dialog.)
+        
+        <pre>Widget.close()</pre>
+        """
+        #if getattr(self,'container',None) != None: self.container.close(self)
+        if (not w):
+            w = self
+        pguglobals.app.close(w)
+
+    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>
+        """
+        if (not code in self.connects):
+            self.connects[code] = []
+        for cb in self.connects[code]:
+            if (cb.func == func):
+                # Already connected to this callback function
+                return
+        # Wrap the callback function and add it to the list
+        cb = SignalCallback()
+        cb.func = func
+        cb.params = params
+        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
+
+    # Returns the top-level widget (usually the Desktop) by following the
+    # chain of 'container' references.
+    def get_toplevel(self):
+        top = self
+        while (getattr(top, "container", None)):
+            top = top.container
+        return top
+
diff --git a/src/pgu/hexvid.py b/src/pgu/hexvid.py
new file mode 100644 (file)
index 0000000..2d4156d
--- /dev/null
@@ -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/src/pgu/high.py b/src/pgu/high.py
new file mode 100644 (file)
index 0000000..e05d22a
--- /dev/null
@@ -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/src/pgu/html.py b/src/pgu/html.py
new file mode 100644 (file)
index 0000000..817c000
--- /dev/null
@@ -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/src/pgu/isovid.py b/src/pgu/isovid.py
new file mode 100644 (file)
index 0000000..d5048ec
--- /dev/null
@@ -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/src/pgu/layout.py b/src/pgu/layout.py
new file mode 100644 (file)
index 0000000..75b6c9a
--- /dev/null
@@ -0,0 +1,4 @@
+print 'pgu.layout','Scheduled to be deprecated.'
+
+from pgu.gui.layout import *
+
diff --git a/src/pgu/text.py b/src/pgu/text.py
new file mode 100644 (file)
index 0000000..1010a87
--- /dev/null
@@ -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/src/pgu/tilevid.py b/src/pgu/tilevid.py
new file mode 100644 (file)
index 0000000..00f730d
--- /dev/null
@@ -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/src/pgu/timer.py b/src/pgu/timer.py
new file mode 100644 (file)
index 0000000..1201b7a
--- /dev/null
@@ -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/src/pgu/vid.py b/src/pgu/vid.py
new file mode 100644 (file)
index 0000000..6890e5d
--- /dev/null
@@ -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/src/songs/MidiToSong.py b/src/songs/MidiToSong.py
new file mode 100644 (file)
index 0000000..f85b06c
--- /dev/null
@@ -0,0 +1,60 @@
+'''\r
+Created on 8 dec. 2009\r
+\r
+@author: Samuel Benveniste\r
+'''\r
+\r
+from mxmMidi.MidiOutStream import MidiOutStream\r
+\r
+class MidiToSong(MidiOutStream):\r
+    '''\r
+    Creates songs from midi files using mxmMidi package\r
+    '''\r
+    def __init__(self):\r
+        self.midiNoteNumbers = []\r
+        self.noteLengths = []\r
+        self.quarterNoteLength = 500\r
+        self.firstNoteOn = True\r
+        self._lastNoteOnTime = 0\r
+\r
+    def header(self, format=0, nTracks=1, division=96):\r
+        print 'format: %s, nTracks: %s, division: %s' % (format, nTracks, division)\r
+        print '----------------------------------'\r
+        print ''\r
+        self.division = division\r
+    \r
+    def tempo(self,value):\r
+        self.quarterNoteLength = value/1000\r
+        print "quarterNoteLength in ms :" + str(self.quarterNoteLength)   \r
+        \r
+    def note_on(self, channel=0, note=0x40, velocity=0x40):\r
+        self.midiNoteNumbers.append(note)\r
+        # if it's the first note_on take note of time (the song begins here)\r
+        # from the second note_on and after, mark the length of the preceding note\r
+        if self.firstNoteOn :\r
+            self.firstNoteOn = False\r
+            self._lastNoteOnTime = self.abs_time()\r
+        else :\r
+            self.noteLengths.append(float(self.abs_time()-self._lastNoteOnTime)/float(self.division))\r
+            self._lastNoteOnTime = self.abs_time()\r
+            \r
+    def eof(self):\r
+        self.noteLengths.append(4)\r
+        for i in range(len(self.midiNoteNumbers)):\r
+            print "note number :" + str(self.midiNoteNumbers[i]) + ", length in quarter Notes :" + str(self.noteLengths[i])\r
+        print"--------------"\r
+        print "end of file"\r
+            \r
+if __name__ == '__main__':\r
+\r
+    # get data\r
+    test_file = '../songs/midis/test.mid'\r
+    f = open(test_file, 'rb')\r
+    \r
+    # do parsing\r
+    from mxmMidi.MidiInFile import MidiInFile\r
+    mts = MidiToSong()\r
+    midiIn = MidiInFile(mts, f)\r
+    midiIn.read()\r
+    f.close()\r
+        
\ No newline at end of file
diff --git a/src/songs/Song.py b/src/songs/Song.py
new file mode 100755 (executable)
index 0000000..c018eae
--- /dev/null
@@ -0,0 +1,137 @@
+'''\r
+Created on 2 oct. 2009\r
+\r
+@author: samsam\r
+'''\r
+import pickle\r
+import os.path\r
+from MidiToSong import MidiToSong\r
+from mxmMidi.MidiInFile import MidiInFile\r
+\r
+class Song:\r
+    '''\r
+    classdocs\r
+    '''\r
+\r
+\r
+    def __init__(self,scale,notesInExtendedScale=[], requiresExtendedScale = False,midiNoteNumbers = None, alterationIndexes = None, alterations = None, modulationIndexes = None, modulationScales = None, lyrics =None, noteLengths = None, quarterNoteLength = 750, name = "unknownSong"):\r
+        '''\r
+        Constructor\r
+        '''\r
+        self.name = name\r
+        self.scale = scale\r
+        self.notes = notesInExtendedScale\r
+        self.lyrics = lyrics\r
+        self.noteLengths = noteLengths\r
+        self.quarterNoteLength = quarterNoteLength\r
+        if midiNoteNumbers == None :\r
+            self.midiNoteNumbers = [self.scale[note] for note in self.notes]\r
+        else:\r
+            self.midiNoteNumbers = midiNoteNumbers\r
+            if self.notes == [] :\r
+                self.assignNotesFromMidiNoteNumbers()\r
+        self.requiresExtendedScale = requiresExtendedScale\r
+        if alterationIndexes != None and alterations != None :\r
+            self.alterNotes(alterationIndexes, alterations)\r
+        if modulationIndexes != None and modulationScales != None :\r
+            self.modulate(modulationIndexes, modulationScales)\r
+        \r
+    def getSongIterator(self):\r
+        while True:\r
+            for i in range(len(self.notes)):\r
+                if self.lyrics :\r
+                    lyrics = self.lyrics[i]\r
+                else :\r
+                    lyrics = None\r
+                    \r
+                if self.noteLengths :\r
+                    noteLength = self.noteLengths[i]\r
+                else :\r
+                    noteLength = 1\r
+                    \r
+                yield [self.notes[i],self.midiNoteNumbers[i],lyrics,noteLength]\r
+                \r
+    def alterNotes(self,noteIndexes,alterations):\r
+        for i in range(len(noteIndexes)) :\r
+            self.midiNoteNumbers[noteIndexes[i]] = self.midiNoteNumbers[noteIndexes[i]] + alterations[i]\r
+    \r
+    def modulate(self,modulationIndexes,scales):\r
+        for i in range(len(scales)):\r
+            if i < len(scales)-1 :\r
+                bound = modulationIndexes[i+1]\r
+            else :\r
+                bound = len(self.notes)\r
+            for j in range(modulationIndexes[i],bound):\r
+                self.midiNoteNumbers[j]=scales[i][self.notes[j]]\r
+                \r
+    def assignNotesFromMidiNoteNumbers(self):\r
+        for i in range(len(self.midiNoteNumbers)):\r
+            noteInExtendedScale = 0\r
+            while self.midiNoteNumbers[i] > self.scale[noteInExtendedScale] and noteInExtendedScale < len(self.scale)-1:\r
+                noteInExtendedScale += 1\r
+            if self.midiNoteNumbers[i]<self.scale[noteInExtendedScale]:\r
+                noteInExtendedScale -= 1\r
+            self.notes.append(noteInExtendedScale)\r
+            \r
+    def save(self,path = None):\r
+        if path == None :\r
+            path = "".join(os.path.join("smwis",self.name),".swmi")      \r
+        f = file(path, 'w')\r
+        pickler = pickle.Pickler(f)\r
+        pickler.dump(self)        \r
+        f.close()\r
+            \r
+def loadSongFromMidi(filePath,scale):\r
+    # get data\r
+    test_file = '../songs/midis/test.mid'\r
+    f = open(test_file, 'rb')\r
+    \r
+    # do parsing\r
+    mts = MidiToSong()\r
+    midiIn = MidiInFile(mts, f)\r
+    midiIn.read()\r
+    f.close()\r
+    \r
+    s = Song(scale,midiNoteNumbers = mts.midiNoteNumbers,quarterNoteLength = mts.quarterNoteLength,noteLengths = mts.noteLengths)\r
+    for nn in s.notes :\r
+        if nn < 3 :\r
+            s.requiresExtendedScale = True            \r
+    return(s)                   \r
+\r
+def loadSong(filePath):\r
+    f = file(filePath, 'r')\r
+    unpickler = pickle.Unpickler(f)\r
+    s = unpickler.load()\r
+    f.close()\r
+    return(s)\r
+\r
+if __name__ == '__main__':\r
+\r
+    # get data\r
+    test_file = '../songs/midis/boheme.mid'\r
+    f = open(test_file, 'rb')\r
+    \r
+    # do parsing\r
+    from mxmMidi.MidiInFile import MidiInFile\r
+    mts = MidiToSong()\r
+    midiIn = MidiInFile(mts, f)\r
+    midiIn.read()\r
+    f.close()\r
+    \r
+    majorScale = [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]\r
+        \r
+    s = Song(majorScale,requiresExtendedScale = True, midiNoteNumbers = mts.midiNoteNumbers,quarterNoteLength = mts.quarterNoteLength,noteLengths = mts.noteLengths)\r
+    for i in range(len(s.midiNoteNumbers)):\r
+        print "note number :" + str(s.midiNoteNumbers[i]) + ", assigned note :" + str(s.notes[i]) + ", duration :" + str(s.noteLengths[i])\r
+    \r
+    s.lyrics = ["je","vous","par-","-le","d'un","temps","que","les","moins","de","vingt","ans","ne","peu-","-vent","pas","con-","-nai-","-tre","Mont-","-martre","en","ce","temps","la","ac-","-cro-","-chait","ses","li-","-las","jus-","-que","sous","nos","fe-","-netres","et","si","l'hum-","-ble","gar-","-ni","qui","nous","ser-","-vait","de","nid","ne","pay-","-yait","pas","de","mi-","-ne","c'est","la","qu'on","s'est","con-","-nus","moi","qui","cri-","-ait","fa-","-mine","et","toi","qui","po-","-sait","nue","la","bo-","-he-","-me","la","bo-","-he-","-me","ca","vou-","-lait","dire","on","est","heu-","-reux","la","bo-","-he-","-me","la","bo-","-he-","-me","nous","ne","man-","-gions","qu'un","jour","sur","deux"]\r
+    \r
+    #s.lyrics = ["quand","nous","chan-","-te-","-rons","le","temps","des","ce-","-rises","et","gai","ros-","-si-","-gnol","et","mer-","-le","mo-","-queur","se-","-ront","tous","en","fe-","-e-","-te","les","bel-","-les","au-","-ront","la","fo-","-lie","en","te","te","et","les","a-","-mou-","-reux","du","so-","-leil","au","coeur","quand","vous","chan-","-te-","-rez","le","temps","des","ce-","-rises","sif-","-fle-","-ra","bien","mieux","le","mer-","-le","mo-","-queur"]    \r
+    \r
+    #s.lyrics=["il","pleut","il","pleut","ber-","-ge-","-re","ren-","-tre","tes","blancs","mou-","-tons","al-","-lons","a","ma","chau-","-mie-","-re","ber-","-ge-","-re","vite","al-","-lons","j'en-","-tends","sur","le","feu-","-illa-","-ge","l'eau","qui","tombe","a","grand","bruit","voi-","-ci","ve-","-nir","l'o-","-ra-","-a-","-ge","voi-","-ci","l'e-","-clair","qui","luit"]\r
+    \r
+    #s.lyrics = ["c'est","une","chan-","-son","qui","nous","res-","-sem-","-ble","toi","tu","m'ai-","-mais","et","je","t'ai-","-mais","nous","e-","-tions","tout","les","deux","en-","-sem-","-ble","toi","qui","m'ai-","-mais","moi","qui","t'ai-","-mais","mais","la","vie","se-","-pare","ceux","qui","s'aiment","tout","dou-","-ce-","-ment","sans","faire","de","bruit","et","la","mer","ef-","-fa-","-ce","sur","le","sa-","-ble","les","pas","des","a-","-mants","de-","-su-","-nis"]\r
+\r
+    # s.lyrics = ["quand","il","me","prend","dans","ses","bras","qu'il","me","par-","-le","tout","bas","je","vois","la","vie","en","ro-","-se","il","me","dit","des","mots","d'a-","-mour","des","mots","de","tous","les","jours","et","ca","m'fait","quel-","-que","cho-","-se","il","est","en-","-tre","dans","mon","coeur","u-","-ne","part","de","bon-","-heur","dont","je","con-","-nais","la","cau-","-se","c'est","lmui","pour","moi","moi","pour","lui","dans","la","vie","il","m'a","pro-","-mis","m'a","ju-","-re","pour","la","vie","et","des","que","je","l'a-","-per-","-cois","a-","-lors","je","sens","en","moi","mon","coeur","qui","bat"]\r
+\r
+    s.save("smwis/boheme.smwi")    
\ No newline at end of file
diff --git a/src/songs/__init__.py b/src/songs/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/songs/midis/bergere.mid b/src/songs/midis/bergere.mid
new file mode 100644 (file)
index 0000000..8deb2f0
Binary files /dev/null and b/src/songs/midis/bergere.mid differ
diff --git a/src/songs/midis/boheme.mid b/src/songs/midis/boheme.mid
new file mode 100644 (file)
index 0000000..f7f2800
Binary files /dev/null and b/src/songs/midis/boheme.mid differ
diff --git a/src/songs/midis/cerises.mid b/src/songs/midis/cerises.mid
new file mode 100644 (file)
index 0000000..d858329
Binary files /dev/null and b/src/songs/midis/cerises.mid differ
diff --git a/src/songs/midis/feuillesmortes.mid b/src/songs/midis/feuillesmortes.mid
new file mode 100644 (file)
index 0000000..2c16ae6
Binary files /dev/null and b/src/songs/midis/feuillesmortes.mid differ
diff --git a/src/songs/midis/midi_export.mid b/src/songs/midis/midi_export.mid
new file mode 100644 (file)
index 0000000..282e8ef
Binary files /dev/null and b/src/songs/midis/midi_export.mid differ
diff --git a/src/songs/midis/test.mid b/src/songs/midis/test.mid
new file mode 100644 (file)
index 0000000..7df4824
Binary files /dev/null and b/src/songs/midis/test.mid differ
diff --git a/src/songs/midis/vierose.mid b/src/songs/midis/vierose.mid
new file mode 100644 (file)
index 0000000..91afe1d
Binary files /dev/null and b/src/songs/midis/vierose.mid differ
diff --git a/src/songs/smwis/Charles Aznavour/boheme.smwi b/src/songs/smwis/Charles Aznavour/boheme.smwi
new file mode 100644 (file)
index 0000000..cd1aa2c
--- /dev/null
@@ -0,0 +1,546 @@
+(i__main__\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I58\r
+aI58\r
+aI60\r
+aI58\r
+aI60\r
+aI61\r
+aI61\r
+aI61\r
+aI63\r
+aI61\r
+aI63\r
+aI65\r
+aI65\r
+aI65\r
+aI67\r
+aI65\r
+aI67\r
+aI68\r
+aI60\r
+aI68\r
+aI67\r
+aI65\r
+aI68\r
+aI67\r
+aI61\r
+aI68\r
+aI67\r
+aI65\r
+aI68\r
+aI67\r
+aI60\r
+aI60\r
+aI60\r
+aI60\r
+aI63\r
+aI62\r
+aI61\r
+aI58\r
+aI58\r
+aI60\r
+aI58\r
+aI60\r
+aI61\r
+aI61\r
+aI61\r
+aI63\r
+aI61\r
+aI63\r
+aI65\r
+aI65\r
+aI65\r
+aI67\r
+aI65\r
+aI67\r
+aI68\r
+aI68\r
+aI68\r
+aI68\r
+aI68\r
+aI72\r
+aI70\r
+aI70\r
+aI68\r
+aI68\r
+aI67\r
+aI70\r
+aI68\r
+aI68\r
+aI67\r
+aI67\r
+aI65\r
+aI64\r
+aI64\r
+aI65\r
+aI65\r
+aI64\r
+aI65\r
+aI58\r
+aI67\r
+aI65\r
+aI68\r
+aI60\r
+aI60\r
+aI63\r
+aI62\r
+aI61\r
+aI60\r
+aI55\r
+aI56\r
+aI60\r
+aI65\r
+aI64\r
+aI65\r
+aI58\r
+aI67\r
+aI65\r
+aI68\r
+aI60\r
+aI68\r
+aI67\r
+aI65\r
+aI63\r
+aI61\r
+aI60\r
+aI58\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'je'\r
+p10\r
+aS'vous'\r
+p11\r
+aS'par-'\r
+p12\r
+aS'-le'\r
+p13\r
+aS"d'un"\r
+p14\r
+aS'temps'\r
+p15\r
+aS'que'\r
+p16\r
+aS'les'\r
+p17\r
+aS'moins'\r
+p18\r
+aS'de'\r
+p19\r
+aS'vingt'\r
+p20\r
+aS'ans'\r
+p21\r
+aS'ne'\r
+p22\r
+aS'peu-'\r
+p23\r
+aS'-vent'\r
+p24\r
+aS'pas'\r
+p25\r
+aS'con-'\r
+p26\r
+aS'-nai-'\r
+p27\r
+aS'-tre'\r
+p28\r
+aS'Mont-'\r
+p29\r
+aS'-martre'\r
+p30\r
+aS'en'\r
+p31\r
+aS'ce'\r
+p32\r
+ag15\r
+aS'la'\r
+p33\r
+aS'ac-'\r
+p34\r
+aS'-cro-'\r
+p35\r
+aS'-chait'\r
+p36\r
+aS'ses'\r
+p37\r
+aS'li-'\r
+p38\r
+aS'-las'\r
+p39\r
+aS'jus-'\r
+p40\r
+aS'-que'\r
+p41\r
+aS'sous'\r
+p42\r
+aS'nos'\r
+p43\r
+aS'fe-'\r
+p44\r
+aS'-netres'\r
+p45\r
+aS'et'\r
+p46\r
+aS'si'\r
+p47\r
+aS"l'hum-"\r
+p48\r
+aS'-ble'\r
+p49\r
+aS'gar-'\r
+p50\r
+aS'-ni'\r
+p51\r
+aS'qui'\r
+p52\r
+aS'nous'\r
+p53\r
+aS'ser-'\r
+p54\r
+aS'-vait'\r
+p55\r
+ag19\r
+aS'nid'\r
+p56\r
+ag22\r
+aS'pay-'\r
+p57\r
+aS'-yait'\r
+p58\r
+ag25\r
+ag19\r
+aS'mi-'\r
+p59\r
+aS'-ne'\r
+p60\r
+aS"c'est"\r
+p61\r
+ag33\r
+aS"qu'on"\r
+p62\r
+aS"s'est"\r
+p63\r
+ag26\r
+aS'-nus'\r
+p64\r
+aS'moi'\r
+p65\r
+ag52\r
+aS'cri-'\r
+p66\r
+aS'-ait'\r
+p67\r
+aS'fa-'\r
+p68\r
+aS'-mine'\r
+p69\r
+ag46\r
+aS'toi'\r
+p70\r
+ag52\r
+aS'po-'\r
+p71\r
+aS'-sait'\r
+p72\r
+aS'nue'\r
+p73\r
+ag33\r
+aS'bo-'\r
+p74\r
+aS'-he-'\r
+p75\r
+aS'-me'\r
+p76\r
+ag33\r
+ag74\r
+ag75\r
+ag76\r
+aS'ca'\r
+p77\r
+aS'vou-'\r
+p78\r
+aS'-lait'\r
+p79\r
+aS'dire'\r
+p80\r
+aS'on'\r
+p81\r
+aS'est'\r
+p82\r
+aS'heu-'\r
+p83\r
+aS'-reux'\r
+p84\r
+ag33\r
+ag74\r
+ag75\r
+ag76\r
+ag33\r
+ag74\r
+ag75\r
+ag76\r
+ag53\r
+ag22\r
+aS'man-'\r
+p85\r
+aS'-gions'\r
+p86\r
+aS"qu'un"\r
+p87\r
+aS'jour'\r
+p88\r
+aS'sur'\r
+p89\r
+aS'deux'\r
+p90\r
+asS'noteLengths'\r
+p91\r
+(lp92\r
+F0.48229166666666667\r
+aF0.49166666666666664\r
+aF0.47395833333333331\r
+aF0.50104166666666672\r
+aF0.53229166666666672\r
+aF0.51666666666666672\r
+aF0.49270833333333336\r
+aF0.49375000000000002\r
+aF0.50624999999999998\r
+aF0.49791666666666667\r
+aF0.48229166666666667\r
+aF0.52812499999999996\r
+aF0.515625\r
+aF0.47708333333333336\r
+aF0.45729166666666665\r
+aF0.51770833333333333\r
+aF0.51458333333333328\r
+aF0.9916666666666667\r
+aF2.6270833333333332\r
+aF0.375\r
+aF0.50624999999999998\r
+aF0.49479166666666669\r
+aF0.55000000000000004\r
+aF0.49687500000000001\r
+aF3.4739583333333335\r
+aF0.48854166666666665\r
+aF0.48229166666666667\r
+aF0.5239583333333333\r
+aF0.47708333333333336\r
+aF0.515625\r
+aF0.53229166666666672\r
+aF0.51458333333333328\r
+aF0.46250000000000002\r
+aF0.50104166666666672\r
+aF0.46770833333333334\r
+aF0.51354166666666667\r
+aF0.49791666666666667\r
+aF0.47604166666666664\r
+aF0.52604166666666663\r
+aF0.49687500000000001\r
+aF0.48020833333333335\r
+aF0.5083333333333333\r
+aF0.50416666666666665\r
+aF0.48645833333333333\r
+aF0.54583333333333328\r
+aF0.47083333333333333\r
+aF0.48645833333333333\r
+aF0.5239583333333333\r
+aF0.484375\r
+aF0.49583333333333335\r
+aF0.49895833333333334\r
+aF0.50208333333333333\r
+aF0.48749999999999999\r
+aF0.53229166666666672\r
+aF0.5541666666666667\r
+aF2.9604166666666667\r
+aF0.51666666666666672\r
+aF0.48645833333333333\r
+aF0.48541666666666666\r
+aF0.49375000000000002\r
+aF0.48229166666666667\r
+aF0.49895833333333334\r
+aF0.52187499999999998\r
+aF0.49479166666666669\r
+aF0.5\r
+aF0.46250000000000002\r
+aF0.52187499999999998\r
+aF0.52604166666666663\r
+aF0.47083333333333333\r
+aF0.51458333333333328\r
+aF0.50937500000000002\r
+aF0.48645833333333333\r
+aF0.49062499999999998\r
+aF4.7041666666666666\r
+aF0.8989583333333333\r
+aF0.51666666666666672\r
+aF0.41979166666666667\r
+aF3.9927083333333333\r
+aF0.97916666666666663\r
+aF0.49270833333333336\r
+aF0.47916666666666669\r
+aF4.1531250000000002\r
+aF0.92812499999999998\r
+aF0.28541666666666665\r
+aF0.29999999999999999\r
+aF2.7749999999999999\r
+aF0.56562500000000004\r
+aF0.49270833333333336\r
+aF0.54583333333333328\r
+aF4.5125000000000002\r
+aF0.95625000000000004\r
+aF0.55000000000000004\r
+aF0.46875\r
+aF4.0250000000000004\r
+aF0.90312499999999996\r
+aF0.55729166666666663\r
+aF0.55000000000000004\r
+aF4.0281250000000002\r
+aF0.43125000000000002\r
+aF0.49479166666666669\r
+aF0.51458333333333328\r
+aF2.4812500000000002\r
+aF0.58125000000000004\r
+aF2.4520833333333334\r
+aF0.515625\r
+aI4\r
+asS'notes'\r
+p93\r
+(lp94\r
+I1\r
+aI1\r
+aI3\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI4\r
+aI3\r
+aI4\r
+aI6\r
+aI6\r
+aI6\r
+aI7\r
+aI6\r
+aI7\r
+aI7\r
+aI3\r
+aI7\r
+aI7\r
+aI6\r
+aI7\r
+aI7\r
+aI3\r
+aI7\r
+aI7\r
+aI6\r
+aI7\r
+aI7\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI4\r
+aI4\r
+aI3\r
+aI1\r
+aI1\r
+aI3\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI4\r
+aI3\r
+aI4\r
+aI6\r
+aI6\r
+aI6\r
+aI7\r
+aI6\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI10\r
+aI8\r
+aI8\r
+aI7\r
+aI7\r
+aI7\r
+aI8\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI6\r
+aI5\r
+aI5\r
+aI6\r
+aI6\r
+aI5\r
+aI6\r
+aI1\r
+aI7\r
+aI6\r
+aI7\r
+aI3\r
+aI3\r
+aI4\r
+aI4\r
+aI3\r
+aI3\r
+aI0\r
+aI0\r
+aI3\r
+aI6\r
+aI5\r
+aI6\r
+aI1\r
+aI7\r
+aI6\r
+aI7\r
+aI3\r
+aI7\r
+aI7\r
+aI6\r
+aI4\r
+aI3\r
+aI3\r
+aI1\r
+aI3\r
+asS'requiresExtendedScale'\r
+p95\r
+I01\r
+sS'quarterNoteLength'\r
+p96\r
+I750\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Edith Piaf/foule.smwi b/src/songs/smwis/Edith Piaf/foule.smwi
new file mode 100644 (file)
index 0000000..058fdc8
--- /dev/null
@@ -0,0 +1,400 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I67\r
+aI67\r
+aI65\r
+aI64\r
+aI65\r
+aI69\r
+aI67\r
+aI67\r
+aI65\r
+aI69\r
+aI67\r
+aI67\r
+aI65\r
+aI70\r
+aI67\r
+aI67\r
+aI65\r
+aI70\r
+aI67\r
+aI67\r
+aI65\r
+aI69\r
+aI67\r
+aI67\r
+aI65\r
+aI69\r
+aI67\r
+aI65\r
+aI63\r
+aI62\r
+aI62\r
+aI62\r
+aI62\r
+aI62\r
+aI62\r
+aI66\r
+aI66\r
+aI66\r
+aI66\r
+aI69\r
+aI69\r
+aI67\r
+aI66\r
+aI69\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI66\r
+aI70\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI66\r
+aI69\r
+aI69\r
+aI66\r
+aI69\r
+aI69\r
+aI67\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI58\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI70\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'Em-'\r
+p10\r
+aS'-por-'\r
+p11\r
+aS'-tes'\r
+p12\r
+aS'par'\r
+p13\r
+aS'la'\r
+p14\r
+aS'fou-'\r
+p15\r
+aS'-le'\r
+p16\r
+aS'qui'\r
+p17\r
+aS'nous'\r
+p18\r
+aS'trai-'\r
+p19\r
+aS'-ne'\r
+p20\r
+ag18\r
+aS'en-'\r
+p21\r
+aS'-trai-'\r
+p22\r
+ag20\r
+aS'e-'\r
+p23\r
+aS'-cra-'\r
+p24\r
+aS'-ses'\r
+p25\r
+aS"l'un"\r
+p26\r
+aS'con-'\r
+p27\r
+aS'-tre'\r
+p28\r
+aS"l'au-"\r
+p29\r
+ag28\r
+ag18\r
+aS'ne'\r
+p30\r
+aS'for-'\r
+p31\r
+aS'-mons'\r
+p32\r
+aS"qu'un"\r
+p33\r
+aS'seul'\r
+p34\r
+aS'corps'\r
+p35\r
+aS'et'\r
+p36\r
+aS'le'\r
+p37\r
+aS'flot'\r
+p38\r
+aS'sans'\r
+p39\r
+ag23\r
+aS'-ffort'\r
+p40\r
+ag18\r
+aS'pousse'\r
+p41\r
+ag21\r
+aS'-chai-'\r
+p42\r
+aS'-nes'\r
+p43\r
+ag26\r
+ag36\r
+ag29\r
+ag28\r
+ag36\r
+ag18\r
+aS'lai-'\r
+p44\r
+aS'-sse'\r
+p45\r
+aS'tous'\r
+p46\r
+aS'deux'\r
+p47\r
+ag23\r
+aS'-pa-'\r
+p48\r
+aS'-nouis-'\r
+p49\r
+ag21\r
+aS'-i-'\r
+p50\r
+aS'-vres'\r
+p51\r
+ag36\r
+aS'heu-'\r
+p52\r
+aS'-reux'\r
+p53\r
+aS'ta-'\r
+p54\r
+aS'-dam'\r
+p55\r
+aS'ta'\r
+p56\r
+ag54\r
+ag55\r
+ag56\r
+ag54\r
+ag55\r
+ag56\r
+ag54\r
+ag55\r
+ag56\r
+ag54\r
+ag55\r
+ag56\r
+ag54\r
+ag55\r
+ag56\r
+asS'noteLengths'\r
+p57\r
+(lp58\r
+I1\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aI3\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aI1\r
+aI1\r
+aF0.5\r
+aF0.5\r
+aF1.5\r
+aI3\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aI1\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aI3\r
+asS'notes'\r
+p59\r
+(lp60\r
+I7\r
+aI7\r
+aI6\r
+aI5\r
+aI6\r
+aI8\r
+aI7\r
+aI7\r
+aI6\r
+aI8\r
+aI7\r
+aI7\r
+aI6\r
+aI9\r
+aI7\r
+aI7\r
+aI6\r
+aI9\r
+aI7\r
+aI7\r
+aI6\r
+aI8\r
+aI7\r
+aI7\r
+aI6\r
+aI8\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI4\r
+aI4\r
+aI4\r
+aI4\r
+aI4\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI8\r
+aI8\r
+aI7\r
+aI6\r
+aI8\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI6\r
+aI9\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI6\r
+aI8\r
+aI8\r
+aI6\r
+aI8\r
+aI8\r
+aI7\r
+asS'requiresExtendedScale'\r
+p61\r
+I00\r
+sS'quarterNoteLength'\r
+p62\r
+I400\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Edith Piaf/vierose.smwi b/src/songs/smwis/Edith Piaf/vierose.smwi
new file mode 100644 (file)
index 0000000..b00a088
--- /dev/null
@@ -0,0 +1,496 @@
+(i__main__\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI55\r
+aI66\r
+aI64\r
+aI62\r
+aI69\r
+aI67\r
+aI66\r
+aI64\r
+aI60\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI55\r
+aI66\r
+aI64\r
+aI62\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI55\r
+aI55\r
+aI67\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI62\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI67\r
+aI71\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI59\r
+aI55\r
+aI66\r
+aI64\r
+aI62\r
+aI64\r
+aI66\r
+aI67\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'quand'\r
+p10\r
+aS'il'\r
+p11\r
+aS'me'\r
+p12\r
+aS'prend'\r
+p13\r
+aS'dans'\r
+p14\r
+aS'ses'\r
+p15\r
+aS'bras'\r
+p16\r
+aS"qu'il"\r
+p17\r
+ag12\r
+aS'par-'\r
+p18\r
+aS'-le'\r
+p19\r
+aS'tout'\r
+p20\r
+aS'bas'\r
+p21\r
+aS'je'\r
+p22\r
+aS'vois'\r
+p23\r
+aS'la'\r
+p24\r
+aS'vie'\r
+p25\r
+aS'en'\r
+p26\r
+aS'ro-'\r
+p27\r
+aS'-se'\r
+p28\r
+ag11\r
+ag12\r
+aS'dit'\r
+p29\r
+aS'des'\r
+p30\r
+aS'mots'\r
+p31\r
+aS"d'a-"\r
+p32\r
+aS'-mour'\r
+p33\r
+ag30\r
+ag31\r
+aS'de'\r
+p34\r
+aS'tous'\r
+p35\r
+aS'les'\r
+p36\r
+aS'jours'\r
+p37\r
+aS'et'\r
+p38\r
+aS'ca'\r
+p39\r
+aS"m'fait"\r
+p40\r
+aS'quel-'\r
+p41\r
+aS'-que'\r
+p42\r
+aS'cho-'\r
+p43\r
+ag28\r
+ag11\r
+aS'est'\r
+p44\r
+aS'en-'\r
+p45\r
+aS'-tre'\r
+p46\r
+ag14\r
+aS'mon'\r
+p47\r
+aS'coeur'\r
+p48\r
+aS'u-'\r
+p49\r
+aS'-ne'\r
+p50\r
+aS'part'\r
+p51\r
+ag34\r
+aS'bon-'\r
+p52\r
+aS'-heur'\r
+p53\r
+aS'dont'\r
+p54\r
+ag22\r
+aS'con-'\r
+p55\r
+aS'-nais'\r
+p56\r
+ag24\r
+aS'cau-'\r
+p57\r
+ag28\r
+aS"c'est"\r
+p58\r
+aS'lmui'\r
+p59\r
+aS'pour'\r
+p60\r
+aS'moi'\r
+p61\r
+ag61\r
+ag60\r
+aS'lui'\r
+p62\r
+ag14\r
+ag24\r
+ag25\r
+ag11\r
+aS"m'a"\r
+p63\r
+aS'pro-'\r
+p64\r
+aS'-mis'\r
+p65\r
+ag63\r
+aS'ju-'\r
+p66\r
+aS'-re'\r
+p67\r
+ag60\r
+ag24\r
+ag25\r
+ag38\r
+ag30\r
+aS'que'\r
+p68\r
+ag22\r
+aS"l'a-"\r
+p69\r
+aS'-per-'\r
+p70\r
+aS'-cois'\r
+p71\r
+aS'a-'\r
+p72\r
+aS'-lors'\r
+p73\r
+ag22\r
+aS'sens'\r
+p74\r
+ag26\r
+ag61\r
+ag47\r
+ag48\r
+aS'qui'\r
+p75\r
+aS'bat'\r
+p76\r
+asS'noteLengths'\r
+p77\r
+(lp78\r
+F2.5385416666666667\r
+aF0.52812499999999996\r
+aF0.88645833333333335\r
+aF1.0291666666666666\r
+aF0.52187499999999998\r
+aF0.453125\r
+aF2.4583333333333335\r
+aF0.5864583333333333\r
+aF0.95937499999999998\r
+aF1.0364583333333333\r
+aF0.52812499999999996\r
+aF0.4375\r
+aF2.6343749999999999\r
+aF0.43125000000000002\r
+aF0.93541666666666667\r
+aF0.99479166666666663\r
+aF0.51666666666666672\r
+aF0.46666666666666667\r
+aF3.2052083333333332\r
+aF2.8843749999999999\r
+aF2.1812499999999999\r
+aF0.6645833333333333\r
+aF1.0562499999999999\r
+aF1.0552083333333333\r
+aF0.484375\r
+aF0.53437500000000004\r
+aF2.3062499999999999\r
+aF0.69791666666666663\r
+aF0.96666666666666667\r
+aF0.95729166666666665\r
+aF0.5625\r
+aF0.48229166666666667\r
+aF2.5791666666666666\r
+aF0.43437500000000001\r
+aF0.95416666666666672\r
+aF1.03125\r
+aF0.62395833333333328\r
+aF0.40625\r
+aF3.0270833333333331\r
+aF8.90625\r
+aF2.2927083333333331\r
+aF0.75624999999999998\r
+aF0.95208333333333328\r
+aF1.0135416666666666\r
+aF0.453125\r
+aF0.55729166666666663\r
+aF2.2895833333333333\r
+aF0.71562499999999996\r
+aF0.95729166666666665\r
+aF1.0447916666666666\r
+aF0.50624999999999998\r
+aF0.47604166666666664\r
+aF2.3885416666666668\r
+aF0.671875\r
+aF0.90416666666666667\r
+aF1.0802083333333334\r
+aF0.46666666666666667\r
+aF0.54166666666666663\r
+aF3.0093749999999999\r
+aF2.8510416666666667\r
+aF0.57604166666666667\r
+aF0.50520833333333337\r
+aF0.54166666666666663\r
+aF0.5\r
+aF0.43333333333333335\r
+aF0.578125\r
+aF0.484375\r
+aF0.40625\r
+aF0.59583333333333333\r
+aF1.5260416666666667\r
+aF0.46458333333333335\r
+aF0.484375\r
+aF0.49583333333333335\r
+aF0.50312500000000004\r
+aF0.51458333333333328\r
+aF0.5385416666666667\r
+aF0.52187499999999998\r
+aF0.72499999999999998\r
+aF0.90729166666666672\r
+aF3.9052083333333334\r
+aF2.0145833333333334\r
+aF0.70104166666666667\r
+aF1.01875\r
+aF1.0927083333333334\r
+aF0.57499999999999996\r
+aF0.48020833333333335\r
+aF2.2312500000000002\r
+aF0.64583333333333337\r
+aF1.0395833333333333\r
+aF1.0791666666666666\r
+aF0.51458333333333328\r
+aF0.50416666666666665\r
+aF2.3979166666666667\r
+aF1.421875\r
+aF1.2749999999999999\r
+aF1.4989583333333334\r
+aI4\r
+asS'notes'\r
+p79\r
+(lp80\r
+I7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI0\r
+aI6\r
+aI5\r
+aI4\r
+aI8\r
+aI7\r
+aI6\r
+aI5\r
+aI3\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI0\r
+aI6\r
+aI5\r
+aI4\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI0\r
+aI0\r
+aI7\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI4\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI7\r
+aI9\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI2\r
+aI0\r
+aI6\r
+aI5\r
+aI4\r
+aI5\r
+aI6\r
+aI7\r
+asS'requiresExtendedScale'\r
+p81\r
+I01\r
+sS'quarterNoteLength'\r
+p82\r
+I666\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/bergere.smwi b/src/songs/smwis/Grands Classiques/bergere.smwi
new file mode 100644 (file)
index 0000000..dbb6b64
--- /dev/null
@@ -0,0 +1,293 @@
+(i__main__\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I64\r
+aI67\r
+aI64\r
+aI67\r
+aI64\r
+aI60\r
+aI55\r
+aI60\r
+aI59\r
+aI60\r
+aI62\r
+aI62\r
+aI64\r
+aI64\r
+aI62\r
+aI64\r
+aI65\r
+aI64\r
+aI67\r
+aI64\r
+aI67\r
+aI69\r
+aI67\r
+aI65\r
+aI64\r
+aI62\r
+aI62\r
+aI61\r
+aI62\r
+aI65\r
+aI65\r
+aI64\r
+aI67\r
+aI65\r
+aI64\r
+aI62\r
+aI64\r
+aI60\r
+aI62\r
+aI64\r
+aI67\r
+aI64\r
+aI67\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI67\r
+aI69\r
+aI67\r
+aI62\r
+aI64\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'il'\r
+p10\r
+aS'pleut'\r
+p11\r
+ag10\r
+ag11\r
+aS'ber-'\r
+p12\r
+aS'-ge-'\r
+p13\r
+aS'-re'\r
+p14\r
+aS'ren-'\r
+p15\r
+aS'-tre'\r
+p16\r
+aS'tes'\r
+p17\r
+aS'blancs'\r
+p18\r
+aS'mou-'\r
+p19\r
+aS'-tons'\r
+p20\r
+aS'al-'\r
+p21\r
+aS'-lons'\r
+p22\r
+aS'a'\r
+p23\r
+aS'ma'\r
+p24\r
+aS'chau-'\r
+p25\r
+aS'-mie-'\r
+p26\r
+ag14\r
+ag12\r
+ag13\r
+ag14\r
+aS'vite'\r
+p27\r
+ag21\r
+ag22\r
+aS"j'en-"\r
+p28\r
+aS'-tends'\r
+p29\r
+aS'sur'\r
+p30\r
+aS'le'\r
+p31\r
+aS'feu-'\r
+p32\r
+aS'-illa-'\r
+p33\r
+aS'-ge'\r
+p34\r
+aS"l'eau"\r
+p35\r
+aS'qui'\r
+p36\r
+aS'tombe'\r
+p37\r
+ag23\r
+aS'grand'\r
+p38\r
+aS'bruit'\r
+p39\r
+aS'voi-'\r
+p40\r
+aS'-ci'\r
+p41\r
+aS've-'\r
+p42\r
+aS'-nir'\r
+p43\r
+aS"l'o-"\r
+p44\r
+aS'-ra-'\r
+p45\r
+aS'-a-'\r
+p46\r
+ag34\r
+ag40\r
+ag41\r
+aS"l'e-"\r
+p47\r
+aS'-clair'\r
+p48\r
+ag36\r
+aS'luit'\r
+p49\r
+asS'noteLengths'\r
+p50\r
+(lp51\r
+F0.54479166666666667\r
+aF0.94999999999999996\r
+aF0.54374999999999996\r
+aF0.94999999999999996\r
+aF0.56770833333333337\r
+aF1.5270833333333333\r
+aF1.48125\r
+aF0.47708333333333336\r
+aF0.48749999999999999\r
+aF0.50624999999999998\r
+aF0.98541666666666672\r
+aF0.56145833333333328\r
+aF2.9812500000000002\r
+aF0.5072916666666667\r
+aF0.48541666666666666\r
+aF0.484375\r
+aF0.95625000000000004\r
+aF0.5854166666666667\r
+aF1.4989583333333334\r
+aF1.45625\r
+aF0.5\r
+aF0.51458333333333328\r
+aF0.5072916666666667\r
+aF0.90312499999999996\r
+aF0.6166666666666667\r
+aF2.9989583333333334\r
+aF0.47395833333333331\r
+aF0.52916666666666667\r
+aF0.51249999999999996\r
+aF0.93333333333333335\r
+aF0.52812499999999996\r
+aF1.5135416666666666\r
+aF1.4791666666666667\r
+aF0.51354166666666667\r
+aF0.52812499999999996\r
+aF0.49166666666666664\r
+aF0.97499999999999998\r
+aF0.5229166666666667\r
+aF2.3718750000000002\r
+aF0.61145833333333333\r
+aF1.0093749999999999\r
+aF0.52708333333333335\r
+aF0.8822916666666667\r
+aF0.58958333333333335\r
+aF0.96458333333333335\r
+aF0.53020833333333328\r
+aF1.5562499999999999\r
+aF0.48541666666666666\r
+aF0.48333333333333334\r
+aF0.49583333333333335\r
+aF0.94270833333333337\r
+aF0.5385416666666667\r
+aI4\r
+asS'notes'\r
+p52\r
+(lp53\r
+I5\r
+aI7\r
+aI5\r
+aI7\r
+aI5\r
+aI3\r
+aI0\r
+aI3\r
+aI2\r
+aI3\r
+aI4\r
+aI4\r
+aI5\r
+aI5\r
+aI4\r
+aI5\r
+aI6\r
+aI5\r
+aI7\r
+aI5\r
+aI7\r
+aI8\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI4\r
+aI3\r
+aI4\r
+aI6\r
+aI6\r
+aI5\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI5\r
+aI3\r
+aI4\r
+aI5\r
+aI7\r
+aI5\r
+aI7\r
+aI5\r
+aI6\r
+aI7\r
+aI8\r
+aI7\r
+aI8\r
+aI7\r
+aI4\r
+aI5\r
+aI3\r
+asS'requiresExtendedScale'\r
+p54\r
+I01\r
+sS'quarterNoteLength'\r
+p55\r
+I600\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/cerises.smwi b/src/songs/smwis/Grands Classiques/cerises.smwi
new file mode 100644 (file)
index 0000000..3b885c1
--- /dev/null
@@ -0,0 +1,362 @@
+(i__main__\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I55\r
+aI60\r
+aI60\r
+aI60\r
+aI60\r
+aI60\r
+aI62\r
+aI62\r
+aI62\r
+aI62\r
+aI60\r
+aI64\r
+aI64\r
+aI64\r
+aI64\r
+aI60\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI64\r
+aI64\r
+aI65\r
+aI64\r
+aI62\r
+aI60\r
+aI60\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI69\r
+aI65\r
+aI65\r
+aI64\r
+aI64\r
+aI64\r
+aI64\r
+aI64\r
+aI67\r
+aI64\r
+aI64\r
+aI62\r
+aI60\r
+aI62\r
+aI55\r
+aI60\r
+aI60\r
+aI60\r
+aI60\r
+aI60\r
+aI62\r
+aI62\r
+aI62\r
+aI62\r
+aI60\r
+aI64\r
+aI64\r
+aI64\r
+aI67\r
+aI64\r
+aI64\r
+aI62\r
+aI60\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'quand'\r
+p10\r
+aS'nous'\r
+p11\r
+aS'chan-'\r
+p12\r
+aS'-te-'\r
+p13\r
+aS'-rons'\r
+p14\r
+aS'le'\r
+p15\r
+aS'temps'\r
+p16\r
+aS'des'\r
+p17\r
+aS'ce-'\r
+p18\r
+aS'-rises'\r
+p19\r
+aS'et'\r
+p20\r
+aS'gai'\r
+p21\r
+aS'ros-'\r
+p22\r
+aS'-si-'\r
+p23\r
+aS'-gnol'\r
+p24\r
+ag20\r
+aS'mer-'\r
+p25\r
+aS'-le'\r
+p26\r
+aS'mo-'\r
+p27\r
+aS'-queur'\r
+p28\r
+aS'se-'\r
+p29\r
+aS'-ront'\r
+p30\r
+aS'tous'\r
+p31\r
+aS'en'\r
+p32\r
+aS'fe-'\r
+p33\r
+aS'-e-'\r
+p34\r
+aS'-te'\r
+p35\r
+aS'les'\r
+p36\r
+aS'bel-'\r
+p37\r
+aS'-les'\r
+p38\r
+aS'au-'\r
+p39\r
+ag30\r
+aS'la'\r
+p40\r
+aS'fo-'\r
+p41\r
+aS'-lie'\r
+p42\r
+ag32\r
+aS'te'\r
+p43\r
+ag43\r
+ag20\r
+ag36\r
+aS'a-'\r
+p44\r
+aS'-mou-'\r
+p45\r
+aS'-reux'\r
+p46\r
+aS'du'\r
+p47\r
+aS'so-'\r
+p48\r
+aS'-leil'\r
+p49\r
+aS'au'\r
+p50\r
+aS'coeur'\r
+p51\r
+ag10\r
+aS'vous'\r
+p52\r
+ag12\r
+ag13\r
+aS'-rez'\r
+p53\r
+ag15\r
+ag16\r
+ag17\r
+ag18\r
+ag19\r
+aS'sif-'\r
+p54\r
+aS'-fle-'\r
+p55\r
+aS'-ra'\r
+p56\r
+aS'bien'\r
+p57\r
+aS'mieux'\r
+p58\r
+ag15\r
+ag25\r
+ag26\r
+ag27\r
+ag28\r
+asS'noteLengths'\r
+p59\r
+(lp60\r
+F0.41666666666666669\r
+aF0.47395833333333331\r
+aF0.44583333333333336\r
+aF0.49270833333333336\r
+aF1.0104166666666667\r
+aF0.57604166666666667\r
+aF0.45729166666666665\r
+aF0.45729166666666665\r
+aF0.51770833333333333\r
+aF0.92604166666666665\r
+aF0.55937499999999996\r
+aF0.515625\r
+aF0.46250000000000002\r
+aF0.49375000000000002\r
+aF0.9916666666666667\r
+aF0.53125\r
+aF0.50104166666666672\r
+aF0.5229166666666667\r
+aF0.49270833333333336\r
+aF1.0260416666666667\r
+aF0.421875\r
+aF0.51041666666666663\r
+aF0.48645833333333333\r
+aF0.53229166666666672\r
+aF1.5\r
+aF1.4770833333333333\r
+aF1.1104166666666666\r
+aF0.42291666666666666\r
+aF0.48229166666666667\r
+aF0.49895833333333334\r
+aF0.5229166666666667\r
+aF1.0385416666666667\r
+aF0.49479166666666669\r
+aF0.47708333333333336\r
+aF0.59687500000000004\r
+aF0.43958333333333333\r
+aF0.46041666666666664\r
+aF0.48749999999999999\r
+aF0.53333333333333333\r
+aF0.46562500000000001\r
+aF0.52083333333333337\r
+aF0.49791666666666667\r
+aF0.98124999999999996\r
+aF0.51145833333333335\r
+aF0.49583333333333335\r
+aF0.53645833333333337\r
+aF0.46770833333333334\r
+aF1.0270833333333333\r
+aF0.5552083333333333\r
+aF0.41354166666666664\r
+aF0.47708333333333336\r
+aF0.51145833333333335\r
+aF1.1114583333333334\r
+aF0.45000000000000001\r
+aF0.46458333333333335\r
+aF0.51041666666666663\r
+aF0.46770833333333334\r
+aF1.0427083333333333\r
+aF0.49583333333333335\r
+aF0.5\r
+aF0.46770833333333334\r
+aF0.52604166666666663\r
+aF1.0479166666666666\r
+aF0.50104166666666672\r
+aF0.46770833333333334\r
+aF0.484375\r
+aF0.49166666666666664\r
+aI4\r
+asS'notes'\r
+p61\r
+(lp62\r
+I0\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI4\r
+aI4\r
+aI4\r
+aI4\r
+aI3\r
+aI5\r
+aI5\r
+aI5\r
+aI5\r
+aI3\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI5\r
+aI5\r
+aI6\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI8\r
+aI6\r
+aI6\r
+aI5\r
+aI5\r
+aI5\r
+aI5\r
+aI5\r
+aI7\r
+aI5\r
+aI5\r
+aI4\r
+aI3\r
+aI4\r
+aI0\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI4\r
+aI4\r
+aI4\r
+aI4\r
+aI3\r
+aI5\r
+aI5\r
+aI5\r
+aI7\r
+aI5\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+asS'requiresExtendedScale'\r
+p63\r
+I01\r
+sS'quarterNoteLength'\r
+p64\r
+I857\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/clairdelalune.smwi b/src/songs/smwis/Grands Classiques/clairdelalune.smwi
new file mode 100644 (file)
index 0000000..64219d8
--- /dev/null
@@ -0,0 +1,255 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I67\r
+aI67\r
+aI67\r
+aI69\r
+aI71\r
+aI69\r
+aI67\r
+aI71\r
+aI69\r
+aI69\r
+aI67\r
+aI67\r
+aI67\r
+aI67\r
+aI69\r
+aI71\r
+aI69\r
+aI67\r
+aI71\r
+aI69\r
+aI69\r
+aI67\r
+aI69\r
+aI69\r
+aI69\r
+aI69\r
+aI64\r
+aI64\r
+aI69\r
+aI67\r
+aI66\r
+aI64\r
+aI62\r
+aI67\r
+aI67\r
+aI67\r
+aI69\r
+aI71\r
+aI69\r
+aI67\r
+aI71\r
+aI69\r
+aI69\r
+aI67\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI66\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'Au'\r
+p10\r
+aS'clair'\r
+p11\r
+aS'de'\r
+p12\r
+aS'la'\r
+p13\r
+aS'lu-'\r
+p14\r
+aS'-ne'\r
+p15\r
+aS'mon'\r
+p16\r
+aS'a-'\r
+p17\r
+aS'-mi'\r
+p18\r
+aS'Pie-'\r
+p19\r
+aS'-rrot'\r
+p20\r
+aS'pre-'\r
+p21\r
+aS'-te'\r
+p22\r
+aS'moi'\r
+p23\r
+aS'ta'\r
+p24\r
+aS'plu-'\r
+p25\r
+aS'-me'\r
+p26\r
+aS'pour'\r
+p27\r
+aS'e-'\r
+p28\r
+aS'-crire'\r
+p29\r
+aS'un'\r
+p30\r
+aS'mot'\r
+p31\r
+aS'ma'\r
+p32\r
+aS'chan-'\r
+p33\r
+aS'-delle'\r
+p34\r
+aS'est'\r
+p35\r
+aS'mor-'\r
+p36\r
+ag22\r
+aS'je'\r
+p37\r
+aS"n'ai"\r
+p38\r
+aS'plus'\r
+p39\r
+ag12\r
+aS'feu'\r
+p40\r
+aS'ou-'\r
+p41\r
+aS'-vre'\r
+p42\r
+ag23\r
+ag24\r
+aS'po-'\r
+p43\r
+aS'-rte'\r
+p44\r
+ag27\r
+aS"l'a-"\r
+p45\r
+aS'-mour'\r
+p46\r
+ag12\r
+aS'Dieu'\r
+p47\r
+asS'noteLengths'\r
+p48\r
+(lp49\r
+I1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI4\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI4\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI4\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI4\r
+asS'notes'\r
+p50\r
+(lp51\r
+I7\r
+aI7\r
+aI7\r
+aI8\r
+aI9\r
+aI8\r
+aI7\r
+aI9\r
+aI8\r
+aI8\r
+aI7\r
+aI7\r
+aI7\r
+aI7\r
+aI8\r
+aI9\r
+aI8\r
+aI7\r
+aI9\r
+aI8\r
+aI8\r
+aI7\r
+aI8\r
+aI8\r
+aI8\r
+aI8\r
+aI5\r
+aI5\r
+aI8\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI7\r
+aI7\r
+aI7\r
+aI8\r
+aI9\r
+aI8\r
+aI7\r
+aI9\r
+aI8\r
+aI8\r
+aI7\r
+asS'requiresExtendedScale'\r
+p52\r
+I00\r
+sS'quarterNoteLength'\r
+p53\r
+I500\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/clairefontaine.smwi b/src/songs/smwis/Grands Classiques/clairefontaine.smwi
new file mode 100644 (file)
index 0000000..4469b01
--- /dev/null
@@ -0,0 +1,238 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I60\r
+aI60\r
+aI64\r
+aI64\r
+aI62\r
+aI64\r
+aI62\r
+aI60\r
+aI60\r
+aI64\r
+aI64\r
+aI62\r
+aI64\r
+aI64\r
+aI64\r
+aI62\r
+aI60\r
+aI64\r
+aI67\r
+aI64\r
+aI67\r
+aI67\r
+aI64\r
+aI60\r
+aI64\r
+aI62\r
+aI60\r
+aI60\r
+aI64\r
+aI64\r
+aI64\r
+aI62\r
+aI64\r
+aI64\r
+aI64\r
+aI64\r
+aI60\r
+aI64\r
+aI62\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'A'\r
+p10\r
+aS'la'\r
+p11\r
+aS'clai-'\r
+p12\r
+aS'-re'\r
+p13\r
+aS'fon-'\r
+p14\r
+aS'-tai-'\r
+p15\r
+aS'-ne'\r
+p16\r
+aS"m'en"\r
+p17\r
+aS'a-'\r
+p18\r
+aS'-llant'\r
+p19\r
+aS'pro-'\r
+p20\r
+aS'-me-'\r
+p21\r
+aS'-ner'\r
+p22\r
+aS"j'ai"\r
+p23\r
+aS'trou-'\r
+p24\r
+aS'-ve'\r
+p25\r
+aS"l'eau"\r
+p26\r
+aS'si'\r
+p27\r
+aS'be-'\r
+p28\r
+aS'-lle'\r
+p29\r
+aS'que'\r
+p30\r
+aS'je'\r
+p31\r
+aS"m'y"\r
+p32\r
+aS'suis'\r
+p33\r
+aS'bai-'\r
+p34\r
+aS'-gne'\r
+p35\r
+aS'il'\r
+p36\r
+aS'y-a'\r
+p37\r
+aS'long-'\r
+p38\r
+aS'-temps'\r
+p39\r
+ag30\r
+ag31\r
+aS"t'aime"\r
+p40\r
+aS'ja-'\r
+p41\r
+aS'-mais'\r
+p42\r
+ag31\r
+aS'ne'\r
+p43\r
+aS"t'ou-"\r
+p44\r
+aS'-blie-'\r
+p45\r
+aS'-rai'\r
+p46\r
+asS'noteLengths'\r
+p47\r
+(lp48\r
+I2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aF0.5\r
+aF0.5\r
+aI2\r
+aI2\r
+aI1\r
+aF0.5\r
+aF0.5\r
+aI1\r
+aI1\r
+aI4\r
+asS'notes'\r
+p49\r
+(lp50\r
+I3\r
+aI3\r
+aI5\r
+aI5\r
+aI4\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+aI5\r
+aI5\r
+aI4\r
+aI5\r
+aI5\r
+aI5\r
+aI4\r
+aI3\r
+aI5\r
+aI7\r
+aI5\r
+aI7\r
+aI7\r
+aI5\r
+aI3\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+aI5\r
+aI5\r
+aI5\r
+aI4\r
+aI5\r
+aI5\r
+aI5\r
+aI5\r
+aI3\r
+aI5\r
+aI4\r
+aI3\r
+asS'requiresExtendedScale'\r
+p51\r
+I00\r
+sS'quarterNoteLength'\r
+p52\r
+I400\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/eauvive.smwi b/src/songs/smwis/Grands Classiques/eauvive.smwi
new file mode 100644 (file)
index 0000000..8f7c4a5
--- /dev/null
@@ -0,0 +1,281 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I64\r
+aI60\r
+aI64\r
+aI60\r
+aI64\r
+aI60\r
+aI62\r
+aI62\r
+aI64\r
+aI65\r
+aI62\r
+aI64\r
+aI60\r
+aI62\r
+aI64\r
+aI60\r
+aI64\r
+aI60\r
+aI64\r
+aI60\r
+aI62\r
+aI62\r
+aI64\r
+aI65\r
+aI62\r
+aI64\r
+aI60\r
+aI62\r
+aI67\r
+aI69\r
+aI67\r
+aI65\r
+aI65\r
+aI62\r
+aI65\r
+aI64\r
+aI60\r
+aI64\r
+aI62\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI64\r
+aI62\r
+aI60\r
+aI62\r
+aI60\r
+aI59\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'Ma'\r
+p10\r
+aS'pe-'\r
+p11\r
+aS'-tite'\r
+p12\r
+aS'est'\r
+p13\r
+aS'co-'\r
+p14\r
+aS'-mme'\r
+p15\r
+aS"l'eau"\r
+p16\r
+aS'elle'\r
+p17\r
+ag13\r
+ag14\r
+ag15\r
+ag16\r
+aS'vi-'\r
+p18\r
+aS'-ve'\r
+p19\r
+aS'e-'\r
+p20\r
+aS'-lle'\r
+p21\r
+aS'court'\r
+p22\r
+aS'comme'\r
+p23\r
+aS'un'\r
+p24\r
+aS'rui-'\r
+p25\r
+aS'-sseau'\r
+p26\r
+aS'que'\r
+p27\r
+aS'les'\r
+p28\r
+aS'en-'\r
+p29\r
+aS'-fants'\r
+p30\r
+aS'pour-'\r
+p31\r
+aS'-sui-'\r
+p32\r
+aS'-vent'\r
+p33\r
+aS've-'\r
+p34\r
+aS'-nez'\r
+p35\r
+ag34\r
+ag35\r
+aS'mes'\r
+p36\r
+aS'a-'\r
+p37\r
+aS'-gneaux'\r
+p38\r
+ag36\r
+ag37\r
+aS'-gne-'\r
+p39\r
+aS'-lets'\r
+p40\r
+aS'ja-'\r
+p41\r
+aS'-mais'\r
+p42\r
+ag41\r
+ag42\r
+aS'vous'\r
+p43\r
+aS'ne'\r
+p44\r
+aS'la'\r
+p45\r
+aS'ra-'\r
+p46\r
+aS'-ttra-'\r
+p47\r
+aS'-pe-'\r
+p48\r
+aS'-rez'\r
+p49\r
+asS'noteLengths'\r
+p50\r
+(lp51\r
+I2\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI3\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI3\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI6\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI6\r
+asS'notes'\r
+p52\r
+(lp53\r
+I5\r
+aI3\r
+aI5\r
+aI3\r
+aI5\r
+aI3\r
+aI4\r
+aI4\r
+aI5\r
+aI6\r
+aI4\r
+aI5\r
+aI3\r
+aI4\r
+aI5\r
+aI3\r
+aI5\r
+aI3\r
+aI5\r
+aI3\r
+aI4\r
+aI4\r
+aI5\r
+aI6\r
+aI4\r
+aI5\r
+aI3\r
+aI4\r
+aI7\r
+aI8\r
+aI7\r
+aI6\r
+aI6\r
+aI4\r
+aI6\r
+aI5\r
+aI3\r
+aI5\r
+aI4\r
+aI3\r
+aI4\r
+aI5\r
+aI6\r
+aI5\r
+aI4\r
+aI3\r
+aI4\r
+aI3\r
+aI2\r
+aI3\r
+asS'requiresExtendedScale'\r
+p54\r
+I01\r
+sS'quarterNoteLength'\r
+p55\r
+I300\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/frerejacques.smwi b/src/songs/smwis/Grands Classiques/frerejacques.smwi
new file mode 100644 (file)
index 0000000..413f14a
--- /dev/null
@@ -0,0 +1,186 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I60\r
+aI62\r
+aI64\r
+aI60\r
+aI60\r
+aI62\r
+aI64\r
+aI60\r
+aI64\r
+aI65\r
+aI67\r
+aI64\r
+aI65\r
+aI67\r
+aI67\r
+aI69\r
+aI67\r
+aI65\r
+aI64\r
+aI60\r
+aI67\r
+aI69\r
+aI67\r
+aI65\r
+aI64\r
+aI60\r
+aI60\r
+aI55\r
+aI60\r
+aI60\r
+aI55\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'Fre-'\r
+p10\r
+aS'-re'\r
+p11\r
+aS'Ja-'\r
+p12\r
+aS'-cques'\r
+p13\r
+aS'fre-'\r
+p14\r
+ag11\r
+aS'-Ja'\r
+p15\r
+ag13\r
+aS'dor-'\r
+p16\r
+aS'-mez'\r
+p17\r
+aS'vous'\r
+p18\r
+ag16\r
+ag17\r
+ag18\r
+aS'so-'\r
+p19\r
+aS'-nnez'\r
+p20\r
+aS'les'\r
+p21\r
+aS'ma-'\r
+p22\r
+aS'-ti-'\r
+p23\r
+aS'-nes'\r
+p24\r
+ag19\r
+ag20\r
+ag21\r
+ag22\r
+ag23\r
+ag24\r
+aS'ding'\r
+p25\r
+ag25\r
+aS'dong'\r
+p26\r
+ag25\r
+ag25\r
+ag26\r
+asS'noteLengths'\r
+p27\r
+(lp28\r
+I1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aF1.5\r
+aF0.5\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+asS'notes'\r
+p29\r
+(lp30\r
+I3\r
+aI4\r
+aI5\r
+aI3\r
+aI3\r
+aI4\r
+aI5\r
+aI3\r
+aI5\r
+aI6\r
+aI7\r
+aI5\r
+aI6\r
+aI7\r
+aI7\r
+aI8\r
+aI7\r
+aI6\r
+aI5\r
+aI3\r
+aI7\r
+aI8\r
+aI7\r
+aI6\r
+aI5\r
+aI3\r
+aI3\r
+aI0\r
+aI3\r
+aI3\r
+aI0\r
+aI3\r
+asS'requiresExtendedScale'\r
+p31\r
+I01\r
+sS'quarterNoteLength'\r
+p32\r
+I600\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/jadbt.smwi b/src/songs/smwis/Grands Classiques/jadbt.smwi
new file mode 100644 (file)
index 0000000..f68ac1d
--- /dev/null
@@ -0,0 +1,227 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I60\r
+aI62\r
+aI64\r
+aI60\r
+aI62\r
+aI62\r
+aI64\r
+aI65\r
+aI65\r
+aI64\r
+aI64\r
+aI60\r
+aI62\r
+aI64\r
+aI60\r
+aI62\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI60\r
+aI67\r
+aI67\r
+aI65\r
+aI64\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI62\r
+aI67\r
+aI67\r
+aI65\r
+aI64\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI62\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S"J'ai"\r
+p10\r
+aS'du'\r
+p11\r
+aS'bon'\r
+p12\r
+aS'ta-'\r
+p13\r
+aS'-bac'\r
+p14\r
+aS'dans'\r
+p15\r
+aS'ma'\r
+p16\r
+ag13\r
+aS'-ba-'\r
+p17\r
+aS'-tie-'\r
+p18\r
+aS'-re'\r
+p19\r
+ag10\r
+ag11\r
+ag12\r
+ag13\r
+ag14\r
+aS'tu'\r
+p20\r
+aS"n'en"\r
+p21\r
+aS'au-'\r
+p22\r
+aS'-ras'\r
+p23\r
+aS'pas'\r
+p24\r
+aS"j'en"\r
+p25\r
+aS'ai'\r
+p26\r
+ag11\r
+aS'fin'\r
+p27\r
+aS'et'\r
+p28\r
+ag11\r
+aS'bien'\r
+p29\r
+aS'ra-'\r
+p30\r
+aS'-pe'\r
+p31\r
+aS'mais'\r
+p32\r
+aS'ce'\r
+p33\r
+aS"n'est"\r
+p34\r
+ag24\r
+aS'pour'\r
+p35\r
+aS'ton'\r
+p36\r
+aS'vi-'\r
+p37\r
+aS'-lain'\r
+p38\r
+aS'nez'\r
+p39\r
+asS'noteLengths'\r
+p40\r
+(lp41\r
+I1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI2\r
+aI2\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI4\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI4\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI1\r
+aI2\r
+aI2\r
+aI4\r
+asS'notes'\r
+p42\r
+(lp43\r
+I3\r
+aI4\r
+aI5\r
+aI3\r
+aI4\r
+aI4\r
+aI5\r
+aI6\r
+aI6\r
+aI5\r
+aI5\r
+aI3\r
+aI4\r
+aI5\r
+aI3\r
+aI4\r
+aI4\r
+aI5\r
+aI6\r
+aI7\r
+aI3\r
+aI7\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI5\r
+aI6\r
+aI7\r
+aI4\r
+aI7\r
+aI7\r
+aI6\r
+aI5\r
+aI4\r
+aI5\r
+aI6\r
+aI7\r
+aI4\r
+asS'requiresExtendedScale'\r
+p44\r
+I00\r
+sS'quarterNoteLength'\r
+p45\r
+I400\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/marseillaise.smwi b/src/songs/smwis/Grands Classiques/marseillaise.smwi
new file mode 100644 (file)
index 0000000..6245477
--- /dev/null
@@ -0,0 +1,547 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I55\r
+aI55\r
+aI55\r
+aI60\r
+aI60\r
+aI62\r
+aI62\r
+aI67\r
+aI64\r
+aI60\r
+aI60\r
+aI64\r
+aI60\r
+aI57\r
+aI65\r
+aI62\r
+aI59\r
+aI60\r
+aI60\r
+aI62\r
+aI64\r
+aI64\r
+aI64\r
+aI65\r
+aI64\r
+aI64\r
+aI62\r
+aI62\r
+aI64\r
+aI65\r
+aI65\r
+aI65\r
+aI67\r
+aI65\r
+aI64\r
+aI67\r
+aI67\r
+aI67\r
+aI64\r
+aI60\r
+aI67\r
+aI64\r
+aI60\r
+aI55\r
+aI55\r
+aI55\r
+aI59\r
+aI62\r
+aI65\r
+aI62\r
+aI59\r
+aI62\r
+aI60\r
+aI58\r
+aI57\r
+aI60\r
+aI60\r
+aI60\r
+aI59\r
+aI60\r
+aI62\r
+aI62\r
+aI63\r
+aI63\r
+aI63\r
+aI63\r
+aI65\r
+aI67\r
+aI62\r
+aI63\r
+aI62\r
+aI60\r
+aI60\r
+aI60\r
+aI63\r
+aI62\r
+aI60\r
+aI60\r
+aI59\r
+aI67\r
+aI67\r
+aI67\r
+aI64\r
+aI60\r
+aI62\r
+aI67\r
+aI67\r
+aI67\r
+aI64\r
+aI60\r
+aI62\r
+aI55\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI62\r
+aI69\r
+aI67\r
+aI64\r
+aI65\r
+aI62\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'A-'\r
+p10\r
+aS'-llons'\r
+p11\r
+aS'en-'\r
+p12\r
+aS'-fants'\r
+p13\r
+aS'de'\r
+p14\r
+aS'la'\r
+p15\r
+aS'pa'\r
+p16\r
+aS'tri-'\r
+p17\r
+aS'-i-'\r
+p18\r
+aS'-e'\r
+p19\r
+aS'le'\r
+p20\r
+aS'jour'\r
+p21\r
+ag14\r
+aS'gloire'\r
+p22\r
+aS'est'\r
+p23\r
+aS'a-'\r
+p24\r
+aS'-rri-'\r
+p25\r
+aS'-ve'\r
+p26\r
+aS'con-'\r
+p27\r
+aS'-tre'\r
+p28\r
+aS'nous'\r
+p29\r
+ag14\r
+ag15\r
+aS'ty-'\r
+p30\r
+aS'-ra-'\r
+p31\r
+aS'-nni-'\r
+p32\r
+ag19\r
+aS"l'e-"\r
+p33\r
+aS'-ten-'\r
+p34\r
+aS'-dard'\r
+p35\r
+aS'san-'\r
+p36\r
+aS'-glant'\r
+p37\r
+ag23\r
+aS'le-'\r
+p38\r
+ag26\r
+ag33\r
+ag34\r
+ag35\r
+ag36\r
+aS'-an-'\r
+p39\r
+ag37\r
+ag23\r
+ag38\r
+ag26\r
+ag12\r
+ag34\r
+aS'-dez'\r
+p40\r
+aS'vous'\r
+p41\r
+aS'dans'\r
+p42\r
+aS'nos'\r
+p43\r
+aS'cam-'\r
+p44\r
+aS'-pa-'\r
+p45\r
+aS'-gnes'\r
+p46\r
+aS'mu-'\r
+p47\r
+aS'-gir'\r
+p48\r
+aS'ces'\r
+p49\r
+aS'fe-'\r
+p50\r
+aS'-ro-'\r
+p51\r
+aS'-ces'\r
+p52\r
+aS'sol-'\r
+p53\r
+aS'-dats'\r
+p54\r
+aS'qui'\r
+p55\r
+aS'vie-'\r
+p56\r
+aS'-nnent'\r
+p57\r
+aS'ju-'\r
+p58\r
+aS'-sque'\r
+p59\r
+ag42\r
+ag43\r
+aS'bras'\r
+p60\r
+aS'e-'\r
+p61\r
+aS'-gor-'\r
+p62\r
+aS'-ger'\r
+p63\r
+ag43\r
+aS'fils'\r
+p64\r
+aS'et'\r
+p65\r
+ag43\r
+aS'com-'\r
+p66\r
+ag45\r
+ag46\r
+aS'aux'\r
+p67\r
+aS'ar-'\r
+p68\r
+aS'-mes'\r
+p69\r
+aS'ci-'\r
+p70\r
+aS'-toy-'\r
+p71\r
+aS'-yen'\r
+p72\r
+aS'for-'\r
+p73\r
+aS'-mez'\r
+p74\r
+aS'vos'\r
+p75\r
+aS'ba-'\r
+p76\r
+aS'-ta-'\r
+p77\r
+aS'-illons'\r
+p78\r
+aS'mar-'\r
+p79\r
+aS'-chons'\r
+p80\r
+ag79\r
+ag80\r
+aS"qu'un"\r
+p81\r
+aS'sang'\r
+p82\r
+aS'im-'\r
+p83\r
+aS'-pur'\r
+p84\r
+ag24\r
+aS'-breu-'\r
+p85\r
+ag26\r
+ag43\r
+aS'si-'\r
+p86\r
+ag78\r
+aS'pon'\r
+p87\r
+ag87\r
+ag87\r
+ag87\r
+asS'noteLengths'\r
+p88\r
+(lp89\r
+I1\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI5\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI3\r
+aI6\r
+aI2\r
+aI1\r
+aI9\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI2\r
+aI1\r
+aI3\r
+aI6\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI2\r
+aI1\r
+aI6\r
+aI2\r
+aI1\r
+aI3\r
+aI2\r
+aI1\r
+aI3\r
+aI2\r
+aI1\r
+aI6\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI3\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI6\r
+aI3\r
+aI2\r
+aI1\r
+aI3\r
+aI2\r
+aI1\r
+aI6\r
+aI3\r
+aI5\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI2\r
+aI1\r
+aI5\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI5\r
+aI1\r
+aI7\r
+aI1\r
+aI2\r
+aI1\r
+aI8\r
+aI1\r
+aI7\r
+aI1\r
+aI2\r
+aI1\r
+aI8\r
+aI3\r
+aI9\r
+aI3\r
+aI9\r
+aI6\r
+aI3\r
+aI3\r
+aI9\r
+aI3\r
+aI8\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI2\r
+aI1\r
+aI8\r
+asS'notes'\r
+p90\r
+(lp91\r
+I0\r
+aI0\r
+aI0\r
+aI3\r
+aI3\r
+aI4\r
+aI4\r
+aI7\r
+aI5\r
+aI3\r
+aI3\r
+aI5\r
+aI3\r
+aI1\r
+aI6\r
+aI4\r
+aI2\r
+aI3\r
+aI3\r
+aI4\r
+aI5\r
+aI5\r
+aI5\r
+aI6\r
+aI5\r
+aI5\r
+aI4\r
+aI4\r
+aI5\r
+aI6\r
+aI6\r
+aI6\r
+aI7\r
+aI6\r
+aI5\r
+aI7\r
+aI7\r
+aI7\r
+aI5\r
+aI3\r
+aI7\r
+aI5\r
+aI3\r
+aI0\r
+aI0\r
+aI0\r
+aI2\r
+aI4\r
+aI6\r
+aI4\r
+aI2\r
+aI4\r
+aI3\r
+aI2\r
+aI1\r
+aI3\r
+aI3\r
+aI3\r
+aI2\r
+aI3\r
+aI4\r
+aI4\r
+aI5\r
+aI5\r
+aI5\r
+aI5\r
+aI6\r
+aI7\r
+aI4\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+aI3\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+aI2\r
+aI7\r
+aI7\r
+aI7\r
+aI5\r
+aI3\r
+aI4\r
+aI7\r
+aI7\r
+aI7\r
+aI5\r
+aI3\r
+aI4\r
+aI0\r
+aI3\r
+aI4\r
+aI5\r
+aI6\r
+aI7\r
+aI8\r
+aI4\r
+aI8\r
+aI7\r
+aI5\r
+aI6\r
+aI4\r
+aI3\r
+asS'requiresExtendedScale'\r
+p92\r
+I01\r
+sS'quarterNoteLength'\r
+p93\r
+I300\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Grands Classiques/papanoel.smwi b/src/songs/smwis/Grands Classiques/papanoel.smwi
new file mode 100644 (file)
index 0000000..92d5aba
--- /dev/null
@@ -0,0 +1,185 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I60\r
+aI65\r
+aI65\r
+aI65\r
+aI67\r
+aI65\r
+aI65\r
+aI67\r
+aI69\r
+aI69\r
+aI69\r
+aI70\r
+aI69\r
+aI67\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI64\r
+aI62\r
+aI60\r
+aI60\r
+aI60\r
+aI65\r
+aI65\r
+aI65\r
+aI67\r
+aI67\r
+aI65\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI58\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI70\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'pe-'\r
+p10\r
+aS'-ti'\r
+p11\r
+aS'Pa-'\r
+p12\r
+aS'-pa'\r
+p13\r
+aS'No-'\r
+p14\r
+aS'-el'\r
+p15\r
+aS'quand'\r
+p16\r
+aS'tu'\r
+p17\r
+aS'de-'\r
+p18\r
+aS'-scen-'\r
+p19\r
+aS'-dras'\r
+p20\r
+aS'du'\r
+p21\r
+aS'ciel'\r
+p22\r
+aS'a-'\r
+p23\r
+aS'-vec'\r
+p24\r
+aS'tes'\r
+p25\r
+aS'jou-'\r
+p26\r
+aS'-ets'\r
+p27\r
+aS'par'\r
+p28\r
+aS'mi-'\r
+p29\r
+aS'-lliers'\r
+p30\r
+aS"n'ou-"\r
+p31\r
+aS'-blie'\r
+p32\r
+aS'pas'\r
+p33\r
+aS'mes'\r
+p34\r
+ag10\r
+aS'-tits'\r
+p35\r
+aS'sou-'\r
+p36\r
+aS'-liers'\r
+p37\r
+asS'noteLengths'\r
+p38\r
+(lp39\r
+I1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI3\r
+aF0.5\r
+aF0.5\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI3\r
+aI1\r
+aF1.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aI3\r
+aF0.5\r
+aF0.5\r
+aI1\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aF0.5\r
+aI3\r
+asS'notes'\r
+p40\r
+(lp41\r
+I3\r
+aI6\r
+aI6\r
+aI6\r
+aI7\r
+aI6\r
+aI6\r
+aI7\r
+aI8\r
+aI8\r
+aI8\r
+aI9\r
+aI8\r
+aI7\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI5\r
+aI4\r
+aI3\r
+aI3\r
+aI3\r
+aI6\r
+aI6\r
+aI6\r
+aI7\r
+aI7\r
+aI6\r
+asS'requiresExtendedScale'\r
+p42\r
+I00\r
+sS'quarterNoteLength'\r
+p43\r
+I500\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Jean Drejac/vinblanc.smwi b/src/songs/smwis/Jean Drejac/vinblanc.smwi
new file mode 100644 (file)
index 0000000..8a18ae6
--- /dev/null
@@ -0,0 +1,356 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I63\r
+aI63\r
+aI63\r
+aI60\r
+aI63\r
+aI65\r
+aI63\r
+aI63\r
+aI63\r
+aI60\r
+aI67\r
+aI65\r
+aI65\r
+aI65\r
+aI65\r
+aI61\r
+aI68\r
+aI67\r
+aI67\r
+aI67\r
+aI68\r
+aI70\r
+aI67\r
+aI63\r
+aI63\r
+aI63\r
+aI63\r
+aI60\r
+aI63\r
+aI65\r
+aI68\r
+aI70\r
+aI72\r
+aI70\r
+aI68\r
+aI72\r
+aI70\r
+aI68\r
+aI65\r
+aI65\r
+aI68\r
+aI67\r
+aI65\r
+aI68\r
+aI65\r
+aI63\r
+aI60\r
+aI63\r
+aI65\r
+aI60\r
+aI63\r
+aI65\r
+aI60\r
+aI63\r
+aI65\r
+aI60\r
+aI63\r
+aI65\r
+aI60\r
+aI63\r
+aI67\r
+aI65\r
+aI63\r
+aI67\r
+aI65\r
+aI63\r
+aI68\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI56\r
+aI58\r
+aI60\r
+aI61\r
+aI63\r
+aI65\r
+aI67\r
+aI68\r
+aI70\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'Ah'\r
+p10\r
+aS'le'\r
+p11\r
+aS'pe-'\r
+p12\r
+aS'-tit'\r
+p13\r
+aS'vin'\r
+p14\r
+aS'blanc'\r
+p15\r
+aS"qu'on"\r
+p16\r
+aS'boit'\r
+p17\r
+aS'sous'\r
+p18\r
+aS'les'\r
+p19\r
+aS'to-'\r
+p20\r
+aS'-nelles'\r
+p21\r
+aS'quand'\r
+p22\r
+ag19\r
+aS'fi-'\r
+p23\r
+aS'-lles'\r
+p24\r
+aS'sont'\r
+p25\r
+aS'belles'\r
+p26\r
+aS'du'\r
+p27\r
+aS'co-'\r
+p28\r
+aS'-te'\r
+p29\r
+aS'de'\r
+p30\r
+aS'No-'\r
+p31\r
+aS'-geant'\r
+p32\r
+aS'et'\r
+p33\r
+aS'puis'\r
+p34\r
+ag30\r
+aS'temps'\r
+p35\r
+aS'en'\r
+p36\r
+ag35\r
+aS'un'\r
+p37\r
+aS'air'\r
+p38\r
+ag30\r
+aS'vie-'\r
+p39\r
+aS'-lle'\r
+p40\r
+aS'ro-'\r
+p41\r
+aS'-man-'\r
+p42\r
+aS'-ce'\r
+p43\r
+aS'sem-'\r
+p44\r
+aS'-ble'\r
+p45\r
+aS'do-'\r
+p46\r
+aS'-nner'\r
+p47\r
+aS'la'\r
+p48\r
+aS'ca-'\r
+p49\r
+aS'-den-'\r
+p50\r
+ag43\r
+aS'pour'\r
+p51\r
+aS'fau-'\r
+p52\r
+aS'-ter'\r
+p53\r
+ag51\r
+ag52\r
+ag53\r
+aS'dans'\r
+p54\r
+ag19\r
+aS'bois'\r
+p55\r
+ag54\r
+ag19\r
+aS'pres'\r
+p56\r
+ag27\r
+ag28\r
+ag29\r
+ag27\r
+ag28\r
+ag29\r
+ag30\r
+ag31\r
+ag32\r
+asS'noteLengths'\r
+p57\r
+(lp58\r
+I1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI1\r
+aI1\r
+aI1\r
+aI2\r
+aI1\r
+aI6\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI3\r
+aI3\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI3\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI1\r
+aI6\r
+asS'notes'\r
+p59\r
+(lp60\r
+I5\r
+aI5\r
+aI5\r
+aI3\r
+aI5\r
+aI6\r
+aI5\r
+aI5\r
+aI5\r
+aI3\r
+aI7\r
+aI6\r
+aI6\r
+aI6\r
+aI6\r
+aI4\r
+aI8\r
+aI7\r
+aI7\r
+aI7\r
+aI8\r
+aI9\r
+aI7\r
+aI5\r
+aI5\r
+aI5\r
+aI5\r
+aI3\r
+aI5\r
+aI6\r
+aI8\r
+aI9\r
+aI10\r
+aI9\r
+aI8\r
+aI10\r
+aI9\r
+aI8\r
+aI6\r
+aI6\r
+aI8\r
+aI7\r
+aI6\r
+aI8\r
+aI6\r
+aI5\r
+aI3\r
+aI5\r
+aI6\r
+aI3\r
+aI5\r
+aI6\r
+aI3\r
+aI5\r
+aI6\r
+aI3\r
+aI5\r
+aI6\r
+aI3\r
+aI5\r
+aI7\r
+aI6\r
+aI5\r
+aI7\r
+aI6\r
+aI5\r
+aI8\r
+asS'requiresExtendedScale'\r
+p61\r
+I00\r
+sS'quarterNoteLength'\r
+p62\r
+I300\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Johnny Hallyday/penitencier.smwi b/src/songs/smwis/Johnny Hallyday/penitencier.smwi
new file mode 100644 (file)
index 0000000..78575af
--- /dev/null
@@ -0,0 +1,199 @@
+(isongs.Song\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I60\r
+aI60\r
+aI60\r
+aI63\r
+aI67\r
+aI65\r
+aI60\r
+aI60\r
+aI72\r
+aI72\r
+aI72\r
+aI70\r
+aI67\r
+aI65\r
+aI67\r
+aI72\r
+aI72\r
+aI72\r
+aI60\r
+aI63\r
+aI65\r
+aI67\r
+aI65\r
+aI60\r
+aI63\r
+aI60\r
+aI60\r
+aI60\r
+aI60\r
+aI59\r
+aI59\r
+aI60\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI58\r
+aI60\r
+aI62\r
+aI63\r
+aI65\r
+aI67\r
+aI69\r
+aI70\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S'Les'\r
+p10\r
+aS'por-'\r
+p11\r
+aS'-tes'\r
+p12\r
+aS'du'\r
+p13\r
+aS'pe-'\r
+p14\r
+aS'-ni-'\r
+p15\r
+aS'-ten-'\r
+p16\r
+aS'-cier'\r
+p17\r
+aS'bien-'\r
+p18\r
+aS'-tot'\r
+p19\r
+aS'vont'\r
+p20\r
+aS'se'\r
+p21\r
+aS're-'\r
+p22\r
+aS'-fer-'\r
+p23\r
+aS'-mer'\r
+p24\r
+aS'et'\r
+p25\r
+aS"c'est"\r
+p26\r
+aS'la'\r
+p27\r
+aS'que'\r
+p28\r
+aS'je'\r
+p29\r
+aS'fi-'\r
+p30\r
+ag15\r
+aS'-rai'\r
+p31\r
+aS'ma'\r
+p32\r
+aS'vie'\r
+p33\r
+aS'comme'\r
+p34\r
+aS"d'au-"\r
+p35\r
+aS'-tres'\r
+p36\r
+aS'gars'\r
+p37\r
+aS"l'ont"\r
+p38\r
+ag30\r
+aS'-nie'\r
+p39\r
+asS'noteLengths'\r
+p40\r
+(lp41\r
+I1\r
+aI5\r
+aI1\r
+aI5\r
+aI1\r
+aI1\r
+aI6\r
+aI4\r
+aI1\r
+aI5\r
+aI1\r
+aI4\r
+aI1\r
+aI1\r
+aI10\r
+aI1\r
+aI1\r
+aI5\r
+aI1\r
+aI4\r
+aI1\r
+aI1\r
+aI5\r
+aI1\r
+aI5\r
+aI1\r
+aI1\r
+aI1\r
+aI4\r
+aI5\r
+aI1\r
+aI12\r
+asS'notes'\r
+p42\r
+(lp43\r
+I3\r
+aI3\r
+aI3\r
+aI5\r
+aI7\r
+aI6\r
+aI3\r
+aI3\r
+aI10\r
+aI10\r
+aI10\r
+aI9\r
+aI7\r
+aI6\r
+aI7\r
+aI10\r
+aI10\r
+aI10\r
+aI3\r
+aI5\r
+aI6\r
+aI7\r
+aI6\r
+aI3\r
+aI5\r
+aI3\r
+aI3\r
+aI3\r
+aI3\r
+aI2\r
+aI2\r
+aI3\r
+asS'requiresExtendedScale'\r
+p44\r
+I01\r
+sS'quarterNoteLength'\r
+p45\r
+I250\r
+sb.
\ No newline at end of file
diff --git a/src/songs/smwis/Yves Montand/feuillesmortes.smwi b/src/songs/smwis/Yves Montand/feuillesmortes.smwi
new file mode 100644 (file)
index 0000000..083fcf2
--- /dev/null
@@ -0,0 +1,364 @@
+(i__main__\r
+Song\r
+p0\r
+(dp1\r
+S'midiNoteNumbers'\r
+p2\r
+(lp3\r
+I64\r
+aI66\r
+aI67\r
+aI72\r
+aI62\r
+aI64\r
+aI66\r
+aI71\r
+aI71\r
+aI60\r
+aI62\r
+aI64\r
+aI69\r
+aI59\r
+aI61\r
+aI63\r
+aI67\r
+aI64\r
+aI66\r
+aI67\r
+aI72\r
+aI62\r
+aI64\r
+aI66\r
+aI71\r
+aI71\r
+aI60\r
+aI62\r
+aI64\r
+aI69\r
+aI66\r
+aI69\r
+aI67\r
+aI64\r
+aI64\r
+aI64\r
+aI63\r
+aI64\r
+aI66\r
+aI66\r
+aI64\r
+aI67\r
+aI67\r
+aI66\r
+aI67\r
+aI69\r
+aI62\r
+aI74\r
+aI72\r
+aI71\r
+aI69\r
+aI71\r
+aI72\r
+aI72\r
+aI69\r
+aI69\r
+aI66\r
+aI72\r
+aI71\r
+aI71\r
+aI64\r
+aI69\r
+aI69\r
+aI67\r
+aI66\r
+aI67\r
+aI59\r
+aI64\r
+asS'scale'\r
+p4\r
+(lp5\r
+I55\r
+aI57\r
+aI59\r
+aI60\r
+aI62\r
+aI64\r
+aI65\r
+aI67\r
+aI69\r
+aI71\r
+aI72\r
+asS'name'\r
+p6\r
+S'unknownSong'\r
+p7\r
+sS'lyrics'\r
+p8\r
+(lp9\r
+S"c'est"\r
+p10\r
+aS'une'\r
+p11\r
+aS'chan-'\r
+p12\r
+aS'-son'\r
+p13\r
+aS'qui'\r
+p14\r
+aS'nous'\r
+p15\r
+aS'res-'\r
+p16\r
+aS'-sem-'\r
+p17\r
+aS'-ble'\r
+p18\r
+aS'toi'\r
+p19\r
+aS'tu'\r
+p20\r
+aS"m'ai-"\r
+p21\r
+aS'-mais'\r
+p22\r
+aS'et'\r
+p23\r
+aS'je'\r
+p24\r
+aS"t'ai-"\r
+p25\r
+ag22\r
+ag15\r
+aS'e-'\r
+p26\r
+aS'-tions'\r
+p27\r
+aS'tout'\r
+p28\r
+aS'les'\r
+p29\r
+aS'deux'\r
+p30\r
+aS'en-'\r
+p31\r
+ag17\r
+ag18\r
+ag19\r
+ag14\r
+ag21\r
+ag22\r
+aS'moi'\r
+p32\r
+ag14\r
+ag25\r
+ag22\r
+aS'mais'\r
+p33\r
+aS'la'\r
+p34\r
+aS'vie'\r
+p35\r
+aS'se-'\r
+p36\r
+aS'-pare'\r
+p37\r
+aS'ceux'\r
+p38\r
+ag14\r
+aS"s'aiment"\r
+p39\r
+ag28\r
+aS'dou-'\r
+p40\r
+aS'-ce-'\r
+p41\r
+aS'-ment'\r
+p42\r
+aS'sans'\r
+p43\r
+aS'faire'\r
+p44\r
+aS'de'\r
+p45\r
+aS'bruit'\r
+p46\r
+ag23\r
+ag34\r
+aS'mer'\r
+p47\r
+aS'ef-'\r
+p48\r
+aS'-fa-'\r
+p49\r
+aS'-ce'\r
+p50\r
+aS'sur'\r
+p51\r
+aS'le'\r
+p52\r
+aS'sa-'\r
+p53\r
+ag18\r
+ag29\r
+aS'pas'\r
+p54\r
+aS'des'\r
+p55\r
+aS'a-'\r
+p56\r
+aS'-mants'\r
+p57\r
+aS'de-'\r
+p58\r
+aS'-su-'\r
+p59\r
+aS'-nis'\r
+p60\r
+asS'noteLengths'\r
+p61\r
+(lp62\r
+F0.6010416666666667\r
+aF0.59791666666666665\r
+aF0.67812499999999998\r
+aF6.0229166666666663\r
+aF0.6322916666666667\r
+aF0.64583333333333337\r
+aF0.63958333333333328\r
+aF0.65104166666666663\r
+aF5.4302083333333337\r
+aF0.64270833333333333\r
+aF0.51249999999999996\r
+aF0.75\r
+aF6.1322916666666663\r
+aF0.63645833333333335\r
+aF0.62708333333333333\r
+aF0.69270833333333337\r
+aF6.083333333333333\r
+aF0.54374999999999996\r
+aF0.69791666666666663\r
+aF0.67500000000000004\r
+aF6.0302083333333334\r
+aF0.57187500000000002\r
+aF0.62083333333333335\r
+aF0.68854166666666672\r
+aF0.6645833333333333\r
+aF5.4333333333333336\r
+aF0.61354166666666665\r
+aF0.55104166666666665\r
+aF0.72291666666666665\r
+aF6.1260416666666666\r
+aF0.69687500000000002\r
+aF0.71979166666666672\r
+aF0.63541666666666663\r
+aF5.9104166666666664\r
+aF0.5708333333333333\r
+aF0.35312500000000002\r
+aF0.6177083333333333\r
+aF0.484375\r
+aF5.7979166666666666\r
+aF1.6114583333333334\r
+aF0.43229166666666669\r
+aF5.9343750000000002\r
+aF1.2916666666666667\r
+aF0.39895833333333336\r
+aF0.34375\r
+aF5.0062499999999996\r
+aF0.9927083333333333\r
+aF1.003125\r
+aF0.97083333333333333\r
+aF6.104166666666667\r
+aF1.6208333333333333\r
+aF0.41666666666666669\r
+aF0.67083333333333328\r
+aF0.38437500000000002\r
+aF0.59583333333333333\r
+aF0.41875000000000001\r
+aF1.4927083333333333\r
+aF0.44583333333333336\r
+aF0.6020833333333333\r
+aF6.291666666666667\r
+aF1.028125\r
+aF3.4343750000000002\r
+aF0.27083333333333331\r
+aF0.36875000000000002\r
+aF3.2697916666666669\r
+aF0.33958333333333335\r
+aF0.33124999999999999\r
+aI4\r
+asS'notes'\r
+p63\r
+(lp64\r
+I5\r
+aI6\r
+aI7\r
+aI10\r
+aI4\r
+aI5\r
+aI6\r
+aI9\r
+aI9\r
+aI3\r
+aI4\r
+aI5\r
+aI8\r
+aI2\r
+aI3\r
+aI4\r
+aI7\r
+aI5\r
+aI6\r
+aI7\r
+aI10\r
+aI4\r
+aI5\r
+aI6\r
+aI9\r
+aI9\r
+aI3\r
+aI4\r
+aI5\r
+aI8\r
+aI6\r
+aI8\r
+aI7\r
+aI5\r
+aI5\r
+aI5\r
+aI4\r
+aI5\r
+aI6\r
+aI6\r
+aI5\r
+aI7\r
+aI7\r
+aI6\r
+aI7\r
+aI8\r
+aI4\r
+aI10\r
+aI10\r
+aI9\r
+aI8\r
+aI9\r
+aI10\r
+aI10\r
+aI8\r
+aI8\r
+aI6\r
+aI10\r
+aI9\r
+aI9\r
+aI5\r
+aI8\r
+aI8\r
+aI7\r
+aI6\r
+aI7\r
+aI2\r
+aI5\r
+asS'requiresExtendedScale'\r
+p65\r
+I01\r
+sS'quarterNoteLength'\r
+p66\r
+I666\r
+sb.
\ No newline at end of file