From: pin Date: Thu, 4 Feb 2010 14:32:46 +0000 (+0000) Subject: Création branche pinbe à partir d'une copie de branches/V7@73. X-Git-Url: https://scm.cri.ensmp.fr/git/minwii.git/commitdiff_plain/928c4fcf26174df9b8cf62d67e0061ad703481e9?ds=sidebyside Création branche pinbe à partir d'une copie de branches/V7@73. git-svn-id: https://svn.cri.ensmp.fr/svn/minwii/trunk@1 fe552daf-6dbe-4428-90eb-1537e0879342 --- 928c4fcf26174df9b8cf62d67e0061ad703481e9 diff --git a/.project b/.project new file mode 100755 index 0000000..30d02f5 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + MINWii + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100755 index 0000000..e35390d --- /dev/null +++ b/.pydevproject @@ -0,0 +1,10 @@ + + + + + +/MINWiiV7/src + +python 2.6 +Default + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..6a3e2eb --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,14 @@ +#Mon Dec 07 03:31:40 CET 2009 +eclipse.preferences.version=1 +encoding//src/mxmMidi/DataTypeConverters.py=ISO-8859-1 +encoding//src/mxmMidi/EventDispatcher.py=ISO-8859-1 +encoding//src/mxmMidi/MidiFileParser.py=ISO-8859-1 +encoding//src/mxmMidi/MidiInFile.py=ISO-8859-1 +encoding//src/mxmMidi/MidiInStream.py=ISO-8859-1 +encoding//src/mxmMidi/MidiOutFile.py=ISO-8859-1 +encoding//src/mxmMidi/MidiOutStream.py=ISO-8859-1 +encoding//src/mxmMidi/MidiToText.py=ISO-8859-1 +encoding//src/mxmMidi/RawInstreamFile.py=ISO-8859-1 +encoding//src/mxmMidi/RawOutstreamFile.py=ISO-8859-1 +encoding//src/mxmMidi/__init__.py=ISO-8859-1 +encoding//src/mxmMidi/constants.py=ISO-8859-1 diff --git a/WiiMouse IR G.PIE b/WiiMouse IR G.PIE new file mode 100755 index 0000000..dad484d --- /dev/null +++ b/WiiMouse IR G.PIE @@ -0,0 +1,157 @@ +Wiimote1.led1 = true +Wiimote2.led1 = true +Wiimote2.led4 = true +Wiimote3.led2 = true +Wiimote4.led2 = true +Wiimote4.led4 = true +Wiimote5.led3 = true +Wiimote6.led3 = true +Wiimote6.led4 = true +//Mouse Control Script using IR +//by vkapadia with much assistance from inio +//vkapadia@vkapadia.com +// +//Calibration: +//To calibrate, run this program and put the Wiimote on a flat surface face-up. +//Then read the values in the debug line (next to the run button). +//Change these values until the debug line reads approx. all zeros. +var.xtrim = -7 +var.ytrim = -17 +var.ztrim = -7 +// +//Options: +var.deadzone = 5 //distance in pixels that you have to move the wiimote in + //order for it to register movement. Creates a "dead zone" around the pointer + //to make it easier to click. Higher = smoother but less accurate. +//fake cursor init +cursor2.visible = true +//more options to be added later + +//Controls: +//Point Wiimote = Move Mouse +//D-Pad = Arrow Keys +//B-Button = Left Click +//Home = Middle Click +//A-Button = Right Click +//Plus and Minus = Control Volume +//One = Unmapped +//Two = Unmapped +// +//If the pointer hits the edge of the screen, the Wiimote will rumble a bit. +// +//The LEDs attempt to emulate KITT's grill from Knight Rider + +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx = wiimote.RawForceX + var.xtrim +var.accy = wiimote.RawForceY + var.ytrim +var.accz = wiimote.RawForceZ + var.ztrim + +if wiimote.dot1vis and wiimote.dot2vis then + + if var.accy > -7 then + var.orientation = 0 + elseif var.accy > -45 then + if var.accx < 0 then + var.orientation = 3 + else + var.orientation = 1 + endif + else + var.orientation = 2 + endif + + if var.leftpoint = 0 then + if var.orientation = 0 then + if wiimote.dot1x < wiimote.dot2x then + var.leftpoint = 1 + else + var.leftpoint = 2 + endif + endif + if var.orientation = 1 then + if wiimote.dot1y > wiimote.dot2y then + var.leftpoint = 1 + else + var.leftpoint = 2 + endif + endif + if var.orientation = 2 then + if wiimote.dot1x > wiimote.dot2x then + var.leftpoint = 1 + else + var.leftpoint = 2 + endif + endif + if var.orientation = 3 then + if wiimote.dot1y < wiimote.dot2y then + var.leftpoint = 1 + else + var.leftpoint = 2 + endif + endif + endif + + if var.leftpoint = 1 then + var.fix1x = wiimote.dot1x + var.fix1y = wiimote.dot1y + var.fix2x = wiimote.dot2x + var.fix2y = wiimote.dot2y + else + var.fix1x = wiimote.dot2x + var.fix1y = wiimote.dot2y + var.fix2x = wiimote.dot1x + var.fix2y = wiimote.dot1y + endif + + var.dx = var.fix2x - var.fix1x + var.dy = var.fix2y - var.fix1y + var.cx = (var.fix1x+var.fix2x)/1024.0 - 1 + var.cy = (var.fix1y+var.fix2y)/1024.0 - .75 + + var.d = sqrt(var.dx*var.dx+var.dy*var.dy) + + var.dx = var.dx / var.d + var.dy = var.dy / var.d + + var.ox = -var.dy*var.cy-var.dx*var.cx; + var.oy = -var.dx*var.cy+var.dy*var.cx; + + var.ax = (var.ox * screen.desktopwidth) + (screen.desktopwidth / 2) + var.ay = (-var.oy * screen.desktopwidth) + (screen.desktopheight / 2) + + var.dx = var.ax - cursor2.posx + var.dy = var.ay - cursor2.posy + + var.d = sqrt((var.dx*var.dx)+(var.dy*var.dy)) + + var.a = 180 / (200 + var.d * var.d * var.d * .001) + + if var.d <= var.deadzone then var.a = 1 + + //debug = var.d + " " + var.a + + var.finalx = cursor2.posx * var.a + var.ax * (1 - var.a) + var.finaly = cursor2.posy * var.a + var.ay * (1 - var.a) + + + cursor2.posx = smooth(var.finalx,3,5) + cursor2.posy = smooth(var.finaly,3,5) + +else + + var.leftpoint = 0 + +endif + +var.xpos = var.finalx +var.ypos = var.finaly +ppjoy1.analog0 = ensuremaprange(var.xpos,0,screen.desktopwidth,-1,1) +ppjoy1.analog1 = ensuremaprange(var.ypos,0,screen.desktopheight,-1,1) + +if wiimote1.B == true + ppjoy1.digital0 = true +else + ppjoy1.digital0 = false +endif + +debug = var.xpos+ " " + var.ypos diff --git a/WiiMouse IR coeff0.1.PIE b/WiiMouse IR coeff0.1.PIE new file mode 100755 index 0000000..8856f61 --- /dev/null +++ b/WiiMouse IR coeff0.1.PIE @@ -0,0 +1,166 @@ +Wiimote1.led1 = true +Wiimote2.led1 = true +Wiimote2.led4 = true +Wiimote3.led2 = true +Wiimote4.led2 = true +Wiimote4.led4 = true +Wiimote5.led3 = true +Wiimote6.led3 = true +Wiimote6.led4 = true +//Mouse Control Script using IR +//by vkapadia with much assistance from inio +//vkapadia@vkapadia.com +// +//Calibration: +//To calibrate, run this program and put the Wiimote on a flat surface face-up. +//Then read the values in the debug line (next to the run button). +//Change these values until the debug line reads approx. all zeros. +var.xtrim1 = -1 +var.ytrim1 = -25 +var.ztrim1 = 2 + +var.xtrim2 = -1 +var.ytrim2 = -25 +var.ztrim2 = 2 + +var.coeff = 0.1 + +// +//Options: +var.deadzone = 5 //distance in pixels that you have to move the wiimote in + //order for it to register movement. Creates a "dead zone" around the pointer + //to make it easier to click. Higher = smoother but less accurate. +//fake cursor init + +//cursor2.visible = true + +//more options to be added later + +//Controls: +//Point Wiimote = Move Mouse +//D-Pad = Arrow Keys +//B-Button = Left Click +//Home = Middle Click +//A-Button = Right Click +//Plus and Minus = Control Volume +//One = Unmapped +//Two = Unmapped +// +//If the pointer hits the edge of the screen, the Wiimote will rumble a bit. +// +//The LEDs attempt to emulate KITT's grill from Knight Rider + +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx1 = wiimote1.RawForceX1 + var.xtrim1 +var.accy1 = wiimote1.RawForceY1 + var.ytrim1 +var.accz1 = wiimote1.RawForceZ1 + var.ztrim1 + +if wiimote1.dot1vis and wiimote1.dot2vis then + + if var.accy1 > -7 then + var.orientation1 = 0 + elseif var.accy1 > -45 then + if var.accx1 < 0 then + var.orientation1 = 3 + else + var.orientation1 = 1 + endif + else + var.orientation1 = 2 + endif + + if var.leftpoint1 = 0 then + if var.orientation1 = 0 then + if wiimote1.dot1x < wiimote1.dot2x then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 1 then + if wiimote1.dot1y > wiimote1.dot2y then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 2 then + if wiimote1.dot1x > wiimote1.dot2x then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation = 3 then + if wiimote1.dot1y < wiimote1.dot2y then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + endif + + if var.leftpoint1 = 1 then + var.fix1x1 = wiimote1.dot1x + var.fix1y1 = wiimote1.dot1y + var.fix2x1 = wiimote1.dot2x + var.fix2y1 = wiimote1.dot2y + else + var.fix1x1 = wiimote1.dot2x + var.fix1y1 = wiimote1.dot2y + var.fix2x1 = wiimote1.dot1x + var.fix2y1 = wiimote1.dot1y + endif + + var.dx1 = var.fix2x1 - var.fix1x1 + var.dy1 = var.fix2y1 - var.fix1y1 + var.cx1 = (var.fix1x1+var.fix2x1)/1024.0 - 1 + var.cy1 = (var.fix1y1+var.fix2y1)/1024.0 - .75 + + var.d1 = sqrt(var.dx1*var.dx1+var.dy1*var.dy1) + + var.dx1 = var.dx1 / var.d1 + var.dy1 = var.dy1 / var.d1 + + var.ox1 = -var.dy1*var.cy1-var.dx1*var.cx1; + var.oy1 = -var.dx1*var.cy1+var.dy1*var.cx1; + + var.ax1 = (var.ox1 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay1 = (-var.oy1* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx1 = var.ax1 - cursor2.posx + var.dy1 = var.ay1 - cursor2.posy + + var.d1 = sqrt((var.dx1*var.dx1)+(var.dy1*var.dy1)) + + var.a1 = 180 / (200 + var.d1 * var.d1 * var.d1 * .001) + + if var.d1 <= var.deadzone then var.a1 = 1 + + //debug = var.d + " " + var.a + + var.finalx1 = cursor2.posx * var.a1 + var.ax1 * (1 - var.a1) + var.finaly1 = cursor2.posy * var.a1 + var.ay1 * (1 - var.a1) + + + cursor2.posx = smooth(var.finalx1,3,5) + cursor2.posy = smooth(var.finaly1,3,5) + +else + + var.leftpoint1 = 0 + +endif + +var.xpos1 = var.finalx1 +var.ypos1 = var.finaly1 +ppjoy1.analog0 = ensuremaprange(var.xpos1,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy1.analog1 = ensuremaprange(var.ypos1,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote1.B or wiimote1.A or wiimote1.Up or wiimote1.down or wiimote1.Left or wiimote1.Right == true + ppjoy1.digital0 = true +else + ppjoy1.digital0 = false +endif + +debug = var.accx1+ " " + var.accy1+ " " + var.accz1 diff --git a/WiiMouse2IR coeff0.1.PIE b/WiiMouse2IR coeff0.1.PIE new file mode 100644 index 0000000..27c9d52 --- /dev/null +++ b/WiiMouse2IR coeff0.1.PIE @@ -0,0 +1,281 @@ +Wiimote1.led1 = true +Wiimote2.led1 = true +Wiimote2.led4 = true +Wiimote3.led2 = true +Wiimote4.led2 = true +Wiimote4.led4 = true +Wiimote5.led3 = true +Wiimote6.led3 = true +Wiimote6.led4 = true +//Mouse Control Script using IR +//by vkapadia with much assistance from inio +//vkapadia@vkapadia.com +// +//Calibration: +//To calibrate, run this program and put the Wiimote on a flat surface face-up. +//Then read the values in the debug line (next to the run button). +//Change these values until the debug line reads approx. all zeros. +var.xtrim1 = -1 +var.ytrim1 = -25 +var.ztrim1 = 2 + +var.xtrim2 = 2 +var.ytrim2 = -30 +var.ztrim2 = 2 + +var.coeff = 0.1 + +// +//Options: +var.deadzone = 5 //distance in pixels that you have to move the wiimote in + //order for it to register movement. Creates a "dead zone" around the pointer + //to make it easier to click. Higher = smoother but less accurate. +//fake cursor init + +//cursor2.visible = true + +//more options to be added later + +//Controls: +//Point Wiimote = Move Mouse +//D-Pad = Arrow Keys +//B-Button = Left Click +//Home = Middle Click +//A-Button = Right Click +//Plus and Minus = Control Volume +//One = Unmapped +//Two = Unmapped +// +//If the pointer hits the edge of the screen, the Wiimote will rumble a bit. +// +//The LEDs attempt to emulate KITT's grill from Knight Rider + +//WIIMOTE 1 +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx1 = wiimote1.RawForceX + var.xtrim1 +var.accy1 = wiimote1.RawForceY + var.ytrim1 +var.accz1 = wiimote1.RawForceZ + var.ztrim1 + +if wiimote1.dot1vis and wiimote1.dot2vis then + + if var.accy1 > -7 then + var.orientation1 = 0 + elseif var.accy1 > -45 then + if var.accx1 < 0 then + var.orientation1 = 3 + else + var.orientation1 = 1 + endif + else + var.orientation1 = 2 + endif + + if var.leftpoint1 = 0 then + if var.orientation1 = 0 then + if wiimote1.dot1x < wiimote1.dot2x then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 1 then + if wiimote1.dot1y > wiimote1.dot2y then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 2 then + if wiimote1.dot1x > wiimote1.dot2x then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation = 3 then + if wiimote1.dot1y < wiimote1.dot2y then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + endif + + if var.leftpoint1 = 1 then + var.fix1x1 = wiimote1.dot1x + var.fix1y1 = wiimote1.dot1y + var.fix2x1 = wiimote1.dot2x + var.fix2y1 = wiimote1.dot2y + else + var.fix1x1 = wiimote1.dot2x + var.fix1y1 = wiimote1.dot2y + var.fix2x1 = wiimote1.dot1x + var.fix2y1 = wiimote1.dot1y + endif + + var.dx1 = var.fix2x1 - var.fix1x1 + var.dy1 = var.fix2y1 - var.fix1y1 + var.cx1 = (var.fix1x1+var.fix2x1)/1024.0 - 1 + var.cy1 = (var.fix1y1+var.fix2y1)/1024.0 - .75 + + var.d1 = sqrt(var.dx1*var.dx1+var.dy1*var.dy1) + + var.dx1 = var.dx1 / var.d1 + var.dy1 = var.dy1 / var.d1 + + var.ox1 = -var.dy1*var.cy1-var.dx1*var.cx1; + var.oy1 = -var.dx1*var.cy1+var.dy1*var.cx1; + + var.ax1 = (var.ox1 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay1 = (-var.oy1* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx1 = var.ax1 - cursor2.posx + var.dy1 = var.ay1 - cursor2.posy + + var.d1 = sqrt((var.dx1*var.dx1)+(var.dy1*var.dy1)) + + var.a1 = 180 / (200 + var.d1 * var.d1 * var.d1 * .001) + + if var.d1 <= var.deadzone then var.a1 = 1 + + //debug = var.d + " " + var.a + + var.finalx1 = cursor2.posx * var.a1 + var.ax1 * (1 - var.a1) + var.finaly1 = cursor2.posy * var.a1 + var.ay1 * (1 - var.a1) + + + cursor2.posx = smooth(var.finalx1,3,5) + cursor2.posy = smooth(var.finaly1,3,5) + +else + + var.leftpoint1 = 0 + +endif + +var.xpos1 = var.finalx1 +var.ypos1 = var.finaly1 +ppjoy1.analog0 = ensuremaprange(var.xpos1,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy1.analog1 = ensuremaprange(var.ypos1,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote1.B or wiimote1.A or wiimote1.Up or wiimote1.down or wiimote1.Left or wiimote1.Right == true + ppjoy1.digital0 = true +else + ppjoy1.digital0 = false +endif + +//WIIMOTE 2 +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx2 = wiimote2.RawForceX + var.xtrim2 +var.accy2 = wiimote2.RawForceY + var.ytrim2 +var.accz2 = wiimote2.RawForceZ + var.ztrim2 + +if wiimote2.dot1vis and wiimote2.dot2vis then + + if var.accy2 > -7 then + var.orientation2 = 0 + elseif var.accy2 > -45 then + if var.accx2 < 0 then + var.orientation2 = 3 + else + var.orientation2 = 1 + endif + else + var.orientation2 = 2 + endif + + if var.leftpoint2 = 0 then + if var.orientation2 = 0 then + if wiimote2.dot1x < wiimote2.dot2x then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + if var.orientation2 = 1 then + if wiimote2.dot1y > wiimote2.dot2y then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + if var.orientation2 = 2 then + if wiimote2.dot1x > wiimote2.dot2x then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + if var.orientation = 3 then + if wiimote2.dot1y < wiimote2.dot2y then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + endif + + if var.leftpoint2 = 1 then + var.fix1x2 = wiimote2.dot1x + var.fix1y2 = wiimote2.dot1y + var.fix2x2 = wiimote2.dot2x + var.fix2y2 = wiimote2.dot2y + else + var.fix1x2 = wiimote2.dot2x + var.fix1y2 = wiimote2.dot2y + var.fix2x2 = wiimote2.dot1x + var.fix2y2 = wiimote2.dot1y + endif + + var.dx2 = var.fix2x2 - var.fix1x2 + var.dy2 = var.fix2y2 - var.fix1y2 + var.cx2 = (var.fix1x2+var.fix2x2)/1024.0 - 1 + var.cy2 = (var.fix1y2+var.fix2y2)/1024.0 - .75 + + var.d2 = sqrt(var.dx2*var.dx2+var.dy2*var.dy2) + + var.dx2 = var.dx2 / var.d2 + var.dy2 = var.dy2 / var.d2 + + var.ox2 = -var.dy2*var.cy2-var.dx2*var.cx2; + var.oy2 = -var.dx2*var.cy2+var.dy2*var.cx2; + + var.ax2 = (var.ox2 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay2 = (-var.oy2* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx2 = var.ax2 - cursor3.posx + var.dy2 = var.ay2 - cursor3.posy + + var.d2 = sqrt((var.dx2*var.dx2)+(var.dy2*var.dy2)) + + var.a2 = 180 / (200 + var.d2 * var.d2 * var.d2 * .001) + + if var.d2 <= var.deadzone then var.a2 = 1 + + //debug = var.d + " " + var.a + + var.finalx2 = cursor3.posx * var.a2 + var.ax2 * (1 - var.a2) + var.finaly2 = cursor3.posy * var.a2 + var.ay2 * (1 - var.a2) + + + cursor3.posx = smooth(var.finalx2,3,5) + cursor3.posy = smooth(var.finaly2,3,5) + +else + + var.leftpoint2 = 0 + +endif + +var.xpos2 = var.finalx2 +var.ypos2 = var.finaly2 +ppjoy2.analog0 = ensuremaprange(var.xpos2,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy2.analog1 = ensuremaprange(var.ypos2,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote2.B or wiimote2.A or wiimote2.Up or wiimote2.down or wiimote2.Left or wiimote2.Right == true + ppjoy2.digital0 = true +else + ppjoy2.digital0 = false +endif + +debug = var.accx2+ " " + var.accy2+ " " + var.accz2 diff --git a/WiiMouse4IR coeff0.1.PIE b/WiiMouse4IR coeff0.1.PIE new file mode 100644 index 0000000..0aac1ab --- /dev/null +++ b/WiiMouse4IR coeff0.1.PIE @@ -0,0 +1,520 @@ +Wiimote1.led1 = true +Wiimote2.led1 = true +Wiimote2.led4 = true +Wiimote3.led2 = true +Wiimote4.led2 = true +Wiimote4.led4 = true +Wiimote5.led3 = true +Wiimote6.led3 = true +Wiimote6.led4 = true +//Mouse Control Script using IR +//by vkapadia with much assistance from inio +//vkapadia@vkapadia.com +// +//Calibration: +//To calibrate, run this program and put the Wiimote on a flat surface face-up. +//Then read the values in the debug line (next to the run button). +//Change these values until the debug line reads approx. all zeros. +var.xtrim1 = -1 +var.ytrim1 = -25 +var.ztrim1 = 2 + +var.xtrim2 = 3 +var.ytrim2 = -30 +var.ztrim2 = 2 + +var.xtrim3 = 2 +var.ytrim3 = -27 +var.ztrim3 = 2 + +var.xtrim4 = 2 +var.ytrim4 = -30 +var.ztrim4 = 2 + +var.coeff = 0.1 + +// +//Options: +var.deadzone = 5 //distance in pixels that you have to move the wiimote in + //order for it to register movement. Creates a "dead zone" around the pointer + //to make it easier to click. Higher = smoother but less accurate. +//fake cursor init + +//cursor2.visible = true + +//more options to be added later + +//Controls: +//Point Wiimote = Move Mouse +//D-Pad = Arrow Keys +//B-Button = Left Click +//Home = Middle Click +//A-Button = Right Click +//Plus and Minus = Control Volume +//One = Unmapped +//Two = Unmapped +// +//If the pointer hits the edge of the screen, the Wiimote will rumble a bit. +// +//The LEDs attempt to emulate KITT's grill from Knight Rider + +//WIIMOTE 1 +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx1 = wiimote1.RawForceX + var.xtrim1 +var.accy1 = wiimote1.RawForceY + var.ytrim1 +var.accz1 = wiimote1.RawForceZ + var.ztrim1 + +if wiimote1.dot1vis and wiimote1.dot2vis then + + if var.accy1 > -7 then + var.orientation1 = 0 + elseif var.accy1 > -45 then + if var.accx1 < 0 then + var.orientation1 = 3 + else + var.orientation1 = 1 + endif + else + var.orientation1 = 2 + endif + + if var.leftpoint1 = 0 then + if var.orientation1 = 0 then + if wiimote1.dot1x < wiimote1.dot2x then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 1 then + if wiimote1.dot1y > wiimote1.dot2y then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 2 then + if wiimote1.dot1x > wiimote1.dot2x then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + if var.orientation1 = 3 then + if wiimote1.dot1y < wiimote1.dot2y then + var.leftpoint1 = 1 + else + var.leftpoint1 = 2 + endif + endif + endif + + if var.leftpoint1 = 1 then + var.fix1x1 = wiimote1.dot1x + var.fix1y1 = wiimote1.dot1y + var.fix2x1 = wiimote1.dot2x + var.fix2y1 = wiimote1.dot2y + else + var.fix1x1 = wiimote1.dot2x + var.fix1y1 = wiimote1.dot2y + var.fix2x1 = wiimote1.dot1x + var.fix2y1 = wiimote1.dot1y + endif + + var.dx1 = var.fix2x1 - var.fix1x1 + var.dy1 = var.fix2y1 - var.fix1y1 + var.cx1 = (var.fix1x1+var.fix2x1)/1024.0 - 1 + var.cy1 = (var.fix1y1+var.fix2y1)/1024.0 - .75 + + var.d1 = sqrt(var.dx1*var.dx1+var.dy1*var.dy1) + + var.dx1 = var.dx1 / var.d1 + var.dy1 = var.dy1 / var.d1 + + var.ox1 = -var.dy1*var.cy1-var.dx1*var.cx1; + var.oy1 = -var.dx1*var.cy1+var.dy1*var.cx1; + + var.ax1 = (var.ox1 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay1 = (-var.oy1* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx1 = var.ax1 - cursor2.posx + var.dy1 = var.ay1 - cursor2.posy + + var.d1 = sqrt((var.dx1*var.dx1)+(var.dy1*var.dy1)) + + var.a1 = 180 / (200 + var.d1 * var.d1 * var.d1 * .001) + + if var.d1 <= var.deadzone then var.a1 = 1 + + //debug = var.d + " " + var.a + + var.finalx1 = cursor2.posx * var.a1 + var.ax1 * (1 - var.a1) + var.finaly1 = cursor2.posy * var.a1 + var.ay1 * (1 - var.a1) + + + cursor2.posx = smooth(var.finalx1,3,5) + cursor2.posy = smooth(var.finaly1,3,5) + +else + + var.leftpoint1 = 0 + +endif + +var.xpos1 = var.finalx1 +var.ypos1 = var.finaly1 +ppjoy1.analog0 = ensuremaprange(var.xpos1,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy1.analog1 = ensuremaprange(var.ypos1,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote1.B or wiimote1.A or wiimote1.Up or wiimote1.down or wiimote1.Left or wiimote1.Right == true + ppjoy1.digital0 = true +else + ppjoy1.digital0 = false +endif + +//WIIMOTE 2 +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx2 = wiimote2.RawForceX + var.xtrim2 +var.accy2 = wiimote2.RawForceY + var.ytrim2 +var.accz2 = wiimote2.RawForceZ + var.ztrim2 + +if wiimote2.dot1vis and wiimote2.dot2vis then + + if var.accy2 > -7 then + var.orientation2 = 0 + elseif var.accy2 > -45 then + if var.accx2 < 0 then + var.orientation2 = 3 + else + var.orientation2 = 1 + endif + else + var.orientation2 = 2 + endif + + if var.leftpoint2 = 0 then + if var.orientation2 = 0 then + if wiimote2.dot1x < wiimote2.dot2x then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + if var.orientation2 = 1 then + if wiimote2.dot1y > wiimote2.dot2y then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + if var.orientation2 = 2 then + if wiimote2.dot1x > wiimote2.dot2x then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + if var.orientation2 = 3 then + if wiimote2.dot1y < wiimote2.dot2y then + var.leftpoint2 = 1 + else + var.leftpoint2 = 2 + endif + endif + endif + + if var.leftpoint2 = 1 then + var.fix1x2 = wiimote2.dot1x + var.fix1y2 = wiimote2.dot1y + var.fix2x2 = wiimote2.dot2x + var.fix2y2 = wiimote2.dot2y + else + var.fix1x2 = wiimote2.dot2x + var.fix1y2 = wiimote2.dot2y + var.fix2x2 = wiimote2.dot1x + var.fix2y2 = wiimote2.dot1y + endif + + var.dx2 = var.fix2x2 - var.fix1x2 + var.dy2 = var.fix2y2 - var.fix1y2 + var.cx2 = (var.fix1x2+var.fix2x2)/1024.0 - 1 + var.cy2 = (var.fix1y2+var.fix2y2)/1024.0 - .75 + + var.d2 = sqrt(var.dx2*var.dx2+var.dy2*var.dy2) + + var.dx2 = var.dx2 / var.d2 + var.dy2 = var.dy2 / var.d2 + + var.ox2 = -var.dy2*var.cy2-var.dx2*var.cx2; + var.oy2 = -var.dx2*var.cy2+var.dy2*var.cx2; + + var.ax2 = (var.ox2 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay2 = (-var.oy2* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx2 = var.ax2 - cursor3.posx + var.dy2 = var.ay2 - cursor3.posy + + var.d2 = sqrt((var.dx2*var.dx2)+(var.dy2*var.dy2)) + + var.a2 = 180 / (200 + var.d2 * var.d2 * var.d2 * .001) + + if var.d2 <= var.deadzone then var.a2 = 1 + + //debug = var.d + " " + var.a + + var.finalx2 = cursor3.posx * var.a2 + var.ax2 * (1 - var.a2) + var.finaly2 = cursor3.posy * var.a2 + var.ay2 * (1 - var.a2) + + + cursor3.posx = smooth(var.finalx2,3,5) + cursor3.posy = smooth(var.finaly2,3,5) + +else + + var.leftpoint2 = 0 + +endif + +var.xpos2 = var.finalx2 +var.ypos2 = var.finaly2 +ppjoy2.analog0 = ensuremaprange(var.xpos2,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy2.analog1 = ensuremaprange(var.ypos2,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote2.B or wiimote2.A or wiimote2.Up or wiimote2.down or wiimote2.Left or wiimote2.Right == true + ppjoy2.digital0 = true +else + ppjoy2.digital0 = false +endif + +debug = var.accx2+ " " + var.accy2+ " " + var.accz2 + +//WIIMOTE 3 +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx3 = wiimote3.RawForceX + var.xtrim3 +var.accy3 = wiimote3.RawForceY + var.ytrim3 +var.accz3 = wiimote3.RawForceZ + var.ztrim3 + +if wiimote3.dot1vis and wiimote3.dot2vis then + + if var.accy3 > -7 then + var.orientation3 = 0 + elseif var.accy3 > -45 then + if var.accx3 < 0 then + var.orientation3 = 3 + else + var.orientation3 = 1 + endif + else + var.orientation3 = 2 + endif + + if var.leftpoint3 = 0 then + if var.orientation3 = 0 then + if wiimote3.dot1x < wiimote3.dot2x then + var.leftpoint3 = 1 + else + var.leftpoint3 = 2 + endif + endif + if var.orientation3 = 1 then + if wiimote3.dot1y > wiimote3.dot2y then + var.leftpoint3 = 1 + else + var.leftpoint3 = 2 + endif + endif + if var.orientation3 = 2 then + if wiimote3.dot1x > wiimote3.dot2x then + var.leftpoint3 = 1 + else + var.leftpoint3 = 2 + endif + endif + if var.orientation3 = 3 then + if wiimote3.dot1y < wiimote3.dot2y then + var.leftpoint3 = 1 + else + var.leftpoint3 = 2 + endif + endif + endif + + if var.leftpoint3 = 1 then + var.fix1x3 = wiimote3.dot1x + var.fix1y3 = wiimote3.dot1y + var.fix2x3 = wiimote3.dot2x + var.fix2y3 = wiimote3.dot2y + else + var.fix1x3 = wiimote3.dot2x + var.fix1y3 = wiimote3.dot2y + var.fix2x3 = wiimote3.dot1x + var.fix2y3 = wiimote3.dot1y + endif + + var.dx3 = var.fix2x3 - var.fix1x3 + var.dy3 = var.fix2y3 - var.fix1y3 + var.cx3 = (var.fix1x3+var.fix2x3)/1024.0 - 1 + var.cy3 = (var.fix1y3+var.fix2y3)/1024.0 - .75 + + var.d3 = sqrt(var.dx3*var.dx3+var.dy3*var.dy3) + + var.dx3 = var.dx3 / var.d3 + var.dy3 = var.dy3 / var.d3 + + var.ox3 = -var.dy3*var.cy3-var.dx3*var.cx3; + var.oy3 = -var.dx3*var.cy3+var.dy3*var.cx3; + + var.ax3 = (var.ox3 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay3 = (-var.oy3* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx3 = var.ax3 - cursor4.posx + var.dy3 = var.ay3 - cursor4.posy + + var.d3 = sqrt((var.dx3*var.dx3)+(var.dy3*var.dy3)) + + var.a3 = 180 / (200 + var.d3 * var.d3 * var.d3 * .001) + + if var.d3 <= var.deadzone then var.a3 = 1 + + //debug = var.d + " " + var.a + + var.finalx3 = cursor4.posx * var.a3 + var.ax3 * (1 - var.a3) + var.finaly3 = cursor4.posy * var.a3 + var.ay3 * (1 - var.a3) + + + cursor4.posx = smooth(var.finalx3,3,5) + cursor4.posy = smooth(var.finaly3,3,5) + +else + + var.leftpoint3 = 0 + +endif + +var.xpos3 = var.finalx3 +var.ypos3 = var.finaly3 +ppjoy3.analog0 = ensuremaprange(var.xpos3,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy3.analog1 = ensuremaprange(var.ypos3,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote3.B or wiimote3.A or wiimote3.Up or wiimote3.down or wiimote3.Left or wiimote3.Right == true + ppjoy3.digital0 = true +else + ppjoy3.digital0 = false +endif + +debug = var.accx1+ " " + var.accy1+ " " + var.accz1 + +//WIIMOTE 3 +//***Do not edit anything below this line unless you know what you are doing.*** +var.accx4 = wiimote4.RawForceX + var.xtrim4 +var.accy4 = wiimote4.RawForceY + var.ytrim4 +var.accz4 = wiimote4.RawForceZ + var.ztrim4 + +if wiimote4.dot1vis and wiimote4.dot2vis then + + if var.accy4 > -7 then + var.orientation4 = 0 + elseif var.accy4 > -45 then + if var.accx4 < 0 then + var.orientation4 = 3 + else + var.orientation4 = 1 + endif + else + var.orientation4 = 2 + endif + + if var.leftpoint4 = 0 then + if var.orientation4 = 0 then + if wiimote4.dot1x < wiimote4.dot2x then + var.leftpoint4 = 1 + else + var.leftpoint4 = 2 + endif + endif + if var.orientation4 = 1 then + if wiimote4.dot1y > wiimote4.dot2y then + var.leftpoint4 = 1 + else + var.leftpoint4 = 2 + endif + endif + if var.orientation4 = 2 then + if wiimote4.dot1x > wiimote4.dot2x then + var.leftpoint4 = 1 + else + var.leftpoint4 = 2 + endif + endif + if var.orientation4 = 3 then + if wiimote4.dot1y < wiimote4.dot2y then + var.leftpoint4 = 1 + else + var.leftpoint4 = 2 + endif + endif + endif + + if var.leftpoint4 = 1 then + var.fix1x4 = wiimote4.dot1x + var.fix1y4 = wiimote4.dot1y + var.fix2x4 = wiimote4.dot2x + var.fix2y4 = wiimote4.dot2y + else + var.fix1x4 = wiimote4.dot2x + var.fix1y4 = wiimote4.dot2y + var.fix2x4 = wiimote4.dot1x + var.fix2y4 = wiimote4.dot1y + endif + + var.dx4 = var.fix2x4 - var.fix1x4 + var.dy4 = var.fix2y4 - var.fix1y4 + var.cx4 = (var.fix1x4+var.fix2x4)/1024.0 - 1 + var.cy4 = (var.fix1y4+var.fix2y4)/1024.0 - .75 + + var.d4 = sqrt(var.dx4*var.dx4+var.dy4*var.dy4) + + var.dx4 = var.dx4 / var.d4 + var.dy4 = var.dy4 / var.d4 + + var.ox4 = -var.dy4*var.cy4-var.dx4*var.cx4; + var.oy4 = -var.dx4*var.cy4+var.dy4*var.cx4; + + var.ax4 = (var.ox4 * var.coeff*screen.desktopwidth) + (screen.desktopwidth* var.coeff / 2) + var.ay4 = (-var.oy4* var.coeff * screen.desktopwidth) + (screen.desktopheight* var.coeff / 2) + + var.dx4 = var.ax4 - cursor5.posx + var.dy4 = var.ay4 - cursor5.posy + + var.d4 = sqrt((var.dx4*var.dx4)+(var.dy4*var.dy4)) + + var.a4 = 180 / (200 + var.d4 * var.d4 * var.d4 * .001) + + if var.d4 <= var.deadzone then var.a4 = 1 + + //debug = var.d + " " + var.a + + var.finalx4 = cursor5.posx * var.a4 + var.ax4 * (1 - var.a4) + var.finaly4 = cursor5.posy * var.a4 + var.ay4 * (1 - var.a4) + + + cursor5.posx = smooth(var.finalx4,3,5) + cursor5.posy = smooth(var.finaly4,3,5) + +else + + var.leftpoint4 = 0 + +endif + +var.xpos4 = var.finalx4 +var.ypos4 = var.finaly4 +ppjoy4.analog0 = ensuremaprange(var.xpos4,0,screen.desktopwidth* var.coeff,-1,1) +ppjoy4.analog1 = ensuremaprange(var.ypos4,0,screen.desktopheight* var.coeff,-1,1) + +if wiimote4.B or wiimote4.A or wiimote4.Up or wiimote4.down or wiimote4.Left or wiimote4.Right == true + ppjoy4.digital0 = true +else + ppjoy4.digital0 = false +endif + diff --git a/src/controllers/Wiimote.py b/src/controllers/Wiimote.py new file mode 100755 index 0000000..7536881 --- /dev/null +++ b/src/controllers/Wiimote.py @@ -0,0 +1,106 @@ +''' +Created on 15 juil. 2009 + +@author: Samuel Benveniste +''' +from gui.constants import * + +class Wiimote: + ''' + Object representing a Wiimote + + number: + The number of the Wiimote + port: + The pypm object representing the MIDI port on which the Wiimote emits + instrument: + The instrument associated with the Wiimote + cursor: + The cursor associated with the Wiimote + ''' + + def __init__(self, number, portNumber, port, instrument, cursor): + ''' + Constructor + + number: + The number of the Wiimote + portNumber: + The number of the port (as numbered by pypm) on which the wiimote emits + port: + The pypm object representing the MIDI port on which the Wiimote emits + instrument: + The instrument associated with the Wiimote + cursor: + The cursor associated with the Wiimote + ''' + + self.number = number + self.portNumber = portNumber + self.port = port + self.instrument = instrument + self.cursor = cursor + self.numberPlayed = 0 + + def getNoteOnHexCode(self): + return (0x90 + self.instrument.channel - 1) + + def getAftertouchHexCode(self): + return (0xA0 + self.instrument.channel - 1) + + def getCCHexCode(self): + return (0xB0 + self.instrument.channel - 1) + + def playNote(self, note, velocity): + noteNumber = self.instrument.getNote(note) + + if noteNumber != None : + noteOnHexCode = self.getNoteOnHexCode() + CCHexCode = self.getCCHexCode() + else : + noteNumber = defaultInstrumentNote + noteOnHexCode = defaultNoteOnHexCode + CCHexCode = defaultCCHexCode + + self.port.write_short(noteOnHexCode, noteNumber , 127) + self.port.write_short(CCHexCode, 07, velocity) + + def playNoteByNoteNumber(self, midiNoteNumber, velocity): + noteNumber = self.instrument.getNoteByNoteNumber(midiNoteNumber) + + if noteNumber != None : + noteOnHexCode = self.getNoteOnHexCode() + CCHexCode = self.getCCHexCode() + else : + noteNumber = defaultInstrumentNote + noteOnHexCode = defaultNoteOnHexCode + CCHexCode = defaultCCHexCode + + self.port.write_short(noteOnHexCode, noteNumber , 127) + self.port.write_short(CCHexCode, 07, velocity) + + self.numberPlayed += 1 + + def stopNote(self, note): + noteNumber = self.instrument.getNote(note) + if noteNumber != None : + noteOnHexCode = self.getNoteOnHexCode() + else : + noteNumber = defaultInstrumentNote + noteOnHexCode = defaultNoteOnHexCode + + self.port.write_short(noteOnHexCode, noteNumber, 0) + + def stopNoteByNoteNumber(self, midiNoteNumber): + noteNumber = self.instrument.getNoteByNoteNumber(midiNoteNumber) + if noteNumber != None : + noteOnHexCode = self.getNoteOnHexCode() + else : + noteNumber = defaultInstrumentNote + noteOnHexCode = defaultNoteOnHexCode + + self.port.write_short(noteOnHexCode, noteNumber, 0) + + def allNotesOff(self): + CCHexCode = self.getCCHexCode() + 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 index 0000000..e69de29 diff --git a/src/cursor/WarpingCursor.py b/src/cursor/WarpingCursor.py new file mode 100755 index 0000000..57985e1 --- /dev/null +++ b/src/cursor/WarpingCursor.py @@ -0,0 +1,155 @@ +''' +Created on 29 mai 2009 + +@author: samsam +''' + +import os, sys, math + +import pygame +from pygame.sprite import Sprite + +class WarpingCursor(Sprite): + ''' + The class for animating the warping cursor + + screen: + the screen on which the WarpingCursor is painted + images: + The images constituting the animation of the cursor + durations: + The duration of each image in the animation + centerPosition: + The Position of the center of the cursor + _imagePointer: + A pointer to the current image + _animationOffset: + The time elapsed since when the current image should have been displayed + ''' + screen = None + images = None + durations = None + centerPosition = None + _imagePointer = None + _animationOffset = None + + + def __init__(self, scr, images, durations, initCenterPosition,flashImage): + ''' + Constructor + + scr: + the screen on which the WarpingCursor is painted + images: + The images constituting the animation of the cursor + durations: + The duration of each image in the animation + initCenterPosition: + The Position of the center of the cursor at the beginning + ''' + self.screen = scr + self.images = images + self.flashImagePath = flashImage + self.durations = durations + self.centerPosition = initCenterPosition + self.flashLength = 100 + self.flashing = False + self.image = pygame.image.load(self.images[0]).convert_alpha() + self._imagePointer = 0 + self._animationOffset = 0 + self._flashTimer = 0 + + def update(self, elapsedTime, centerPosition): + ''' + Update the cursor's look and position + + elapsedTime: + The time passed since the previous update + centerPosition: + the new position of the creep + ''' + self._updateImage(elapsedTime) + self.centerPosition = centerPosition + if self.flashing : + self._flashTimer += elapsedTime + if self._flashTimer > self.flashLength: + self.flashing = False + + def _updateImage(self, elapsedTime): + ''' + Update the cursor's image + + elapsedTime: + The time passed since the previous update + ''' + self._animationOffset += elapsedTime + + if self._animationOffset > self.durations[self._imagePointer]: + #New animation offset is computed first, before updating the pointer + self._animationOffset -= self.durations[self._imagePointer] + #point to the next image (restarts from the beginning when it reaches the end) + self._imagePointer = (self._imagePointer + 1) % len(self.images) + + if self.flashing: + self.image = pygame.image.load(self.flashImagePath).convert_alpha() + else : + self.image = pygame.image.load(self.images[self._imagePointer]).convert_alpha() + + def flash(self,flashLength = None): + self._flashTimer = 0 + self.flashing = True + if flashLength: + self.flashlength = flashLength + + def blit(self,surface): + ''' + Draw the circle on surface + ''' + + newPos = (self.centerPosition[0] - self.image.get_width() / 2, self.centerPosition[1] - self.image.get_height() / 2) + surface.blit(self.image, newPos) + +def createImageListFromPath(path, imageCount): + ''' + Create a list of images for a cursor (the concatenation of the original and reversed lists of images). + Images must be stored as path/imageNumber.png + + path: + The folder where the images for that cursor are stored + imageCount: + The number of images in the folder + ''' + + tempImages = [''.join([path, '/', str(i), '.png']) for i in range(imageCount)] + #tempImagesReversed = tempImages[:] + #tempImagesReversed.reverse() + #return(tempImages+tempImagesReversed) + return(tempImages) + +#testing +if __name__ == "__main__" : + window = pygame.display.set_mode((1680, 1050), pygame.FULLSCREEN) + screen = pygame.display.get_surface() + clock = pygame.time.Clock() + + images = createImageListFromPath('cursorImages/black',11) + durations = [50 for i in range(22)] + position = (400, 300) + cursor = WarpingCursor(screen, images, durations, position) + + while True: + # Limit frame speed to 50 FPS + # + timePassed = clock.tick(50) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + sys.exit() + if event.type == pygame.MOUSEMOTION: + position = event.pos + + cursor.update(timePassed, position) + cursor.blit(screen) + pygame.display.flip() + + diff --git a/src/cursor/__init__.py b/src/cursor/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/cursor/cursorImages/black/0.png b/src/cursor/cursorImages/black/0.png new file mode 100755 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 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 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 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 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 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 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 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 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 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 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 index 0000000..7c3c9b4 --- /dev/null +++ b/src/cursor/cursorImages/black/cursorBlack.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/cursor/cursorImages/black/flash.png b/src/cursor/cursorImages/black/flash.png new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..34c72b0 --- /dev/null +++ b/src/cursor/cursorImages/blue/cursorBlue.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/cursor/cursorImages/green/0.png b/src/cursor/cursorImages/green/0.png new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 index 0000000..34e8bc6 --- /dev/null +++ b/src/cursor/cursorImages/green/cursorGreen.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/cursor/cursorImages/red/0.png b/src/cursor/cursorImages/red/0.png new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 index 0000000..54f5fc0 --- /dev/null +++ b/src/cursor/cursorImages/red/cursorRed.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/data/themes/default/Vera.ttf b/src/data/themes/default/Vera.ttf new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..e4ef48b --- /dev/null +++ b/src/data/themes/default/config.txt @@ -0,0 +1,291 @@ +desktop background desktop.png + +input font Vera.ttf 16 +input background input.normal.png +input color #000000 +input:focus background input.focus.png +input padding_left 6 +input padding_right 6 +input padding_top 3 +input padding_bottom 3 + +link font Vera.ttf 24 +link color #0000FF +link:hover color #FF0000 +link:down color #00FF00 + +label font Vera.ttf 16 +label color #000000 + +document font Vera.ttf 16 +document color #000000 +div font Vera.ttf 16 +div color #000000 + +td font Vera.ttf 16 +td color #000000 +th font Vera.ttf 16 +th color #000000 + +h1 font Vera.ttf 24 +h1 color #000000 +h2 font Vera.ttf 20 +h2 color #000000 +h3 font Vera.ttf 16 +h3 color #000000 +h4 font Vera.ttf 14 +h4 color #000000 +h5 font Vera.ttf 12 +h5 color #000000 +h6 font Vera.ttf 10 +h6 color #000000 + +ul font Vera.ttf 16 +ul color #000000 +ol font Vera.ttf 16 +ol color #000000 +li font Vera.ttf 16 +li color #000000 +li padding_left 32 + +pre font mono 16 +pre color #000000 +code font mono 16 +code color #000000 + +checkbox off checkbox.off.normal.tga +checkbox on checkbox.on.normal.tga +checkbox:hover off checkbox.off.hover.tga +checkbox:hover on checkbox.on.hover.tga +checkbox:down off checkbox.off.hover.tga +checkbox:down on checkbox.on.hover.tga + +switch off checkbox.off.normal.tga +switch on checkbox.on.normal.tga +switch:hover off checkbox.off.hover.tga +switch:hover on checkbox.on.hover.tga +switch:down off checkbox.off.hover.tga +switch:down on checkbox.on.hover.tga + +radio off radio.off.normal.tga +radio on radio.on.normal.tga +radio:hover off radio.off.hover.tga +radio:hover on radio.on.hover.tga +radio:down off radio.off.hover.tga +radio:down on radio.on.hover.tga + +button background button.normal.tga +button:hover background button.hover.tga +button:down background button.down.tga +button padding_left 8 +button padding_right 8 +button padding_top 1 +button padding_bottom 1 +button.label font Vera.ttf 16 +button.label color #000000 + +slider background slider.tga +slider bar slider.bar.normal.tga +slider:hover bar slider.bar.hover.tga +slider width 16 +slider height 16 + +hslider background hslider.tga +hslider bar hslider.bar.normal.tga +hslider:hover bar hslider.bar.hover.tga +hslider:down bar hslider.bar.hover.tga +hslider width 16 +hslider height 16 + +vslider background vslider.tga +vslider bar vslider.bar.normal.tga +vslider:hover bar vslider.bar.hover.tga +vslider:down bar vslider.bar.hover.tga +vslider width 16 +vslider height 16 + +xhscrollbar height 16 +xhscrollbar background scroller.slide.h.tga +xhscrollbar bar scroller.slide.bar.normal.tga +xhscrollbar:hover bar scroller.slide.bar.hover.tga + +xvscrollbar width 16 +xvscrollbar background scroller.slide.v.tga +xvscrollbar bar scroller.slide.bar.normal.tga +xvscrollbar:hover bar scroller.slide.bar.hover.tga + +hscrollbar.slider background hslider.tga +hscrollbar.slider bar hslider.bar.normal.tga +hscrollbar.slider:hover bar hslider.bar.hover.tga +hscrollbar.slider:down bar hslider.bar.hover.tga +hscrollbar.slider width 16 +hscrollbar.slider height 16 +hscrollbar minus hslider.left.tga +hscrollbar plus hslider.right.tga + +vscrollbar.slider background vslider.tga +vscrollbar.slider bar vslider.bar.normal.tga +vscrollbar.slider:hover bar vslider.bar.hover.tga +vscrollbar.slider:down bar vslider.bar.hover.tga +vscrollbar.slider width 16 +vscrollbar.slider height 16 +vscrollbar minus vslider.up.tga +vscrollbar plus vslider.down.tga + + +select.selected background select.selected.normal.tga +select.selected:hover background select.selected.hover.tga +select.selected:down background select.selected.down.tga +select.selected padding_left 4 +select.selected padding_right 4 +select.selected padding_top 1 +select.selected padding_bottom 1 +select.arrow background select.arrow.normal.tga +select.arrow:hover background select.arrow.hover.tga +select.arrow:down background select.arrow.down.tga +select.arrow padding_left 1 +select.arrow padding_right 1 + +select.options background select.options.png +select.option background select.option.normal.png +select.option:hover background select.option.hover.png +select.option:down background select.option.hover.png +select.option padding_left 4 +select.option padding_right 4 +select.option padding_top 1 +select.option padding_bottom 1 +#select.option border_top 1 +#select.option border_right 1 +#select.option border_bottom 1 +#select.option border_left 1 + +select.option.label font Vera.ttf 16 +select.option.label color #000000 +select.options padding_left 1 +select.options padding_right 1 +select.options padding_top 1 +select.options padding_bottom 1 +select arrow select.arrow.png + + +dialog background dialog.bar.png +xdialog.bar background dialog.bar.png +dialog.bar padding_left 8 +dialog.bar padding_right 8 +dialog.bar padding_top 2 +dialog.bar padding_bottom 1 +dialog.bar.close image dialog.close.normal.tga +dialog.bar.close:hover image dialog.close.hover.tga +dialog.bar.close:down image dialog.close.down.tga +dialog.main background dialog.png +dialog.main padding_left 8 +dialog.main padding_right 8 +dialog.main padding_top 4 +dialog.main padding_bottom 4 + +keysym font Vera.ttf 16 +keysym background input.normal.png +keysym color #000000 +keysym:focus background input.focus.png +keysym padding_left 6 +keysym padding_right 6 +keysym padding_top 3 +keysym padding_bottom 3 + +tool background tool.normal.tga +tool:hover background tool.hover.tga +tool:down background tool.down.tga +tool padding_left 4 +tool padding_right 4 +tool padding_top 1 +tool padding_bottom 1 +tool.label font Vera.ttf 16 +tool.label color #000000 + +menu background menu.normal.tga +menu:hover background menu.hover.tga +menu:down background menu.down.tga +menu padding_left 6 +menu padding_right 6 +menu padding_top 3 +menu padding_bottom 3 +menu.label font Vera.ttf 16 +menu.label color #000000 + +menu-open background menu.down.tga +menu-open:hover background menu.down.tga +menu-open:down background menu.down.tga +menu-open padding_left 6 +menu-open padding_right 6 +menu-open padding_top 3 +menu-open padding_bottom 3 + +menu.options background select.options.png +menu.option background select.option.normal.png +menu.option:hover background select.option.hover.png +menu.option:down background select.option.hover.png +menu.option padding_left 6 +menu.option padding_right 6 +menu.option padding_top 1 +menu.option padding_bottom 1 +menu.option.label font Vera.ttf 16 +menu.option.label color #000000 +menu.options padding_left 1 +menu.options padding_right 1 +menu.options padding_top 1 +menu.options padding_bottom 1 +menu arrow select.arrow.tga + + +scrollarea.content background #ffffff +scrollarea.content padding_left 1 +scrollarea.content padding_right 1 +scrollarea.content padding_top 1 +scrollarea.content padding_bottom 1 + + +list.item background list.item.normal.png +list.item:hover background list.item.down.png +list.item:down background list.item.down.png +list.item padding_left 4 +list.item padding_right 4 +list.item padding_top 2 +list.item padding_bottom 2 +list.item margin_bottom 1 +list.item align -1 +list.item.label font Vera.ttf 14 +list.item.label color #000000 + +list background list.png +list padding_left 1 +list padding_right 1 +list padding_top 1 +list padding_bottom 1 +list.content background #eeeeee +list.content padding_left 1 +list.content padding_right 1 +list.content padding_top 1 +list.content padding_bottom 1 + +filedialog.folder image filebrowser.folder.png +filedialog.label font Vera.ttf 14 +filedialog.label color #000000 +filedialog.title.label font Vera.ttf 16 +filedialog.title.label color #000000 +filedialog.input font Vera.ttf 14 +filedialog.input background input.normal.png +filedialog.input color #000000 +filedialog.input:focus background input.focus.png +filedialog.input padding_left 6 +filedialog.input padding_right 6 +filedialog.input padding_top 3 +filedialog.input padding_bottom 3 + +dialog.title.label font Vera.ttf 16 +dialog.title.label color #000000 + + +progressbar background progressbar.tga +progressbar bar progressbar.bar.tga +progressbar width 16 +progressbar height 16 diff --git a/src/data/themes/default/console.input.focus.png b/src/data/themes/default/console.input.focus.png new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..a161556 --- /dev/null +++ b/src/data/themes/default/generate.py @@ -0,0 +1,98 @@ +import pygame +from pygame.locals import * +pygame.display.init() +pygame.display.set_mode((80,80),32) + +def prep(name): + fname = name+".png" + img = pygame.image.load(fname) + w,h = img.get_width()/2,img.get_height()/2 + + out = pygame.Surface((w*3,h*3),SWSURFACE|SRCALPHA,32) + out.fill((0,0,0,0)) + out.blit(img.subsurface(0,0,w,h),(0,0)) + out.blit(img.subsurface(w,0,w,h),(w*2,0)) + out.blit(img.subsurface(0,h,w,h),(0,h*2)) + out.blit(img.subsurface(w,h,w,h),(w*2,h*2)) + for i in range(0,w): + img = out.subsurface((w-1,0,1,h*3)).convert_alpha() + out.blit(img,(w+i,0)) + for i in range(0,h): + img = out.subsurface((0,h-1,w*3,1)).convert_alpha() + out.blit(img,(0,h+i)) + + return out,w,h + +todo = [ + ('button.normal','dot.normal',None,3,3,'789456123'), + ('button.hover','dot.hover',None,3,3,'789456123'), + ('button.down','dot.down',None,3,3,'789456123'), + + ('checkbox.off.normal','box.normal',None,2,2,'7913'), + ('checkbox.on.normal','box.down','check',2,2,'7913'), + ('checkbox.off.hover','box.hover',None,2,2,'7913'), + ('checkbox.on.hover','box.hover','check',2,2,'7913'), + + ('radio.off.normal','dot.normal',None,2,2,'7913'), + ('radio.on.normal','dot.down','radio',2,2,'7913'), + ('radio.off.hover','dot.hover',None,2,2,'7913'), + ('radio.on.hover','dot.hover','radio',2,2,'7913'), + + ('tool.normal','box.normal',None,3,3,'789456123'), + ('tool.hover','box.hover',None,3,3,'789456123'), + ('tool.down','box.down',None,3,3,'789456123'), + + ('hslider','idot.normal',None,3,3,'789456123'), + ('hslider.bar.normal','dot.normal',None,3,3,'789456123'), + ('hslider.bar.hover','dot.hover',None,3,3,'789456123'), + ('hslider.left','sbox.normal','left',2,2,'7913'), + ('hslider.right','sbox.normal','right',2,2,'7913'), + + + ('vslider','idot.normal',None,3,3,'789456123'), + ('vslider.bar.normal','vdot.normal',None,3,3,'789456123'), + ('vslider.bar.hover','vdot.hover',None,3,3,'789456123'), + ('vslider.up','vsbox.normal','up',2,2,'7913'), + ('vslider.down','vsbox.normal','down',2,2,'7913'), + + ('dialog.close.normal','rdot.hover',None,2,2,'7913'), + ('dialog.close.hover','rdot.hover','x',2,2,'7913'), + ('dialog.close.down','rdot.down','x',2,2,'7913'), + + ('menu.normal','desktop',None,1,1,'7'), + ('menu.hover','box.normal',None,3,3,'789456123'), + ('menu.down','box.down',None,3,3,'789456123'), + + ('select.selected.normal','box.normal',None,3,3,'788455122'), + ('select.selected.hover','box.hover',None,3,3,'788455122'), + ('select.selected.down','box.down',None,3,3,'788455122'), + + ('select.arrow.normal','box.hover',None,3,3,'889556223'), + ('select.arrow.hover','box.hover',None,3,3,'889556223'), + ('select.arrow.down','box.down',None,3,3,'889556223'), + + ('progressbar','sbox.normal',None,3,3,'789456123'), + ('progressbar.bar','box.hover',None,3,3,'789456123'), + ] + +for fname,img,over,ww,hh,s in todo: + print fname + img,w,h = prep(img) + out = pygame.Surface((ww*w,hh*h),SWSURFACE|SRCALPHA,32) + out.fill((0,0,0,0)) + n = 0 + for y in range(0,hh): + for x in range(0,ww): + c = int(s[n]) + xx,yy = (c-1)%3,2-(c-1)/3 + out.blit(img.subsurface((xx*w,yy*h,w,h)),(x*w,y*h)) + n += 1 + if over != None: + over = pygame.image.load(over+".png") + out.blit(over,(0,0)) + pygame.image.save(out,fname+".tga") + + + + + diff --git a/src/data/themes/default/hslider.bar.hover.tga b/src/data/themes/default/hslider.bar.hover.tga new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..f6541e4 --- /dev/null +++ b/src/data/themes/default/notes.txt @@ -0,0 +1,8 @@ +dot and box.xcf: + +color -170 + +.down +.hover +64 brightness +.normal, grayscale +127 brightness, +48 contrast + diff --git a/src/data/themes/default/out.tga b/src/data/themes/default/out.tga new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..0ea2006 --- /dev/null +++ b/src/data/themes/gray/config.txt @@ -0,0 +1,244 @@ +desktop background desktop.png + +input font Vera.ttf 16 +input background input.normal.png +input color #000000 +input:focus background input.focus.png +input padding_left 6 +input padding_right 6 +input padding_top 3 +input padding_bottom 3 + +label font Vera.ttf 16 +label color #000000 + +document font Vera.ttf 16 +document color #000000 +div font Vera.ttf 16 +div color #000000 + +td font Vera.ttf 16 +td color #000000 +th font Vera.ttf 16 +th color #000000 + +h1 font Vera.ttf 24 +h1 color #000000 +h2 font Vera.ttf 20 +h2 color #000000 +h3 font Vera.ttf 16 +h3 color #000000 +h4 font Vera.ttf 14 +h4 color #000000 +h5 font Vera.ttf 12 +h5 color #000000 +h6 font Vera.ttf 10 +h6 color #000000 + +ul font Vera.ttf 16 +ul color #000000 +ol font Vera.ttf 16 +ol color #000000 +li font Vera.ttf 16 +li color #000000 +li padding_left 32 + +pre font mono 16 +pre color #000000 +code font mono 16 +code color #000000 + +checkbox off checkbox.off.normal.png +checkbox on checkbox.on.normal.png +checkbox:down off checkbox.off.down.png +checkbox:down on checkbox.on.down.png + +switch off checkbox.off.normal.png +switch on checkbox.on.normal.png +switch:down off checkbox.off.down.png +switch:down on checkbox.on.down.png + +radio off radio.off.normal.png +radio on radio.on.normal.png +radio:down off radio.off.down.png +radio:down on radio.on.down.png + +button background button.normal.png +button:down background button.down.png +button padding_left 8 +button padding_right 8 +button padding_top 1 +button padding_bottom 1 +button.label font Vera.ttf 16 +button.label color #000000 + +slider background slider.png +slider bar slider.bar.normal.png +slider:down bar slider.bar.down.png +slider width 16 +slider height 16 + +hslider background slider.png +hslider bar slider.bar.normal.png +hslider:down bar slider.bar.down.png +hslider width 16 +hslider height 16 + +vslider background slider.png +vslider bar slider.bar.normal.png +vslider:down bar slider.bar.down.png +vslider width 16 +vslider height 16 + +select.selected background select.selected.normal.png +#select.selected:down background select.selected.down.png +select.selected padding_left 4 +select.selected padding_right 4 +select.selected padding_top 1 +select.selected padding_bottom 1 +select.arrow background select.arrow.normal.png +select.arrow:down background select.arrow.down.png +select.arrow padding_left 1 +select.arrow padding_right 1 + +select.options background select.options.png +select.option background select.option.normal.png +#select.option:hover background select.option.hover.png +select.option padding_left 4 +select.option padding_right 4 +select.option padding_top 1 +select.option padding_bottom 1 +#select.option border_top 1 +#select.option border_right 1 +#select.option border_bottom 1 +#select.option border_left 1 + +select.option.label font Vera.ttf 16 +select.option.label color #000000 +select.options padding_left 1 +select.options padding_right 1 +select.options padding_top 1 +select.options padding_bottom 1 +select arrow select.arrow.png + + +dialog.bar background dialog.bar.png +dialog.bar padding_left 8 +dialog.bar padding_right 8 +dialog.bar padding_top 2 +dialog.bar padding_bottom 1 +dialog.bar.close image dialog.close.normal.png +dialog.bar.close:down image dialog.close.down.png +dialog.main background dialog.png +dialog.main padding_left 8 +dialog.main padding_right 8 +dialog.main padding_top 4 +dialog.main padding_bottom 4 + +keysym font helvetica 16 +keysym background input.normal.png +keysym color #000000 +keysym:focus background input.focus.png +keysym padding_left 6 +keysym padding_right 6 +keysym padding_top 3 +keysym padding_bottom 3 + +tool background tool.normal.png +tool:down background tool.down.png +tool padding_left 4 +tool padding_right 4 +tool padding_top 1 +tool padding_bottom 1 +tool.label font Vera.ttf 16 +tool.label color #000000 + +menu background menu.normal.png +menu:hover background menu.hover.png +menu:down background menu.down.png +menu padding_left 6 +menu padding_right 6 +menu padding_top 3 +menu padding_bottom 3 +menu.label font Vera.ttf 16 +menu.label color #000000 + +menu-open background menu.down.png +menu-open:down background menu.down.png +menu-open padding_left 6 +menu-open padding_right 6 +menu-open padding_top 3 +menu-open padding_bottom 3 + +menu.options background select.options.png +menu.option background menu.option.normal.png +menu.option:hover background menu.option.hover.png +#menu.option.label color #FF0000 +menu.option padding_left 6 +menu.option padding_right 6 +menu.option padding_top 1 +menu.option padding_bottom 1 +menu.option.label font Vera.ttf 16 +menu.option.label color #000000 +menu.options padding_left 1 +menu.options padding_right 1 +menu.options padding_top 1 +menu.options padding_bottom 1 +menu arrow select.arrow.png + + +scrollarea.content background #ffffff +scrollarea.content padding_left 1 +scrollarea.content padding_right 1 +scrollarea.content padding_top 1 +scrollarea.content padding_bottom 1 +hscrollbar height 15 +##hscrollbar background scroller.slide.h.png +hscrollbar background slider.png +##hscrollbar bar scroller.slide.bar.normal.png +hscrollbar bar slider.bar.normal.png +##hscrollbar:down bar scroller.slide.bar.down.png +vscrollbar width 15 +##vscrollbar background scroller.slide.v.png +vscrollbar background slider.png +##vscrollbar bar scroller.slide.bar.normal.png +vscrollbar bar slider.bar.normal.png +##vscrollbar:down bar scroller.slide.bar.down.png + +list.item background list.item.normal.png +#list.item:down background list.item.down.png +list.item padding_left 4 +list.item padding_right 4 +list.item padding_top 2 +list.item padding_bottom 2 +list.item margin_bottom 1 +list.item align -1 +list.item.label font Vera.ttf 14 +list.item.label color #000000 + +list background list.png +list padding_left 1 +list padding_right 1 +list padding_top 1 +list padding_bottom 1 +list.content background #eeeeee +list.content padding_left 1 +list.content padding_right 1 +list.content padding_top 1 +list.content padding_bottom 1 + +filedialog.folder image filebrowser.folder.png +filedialog.label font Vera.ttf 14 +filedialog.label color #000000 +filedialog.title.label font Vera.ttf 16 +filedialog.title.label color #000000 +filedialog.input font Vera.ttf 14 +filedialog.input background input.normal.png +filedialog.input color #000000 +filedialog.input:focus background input.focus.png +filedialog.input padding_left 6 +filedialog.input padding_right 6 +filedialog.input padding_top 3 +filedialog.input padding_bottom 3 + + diff --git a/src/data/themes/gray/console.input.focus.png b/src/data/themes/gray/console.input.focus.png new file mode 100644 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..b663eb0 --- /dev/null +++ b/src/data/themes/tools/config.txt @@ -0,0 +1,11 @@ +tool.draw image icons48.draw.tga +tool.pixel image icons48.pixel.tga +tool.line image icons48.line.tga +tool.fill image icons48.fill.tga + +tool.select image icons48.select.tga +tool.eraser image icons48.eraser.tga + +tool.tile image icons48.tile.tga +tool.code image icons48.code.tga +tool.bkgr image icons48.bkgr.tga diff --git a/src/data/themes/tools/icons48.bkgr.tga b/src/data/themes/tools/icons48.bkgr.tga new file mode 100644 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 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 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 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 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 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 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 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 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 index 0000000..e69de29 diff --git a/src/dataTools/__init__.pyc b/src/dataTools/__init__.pyc new file mode 100644 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 index 0000000..2c8391d --- /dev/null +++ b/src/dataTools/odict.py @@ -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 ,' + 'Michael Foord ') + +__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]) + + """ + 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 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 index 0000000..e69de29 diff --git a/src/gradients/gradients.py b/src/gradients/gradients.py new file mode 100755 index 0000000..cac1f2e --- /dev/null +++ b/src/gradients/gradients.py @@ -0,0 +1,576 @@ +#Copyright 2006 DR0ID 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 self.blinkLength: + self._blinkOffset -= self.blinkLength + self.blinkOn = not self.blinkOn + + if self.replay: + self.eventLog.update(timePassed) + pickledEventsToPost = self.eventLog.getPickledEvents() + for pickledEvent in pickledEventsToPost: + pygame.event.post(pickledEvent.event) + + events = pygame.event.get() + + if not self.replay: + pickledEvents = [PickleableEvent(event.type,event.dict) for event in events] + if pickledEvents != [] : + self.eventLog.appendEventGroup(pickledEvents) + + for event in events: + self.input(event) + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i]) + if self.buttonDown[i] : + self.wiimotes[i].cursor.flash() + self.wiimotes[i].cursor.blit(self.playerScreen) + + self.screen.blit(self.playerScreen, (0,0)) + + pygame.display.flip() + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].stopNote(self.notes[i]) + + def drawBackground(self): + self.savedScreen.fill((255,255,255)) + + if self.extendedScale : + self.scaleSize = self.longScaleSize + else: + self.scaleSize = self.shortScaleSize + + 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)] + #inflate last noteRect to cover the far right pixels + self.noteRects[-1].width = self.noteRects[-1].width + 1 + + #create bounding rect + self.boundingRect = self.noteRects[0].unionall(self.noteRects) + + #fill the rectangles with a color gradient + #We start with blue + startingHue = 0.66666666666666663 + + for rectNumber in range(self.scaleSize): + colorRatio = float(rectNumber) / (self.scaleSize - 1) + #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up + hue = startingHue * (1 - colorRatio) + if self.song != None: + if rectNumber + self.offset == self.highlightedNote: + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.6, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.9, 1) + else: + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.2, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.4, 1) + else: + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.6, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.9, 1) + + #convert to rgb ranging from 0 to 255 + bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)] + topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)] + #add transparency + bottomColorRgb.append(255) + topColorRgb.append(255) + #convert to tuple + bottomColorRgb = tuple(bottomColorRgb) + topColorRgb = tuple(topColorRgb) + + self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber]) + + textBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber].get_width())/2, + self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber].get_height()) + + self.savedScreen.blit(self.renderedNoteNames[rectNumber], textBlitPoint) + + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2) + + + if self.song != None and self.blinkOn: + borderSize = self.borderSize + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize) + + def initializeWiimotes(self): + for loop in self.wiimotes: + if loop.port == None : + loop.port = pygame.midi.Output(loop.portNumber) + self.notes.append(0) + self.buttonDown.append(False) + self.velocityLock.append(False) + + def updateCursorPositionFromJoy(self, joyEvent): + joyName = pygame.joystick.Joystick(joyEvent.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if correctedJoyId < len(self.cursorPositions): + if joyEvent.axis == 0 : + self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1]) + if joyEvent.axis == 1 : + self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height())) + + def heightToVelocity(self, pos, controllerNumber): + if self.song != None: + if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + else : + if self.easyMode: + velocity = None + else: + velocity = self.minimalVelocity/3 + else: + if self.boundingRect.collidepoint(pos): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + else : + velocity = self.minimalVelocity + return(velocity) + + def widthToNote(self, pos): + nn = 0 + try : + while self.noteRects[nn].collidepoint(pos) == False: + nn = nn + 1 + return(nn + self.offset) + except(IndexError): + return(None) + + def input(self, event): + + if event.type == pygame.QUIT: + for loop in self.wiimotes: + del loop.port + pygame.midi.quit() + sys.exit(0) + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + self.done = True + + if event.key == pygame.K_i: + self.backToInstrumentChoice = True + self.done = True + + if event.type == pygame.JOYAXISMOTION: + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.updateCursorPositionFromJoy(event) + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if self.buttonDown[correctedJoyId]: + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + if self.cascade: + n = self.widthToNote(pos) + if n != self.notes[correctedJoyId]: + wiimote.stopNote(self.notes[correctedJoyId]) + self.notes[correctedJoyId] = n + + if self.song != None : + if self.highlightedNote == self.notes[correctedJoyId]: + self.highlightedNote = self.songIterator.next() + self.velocityLock[correctedJoyId] = True + else: + self.velocityLock[correctedJoyId] = False + + velocity = self.heightToVelocity(pos, correctedJoyId) + + wiimote.playNote(self.notes[correctedJoyId],velocity) + + if event.type == pygame.JOYBUTTONDOWN : + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if not self.buttonDown[correctedJoyId]: + savedNote = self.notes[correctedJoyId] + self.notes[correctedJoyId] = self.widthToNote(pos) + + if self.song != None : + if self.highlightedNote == self.notes[correctedJoyId]: + self.highlightedNote = self.songIterator.next() + self.velocityLock[correctedJoyId] = True + + velocity = self.heightToVelocity(pos, correctedJoyId) + + if velocity != None : + if self.easyMode : + wiimote.stopNote(savedNote) + wiimote.playNote(self.notes[correctedJoyId],velocity) + self.buttonDown[correctedJoyId] = True + + if event.type == pygame.JOYBUTTONUP: + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.buttonDown[correctedJoyId] = False + wiimote = self.wiimotes[correctedJoyId] + if not self.easyMode: + wiimote.stopNote(self.notes[correctedJoyId]) + self.velocityLock[correctedJoyId] = False + + if event.type == pygame.MOUSEMOTION: + + self.updateCursorPositionFromMouse(event) + + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if self.buttonDown[correctedJoyId]: + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + if self.cascade: + n = self.widthToNote(pos) + if n != self.notes[correctedJoyId]: + wiimote.stopNote(self.notes[correctedJoyId]) + self.notes[correctedJoyId] = n + + if self.song != None : + if self.highlightedNote == self.notes[correctedJoyId]: + self.highlightedNote = self.songIterator.next() + self.velocityLock[correctedJoyId] = True + else: + self.velocityLock[correctedJoyId] = False + + velocity = self.heightToVelocity(pos, correctedJoyId) + + wiimote.playNote(self.notes[correctedJoyId],velocity) + + if event.type == pygame.MOUSEBUTTONDOWN: + + if event.button == 1: + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if not self.buttonDown[correctedJoyId]: + self.notes[correctedJoyId] = self.widthToNote(pos) + + if self.song != None : + if self.highlightedNote == self.notes[correctedJoyId]: + self.highlightedNote = self.songIterator.next() + self.velocityLock[correctedJoyId] = True + + velocity = self.heightToVelocity(pos, correctedJoyId) + + wiimote.playNote(self.notes[correctedJoyId],velocity) + self.buttonDown[correctedJoyId] = True + + if event.button == 2: + + self.done = True + + if event.type == pygame.MOUSEBUTTONUP: + + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + wiimote.stopNote(self.notes[correctedJoyId]) + self.buttonDown[correctedJoyId] = False + self.velocityLock[correctedJoyId] = False + + def hasChanged(self): + changed = False + if self.song != None: + if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote: + self.savedBlinkOn = self.blinkOn + self.savedHighlightedNote = self.highlightedNote + changed = True + return(changed) + + def updateCursorPositionFromMouse(self, mouseEvent): + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + self.cursorPositions[correctedJoyId] = mouseEvent.pos + + def moveToNextNote(self): + while True: + if self.song == None: + yield(None) + else: + for note in self.song: + yield note diff --git a/src/gui/SongFamiliarizer.py b/src/gui/SongFamiliarizer.py new file mode 100644 index 0000000..69ab03c --- /dev/null +++ b/src/gui/SongFamiliarizer.py @@ -0,0 +1,583 @@ +''' +Created on 23 juil. 2009 + +@author: Samuel Benveniste +''' +from math import floor, ceil +import pygame +import sys +import colorsys +import constants +from gradients import gradients +from logging.PickleableEvent import PickleableEvent +from logging.EventLog import EventLog + + +class SongFamiliarizer: + ''' + The screen on which the game is played + + wiimotes: + The wiimotes used in this session + window: + The main display window + screen: + The main display surface + clock: + The clock used to animate the screen + savedScreen: + The background that is painted every time + playerScreen: + The buffer for painting everything before bliting + width: + The width of the window in pixels + height: + The height of the window in pixels + extendScale : + True if the scale is G to C instead of C to C + cascade: + True if crossing from note to note with a button pressed triggers a new note + scaleSize: + The size of the scale used + cursorPositions: + The positions of the cursors on the screen, in pixels + ''' + + + + 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): + ''' + Constructor + ''' + self.firstClickTime = None + self.firstClickInTime = None + self.duration = None + self.clicks = 0 + self.clicksIn = 0 + + pygame.font.init() + self.font = pygame.font.Font(None,60) + self.congratulations = ["Bien !","Tres Bien !","Bravo !","Excellent !","Felicitations !"] + self.renderedCongratulations = [self.font.render(congratulation,False,(0,0,0)) for congratulation in self.congratulations] + self.congratulationCount = None + self.isCongratulating = False + self.congratulationTimer = 0 + self.congratulationLength = 2000 + self.congratulationPos = None + + self.blinkLength = 200 + self.minimalVelocity = 90 + self.shortScaleSize = 8 + self.longScaleSize = 11 + if not extendedScale: + self.offset = self.longScaleSize - self.shortScaleSize + else: + self.offset = 0 + self.borderSize = 5 + self.highlightedNote = 0 + self.highlightedNoteNumber = 0 + self.syllabus = None + self.savedHighlightedNote = 0 + self.scaleFactor = 1 + self.level = 3 + + self.wiimotes = wiimotes + self.activeWiimotes = activeWiimotes + self.window = window + self.screen = screen + self.width = int(floor(screen.get_width()*self.scaleFactor)) + self.height = int(floor(screen.get_height()*self.scaleFactor)) + self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2) + self.joys = joys + self.clock = clock + self.cursorPositions = [] + self.savedScreen = pygame.Surface(self.screen.get_size()) + self.savedScreen.fill((255,255,255)) + self.playerScreen = pygame.Surface(self.savedScreen.get_size()) + self.playerScreen.blit(self.savedScreen, (0, 0)) + self.extendedScale = extendedScale + self.cascade = cascade + self.portOffset =portOffset + self.eventLog = eventLog + self.song = song + self.songIterator = self.song.getSongIterator() + self.midiNoteNumbers = self.song.scale + self.replay = replay + self.quarterNoteLength = 800 + self.cascadeLockLengthMultiplier = 1 + self.cascadeLockLength = self.quarterNoteLength * self.cascadeLockLengthMultiplier + + self.defaultInstrumentChannel = defaultInstrumentChannel + self.defaultNote = defaultNote + + self.done = False + self.backToInstrumentChoice = False + self.easyMode = easyMode + + if eventLog == None: + self.eventLog = EventLog() + self.replay = False + else: + self.eventLog = eventLog + self.replay = replay + + #Initializes the highlightedNote and highlightedNoteNumber etc... + self.moveToNextNote() + + self.blinkOn = False + self.savedBlinkOn = False + ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two + ##i.e. it guarantees that there will be an attack between two identical consecutive notes + self.highlightIsFree = True + + self.noteRects = [] + self.boundingRect = None + self.notes = [] + + self.buttonDown = [] + self.velocityLock = [] + + self._blinkOffset = 0 + self._cascadeLockTimer = 0 + self.cascadeIsFree = True + + self.font = pygame.font.Font(None,50) + self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers] + + self.drawBackground() + self.initializeWiimotes() + + events = pygame.event.get() + + #The main loop + while not self.done : + + #Clear the cursors from the screen + if self.hasChanged(): + self.drawBackground() + self.playerScreen.blit(self.savedScreen, (0, 0)) + + # Limit frame speed to 50 FPS + # + timePassed = self.clock.tick(10000) + + self._blinkOffset += timePassed + if self.buttonDown and not self.cascadeIsFree : + self._cascadeLockTimer += timePassed + if self._cascadeLockTimer > self.cascadeLockLength : + self.cascadeIsFree = True + + + if self._blinkOffset > self.blinkLength: + self._blinkOffset -= self.blinkLength + self.blinkOn = not self.blinkOn + + if self.replay: + self.eventLog.update(timePassed) + pickledEventsToPost = self.eventLog.getPickledEvents() + for pickledEvent in pickledEventsToPost: + pygame.event.post(pickledEvent.event) + + events = pygame.event.get() + + if not self.replay: + pickledEvents = [PickleableEvent(event.type,event.dict) for event in events] + if pickledEvents != [] : + self.eventLog.appendEventGroup(pickledEvents) + + for event in events: + self.input(event) + + if self.isCongratulating : + self.congratulationTimer += timePassed + if self.congratulationTimer < self.congratulationLength : + self.blitCongratulation() + else : + self.isCongratulating = False + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i]) + if self.buttonDown[i] : + self.wiimotes[i].cursor.flash() + self.wiimotes[i].cursor.blit(self.playerScreen) + + self.screen.blit(self.playerScreen, (0,0)) + + pygame.display.flip() + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].stopNoteByNoteNumber(self.midiNoteNumbers[self.notes[i]]) + if self.replay : + self.duration = self.eventLog.getCurrentTime() + + def drawBackground(self): + self.savedScreen.fill((255,255,255)) + + if self.extendedScale : + self.scaleSize = self.longScaleSize + else: + self.scaleSize = self.shortScaleSize + + 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)] + #inflate last noteRect to cover the far right pixels + self.noteRects[-1].width = self.noteRects[-1].width + 1 + + self.noteRects[self.highlightedNote-self.offset].inflate_ip(self.noteRects[self.highlightedNote-self.offset].width*2,0) + + #create bounding rect + self.boundingRect = self.noteRects[0].unionall(self.noteRects) + + self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers] + + #fill the rectangles with a color gradient + #We start with blue + startingHue = 0.66666666666666663 + +# for rectNumber in range(self.scaleSize): +# colorRatio = float(rectNumber) / (self.scaleSize - 1) +# #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up +# hue = startingHue * (1 - colorRatio) +# if rectNumber + self.offset != self.highlightedNote: +# #The color of the bottom of the rectangle in hls coordinates +# bottomColorHls = (hue, 0.1, 1) +# #The color of the top of the rectangle in hls coordinates +# topColorHls = (hue, 0.1, 1) +# +# #convert to rgb ranging from 0 to 255 +# bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)] +# topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)] +# #add transparency +# bottomColorRgb.append(255) +# topColorRgb.append(255) +# #convert to tuple +# bottomColorRgb = tuple(bottomColorRgb) +# topColorRgb = tuple(topColorRgb) +# +# self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber]) +# +# noteNameBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber].get_width())/2, +# self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber].get_height()) +# +# self.savedScreen.blit(self.renderedNoteNames[rectNumber], noteNameBlitPoint) +# +# pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2) + + colorRatio = float(self.highlightedNote-self.offset) / (self.scaleSize - 1) + #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up + hue = startingHue * (1 - colorRatio) + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.6, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.9, 1) + + #convert to rgb ranging from 0 to 255 + bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)] + topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)] + #add transparency + bottomColorRgb.append(255) + topColorRgb.append(255) + #convert to tuple + bottomColorRgb = tuple(bottomColorRgb) + topColorRgb = tuple(topColorRgb) + + self.savedScreen.blit(gradients.vertical(self.noteRects[self.highlightedNote-self.offset].size, topColorRgb, bottomColorRgb), self.noteRects[self.highlightedNote-self.offset]) + +# noteNameBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-self.renderedNoteNames[self.highlightedNote-self.offset].get_width())/2, +# self.noteRects[self.highlightedNote-self.offset].bottom-self.renderedNoteNames[self.highlightedNote-self.offset].get_height()) +# +# self.savedScreen.blit(self.renderedNoteNames[self.highlightedNote-self.offset], noteNameBlitPoint) +# +# if self.syllabus : +# renderedSyllabus = self.font.render(self.syllabus,False,(0,0,0)) +# +# syllabusBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-renderedSyllabus.get_width())/2, +# self.noteRects[self.highlightedNote-self.offset].centery-renderedSyllabus.get_height()/2) +# +# self.savedScreen.blit(renderedSyllabus, syllabusBlitPoint) + + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[self.highlightedNote-self.offset], 2) + +# if self.song != None and self.blinkOn: +# borderSize = self.borderSize +# pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize) + + def initializeWiimotes(self): + for loop in self.wiimotes: + if loop.port == None : + loop.port = pygame.midi.Output(loop.portNumber) + self.notes.append(0) + self.cursorPositions.append(loop.cursor.centerPosition) + self.buttonDown.append(False) + self.velocityLock.append(False) + + def updateCursorPositionFromJoy(self, joyEvent): + joyName = pygame.joystick.Joystick(joyEvent.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if correctedJoyId < len(self.cursorPositions): + if joyEvent.axis == 0 : + self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1]) + if joyEvent.axis == 1 : + self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height())) + + def heightToVelocity(self, pos, controllerNumber): + if self.song != None: + if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + else : + if self.easyMode: + velocity = None + else: + velocity = 60 + else: + if self.boundingRect.collidepoint(pos): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + else : + velocity = self.minimalVelocity + return(velocity) + + def widthToNote(self, pos): + nn = 0 + try : + if self.noteRects[self.highlightedNote-self.offset].collidepoint(pos) : + return self.highlightedNote + else : + while self.noteRects[nn].collidepoint(pos) == False: + nn = nn + 1 + return(nn + self.offset) + except(IndexError): + return(None) + + def congratulate(self,targetRect,posy): + if self.congratulationCount != None : + if self.congratulationCount < len(self.congratulations)-1: + self.congratulationCount += 1 + else : + self.congratulationCount = 0 + self.congratulationTimer = 0 + self.congratulationPos = (targetRect.left+(targetRect.width-self.renderedCongratulations[self.congratulationCount].get_width())/2,posy) + self.isCongratulating = True + + def resetCongratulation(self): + self.congratulationCount = None + self.congratulationPos = None + self.isCongratulating = False + + def blitCongratulation(self): + self.playerScreen.blit(self.renderedCongratulations[self.congratulationCount],self.congratulationPos) + + def input(self, event): + + if event.type == pygame.QUIT: + for loop in self.wiimotes: + del loop.port + pygame.midi.quit() + sys.exit(0) + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + self.nextLevel = None + self.done = True + + if event.key == pygame.K_w: + self.nextLevel = 0 + self.done = True + + if event.key == pygame.K_e: + self.nextLevel = 1 + self.done = True + + if event.key == pygame.K_r: + self.nextLevel = 2 + self.done = True + + if event.key == pygame.K_t: + self.nextLevel = 3 + self.done = True + + if event.type == pygame.JOYAXISMOTION: + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.updateCursorPositionFromJoy(event) + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if self.buttonDown[correctedJoyId]: + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None : + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + if self.cascade and self.cascadeIsFree : + n = self.widthToNote(pos) + if self.highlightedNote == n: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.moveToNextNote() + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + + if event.type == pygame.JOYBUTTONDOWN : + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + self.wiimotes[correctedJoyId].cursor.flash() + if self.replay: + self.clicks += 1 + if self.firstClickTime == None : + self.firstClickTime = self.eventLog.getCurrentTime() + + if not self.buttonDown[correctedJoyId]: + n = self.widthToNote(pos) + if self.highlightedNote == n: + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + if self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1]) + if self.replay: + self.clicksIn += 1 + if self.firstClickInTime == None : + self.firstClickInTime = self.eventLog.getCurrentTime() + + self.moveToNextNote() + else : + self.resetCongratulation() + if not self.easyMode : + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None : + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.buttonDown[correctedJoyId] = True + + if event.type == pygame.JOYBUTTONUP: + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.buttonDown[correctedJoyId] = False + wiimote = self.wiimotes[correctedJoyId] + if not self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.velocityLock[correctedJoyId] = False + + if event.type == pygame.MOUSEMOTION: + + self.updateCursorPositionFromMouse(event) + + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if self.buttonDown[correctedJoyId]: + self.wiimotes[correctedJoyId].cursor.flash() + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None : + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + if self.cascade and self.cascadeIsFree : + n = self.widthToNote(pos) + if self.highlightedNote == n: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.moveToNextNote() + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + + if event.type == pygame.MOUSEBUTTONDOWN: + + if event.button == 1: + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + self.wiimotes[correctedJoyId].cursor.flash() + if self.replay: + self.clicks += 1 + if self.firstClickTime == None : + self.firstClickTime = self.eventLog.getCurrentTime() + + if not self.buttonDown[correctedJoyId]: + n = self.widthToNote(pos) + if self.highlightedNote == n: + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + if self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1]) + if self.replay: + self.clicksIn += 1 + if self.firstClickInTime == None : + self.firstClickInTime = self.eventLog.getCurrentTime() + + self.moveToNextNote() + else : + self.resetCongratulation() + if not self.easyMode : + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None : + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.buttonDown[correctedJoyId] = True + + if event.button == 2: + + self.done = True + + if event.type == pygame.MOUSEBUTTONUP: + if event.button == 1 : + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + self.buttonDown[correctedJoyId] = False + if not self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.velocityLock[correctedJoyId] = False + + def hasChanged(self): + changed = False + if self.song != None: + if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote: + self.savedBlinkOn = self.blinkOn + self.savedHighlightedNote = self.highlightedNote + changed = True + return(changed) + + def updateCursorPositionFromMouse(self, mouseEvent): + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + self.cursorPositions[correctedJoyId] = mouseEvent.pos + + def moveToNextNote(self): + self.savedMidiNoteNumbers = self.midiNoteNumbers[:] + self.highlightedNote, self.highlightedNoteNumber, self.syllabus, self.cascadeLockLengthMultiplier = self.songIterator.next() + self.midiNoteNumbers[self.highlightedNote] = self.highlightedNoteNumber diff --git a/src/gui/SongPlayingScreen.py b/src/gui/SongPlayingScreen.py new file mode 100644 index 0000000..a06e831 --- /dev/null +++ b/src/gui/SongPlayingScreen.py @@ -0,0 +1,564 @@ +''' +Created on 23 juil. 2009 + +@author: Samuel Benveniste +''' +from math import floor, ceil +import pygame +import sys +import colorsys +import constants +from gradients import gradients +from logging.PickleableEvent import PickleableEvent + + +class SongPlayingScreen: + ''' + The screen on which the game is played + + wiimotes: + The wiimotes used in this session + window: + The main display window + screen: + The main display surface + clock: + The clock used to animate the screen + savedScreen: + The background that is painted every time + playerScreen: + The buffer for painting everything before bliting + width: + The width of the window in pixels + height: + The height of the window in pixels + extendScale : + True if the scale is G to C instead of C to C + cascade: + True if crossing from note to note with a button pressed triggers a new note + scaleSize: + The size of the scale used + cursorPositions: + The positions of the cursors on the screen, in pixels + ''' + + + + def __init__(self, instrumentChoice, song, cascade=False, extendedScale=False, easyMode = False, alwaysDown = False, eventLog = None, replay = None, defaultInstrumentChannel = 16, defaultNote = 60): + ''' + Constructor + ''' + self.songDurations = [] + self.totalDuration = None + self.clicks = [0] + self.clicksIn = [0] + self.clicksPerMinute = [0] + self.clicksInPerMinute = [0] + self.meanTimeBetweenNotes = [] + self.firstClick = None + self.firstClickIn = None + + self.blinkLength = 200 + self.minimalVelocity = 90 + self.shortScaleSize = 8 + self.longScaleSize = 11 + if not extendedScale: + self.offset = self.longScaleSize - self.shortScaleSize + else: + self.offset = 0 + self.borderSize = 5 + self.highlightedNote = 0 + self.highlightedNoteNumber = 0 + self.syllabus = None + self.savedHighlightedNote = 0 + self.alwaysDown = alwaysDown + self.nextLevel = None + + self.wiimotes = instrumentChoice.wiimotes + self.activeWiimotes = instrumentChoice.activeWiimotes + self.window = instrumentChoice.window + self.screen = instrumentChoice.screen + self.blitOrigin = instrumentChoice.blitOrigin + self.clock = instrumentChoice.clock + self.width = instrumentChoice.width + self.height = instrumentChoice.height + self.cursorPositions = instrumentChoice.cursorPositions + self.savedScreen = instrumentChoice.savedScreen + self.playerScreen = instrumentChoice.playerScreen + self.extendedScale = extendedScale + self.cascade = cascade + self.joys = instrumentChoice.joys + self.portOffset = instrumentChoice.portOffset + if eventLog == None : + self.eventLog = instrumentChoice.eventLog + else : + self.eventLog = eventLog + self.cursorPositions = instrumentChoice.cursorPositions + self.song = song + self.songIterator = self.song.getSongIterator() + self.midiNoteNumbers = self.song.scale + if replay == None : + self.replay = instrumentChoice.replay + else : + self.replay = replay + self.quarterNoteLength = song.quarterNoteLength + self.cascadeLockLengthMultiplier = 1 + self.nextCascadeLockLengthMultiplier = 1 + self.cascadeLockLength = self.quarterNoteLength * self.cascadeLockLengthMultiplier + + self.defaultInstrumentChannel = defaultInstrumentChannel + self.defaultNote = defaultNote + + self.done = False + self.backToInstrumentChoice = False + self.easyMode = easyMode + + #Initializes the highlightedNote and highlightedNoteNumber etc... + self.moveToNextNote() + self.cascadeLockLengthMultiplier = self.nextCascadeLockLengthMultiplier + + self.blinkOn = False + self.savedBlinkOn = False + ##Will prevent the song to move on if two consecutive notes are identical and the buttons have not been released in between the two + ##i.e. it guarantees that there will be an attack between two identical consecutive notes + self.highlightIsFree = True + + self.noteRects = [] + self.boundingRect = None + self.notes = [] + + self.buttonDown = [] + self.velocityLock = [] + + self._blinkOffset = 0 + self._cascadeLockTimer = 0 + self.cascadeIsFree = True + + self.font = pygame.font.Font(None,80) + self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers] + + self.drawBackground() + self.initializeWiimotes() + + self.songStartTime = self.eventLog.getCurrentTime() + + #The main loop + while not self.done : + + #Clear the cursors from the screen + if self.hasChanged(): + self.drawBackground() + self.playerScreen.blit(self.savedScreen, (0, 0)) + + # Limit frame speed to 50 FPS + # + timePassed = self.clock.tick(10000) + + self._blinkOffset += timePassed + if (self.buttonDown or self.alwaysDown) and not self.cascadeIsFree : + self._cascadeLockTimer += timePassed + if self._cascadeLockTimer > self.cascadeLockLengthMultiplier*self.quarterNoteLength : + self.cascadeIsFree = True + self.cascadeLockLengthMultiplier = self.nextCascadeLockLengthMultiplier + + + if self._blinkOffset > self.blinkLength: + self._blinkOffset -= self.blinkLength + self.blinkOn = not self.blinkOn + + if self.replay: + self.eventLog.update(timePassed) + pickledEventsToPost = self.eventLog.getPickledEvents() + for pickledEvent in pickledEventsToPost: + pygame.event.post(pickledEvent.event) + + events = pygame.event.get() + + if not self.replay: + pickledEvents = [PickleableEvent(event.type,event.dict) for event in events] + if pickledEvents != [] : + self.eventLog.appendEventGroup(pickledEvents) + + for event in events: + self.input(event) + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i]) + if self.buttonDown[i] or self.alwaysDown: + self.wiimotes[i].cursor.flash() + self.wiimotes[i].cursor.blit(self.playerScreen) + + self.screen.blit(self.playerScreen, (0,0)) + + pygame.display.flip() + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].stopNoteByNoteNumber(self.midiNoteNumbers[self.notes[i]]) + if self.replay : + self.totalDuration = self.eventLog.getCurrentTime() + + def drawBackground(self): + self.savedScreen.fill((255,255,255)) + + if self.extendedScale : + self.scaleSize = self.longScaleSize + else: + self.scaleSize = self.shortScaleSize + + 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)] + #inflate last noteRect to cover the far right pixels + self.noteRects[-1].width = self.noteRects[-1].width + 1 + + self.noteRects[self.highlightedNote-self.offset].inflate_ip(self.noteRects[self.highlightedNote-self.offset].width*2,0) + + #create bounding rect + self.boundingRect = self.noteRects[0].unionall(self.noteRects) + + self.renderedNoteNames = [self.font.render(constants.noteNumberToName(note),False,(0,0,0)) for note in self.midiNoteNumbers] + + #fill the rectangles with a color gradient + #We start with blue + startingHue = 0.66666666666666663 + + for rectNumber in range(self.scaleSize): + colorRatio = float(rectNumber) / (self.scaleSize - 1) + #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up + hue = startingHue * (1 - colorRatio) + if rectNumber + self.offset != self.highlightedNote: + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.1, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.1, 1) + + #convert to rgb ranging from 0 to 255 + bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)] + topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)] + #add transparency + bottomColorRgb.append(255) + topColorRgb.append(255) + #convert to tuple + bottomColorRgb = tuple(bottomColorRgb) + topColorRgb = tuple(topColorRgb) + + self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber]) + + noteNameBlitPoint = (self.noteRects[rectNumber].left+(self.noteRects[rectNumber].width-self.renderedNoteNames[rectNumber+self.offset].get_width())/2, + self.noteRects[rectNumber].bottom-self.renderedNoteNames[rectNumber+self.offset].get_height()) + + self.savedScreen.blit(self.renderedNoteNames[rectNumber+self.offset], noteNameBlitPoint) + + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2) + + colorRatio = float(self.highlightedNote-self.offset) / (self.scaleSize - 1) + #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up + hue = startingHue * (1 - colorRatio) + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.6, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.9, 1) + + #convert to rgb ranging from 0 to 255 + bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)] + topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)] + #add transparency + bottomColorRgb.append(255) + topColorRgb.append(255) + #convert to tuple + bottomColorRgb = tuple(bottomColorRgb) + topColorRgb = tuple(topColorRgb) + + self.savedScreen.blit(gradients.vertical(self.noteRects[self.highlightedNote-self.offset].size, topColorRgb, bottomColorRgb), self.noteRects[self.highlightedNote-self.offset]) + + noteNameBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-self.renderedNoteNames[self.highlightedNote].get_width())/2, + self.noteRects[self.highlightedNote-self.offset].bottom-self.renderedNoteNames[self.highlightedNote].get_height()) + + self.savedScreen.blit(self.renderedNoteNames[self.highlightedNote], noteNameBlitPoint) + + if self.syllabus : + renderedSyllabus = self.font.render(self.syllabus,False,(0,0,0)) + + syllabusBlitPoint = (self.noteRects[self.highlightedNote-self.offset].left+(self.noteRects[self.highlightedNote-self.offset].width-renderedSyllabus.get_width())/2, + self.noteRects[self.highlightedNote-self.offset].centery-renderedSyllabus.get_height()/2) + + self.savedScreen.blit(renderedSyllabus, syllabusBlitPoint) + + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[self.highlightedNote-self.offset], 2) + + if self.song != None and self.blinkOn: + borderSize = self.borderSize + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 0), self.noteRects[self.highlightedNote-self.offset].inflate(borderSize/2,borderSize/2), borderSize) + + def initializeWiimotes(self): + for loop in self.wiimotes: + if loop.port == None : + loop.port = pygame.midi.Output(loop.portNumber) + self.notes.append(0) + self.buttonDown.append(False) + self.velocityLock.append(False) + + def updateCursorPositionFromJoy(self, joyEvent): + joyName = pygame.joystick.Joystick(joyEvent.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if correctedJoyId < len(self.cursorPositions): + if joyEvent.axis == 0 : + self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1]) + if joyEvent.axis == 1 : + self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height())) + + def heightToVelocity(self, pos, controllerNumber): + if self.song != None: + if self.boundingRect.collidepoint(pos) and (self.highlightedNote == self.notes[controllerNumber] or self.velocityLock[controllerNumber]): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + else : + if self.easyMode: + velocity = None + else: + velocity = 60 + else: + if self.boundingRect.collidepoint(pos): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + else : + velocity = self.minimalVelocity + return(velocity) + + def widthToNote(self, pos): + nn = 0 + try : + if self.noteRects[self.highlightedNote-self.offset].collidepoint(pos) : + return self.highlightedNote + else : + while self.noteRects[nn].collidepoint(pos) == False: + nn = nn + 1 + return(nn + self.offset) + except(IndexError): + return(None) + + def logClick(self): + self.clicks[-1] += 1 + if self.firstClick == None : + self.firstClick = self.eventLog.getCurrentTime() + minute = int(floor((self.eventLog.getCurrentTime()-self.songStartTime)/60000)) + if minute > len(self.clicksPerMinute)-1: + self.clicksPerMinute.append(0) + self.clicksPerMinute[-1] += 1 + + def logClickIn(self): + self.clicksIn[-1] += 1 + if self.clicksIn[-1] > len(self.song.notes)-1 : + self.clicksIn.append(0) + self.clicks.append(0) + self.songDurations.append(self.eventLog.getCurrentTime()) + if self.firstClickIn == None : + self.firstClickIn = self.eventLog.getCurrentTime() + minute = int(floor((self.eventLog.getCurrentTime()-self.songStartTime)/60000)) + if minute > len(self.clicksInPerMinute)-1: + self.clicksInPerMinute.append(0) + self.clicksInPerMinute[-1]+=1 + + def input(self, event): + + if event.type == pygame.QUIT: + for loop in self.wiimotes: + del loop.port + pygame.midi.quit() + sys.exit(0) + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + self.nextLevel = None + self.done = True + + if event.key == pygame.K_i: + self.backToInstrumentChoice = True + self.done = True + + if event.key == pygame.K_w: + self.nextLevel = 0 + self.done = True + + if event.key == pygame.K_e: + self.nextLevel = 1 + self.done = True + + if event.key == pygame.K_r: + self.nextLevel = 2 + self.done = True + + if event.key == pygame.K_t: + self.nextLevel = 3 + self.done = True + + if event.type == pygame.JOYAXISMOTION: + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.updateCursorPositionFromJoy(event) + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if (self.buttonDown[correctedJoyId] or self.alwaysDown): + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None : + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + if self.cascade and self.cascadeIsFree : + n = self.widthToNote(pos) + if self.highlightedNote == n: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.moveToNextNote() + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + + if event.type == pygame.JOYBUTTONDOWN : + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + self.wiimotes[correctedJoyId].cursor.flash() + if self.replay: + self.logClick() + + if not (self.buttonDown[correctedJoyId] or self.alwaysDown): + n = self.widthToNote(pos) + if self.highlightedNote == n: + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + if self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + if self.replay : + self.logClickIn() + self.moveToNextNote() + else : + if not self.easyMode : + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None and self.notes[correctedJoyId] != None : + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.buttonDown[correctedJoyId] = True + + if event.type == pygame.JOYBUTTONUP: + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.buttonDown[correctedJoyId] = False + wiimote = self.wiimotes[correctedJoyId] + if not self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.velocityLock[correctedJoyId] = False + + if event.type == pygame.MOUSEMOTION: + + self.updateCursorPositionFromMouse(event) + + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if (self.buttonDown[correctedJoyId] or self.alwaysDown): + self.wiimotes[correctedJoyId].cursor.flash() + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None : + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + if self.cascade and self.cascadeIsFree : + n = self.widthToNote(pos) + if self.highlightedNote == n: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.moveToNextNote() + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + + if event.type == pygame.MOUSEBUTTONDOWN: + + if event.button == 1: + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + self.wiimotes[correctedJoyId].cursor.flash() + if self.replay: + self.logClick() + + if not (self.buttonDown[correctedJoyId] or self.alwaysDown): + n = self.widthToNote(pos) + if self.highlightedNote == n: + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + if self.easyMode: + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + self.velocityLock[correctedJoyId] = True + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + if self.replay : + self.logClickIn() + self.moveToNextNote() + else : + if not self.easyMode : + self._cascadeLockTimer = 0 + self.cascadeIsFree = False + self.notes[correctedJoyId] = n + velocity = self.heightToVelocity(pos, correctedJoyId) + if velocity != None and self.notes[correctedJoyId] != None : + wiimote.playNoteByNoteNumber(self.midiNoteNumbers[self.notes[correctedJoyId]],velocity) + self.buttonDown[correctedJoyId] = True + + if event.button == 2: + + self.done = True + + if event.type == pygame.MOUSEBUTTONUP: + if event.button == 1 : + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + self.buttonDown[correctedJoyId] = False + if not self.easyMode: + if self.notes[correctedJoyId] != None : + wiimote.stopNoteByNoteNumber(self.savedMidiNoteNumbers[self.notes[correctedJoyId]]) + self.velocityLock[correctedJoyId] = False + + def hasChanged(self): + changed = False + if self.song != None: + if self.blinkOn != self.savedBlinkOn or self.highlightedNote != self.savedHighlightedNote: + self.savedBlinkOn = self.blinkOn + self.savedHighlightedNote = self.highlightedNote + changed = True + return(changed) + + def updateCursorPositionFromMouse(self, mouseEvent): + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + self.cursorPositions[correctedJoyId] = mouseEvent.pos + + def moveToNextNote(self): + self.savedMidiNoteNumbers = self.midiNoteNumbers[:] + self.highlightedNote, self.highlightedNoteNumber, self.syllabus, self.nextCascadeLockLengthMultiplier = self.songIterator.next() + self.midiNoteNumbers[self.highlightedNote] = self.highlightedNoteNumber diff --git a/src/gui/StaticFamiliarizer.py b/src/gui/StaticFamiliarizer.py new file mode 100644 index 0000000..0e095c8 --- /dev/null +++ b/src/gui/StaticFamiliarizer.py @@ -0,0 +1,457 @@ +''' +Created on 23 juil. 2009 + +@author: Samuel Benveniste +''' +from math import floor, ceil +import pygame +import pygame.midi +import sys +import colorsys +import constants +from gradients import gradients +from logging.PickleableEvent import PickleableEvent +from instruments.Instrument import Instrument +from cursor.WarpingCursor import * +from controllers.Wiimote import Wiimote +from logging.EventLog import EventLog + + +class StaticFamiliarizer: + ''' + The screen on which the game is played + + wiimotes: + The wiimotes used in this session + window: + The main display window + screen: + The main display surface + clock: + The clock used to animate the screen + savedScreen: + The background that is painted every time + playerScreen: + The buffer for painting everything before bliting + width: + The width of the window in pixels + height: + The height of the window in pixels + extendScale : + True if the scale is G to C instead of C to C + cascade: + True if crossing from note to note with a button pressed triggers a new note + scaleSize: + The size of the scale used + cursorPositions: + The positions of the cursors on the screen, in pixels + ''' + + + + def __init__(self, wiimotes, window, screen, clock, joys, portOffset,activeWiimotes,replay = False, level = 0, defaultInstrumentChannel = 16, defaultNote = 60, eventLog = None): + ''' + Constructor + ''' + self.firstClickTime = None + self.firstClickInTime = None + self.duration = None + self.clicks = 0 + self.clicksIn = 0 + + pygame.font.init() + self.font = pygame.font.Font(None,60) + self.congratulations = ["Bien !","Tres Bien !","Bravo !","Excellent !","Felicitations !"] + self.renderedCongratulations = [self.font.render(congratulation,False,(0,0,0)) for congratulation in self.congratulations] + self.congratulationCount = None + self.isCongratulating = False + self.congratulationTimer = 0 + self.congratulationLength = 2000 + self.congratulationPos = None + + self.blinkLength = 200 + self.minimalVelocity = 64 + self.shortScaleSize = 8 + self.longScaleSize = 11 + self.borderSize = 5 + self.savedHighlightedNote = 0 + self.scaleFactor = 1 + self.wiimotes = wiimotes + self.window = window + self.screen = screen + self.clock = clock + self.width = int(floor(screen.get_width()*self.scaleFactor)) + self.height = int(floor(screen.get_height()*self.scaleFactor)) + self.blitOrigin = ((self.screen.get_width()-self.width)/2,(self.screen.get_height()-self.height)/2) + self.joys = joys + self.portOffset = portOffset + self.savedScreen = pygame.Surface(self.screen.get_size()) + self.savedScreen.fill((255,255,255)) + self.playerScreen = pygame.Surface(self.savedScreen.get_size()) + self.playerScreen.blit(self.savedScreen, (0, 0)) + self.cursorPositions = [] + self.level = level + self.nextLevel = None + self.activeWiimotes = activeWiimotes + + for i in range(len(self.wiimotes)): + #Set the screen for the cursors (it can't be set before) + self.wiimotes[i].cursor.screen = self.playerScreen + self.cursorPositions.append(self.wiimotes[i].cursor.centerPosition) + + if eventLog == None: + self.eventLog = EventLog() + self.replay = False + else: + self.eventLog = eventLog + self.replay = replay + + self.defaultInstrumentChannel = defaultInstrumentChannel + self.defaultNote = defaultNote + + self.done = False + self.backToInstrumentChoice = False + self.easyMode = False + + self.noteRects = [] + self.boundingRect = None + self.notes = [] + self.buttonDown = [] + self.velocityLock = [] + + self.drawBackground() + self.initializeWiimotes() + events = pygame.event.get() + + #The main loop + while not self.done : + + self.playerScreen.blit(self.savedScreen, (0, 0)) + + # Limit frame speed to 50 FPS + # + timePassed = self.clock.tick(10000) + + if self.replay: + self.eventLog.update(timePassed) + pickledEventsToPost = self.eventLog.getPickledEvents() + for pickledEvent in pickledEventsToPost: + pygame.event.post(pickledEvent.event) + + events = pygame.event.get() + + if not self.replay: + pickledEvents = [PickleableEvent(event.type,event.dict) for event in events] + if pickledEvents != [] : + self.eventLog.appendEventGroup(pickledEvents) + + for event in events: + self.input(event) + + if self.isCongratulating : + self.congratulationTimer += timePassed + if self.congratulationTimer < self.congratulationLength : + self.blitCongratulation() + else : + self.isCongratulating = False + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + self.wiimotes[i].cursor.update(timePassed, self.cursorPositions[i]) + if self.buttonDown[i] : + self.wiimotes[i].cursor.flash() + self.wiimotes[i].cursor.blit(self.playerScreen) + + self.screen.blit(self.playerScreen, (0,0)) + + pygame.display.flip() + + for i in range(len(self.wiimotes)): + if self.activeWiimotes[i]: + if self.notes[i] != None : + self.wiimotes[i].stopNote(self.notes[i]) + if self.replay : + self.duration = self.eventLog.getCurrentTime() + + def drawBackground(self): + self.savedScreen.fill((255,255,255)) + if self.level == 0 : + A = [4] + else : + A = [1,7] + + 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] + + #create bounding rect + self.boundingRect = self.noteRects[0].unionall(self.noteRects) + + #fill the rectangles with a color gradient + #We start with blue + startingHue = 0.66666666666666663 + + for rectNumber in range(len(self.noteRects)) : + colorRatio = float(A[rectNumber]) / (11 - 1) + #hue will go from 0.6666... (blue) to 0 (red) as colorRation goes up + hue = startingHue * (1 - colorRatio) + #The color of the bottom of the rectangle in hls coordinates + bottomColorHls = (hue, 0.6, 1) + #The color of the top of the rectangle in hls coordinates + topColorHls = (hue, 0.9, 1) + + #convert to rgb ranging from 0 to 255 + bottomColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*bottomColorHls)] + topColorRgb = [floor(255 * i) for i in colorsys.hls_to_rgb(*topColorHls)] + #add transparency + bottomColorRgb.append(255) + topColorRgb.append(255) + #convert to tuple + bottomColorRgb = tuple(bottomColorRgb) + topColorRgb = tuple(topColorRgb) + + self.savedScreen.blit(gradients.vertical(self.noteRects[rectNumber].size, topColorRgb, bottomColorRgb), self.noteRects[rectNumber]) + + pygame.draw.rect(self.savedScreen, pygame.Color(0, 0, 0, 255), self.noteRects[rectNumber], 2) + + def initializeWiimotes(self): + for loop in self.wiimotes: + if loop.port == None : + loop.port = pygame.midi.Output(loop.portNumber) + self.notes.append(0) + self.buttonDown.append(False) + self.velocityLock.append(False) + + def updateCursorPositionFromJoy(self, joyEvent): + joyName = pygame.joystick.Joystick(joyEvent.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if correctedJoyId < len(self.cursorPositions): + if joyEvent.axis == 0 : + self.cursorPositions[correctedJoyId] = (int((joyEvent.value + 1) / 2 * self.screen.get_width()), self.cursorPositions[correctedJoyId][1]) + if joyEvent.axis == 1 : + self.cursorPositions[correctedJoyId] = (self.cursorPositions[correctedJoyId][0], int((joyEvent.value + 1) / 2 * self.screen.get_height())) + + def heightToVelocity(self, pos, controllerNumber): + velocity = int(floor((1 - (float(pos[1])-self.blitOrigin[1]) / self.height) * (127-self.minimalVelocity))+self.minimalVelocity) + return(velocity) + + def widthToNote(self, pos): + nn = 0 + try : + while self.noteRects[nn].collidepoint(pos) == False: + nn = nn + 1 + return(nn) + except(IndexError): + return(None) + + def congratulate(self,targetRect,posy): + if self.congratulationCount != None : + if self.congratulationCount < len(self.congratulations)-1: + self.congratulationCount += 1 + else : + self.congratulationCount = 0 + self.congratulationTimer = 0 + self.congratulationPos = (targetRect.left+(targetRect.width-self.renderedCongratulations[self.congratulationCount].get_width())/2,posy) + self.isCongratulating = True + + def resetCongratulation(self): + self.congratulationCount = None + self.congratulationPos = None + self.isCongratulating = False + + def blitCongratulation(self): + self.playerScreen.blit(self.renderedCongratulations[self.congratulationCount],self.congratulationPos) + + def input(self, event): + + print event + + if event.type == pygame.QUIT: + for loop in self.wiimotes: + del loop.port + pygame.midi.quit() + sys.exit(0) + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_q: + self.nextLevel = None + self.done = True + + if event.key == pygame.K_w: + self.nextLevel = 0 + self.done = True + + if event.key == pygame.K_e: + self.nextLevel = 1 + self.done = True + + if event.key == pygame.K_r: + self.nextLevel = 2 + self.done = True + + if event.key == pygame.K_t: + self.nextLevel = 3 + self.done = True + + if event.type == pygame.JOYAXISMOTION: + + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + self.updateCursorPositionFromJoy(event) + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if self.buttonDown[correctedJoyId]: + wiimote.cursor.flash() + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + + if event.type == pygame.JOYBUTTONDOWN : + + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + wiimote.cursor.flash() + if self.replay : + self.clicks += 1 + if self.firstClickTime == None : + self.firstClickTime = self.eventLog.getCurrentTime() + + if not self.buttonDown[correctedJoyId]: + self.notes[correctedJoyId] = self.widthToNote(pos) + + velocity = self.heightToVelocity(pos, correctedJoyId) + + if self.notes[correctedJoyId] != None : + wiimote.playNote(self.notes[correctedJoyId],velocity) + self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1]) + if self.replay : + self.clicksIn += 1 + if self.firstClickInTime == None : + self.firstClickInTime = self.eventLog.getCurrentTime() + else : + self.resetCongratulation() + + self.buttonDown[correctedJoyId] = True + + if event.type == pygame.JOYBUTTONUP: + joyName = pygame.joystick.Joystick(event.joy).get_name() + correctedJoyId = constants.joyNames.index(joyName) + if self.activeWiimotes[correctedJoyId]: + wiimote = self.wiimotes[correctedJoyId] + wiimote.stopNote(self.notes[correctedJoyId]) + self.buttonDown[correctedJoyId] = False + self.velocityLock[correctedJoyId] = False + + if event.type == pygame.MOUSEMOTION: + + self.updateCursorPositionFromMouse(event) + + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + + if self.buttonDown[correctedJoyId]: + wiimote.cursor.flash() + if self.notes[correctedJoyId] != None: + velocity = self.heightToVelocity(pos, correctedJoyId) + CCHexCode = wiimote.getCCHexCode() + wiimote.port.write_short(CCHexCode, 07, velocity) + + if event.type == pygame.MOUSEBUTTONDOWN: + + if event.button == 1: + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + pos = self.cursorPositions[correctedJoyId] + wiimote.cursor.flash() + if self.replay : + self.clicks += 1 + if self.firstClickTime == None : + self.firstClickTime = self.eventLog.getCurrentTime() + + if not self.buttonDown[correctedJoyId]: + self.notes[correctedJoyId] = self.widthToNote(pos) + + velocity = self.heightToVelocity(pos, correctedJoyId) + + if self.notes[correctedJoyId] != None : + wiimote.playNote(self.notes[correctedJoyId],velocity) + self.congratulate(self.noteRects[self.notes[correctedJoyId]],pos[1]) + if self.replay : + self.clicksIn += 1 + if self.firstClickInTime == None : + self.firstClickInTime = self.eventLog.getCurrentTime() + else : + self.resetCongratulation() + + self.buttonDown[correctedJoyId] = True + + if event.button == 2: + + self.done = True + + if event.type == pygame.MOUSEBUTTONUP: + + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + wiimote = self.wiimotes[correctedJoyId] + wiimote.stopNote(self.notes[correctedJoyId]) + self.buttonDown[correctedJoyId] = False + self.velocityLock[correctedJoyId] = False + + def hasChanged(self): + return(True) + + def updateCursorPositionFromMouse(self, mouseEvent): + correctedJoyId = 0 + while not self.activeWiimotes[correctedJoyId] : + correctedJoyId += 1 + self.cursorPositions[correctedJoyId] = mouseEvent.pos + +if __name__ == "__main__" : + pygame.init() + modeResolution = (1024,768) + window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN) + pygame.font.init() + + pygame.midi.init() + instruments = [Instrument(constants.scaleDict["majorScale"], i + 1, "".join(["../instruments/instrumentImages/", constants.instrumentImagePathList[i], ".jpg"]), constants.octaves[i]) for i in range(9)] + + joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())] + for joy in joys: + if joy[1] in constants.joyNames: + pygame.joystick.Joystick(joy[0]).init() + + ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())] + portOffset = ports.index(constants.portNames[0]) + print(portOffset) + + events = pygame.event.get() + + screen = pygame.display.get_surface() + clock = pygame.time.Clock() + cursorImages = [createImageListFromPath('../cursor/cursorImages/black', 11),createImageListFromPath('../cursor/cursorImages/red', 11)] + durations = [75 for i in range(len(cursorImages[0]))] + + wiimoteCount = 1 + cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),flashImage = '../cursor/cursorImages/black/flash.png' ) for i in range(wiimoteCount)] + wiimotes = [Wiimote(i, i + portOffset, None, instruments[i], cursors[i]) for i in range(wiimoteCount)] + + fam = StaticFamiliarizer(instruments, wiimotes, window, screen, clock, joys, portOffset) + + for loop in fam.wiimotes: + del loop.port + + pygame.midi.quit() + + pygame.quit() diff --git a/src/gui/__init__.py b/src/gui/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/gui/constants.py b/src/gui/constants.py new file mode 100755 index 0000000..df13903 --- /dev/null +++ b/src/gui/constants.py @@ -0,0 +1,130 @@ +from songs.Song import Song,loadSongFromMidi +from dataTools.odict import OrderedDict + +joyNames = ["PPJoy Virtual joystick 1", "PPJoy Virtual joystick 2", "PPJoy Virtual joystick 3", "PPJoy Virtual joystick 4"] +portNames = ["Out To MIDI Yoke: 1","Out To MIDI Yoke: 2","Out To MIDI Yoke: 3","Out To MIDI Yoke: 4"] + +readabilityDict = OrderedDict([("majeure" , "majorScale"), + ("mineure naturelle" , "minorScale"), + ("majeure myxolydienne" , "myxolydianScale"), + ("mineure dorienne" , "dorianScale"), + ("phrygienne espagnole" , "spanishPhrygianScale"), + ("lydienne" , "lydianScale"), + ("phrygienne" , "phrygianScale"), + ("J'ai du bon tabac" , "jadbt"), + ("L'eau vive" , "eauvive"), + ("Le penitencier" , "penitencier"), + ("La foule" , "foule"), + ("Petit papa noel" , "papanoel"), + ("La marseillaise" , "marseillaise"), + ("A la claire fontaine" , "clairefontaine"), + ("Au clair de la lune" , "clairdelalune"), + ("Frere jacques" , "frerejacques"), + ("Le petit vin blanc" , "vinblanc"), + ("La vie en rose","vierose"), + ("Les feuilles mortes","feuillesmortes"), + ("Il pleut bergere","bergere"), + ("Le temps des cerises","cerises"), + ("La boheme","boheme"), + ("Chanson Test","test"), + ("Improvisation" , "none"), + ("Do/Do","C/C"), + ("Sol/Do","G/C"), + ("Oui","Yes"), + ("Non","No"), + ("Tres facile","veryEasy"), + ("Facile","easy"), + ("Normal","normal"), + ("Expert","expert")]) + +reversedReadabilityDict = dict(zip(readabilityDict.values(),readabilityDict.keys())) + +rangeDict = {"C/C":False,"G/C":True} + +cascadeDict = {"Yes":True,"No":False} + +modeDict = OrderedDict([("veryEasy",0),("easy",1),("normal",2),("expert",3)]) +print modeDict['veryEasy'] + +scaleDict = OrderedDict ([("majorScale" , [55, 57, 59, 60, 62, 64, 65, 67, 69, 71, 72]), + ("minorScale" , [55, 56, 58, 60, 62, 63, 65, 67, 68, 70, 72]), + ("myxolydianScale" , [55, 57, 58, 60, 62, 64, 65, 67, 69, 70, 72]), + ("dorianScale" , [55, 57, 58, 60, 62, 63, 65, 67, 69, 70, 72]), + ("spanishPhrygianScale" , [55,57,58,60,62,63,66,67,69,70,72]), + ("lydianScale" , [55, 57, 59, 60, 62, 64, 66, 67, 69, 71, 72]), + ("phrygianScale" , [55, 56, 58, 60, 61, 63, 65, 67, 68, 70, 72])]) + +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, + 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"], + 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], + quarterNoteLength = 400)), + ("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], + True, + 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"], + 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], + quarterNoteLength = 300)), + ("penitencier" , + 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], + True, + alterationIndexes = [-2,-3], + alterations = [1,1], + 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"], + 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], + quarterNoteLength = 250)), + ("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, + 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"], + 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], + quarterNoteLength = 500)), + ("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"]], + 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"], + 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], + quarterNoteLength = 400)), + ("clairefontaine" , Song(scaleDict["majorScale"], + [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], + False, + 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"], + 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], + quarterNoteLength = 400)), + ("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, + 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"], + 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], + quarterNoteLength = 500)), + ("frerejacques" , + Song(scaleDict["majorScale"], + [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], + True, + 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"], + 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], + quarterNoteLength = 600)), + ("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"]], + 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"], + 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], + quarterNoteLength = 300)), + ("vinblanc" , + 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], + False, + 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"], + 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], + quarterNoteLength = 300)), + ("none" , None)]) + +instrumentImagePathList = ["piano", "guitare", "accordeon", "violon", "flute", "tuba", "orgue", "violoncelle", "celesta"] +octaves = [0, -1, 0, 1, 1, -2, 0, -1, 0] + +defaultInstrumentChannel = 16 +defaultInstrumentNote = 60 +defaultCCHexCode = 0xB0+defaultInstrumentChannel - 1 +defaultNoteOnHexCode = 0x90+defaultInstrumentChannel - 1 + +songScaleFactor = 0.99 + +fileName = "../../../saves/22-01-2009-coll1-v65-" + +def noteNumberToName(noteNumber): + names = ["Do","Do#","R\xe9","Mib","Mi","Fa","Fa#","Sol","Sol#","La","Sib","Si"] + return(names[noteNumber%12]) + +if __name__ == "__main__": + key = "papanoel" + if songDict[key] != None : + songDict[key].save("../songs/smwis/"+str(key)+".smwi") diff --git a/src/instruments/Instrument.py b/src/instruments/Instrument.py new file mode 100755 index 0000000..fb54d3a --- /dev/null +++ b/src/instruments/Instrument.py @@ -0,0 +1,46 @@ +''' +Created on 15 juil. 2009 + +@author: Samuel Benveniste +''' + +class Instrument: + ''' + Object representing an instrument. + + notes: + The MIDI numbers of the notes played by this instrument (usually a scale) + channel: + The channel corresponding to the instrument in the synthesizer + image: + The image for the instrument + ''' + + def __init__(self, notes, channel, image, octave = 0): + ''' + Constructor + + notes: + The MIDI numbers of the notes played by this instrument (usually a scale) + channel: + The channel corresponding to the instrument in the synthesizer + image: + The image for the instrument + ''' + + self.notes = [loop+12*octave for loop in notes] + self.octave = octave + self.channel = channel + self.image = image + + def getNote(self,noteNumber): + if noteNumber == None : + return(None) + else : + return(self.notes[noteNumber]) + + def getNoteByNoteNumber(self,baseMidiNoteNumber): + if baseMidiNoteNumber == None: + return(None) + else : + 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 index 0000000..e69de29 diff --git a/src/instruments/instrumentImages/accordeon.jpg b/src/instruments/instrumentImages/accordeon.jpg new file mode 100755 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 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 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 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 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 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 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 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 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 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 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 index 0000000..e69de29 diff --git a/src/logging/EventLog.py b/src/logging/EventLog.py new file mode 100755 index 0000000..3e11f25 --- /dev/null +++ b/src/logging/EventLog.py @@ -0,0 +1,69 @@ +''' +Created on 21 aout 2009 + +@author: Samuel Benveniste +''' +import time + +class EventLog(): + ''' + classdocs + ''' + + def __init__(self,eventGroups = [], times = []): + ''' + Constructor + ''' + self.eventGroups = eventGroups + self.times = times + self.rate = 1 + self.yielder = self.eventGroupYielder() + self.yieldPointer = 0 + + self._timeCounter = 0 + + def __getstate__(self): + d = [] + d.append(self.eventGroups) + d.append(self.times) + return d + + def __setstate__(self,d): + self.eventGroups = d[0] + self.times = d[1] + self.yielder = self.eventGroupYielder() + + self._timeCounter = 0 + + def appendEventGroup(self, eventGroup): + t = time.clock()*1000 + self.times.append(t) + self.eventGroups.append(eventGroup) + + def update(self,timePassed): + self._timeCounter += timePassed + + def setReplayRate(self,rate): + self.rate = rate + self.yielder = self.eventGroupYielder() + + def getPickledEvents(self): + return(self.yielder.next()) + + def getCurrentTime(self): + return(self.times[self.yieldPointer]) + + def eventGroupYielder(self): + ''' + Will return the next event to post if enough time has passed and [] otherwise + ''' + i = 0 + while i in range(len(self.eventGroups)): + print "rate is " + str(self.rate) + if self._timeCounter*self.rate>self.times[i]: + print str(self._timeCounter*self.rate)+" > "+ str(self.times[i]) + self.yieldPointer = i + yield self.eventGroups[i] + i += 1 + else: + yield [] diff --git a/src/logging/FamiliarizerLog.py b/src/logging/FamiliarizerLog.py new file mode 100644 index 0000000..e84d1c2 --- /dev/null +++ b/src/logging/FamiliarizerLog.py @@ -0,0 +1,100 @@ +''' +Created on 28 aout 2009 + +@author: Samuel Benveniste +''' + +import os +import sys +import subprocess +import re + +import pygame +import pygame.midi +import pickle + +from gui.constants import * + +from gui.PlayingScreen import PlayingScreen +from gui.InstrumentChoice import InstrumentChoice +from instruments.Instrument import Instrument +from cursor.WarpingCursor import * +from controllers.Wiimote import Wiimote +from songs.Song import Song +from gui.StaticFamiliarizer import StaticFamiliarizer +from gui.SongFamiliarizer import SongFamiliarizer +from gui.SongPlayingScreen import SongPlayingScreen +from gui.DummyInstrumentChoice import DummyInstrumentChoice + +class FamiliarizerLog(): + ''' + classdocs + ''' + + def __init__(self,eventLog,level,activeWiimotes): + ''' + Constructor + ''' + self.eventLog = eventLog + self.level = level + self.activeWiimotes = activeWiimotes + self.scale = scaleDict["majorScale"] + +if __name__ == '__main__': + + f = file('../../../saves/19-01-2009-testcoll1-v65-1.fmwi', 'r') + unpickler = pickle.Unpickler(f) + log = unpickler.load() + f.close() + + pygame.init() + modeResolution = (1024,768) + window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN) + pygame.midi.init() + instruments = [Instrument(log.scale, i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)] + + joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())] + for joy in joys: + if joy[1] in joyNames: + pygame.joystick.Joystick(joy[0]).init() + + ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())] + portOffset = ports.index(portNames[0]) + print(portOffset) + + screen = pygame.display.get_surface() + clock = pygame.time.Clock() + cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']] + durations = [75 for i in range(len(cursorImages))] + + extsc = True + casc = False + easyMode = True + + song = Song(scaleDict["majorScale"],[3,9,6,4,1,8,5,7,2,10],True) + + wiimoteCount = 4 + + cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),'../cursor/cursorImages/black/flash.png') for i in range(wiimoteCount)] + wiimotes = [Wiimote(i, i + portOffset, None, instruments[0], cursors[i]) for i in range(wiimoteCount)] + dummyInstrumentChoice = DummyInstrumentChoice(wiimotes, window, screen, clock, joys, portOffset, log.activeWiimotes) + if log.level < 2 : + familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = log.level,eventLog = log.eventLog,replay = True) + elif log.level == 2 : + familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = log.eventLog,replay = True) + else : + familiarize = SongPlayingScreen(dummyInstrumentChoice,songDict["clairdelalune"],easyMode = True,eventLog = log.eventLog,replay = True) + + while familiarize.nextLevel != None : + if familiarize.nextLevel < 2 : + familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = familiarize.nextLevel,eventLog = familiarize.eventLog,replay = True) + elif familiarize.nextLevel == 2 : + familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = familiarize.eventLog,replay = True) + else : + familiarize = SongPlayingScreen(dummyInstrumentChoice,songDict["clairdelalune"],easyMode = True,eventLog = familiarize.eventLog,replay = True) + + for wiimote in wiimotes: + del wiimote.port + + pygame.midi.quit() + pygame.quit() \ No newline at end of file diff --git a/src/logging/Log.py b/src/logging/Log.py new file mode 100755 index 0000000..4aef88b --- /dev/null +++ b/src/logging/Log.py @@ -0,0 +1,135 @@ +''' +Created on 28 aout 2009 + +@author: Samuel Benveniste +''' + +import os +import sys +import subprocess +import re + +import pygame +import pygame.midi +import pickle + +from gui.constants import * + +from gui.PlayingScreen import PlayingScreen +from gui.SongPlayingScreen import SongPlayingScreen +from gui.InstrumentChoice import InstrumentChoice +from instruments.Instrument import Instrument +from cursor.WarpingCursor import * +from controllers.Wiimote import Wiimote +from songs.Song import Song + + +class Log(): + ''' + classdocs + ''' + + + def __init__(self,eventLog,scale,extendedScale,cascade,song,mode,activeWiimotes,easyMode = True): + ''' + Constructor + ''' + self.eventLog = eventLog + self.scale = scale + self.extendedScale = extendedScale + self.cascade = cascade + self.song = song + self.activeWiimotes = activeWiimotes + self.mode = mode + +if __name__ == '__main__': + + f = file('../../saves/4-12-2009-B1-v50-1.mwi', 'r') + unpickler = pickle.Unpickler(f) + log = unpickler.load() + f.close() + + pygame.init() + pygame.midi.init() + instruments = [Instrument(scaleDict["majorScale"], i + 1, "".join(["../instruments/instrumentImages/", instrumentImagePathList[i], ".jpg"]), octaves[i]) for i in range(9)] + + joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())] + for joy in joys: + if joy[1] in joyNames: + pygame.joystick.Joystick(joy[0]).init() + + ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())] + portOffset = ports.index(portNames[0]) + print(portOffset) + + modeResolution = (1024,768) + window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN) + screen = pygame.display.get_surface() + clock = pygame.time.Clock() + cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']] + durations = [75 for i in range(len(cursorImages[0]))] + + wiimoteCount = 4 + cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),flashImage = '../cursor/cursorImages/black/flash.png' ) for i in range(wiimoteCount)] + wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(wiimoteCount)] + + if log.song != None : + + if log.mode == 0 : + log.extendedScale = log.song.requiresExtendedScale + log.cascade = True + log.easyMode = True + elif log.mode == 1 : + log.extendedScale = log.song.requiresExtendedScale + log.cascade = True + log.easyMode = True + elif log.mode == 2: + log.extendedScale = log.song.requiresExtendedScale + log.cascade = False + log.easyMode = True + elif log.mode == 3: + log.extendedScale = True + log.cascade = False + log.easyMode = False + + choice = InstrumentChoice(instruments, wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes, eventLog = log.eventLog,scaleFactor = songScaleFactor,replay = True) + play = SongPlayingScreen(choice, log.song,log.cascade, log.extendedScale,log.easyMode) + + else: + + if log.mode == 0 : + log.extendedScale = False + log.cascade = False + elif log.mode == 1 : + log.extendedScale = True + log.cascade = False + elif log.mode == 2: + log.extendedScale = False + log.cascade = True + elif log.mode == 3: + log.extendedScale = True + log.cascade = True + + choice = InstrumentChoice(instruments, wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,eventLog = log.eventLog,replay = True) + play = PlayingScreen(choice, None,log.cascade, log.extendedScale) + + while play.backToInstrumentChoice == True : + + for wiimote in wiimotes: + del wiimote.port + + wiimotes = [Wiimote(i, i + portOffset, None, None, cursors[i]) for i in range(wiimoteCount)] + previousEventLog = play.eventLog + + if log.song != None : + choice = InstrumentChoice(instruments, wiimotes,window, screen, clock, joys, portOffset, log.activeWiimotes,eventLog = previousEventLog, replay = True, scaleFactor = songScaleFactor) + play = SongPlayingScreen(choice, log.song, False, log.extendedScale,log.easyMode) + else: + choice = InstrumentChoice(instruments, wiimotes, log.window, screen, clock, joys, portOffset,log.activeWiimotes, eventLog = previousEventLog, replay = True) + play = PlayingScreen(choice, None, log.cascade, log.extendedScale) + + for wiimote in wiimotes: + del wiimote.port + + pygame.midi.quit() + pygame.quit() \ No newline at end of file diff --git a/src/logging/LogPGUAnalyzer.py b/src/logging/LogPGUAnalyzer.py new file mode 100644 index 0000000..a3551fd --- /dev/null +++ b/src/logging/LogPGUAnalyzer.py @@ -0,0 +1,42 @@ +''' +Created on 25 janv. 2010 + +@author: Samuel Benveniste +''' + +from LogPGUPlayer import LogPGUPlayer + +class LogPGUAnalyzer(LogPGUPlayer): + ''' + classdocs + ''' + + + def __init__(self): + ''' + Constructor + ''' + + self.firstStepDurations = [] + self.firstStepClicks = [] + self.firstStepClicksIn = [] + + self.secondStepDurations = [] + self.secondStepClicks = [] + self.secondStepClicksIn = [] + + self.thirdStepDurations = [] + self.thirdStepClicks = [] + self.thirdStepClicksIn = [] + + self.songDurations = [] + self.songClicks = [] + self.songClicksIn = [] + self.songClicksPerMinute = [] + self.songClicksInPerMinute = [] + self.songTotalDurations = [] + + self.meanTimeBetweenNotes = [] + + LogPGUPlayer.__init__(self,20) + \ No newline at end of file diff --git a/src/logging/LogPGUPlayer.py b/src/logging/LogPGUPlayer.py new file mode 100644 index 0000000..cf1a670 --- /dev/null +++ b/src/logging/LogPGUPlayer.py @@ -0,0 +1,237 @@ +''' +Created on 25 janv. 2010 + +@author: Samuel Benveniste +''' + +import pygame +import pickle + +from pgu import gui as pguGui + +from gui import constants + +from instruments.Instrument import Instrument +from songs.Song import Song +from cursor.WarpingCursor import * +from gui.StaticFamiliarizer import StaticFamiliarizer +from gui.SongFamiliarizer import SongFamiliarizer +from gui.SongPlayingScreen import SongPlayingScreen +from gui.DummyInstrumentChoice import DummyInstrumentChoice +from controllers.Wiimote import Wiimote + +class LogPGUPlayer(pguGui.Desktop): + ''' + classdocs + ''' + + + def __init__(self,rate): + ''' + Constructor + ''' + self.firstStepDurations = [] + self.firstStepClicks = [] + self.firstStepClicksIn = [] + + self.secondStepDurations = [] + self.secondStepClicks = [] + self.secondStepClicksIn = [] + + self.thirdStepDurations = [] + self.thirdStepClicks = [] + self.thirdStepClicksIn = [] + + self.songDurations = [] + self.songClicks = [] + self.songClicksIn = [] + self.songClicksPerMinute = [] + self.songClicksInPerMinute = [] + self.songTotalDurations = [] + + self.meanTimeBetweenNotes = [] + + pguGui.Desktop.__init__(self) + + self.replayRate = rate + #pguGui.theme.load('../data/themes/default') + + self.connect(pguGui.QUIT,self.quit,None) + + main = pguGui.Container(width=500, height=400) #, background=(220, 220, 220) ) + + + main.add(pguGui.Label("File Dialog Example", cls="h1"), 20, 20) + + + td_style = {'padding_right': 10} + t = pguGui.Table() + t.tr() + t.td( pguGui.Label('File Name:') , style=td_style ) + self.input_file = pguGui.Input() + t.td( self.input_file, style=td_style ) + self.browseButton = pguGui.Button("Browse...") + t.td( self.browseButton, style=td_style ) + self.browseButton.connect(pguGui.CLICK, self.open_file_browser, None) + + self.goButton = pguGui.Button("Go") + + self.goButton.connect(pguGui.CLICK, self.goButtonClicked,None) + + self.quitButton = pguGui.Button("Fin") + self.quitButton.connect(pguGui.CLICK,self.quit,None) + + t.td( self.browseButton, style=td_style ) + t.td( self.goButton, style=td_style ) + t.td( self.quitButton, style=td_style ) + + main.add(t, 20, 100) + + self.run(main) + #import profile + #profile.run('app.run(main)') + + def open_file_browser(self,data=None): + d = pguGui.FileDialog(path = "../../../saves") + d.connect(pguGui.CHANGE, self.handle_file_browser_closed, d) + d.open() + + + def handle_file_browser_closed(self,dlg): + if dlg.value: self.input_file.value = dlg.value + + def goButtonClicked(self,data=None): + if self.input_file.value.endswith(".fmwi"): + f = file(self.input_file.value, 'r') + unpickler = pickle.Unpickler(f) + log = unpickler.load() + f.close() + + log.eventLog.setReplayRate(self.replayRate) + + pygame.midi.init() + instruments = [Instrument(log.scale, i + 1, "".join(["../instruments/instrumentImages/", constants.instrumentImagePathList[i], ".jpg"]), constants.octaves[i]) for i in range(9)] + + joys = [[id,pygame.joystick.Joystick(id).get_name()] for id in range(pygame.joystick.get_count())] + for joy in joys: + if joy[1] in constants.joyNames: + pygame.joystick.Joystick(joy[0]).init() + + ports = [pygame.midi.get_device_info(id)[1] for id in range(pygame.midi.get_count())] + portOffset = ports.index(constants.portNames[0]) + print(portOffset) + + screen = pygame.display.get_surface() + clock = pygame.time.Clock() + cursorImages=[['../cursor/cursorImages/black/10.png'],['../cursor/cursorImages/red/10.png'],['../cursor/cursorImages/blue/10.png'],['../cursor/cursorImages/green/10.png']] + durations = [75 for i in range(len(cursorImages))] + + extsc = True + casc = False + easyMode = True + + song = Song(constants.scaleDict["majorScale"],[3,9,6,4,1,8,5,7,2,10],True) + + wiimoteCount = 4 + + cursors = [WarpingCursor(None, cursorImages[i], durations, (300 * i, 300 * i),'../cursor/cursorImages/black/flash.png') for i in range(wiimoteCount)] + wiimotes = [Wiimote(i, i + portOffset, None, instruments[0], cursors[i]) for i in range(wiimoteCount)] + dummyInstrumentChoice = DummyInstrumentChoice(wiimotes, window, screen, clock, joys, portOffset, log.activeWiimotes) + if log.level < 2 : + familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = log.level,eventLog = log.eventLog,replay = True) + self.fillStaticFamiliarizerStats(familiarize) + elif log.level == 2 : + familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = log.eventLog,replay = True) + self.fillSongFamiliarizerStats(familiarize) + else : + familiarize = SongPlayingScreen(dummyInstrumentChoice,constants.songDict["clairdelalune"],easyMode = True,eventLog = log.eventLog,replay = True) + self.fillSongStats(familiarize) + + while familiarize.nextLevel != None : + if familiarize.nextLevel < 2 : + familiarize = StaticFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,log.activeWiimotes,level = familiarize.nextLevel,eventLog = familiarize.eventLog,replay = True) + self.fillStaticFamiliarizerStats(familiarize) + elif familiarize.nextLevel == 2 : + familiarize = SongFamiliarizer(wiimotes, window, screen, clock, joys, portOffset,song,log.activeWiimotes,casc,extsc,easyMode,eventLog = familiarize.eventLog,replay = True) + self.fillSongFamiliarizerStats(familiarize) + else : + familiarize = SongPlayingScreen(dummyInstrumentChoice,constants.songDict["clairdelalune"],easyMode = True,eventLog = familiarize.eventLog,replay = True) + self.fillSongStats(familiarize) + + for wiimote in wiimotes: + del wiimote.port + + pygame.midi.quit() + self.printStatsToFile() + + def fillStaticFamiliarizerStats(self,familiarizer): + if familiarizer.level == 0 : + self.firstStepClicks.append(familiarizer.clicks) + self.firstStepClicksIn.append(familiarizer.clicksIn) + self.firstStepDurations.append(familiarizer.duration) + + if familiarizer.level == 1 : + self.secondStepClicks.append(familiarizer.clicks) + self.secondStepClicksIn.append(familiarizer.clicksIn) + self.secondStepDurations.append(familiarizer.duration) + + def fillSongFamiliarizerStats(self,familiarizer): + self.thirdStepClicks.append(familiarizer.clicks) + self.thirdStepClicksIn.append(familiarizer.clicksIn) + self.thirdStepDurations.append(familiarizer.duration) + + def fillSongStats(self,familiarizer): + self.songClicks.append(familiarizer.clicks) + self.songClicksIn.append(familiarizer.clicksIn) + self.songClicksPerMinute.append(familiarizer.clicksPerMinute) + self.songClicksInPerMinute.append(familiarizer.clicksInPerMinute) + self.songDurations.append(familiarizer.songDurations) + self.songTotalDurations.append(familiarizer.totalDuration) + + def statsToFormattedString(self): + return("First step durations :\n"+"\n"+ + str(self.firstStepDurations)+"\n"+"\n"+ + "First step clicks :\n"+"\n"+ + str(self.firstStepClicks)+"\n"+"\n"+ + "First step clicksIn :\n"+"\n"+ + str(self.firstStepClicksIn)+"\n"+"\n"+ + "Second step durations :\n"+"\n"+ + str(self.secondStepDurations)+"\n"+"\n"+ + "Second step clicks :\n"+"\n"+ + str(self.secondStepClicks)+"\n"+"\n"+ + "Second step clicksIn :\n"+"\n"+ + str(self.secondStepClicksIn)+"\n"+"\n"+ + "Third step durations :\n"+"\n"+ + str(self.thirdStepDurations)+"\n"+"\n"+ + "Third step clicks :\n"+"\n"+ + str(self.thirdStepClicks)+"\n"+"\n"+ + "Third step clicksIn :\n"+"\n"+ + str(self.thirdStepClicksIn)+"\n"+"\n"+ + "song durations :\n"+"\n"+ + str(self.songDurations)+"\n"+"\n"+ + "song clicks :\n"+"\n"+ + str(self.songClicks)+"\n"+"\n"+ + "song clicksIn :\n"+"\n"+ + str(self.songClicksIn)+"\n"+"\n"+ + "song clicks per minute:\n"+"\n"+ + str(self.songClicksPerMinute)+"\n"+"\n"+ + "song clicksIn per minute :\n"+"\n"+ + str(self.songClicksInPerMinute)+"\n"+"\n"+ + "song total durations :\n"+"\n"+ + str(self.songTotalDurations)+"\n"+"\n") + + def printStatsToFile(self,path=None): + if path == None : + path = self.input_file.value.replace(".fmwi",".txt") + file = open(path,"w") + file.write("Log ID : "+self.input_file.value+"\n"+"\n") + file.write(self.statsToFormattedString()) + file.close() + +if __name__ == "__main__": + pygame.init() + modeResolution = (1024,768) + window = pygame.display.set_mode(modeResolution,pygame.FULLSCREEN) + logConfig = LogPGUPlayer(10000) + pygame.quit() + \ No newline at end of file diff --git a/src/logging/PickleableEvent.py b/src/logging/PickleableEvent.py new file mode 100755 index 0000000..ab218f8 --- /dev/null +++ b/src/logging/PickleableEvent.py @@ -0,0 +1,24 @@ +import pygame +import copy +import pickle + +from pygame.event import Event + +class PickleableEvent(object): + "A pygame.Event that can be serialized." + + def __init__(self,type,dict): + self.__dict__ = copy.copy(dict) + self.type = type + self.event = Event(self.type,dict) + + def __getstate__(self): + d = [] + d.append(self.type) + d.append(copy.copy(self.event.dict)) + return d + + def __setstate__(self, d): + self.__dict__ = copy.copy(d[1]) + self.type = d[0] + 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 index 0000000..e69de29 diff --git a/src/mxmMidi/DataTypeConverters.py b/src/mxmMidi/DataTypeConverters.py new file mode 100644 index 0000000..25115de --- /dev/null +++ b/src/mxmMidi/DataTypeConverters.py @@ -0,0 +1,217 @@ +# -*- coding: ISO-8859-1 -*- + +from struct import pack, unpack + +""" +This module contains functions for reading and writing the special data types +that a midi file contains. +""" + +""" +nibbles are four bits. A byte consists of two nibles. +hiBits==0xF0, loBits==0x0F Especially used for setting +channel and event in 1. byte of musical midi events +""" + + + +def getNibbles(byte): + """ + Returns hi and lo bits in a byte as a tuple + >>> getNibbles(142) + (8, 14) + + Asserts byte value in byte range + >>> getNibbles(256) + Traceback (most recent call last): + ... + ValueError: Byte value out of range 0-255: 256 + """ + if not 0 <= byte <= 255: + raise ValueError('Byte value out of range 0-255: %s' % byte) + return (byte >> 4 & 0xF, byte & 0xF) + + +def setNibbles(hiNibble, loNibble): + """ + Returns byte with value set according to hi and lo bits + Asserts hiNibble and loNibble in range(16) + >>> setNibbles(8, 14) + 142 + + >>> setNibbles(8, 16) + Traceback (most recent call last): + ... + ValueError: Nible value out of range 0-15: (8, 16) + """ + if not (0 <= hiNibble <= 15) or not (0 <= loNibble <= 15): + raise ValueError('Nible value out of range 0-15: (%s, %s)' % (hiNibble, loNibble)) + return (hiNibble << 4) + loNibble + + + +def readBew(value): + """ + Reads string as big endian word, (asserts len(value) in [1,2,4]) + >>> readBew('aáâã') + 1642193635L + >>> readBew('aá') + 25057 + """ + return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0] + + +def writeBew(value, length): + """ + Write int as big endian formatted string, (asserts length in [1,2,4]) + Difficult to print the result in doctest, so I do a simple roundabout test. + >>> readBew(writeBew(25057, 2)) + 25057 + >>> readBew(writeBew(1642193635L, 4)) + 1642193635L + """ + return pack('>%s' % {1:'B', 2:'H', 4:'L'}[length], value) + + + +""" +Variable Length Data (varlen) is a data format sprayed liberally throughout +a midi file. It can be anywhere from 1 to 4 bytes long. +If the 8'th bit is set in a byte another byte follows. The value is stored +in the lowest 7 bits of each byte. So max value is 4x7 bits = 28 bits. +""" + + +def readVar(value): + """ + Converts varlength format to integer. Just pass it 0 or more chars that + might be a varlen and it will only use the relevant chars. + use varLen(readVar(value)) to see how many bytes the integer value takes. + asserts len(value) >= 0 + >>> readVar('€@') + 64 + >>> readVar('áâãa') + 205042145 + """ + sum = 0 + for byte in unpack('%sB' % len(value), value): + sum = (sum << 7) + (byte & 0x7F) + if not 0x80 & byte: break # stop after last byte + return sum + + + +def varLen(value): + """ + Returns the the number of bytes an integer will be when + converted to varlength + """ + if value <= 127: + return 1 + elif value <= 16383: + return 2 + elif value <= 2097151: + return 3 + else: + return 4 + + +def writeVar(value): + "Converts an integer to varlength format" + sevens = to_n_bits(value, varLen(value)) + for i in range(len(sevens)-1): + sevens[i] = sevens[i] | 0x80 + return fromBytes(sevens) + + +def to_n_bits(value, length=1, nbits=7): + "returns the integer value as a sequence of nbits bytes" + bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)] + bytes.reverse() + return bytes + + +def toBytes(value): + "Turns a string into a list of byte values" + return unpack('%sB' % len(value), value) + + +def fromBytes(value): + "Turns a list of bytes into a string" + if not value: + return '' + return pack('%sB' % len(value), *value) + + + +if __name__ == '__main__': + +# print to7bits(0, 3) +# print to7bits(127, 3) +# print to7bits(255, 3) +# print to7bits(65536, 3) + + # simple test cases + +# print 'getHiLoHex', getNibbles(16) +# print 'setHiLoHex', setNibbles(1,0) +# +# print 'readBew', readBew('aáâã') +# print 'writeBew', writeBew(1642193635, 4) +# +# print 'varLen', varLen(1) +# + print 'readVar', readVar('€@') + print 'writeVar', writeVar(8192) + + print 'readVar', readVar('áâãa') + print 'writeVar', writeVar(205058401) +# +# vartest = '\x82\xF7\x80\x00' +# print 'toBytes', toBytes(vartest) +# print 'fromBytes', fromBytes([48, 49, 50,]) + + +# instr = '\xFF\xFF\xFF\x00' +# print 'readVar', readVar(instr) +# inst2 = 268435455 +# print inst2 +# print writeVar(inst2) +# print writeVar(readVar(instr)) + + s1 = 0x00000000 + print '%08X -' % s1, '00', writeVar(s1) + s2 = 0x00000040 + print '%08X -' % s2, '40', writeVar(s2) + s3 = 0x0000007F + print '%08X -' % s3, '7F', writeVar(s3) + s4 = 0x00000080 + print '%08X -' % s4, '81 00', writeVar(s4) + s5 = 0x00002000 + print '%08X -' % s5, 'C0 00', writeVar(s5) + s6 = 0x00003FFF + print '%08X -' % s6, 'FF 7F', writeVar(s6) + s7 = 0x00004000 + print '%08X -' % s7, '81 80 00', writeVar(s7) + s8 = 0x00100000 + print '%08X -' % s8, 'C0 80 00', writeVar(s8) + s9 = 0x001FFFFF + print '%08X -' % s9, 'FF FF 7F', writeVar(s9) + s10 = 0x00200000 + print '%08X -' % s10, '81 80 80 00', writeVar(s10) + s11 = 0x08000000 + print '%08X -' % s11, 'C0 80 80 00', writeVar(s11) + s12 = 0x0FFFFFFF + print '%08X -' % s12, 'FF FF FF 7F', writeVar(s12) + + + + + + + + + + + + \ No newline at end of file diff --git a/src/mxmMidi/DataTypeConverters.pyc b/src/mxmMidi/DataTypeConverters.pyc new file mode 100644 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 index 0000000..3c07a33 --- /dev/null +++ b/src/mxmMidi/EventDispatcher.py @@ -0,0 +1,287 @@ +# -*- coding: ISO-8859-1 -*- + +# std library +from struct import unpack + +# custom +from DataTypeConverters import readBew, readVar, varLen, toBytes + +# uhh I don't really like this, but there are so many constants to +# import otherwise +from constants import * + + +class EventDispatcher: + + + def __init__(self, outstream): + + """ + + The event dispatcher generates events on the outstream. + + """ + + # internal values, don't mess with 'em directly + self.outstream = outstream + + # public flags + + # A note_on with a velocity of 0x00 is actually the same as a + # note_off with a velocity of 0x40. When + # "convert_zero_velocity" is set, the zero velocity note_on's + # automatically gets converted into note_off's. This is a less + # suprising behaviour for those that are not into the intimate + # details of the midi spec. + self.convert_zero_velocity = 1 + + # If dispatch_continuos_controllers is true, continuos + # controllers gets dispatched to their defined handlers. Else + # they just trigger the "continuous_controller" event handler. + self.dispatch_continuos_controllers = 1 # NOT IMPLEMENTED YET + + # If dispatch_meta_events is true, meta events get's dispatched + # to their defined events. Else they all they trigger the + # "meta_event" handler. + self.dispatch_meta_events = 1 + + + + def header(self, format, nTracks, division): + "Triggers the header event" + self.outstream.header(format, nTracks, division) + + + def start_of_track(self, current_track): + "Triggers the start of track event" + + # I do this twice so that users can overwrite the + # start_of_track event handler without worrying whether the + # track number is updated correctly. + self.outstream.set_current_track(current_track) + self.outstream.start_of_track(current_track) + + + def sysex_event(self, data): + "Dispatcher for sysex events" + self.outstream.sysex_event(data) + + + def eof(self): + "End of file!" + self.outstream.eof() + + + def update_time(self, new_time=0, relative=1): + "Updates relative/absolute time." + self.outstream.update_time(new_time, relative) + + + def reset_time(self): + "Updates relative/absolute time." + self.outstream.reset_time() + + + # Event dispatchers for similar types of events + + + def channel_messages(self, hi_nible, channel, data): + + "Dispatches channel messages" + + stream = self.outstream + data = toBytes(data) + + if (NOTE_ON & 0xF0) == hi_nible: + note, velocity = data + # note_on with velocity 0x00 are same as note + # off with velocity 0x40 according to spec! + if velocity==0 and self.convert_zero_velocity: + stream.note_off(channel, note, 0x40) + else: + stream.note_on(channel, note, velocity) + + elif (NOTE_OFF & 0xF0) == hi_nible: + note, velocity = data + stream.note_off(channel, note, velocity) + + elif (AFTERTOUCH & 0xF0) == hi_nible: + note, velocity = data + stream.aftertouch(channel, note, velocity) + + elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible: + controller, value = data + # A lot of the cc's are defined, so we trigger those directly + if self.dispatch_continuos_controllers: + self.continuous_controllers(channel, controller, value) + else: + stream.continuous_controller(channel, controller, value) + + elif (PATCH_CHANGE & 0xF0) == hi_nible: + program = data[0] + stream.patch_change(channel, program) + + elif (CHANNEL_PRESSURE & 0xF0) == hi_nible: + pressure = data[0] + stream.channel_pressure(channel, pressure) + + elif (PITCH_BEND & 0xF0) == hi_nible: + hibyte, lobyte = data + value = (hibyte<<7) + lobyte + stream.pitch_bend(channel, value) + + else: + + raise ValueError, 'Illegal channel message!' + + + + def continuous_controllers(self, channel, controller, value): + + "Dispatches channel messages" + + stream = self.outstream + + # I am not really shure if I ought to dispatch continuous controllers + # There's so many of them that it can clutter up the OutStream + # classes. + + # So I just trigger the default event handler + stream.continuous_controller(channel, controller, value) + + + + def system_commons(self, common_type, common_data): + + "Dispatches system common messages" + + stream = self.outstream + + # MTC Midi time code Quarter value + if common_type == MTC: + data = readBew(common_data) + msg_type = (data & 0x07) >> 4 + values = (data & 0x0F) + stream.midi_time_code(msg_type, values) + + elif common_type == SONG_POSITION_POINTER: + hibyte, lobyte = toBytes(common_data) + value = (hibyte<<7) + lobyte + stream.song_position_pointer(value) + + elif common_type == SONG_SELECT: + data = readBew(common_data) + stream.song_select(data) + + elif common_type == TUNING_REQUEST: + # no data then + stream.tuning_request(time=None) + + + + def meta_event(self, meta_type, data): + + "Dispatches meta events" + + stream = self.outstream + + # SEQUENCE_NUMBER = 0x00 (00 02 ss ss (seq-number)) + if meta_type == SEQUENCE_NUMBER: + number = readBew(data) + stream.sequence_number(number) + + # TEXT = 0x01 (01 len text...) + elif meta_type == TEXT: + stream.text(data) + + # COPYRIGHT = 0x02 (02 len text...) + elif meta_type == COPYRIGHT: + stream.copyright(data) + + # SEQUENCE_NAME = 0x03 (03 len text...) + elif meta_type == SEQUENCE_NAME: + stream.sequence_name(data) + + # INSTRUMENT_NAME = 0x04 (04 len text...) + elif meta_type == INSTRUMENT_NAME: + stream.instrument_name(data) + + # LYRIC = 0x05 (05 len text...) + elif meta_type == LYRIC: + stream.lyric(data) + + # MARKER = 0x06 (06 len text...) + elif meta_type == MARKER: + stream.marker(data) + + # CUEPOINT = 0x07 (07 len text...) + elif meta_type == CUEPOINT: + stream.cuepoint(data) + + # PROGRAM_NAME = 0x08 (05 len text...) + elif meta_type == PROGRAM_NAME: + stream.program_name(data) + + # DEVICE_NAME = 0x09 (09 len text...) + elif meta_type == DEVICE_NAME: + stream.device_name(data) + + # MIDI_CH_PREFIX = 0x20 (20 01 channel) + elif meta_type == MIDI_CH_PREFIX: + channel = readBew(data) + stream.midi_ch_prefix(channel) + + # MIDI_PORT = 0x21 (21 01 port (legacy stuff)) + elif meta_type == MIDI_PORT: + port = readBew(data) + stream.midi_port(port) + + # END_OFF_TRACK = 0x2F (2F 00) + elif meta_type == END_OF_TRACK: + stream.end_of_track() + + # TEMPO = 0x51 (51 03 tt tt tt (tempo in us/quarternote)) + elif meta_type == TEMPO: + b1, b2, b3 = toBytes(data) + # uses 3 bytes to represent time between quarter + # notes in microseconds + stream.tempo((b1<<16) + (b2<<8) + b3) + + # SMTP_OFFSET = 0x54 (54 05 hh mm ss ff xx) + elif meta_type == SMTP_OFFSET: + hour, minute, second, frame, framePart = toBytes(data) + stream.smtp_offset( + hour, minute, second, frame, framePart) + + # TIME_SIGNATURE = 0x58 (58 04 nn dd cc bb) + elif meta_type == TIME_SIGNATURE: + nn, dd, cc, bb = toBytes(data) + stream.time_signature(nn, dd, cc, bb) + + # KEY_SIGNATURE = 0x59 (59 02 sf mi) + elif meta_type == KEY_SIGNATURE: + sf, mi = toBytes(data) + stream.key_signature(sf, mi) + + # SPECIFIC = 0x7F (Sequencer specific event) + elif meta_type == SPECIFIC: + meta_data = toBytes(data) + stream.sequencer_specific(meta_data) + + # Handles any undefined meta events + else: # undefined meta type + meta_data = toBytes(data) + stream.meta_event(meta_type, meta_data) + + + + + +if __name__ == '__main__': + + + from MidiToText import MidiToText + + outstream = MidiToText() + dispatcher = EventDispatcher(outstream) + 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 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 index 0000000..2b447bc --- /dev/null +++ b/src/mxmMidi/MidiFileParser.py @@ -0,0 +1,192 @@ +# -*- coding: ISO-8859-1 -*- + +# std library +from struct import unpack + +# uhh I don't really like this, but there are so many constants to +# import otherwise +from constants import * + +from EventDispatcher import EventDispatcher + +class MidiFileParser: + + """ + + The MidiFileParser is the lowest level parser that see the data as + midi data. It generates events that gets triggered on the outstream. + + """ + + def __init__(self, raw_in, outstream): + + """ + raw_data is the raw content of a midi file as a string. + """ + + # internal values, don't mess with 'em directly + self.raw_in = raw_in + self.dispatch = EventDispatcher(outstream) + + # Used to keep track of stuff + self._running_status = None + + + + + def parseMThdChunk(self): + + "Parses the header chunk" + + raw_in = self.raw_in + + header_chunk_type = raw_in.nextSlice(4) + header_chunk_zise = raw_in.readBew(4) + + # check if it is a proper midi file + if header_chunk_type != 'MThd': + raise TypeError, "It is not a valid midi file!" + + # Header values are at fixed locations, so no reason to be clever + self.format = raw_in.readBew(2) + self.nTracks = raw_in.readBew(2) + self.division = raw_in.readBew(2) + + # Theoretically a header larger than 6 bytes can exist + # but no one has seen one in the wild + # But correctly ignore unknown data if it is though + if header_chunk_zise > 6: + raw_in.moveCursor(header_chunk_zise-6) + + # call the header event handler on the stream + self.dispatch.header(self.format, self.nTracks, self.division) + + + + def parseMTrkChunk(self): + + "Parses a track chunk. This is the most important part of the parser." + + # set time to 0 at start of a track + self.dispatch.reset_time() + + dispatch = self.dispatch + raw_in = self.raw_in + + # Trigger event at the start of a track + dispatch.start_of_track(self._current_track) + # position cursor after track header + raw_in.moveCursor(4) + # unsigned long is 4 bytes + tracklength = raw_in.readBew(4) + track_endposition = raw_in.getCursor() + tracklength # absolute position! + + while raw_in.getCursor() < track_endposition: + + # find relative time of the event + time = raw_in.readVarLen() + dispatch.update_time(time) + + # be aware of running status!!!! + peak_ahead = raw_in.readBew(move_cursor=0) + if (peak_ahead & 0x80): + # the status byte has the high bit set, so it + # was not running data but proper status byte + status = self._running_status = raw_in.readBew() + else: + # use that darn running status + status = self._running_status + # could it be illegal data ?? Do we need to test for that? + # I need more example midi files to be shure. + + # Also, while I am almost certain that no realtime + # messages will pop up in a midi file, I might need to + # change my mind later. + + # we need to look at nibbles here + hi_nible, lo_nible = status & 0xF0, status & 0x0F + + # match up with events + + # Is it a meta_event ?? + # these only exists in midi files, not in transmitted midi data + # In transmitted data META_EVENT (0xFF) is a system reset + if status == META_EVENT: + meta_type = raw_in.readBew() + meta_length = raw_in.readVarLen() + meta_data = raw_in.nextSlice(meta_length) + dispatch.meta_event(meta_type, meta_data) + + + # Is it a sysex_event ?? + elif status == SYSTEM_EXCLUSIVE: + # ignore sysex events + sysex_length = raw_in.readVarLen() + # don't read sysex terminator + sysex_data = raw_in.nextSlice(sysex_length-1) + # only read last data byte if it is a sysex terminator + # It should allways be there, but better safe than sorry + if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE: + eo_sysex = raw_in.readBew() + dispatch.sysex_event(sysex_data) + # the sysex code has not been properly tested, and might be fishy! + + + # is it a system common event? + elif hi_nible == 0xF0: # Hi bits are set then + data_sizes = { + MTC:1, + SONG_POSITION_POINTER:2, + SONG_SELECT:1, + } + data_size = data_sizes.get(hi_nible, 0) + common_data = raw_in.nextSlice(data_size) + common_type = lo_nible + dispatch.system_common(common_type, common_data) + + + # Oh! Then it must be a midi event (channel voice message) + else: + data_sizes = { + PATCH_CHANGE:1, + CHANNEL_PRESSURE:1, + NOTE_OFF:2, + NOTE_ON:2, + AFTERTOUCH:2, + CONTINUOUS_CONTROLLER:2, + PITCH_BEND:2, + } + data_size = data_sizes.get(hi_nible, 0) + channel_data = raw_in.nextSlice(data_size) + event_type, channel = hi_nible, lo_nible + dispatch.channel_messages(event_type, channel, channel_data) + + + def parseMTrkChunks(self): + "Parses all track chunks." + for t in range(self.nTracks): + self._current_track = t + self.parseMTrkChunk() # this is where it's at! + self.dispatch.eof() + + + +if __name__ == '__main__': + + # get data + test_file = 'test/midifiles/minimal.mid' + test_file = 'test/midifiles/cubase-minimal.mid' + test_file = 'test/midifiles/Lola.mid' +# f = open(test_file, 'rb') +# raw_data = f.read() +# f.close() +# +# +# # do parsing + from MidiToText import MidiToText + from RawInstreamFile import RawInstreamFile + + midi_in = MidiFileParser(RawInstreamFile(test_file), MidiToText()) + midi_in.parseMThdChunk() + midi_in.parseMTrkChunks() + \ No newline at end of file diff --git a/src/mxmMidi/MidiFileParser.pyc b/src/mxmMidi/MidiFileParser.pyc new file mode 100644 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 index 0000000..57f4c7e --- /dev/null +++ b/src/mxmMidi/MidiInFile.py @@ -0,0 +1,55 @@ +# -*- coding: ISO-8859-1 -*- + +from RawInstreamFile import RawInstreamFile +from MidiFileParser import MidiFileParser + + +class MidiInFile: + + """ + + Parses a midi file, and triggers the midi events on the outStream + object. + + Get example data from a minimal midi file, generated with cubase. + >>> test_file = 'C:/Documents and Settings/maxm/Desktop/temp/midi/src/midi/tests/midifiles/minimal-cubase-type0.mid' + + Do parsing, and generate events with MidiToText, + so we can see what a minimal midi file contains + >>> from MidiToText import MidiToText + >>> midi_in = MidiInFile(MidiToText(), test_file) + >>> midi_in.read() + format: 0, nTracks: 1, division: 480 + ---------------------------------- + + Start - track #0 + sequence_name: Type 0 + tempo: 500000 + time_signature: 4 2 24 8 + note_on - ch:00, note:48, vel:64 time:0 + note_off - ch:00, note:48, vel:40 time:480 + End of track + + End of file + + + """ + + def __init__(self, outStream, infile): + # these could also have been mixins, would that be better? Nah! + self.raw_in = RawInstreamFile(infile) + self.parser = MidiFileParser(self.raw_in, outStream) + + + def read(self): + "Start parsing the file" + p = self.parser + p.parseMThdChunk() + p.parseMTrkChunks() + + + def setData(self, data=''): + "Sets the data from a plain string" + self.raw_in.setData(data) + + diff --git a/src/mxmMidi/MidiInFile.pyc b/src/mxmMidi/MidiInFile.pyc new file mode 100644 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 index 0000000..22f7e09 --- /dev/null +++ b/src/mxmMidi/MidiInStream.py @@ -0,0 +1,52 @@ +# -*- coding: ISO-8859-1 -*- + +from MidiOutStream import MidiOutStream + +class MidiInStream: + + """ + Takes midi events from the midi input and calls the apropriate + method in the eventhandler object + """ + + def __init__(self, midiOutStream, device): + + """ + + Sets a default output stream, and sets the device from where + the input comes + + """ + + if midiOutStream is None: + self.midiOutStream = MidiOutStream() + else: + self.midiOutStream = midiOutStream + + + def close(self): + + """ + Stop the MidiInstream + """ + + + def read(self, time=0): + + """ + + Start the MidiInstream. + + "time" sets timer to specific start value. + + """ + + + def resetTimer(self, time=0): + """ + + Resets the timer, probably a good idea if there is some kind + of looping going on + + """ + diff --git a/src/mxmMidi/MidiOutFile.py b/src/mxmMidi/MidiOutFile.py new file mode 100644 index 0000000..a39bc9f --- /dev/null +++ b/src/mxmMidi/MidiOutFile.py @@ -0,0 +1,448 @@ +# -*- coding: ISO-8859-1 -*- + +from MidiOutStream import MidiOutStream +from RawOutstreamFile import RawOutstreamFile + +from constants import * +from DataTypeConverters import fromBytes, writeVar + +class MidiOutFile(MidiOutStream): + + + """ + MidiOutFile is an eventhandler that subclasses MidiOutStream. + """ + + + def __init__(self, raw_out=''): + + self.raw_out = RawOutstreamFile(raw_out) + MidiOutStream.__init__(self) + + + def write(self): + self.raw_out.write() + + + def event_slice(self, slc): + """ + Writes the slice of an event to the current track. Correctly + inserting a varlen timestamp too. + """ + trk = self._current_track_buffer + trk.writeVarLen(self.rel_time()) + trk.writeSlice(slc) + + + ##################### + ## Midi events + + + def note_on(self, channel=0, note=0x40, velocity=0x40): + + """ + channel: 0-15 + note, velocity: 0-127 + """ + slc = fromBytes([NOTE_ON + channel, note, velocity]) + self.event_slice(slc) + + + def note_off(self, channel=0, note=0x40, velocity=0x40): + + """ + channel: 0-15 + note, velocity: 0-127 + """ + slc = fromBytes([NOTE_OFF + channel, note, velocity]) + self.event_slice(slc) + + + def aftertouch(self, channel=0, note=0x40, velocity=0x40): + + """ + channel: 0-15 + note, velocity: 0-127 + """ + slc = fromBytes([AFTERTOUCH + channel, note, velocity]) + self.event_slice(slc) + + + def continuous_controller(self, channel, controller, value): + + """ + channel: 0-15 + controller, value: 0-127 + """ + slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value]) + self.event_slice(slc) + # These should probably be implemented + # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums + + + def patch_change(self, channel, patch): + + """ + channel: 0-15 + patch: 0-127 + """ + slc = fromBytes([PATCH_CHANGE + channel, patch]) + self.event_slice(slc) + + + def channel_pressure(self, channel, pressure): + + """ + channel: 0-15 + pressure: 0-127 + """ + slc = fromBytes([CHANNEL_PRESSURE + channel, pressure]) + self.event_slice(slc) + + + def pitch_bend(self, channel, value): + + """ + channel: 0-15 + value: 0-16383 + """ + msb = (value>>7) & 0xFF + lsb = value & 0xFF + slc = fromBytes([PITCH_BEND + channel, msb, lsb]) + self.event_slice(slc) + + + + + ##################### + ## System Exclusive + +# def sysex_slice(sysex_type, data): +# "" +# sysex_len = writeVar(len(data)+1) +# self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE) +# + def system_exclusive(self, data): + + """ + data: list of values in range(128) + """ + sysex_len = writeVar(len(data)+1) + self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE)) + + + ##################### + ## Common events + + def midi_time_code(self, msg_type, values): + """ + msg_type: 0-7 + values: 0-15 + """ + value = (msg_type<<4) + values + self.event_slice(fromBytes([MIDI_TIME_CODE, value])) + + + def song_position_pointer(self, value): + + """ + value: 0-16383 + """ + lsb = (value & 0x7F) + msb = (value >> 7) & 0x7F + self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb])) + + + def song_select(self, songNumber): + + """ + songNumber: 0-127 + """ + self.event_slice(fromBytes([SONG_SELECT, songNumber])) + + + def tuning_request(self): + + """ + No values passed + """ + self.event_slice(chr(TUNING_REQUEST)) + + + ######################### + # header does not really belong here. But anyhoo!!! + + def header(self, format=0, nTracks=1, division=96): + + """ + format: type of midi file in [0,1,2] + nTracks: number of tracks. 1 track for type 0 file + division: timing division ie. 96 ppq. + + """ + raw = self.raw_out + raw.writeSlice('MThd') + bew = raw.writeBew + bew(6, 4) # header size + bew(format, 2) + bew(nTracks, 2) + bew(division, 2) + + + def eof(self): + + """ + End of file. No more events to be processed. + """ + # just write the file then. + self.write() + + + ##################### + ## meta events + + + def meta_slice(self, meta_type, data_slice): + "Writes a meta event" + slc = fromBytes([META_EVENT, meta_type]) + \ + writeVar(len(data_slice)) + data_slice + self.event_slice(slc) + + + def meta_event(self, meta_type, data): + """ + Handles any undefined meta events + """ + self.meta_slice(meta_type, fromBytes(data)) + + + def start_of_track(self, n_track=0): + """ + n_track: number of track + """ + self._current_track_buffer = RawOutstreamFile() + self.reset_time() + self._current_track += 1 + + + def end_of_track(self): + """ + Writes the track to the buffer. + """ + raw = self.raw_out + raw.writeSlice(TRACK_HEADER) + track_data = self._current_track_buffer.getvalue() + # wee need to know size of track data. + eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0]) + raw.writeBew(len(track_data)+len(eot_slice), 4) + # then write + raw.writeSlice(track_data) + raw.writeSlice(eot_slice) + + + + def sequence_number(self, value): + + """ + value: 0-65535 + """ + self.meta_slice(meta_type, writeBew(value, 2)) + + + def text(self, text): + """ + Text event + text: string + """ + self.meta_slice(TEXT, text) + + + def copyright(self, text): + + """ + Copyright notice + text: string + """ + self.meta_slice(COPYRIGHT, text) + + + def sequence_name(self, text): + """ + Sequence/track name + text: string + """ + self.meta_slice(SEQUENCE_NAME, text) + + + def instrument_name(self, text): + + """ + text: string + """ + self.meta_slice(INSTRUMENT_NAME, text) + + + def lyric(self, text): + + """ + text: string + """ + self.meta_slice(LYRIC, text) + + + def marker(self, text): + + """ + text: string + """ + self.meta_slice(MARKER, text) + + + def cuepoint(self, text): + + """ + text: string + """ + self.meta_slice(CUEPOINT, text) + + + def midi_ch_prefix(self, channel): + + """ + channel: midi channel for subsequent data + (deprecated in the spec) + """ + self.meta_slice(MIDI_CH_PREFIX, chr(channel)) + + + def midi_port(self, value): + + """ + value: Midi port (deprecated in the spec) + """ + self.meta_slice(MIDI_CH_PREFIX, chr(value)) + + + def tempo(self, value): + + """ + value: 0-2097151 + tempo in us/quarternote + (to calculate value from bpm: int(60,000,000.00 / BPM)) + """ + hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff) + self.meta_slice(TEMPO, fromBytes([hb, mb, lb])) + + + def smtp_offset(self, hour, minute, second, frame, framePart): + + """ + hour, + minute, + second: 3 bytes specifying the hour (0-23), minutes (0-59) and + seconds (0-59), respectively. The hour should be + encoded with the SMPTE format, just as it is in MIDI + Time Code. + frame: A byte specifying the number of frames per second (one + of : 24, 25, 29, 30). + framePart: A byte specifying the number of fractional frames, + in 100ths of a frame (even in SMPTE-based tracks + using a different frame subdivision, defined in the + MThd chunk). + """ + self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart])) + + + + def time_signature(self, nn, dd, cc, bb): + + """ + nn: Numerator of the signature as notated on sheet music + dd: Denominator of the signature as notated on sheet music + The denominator is a negative power of 2: 2 = quarter + note, 3 = eighth, etc. + cc: The number of MIDI clocks in a metronome click + bb: The number of notated 32nd notes in a MIDI quarter note + (24 MIDI clocks) + """ + self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb])) + + + + + def key_signature(self, sf, mi): + + """ + sf: is a byte specifying the number of flats (-ve) or sharps + (+ve) that identifies the key signature (-7 = 7 flats, -1 + = 1 flat, 0 = key of C, 1 = 1 sharp, etc). + mi: is a byte specifying a major (0) or minor (1) key. + """ + self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi])) + + + + def sequencer_specific(self, data): + + """ + data: The data as byte values + """ + self.meta_slice(SEQUENCER_SPECIFIC, data) + + + + + +# ##################### +# ## realtime events + +# These are of no use in a midi file, so they are ignored!!! + +# def timing_clock(self): +# def song_start(self): +# def song_stop(self): +# def song_continue(self): +# def active_sensing(self): +# def system_reset(self): + + + +if __name__ == '__main__': + + out_file = 'test/midifiles/midiout.mid' + midi = MidiOutFile(out_file) + +#format: 0, nTracks: 1, division: 480 +#---------------------------------- +# +#Start - track #0 +#sequence_name: Type 0 +#tempo: 500000 +#time_signature: 4 2 24 8 +#note_on - ch:00, note:48, vel:64 time:0 +#note_off - ch:00, note:48, vel:40 time:480 +#End of track +# +#End of file + + + midi.header(0, 1, 480) + + midi.start_of_track() + midi.sequence_name('Type 0') + midi.tempo(750000) + midi.time_signature(4, 2, 24, 8) + ch = 0 + for i in range(127): + midi.note_on(ch, i, 0x64) + midi.update_time(96) + midi.note_off(ch, i, 0x40) + midi.update_time(0) + + midi.update_time(0) + midi.end_of_track() + + midi.eof() # currently optional, should it do the write instead of write?? + + + midi.write() \ No newline at end of file diff --git a/src/mxmMidi/MidiOutStream.py b/src/mxmMidi/MidiOutStream.py new file mode 100644 index 0000000..c128fa6 --- /dev/null +++ b/src/mxmMidi/MidiOutStream.py @@ -0,0 +1,471 @@ +# -*- coding: ISO-8859-1 -*- + +class MidiOutStream: + + + """ + + MidiOutstream is Basically an eventhandler. It is the most central + class in the Midi library. You use it both for writing events to + an output stream, and as an event handler for an input stream. + + This makes it extremely easy to take input from one stream and + send it to another. Ie. if you want to read a Midi file, do some + processing, and send it to a midiport. + + All time values are in absolute values from the opening of a + stream. To calculate time values, please use the MidiTime and + MidiDeltaTime classes. + + """ + + def __init__(self): + + # the time is rather global, so it needs to be stored + # here. Otherwise there would be no really simple way to + # calculate it. The alternative would be to have each event + # handler do it. That sucks even worse! + self._absolute_time = 0 + self._relative_time = 0 + self._current_track = 0 + self._running_status = None + + # time handling event handlers. They should be overwritten with care + + def update_time(self, new_time=0, relative=1): + """ + Updates the time, if relative is true, new_time is relative, + else it's absolute. + """ + if relative: + self._relative_time = new_time + self._absolute_time += new_time + else: + self._relative_time = new_time - self._absolute_time + self._absolute_time = new_time + + def reset_time(self): + """ + reset time to 0 + """ + self._relative_time = 0 + self._absolute_time = 0 + + def rel_time(self): + "Returns the relative time" + return self._relative_time + + def abs_time(self): + "Returns the absolute time" + return self._absolute_time + + # running status methods + + def reset_run_stat(self): + "Invalidates the running status" + self._running_status = None + + def set_run_stat(self, new_status): + "Set the new running status" + self._running_status = new_status + + def get_run_stat(self): + "Set the new running status" + return self._running_status + + # track handling event handlers + + def set_current_track(self, new_track): + "Sets the current track number" + self._current_track = new_track + + def get_current_track(self): + "Returns the current track number" + return self._current_track + + + ##################### + ## Midi events + + + def channel_message(self, message_type, channel, data): + """The default event handler for channel messages""" + pass + + + def note_on(self, channel=0, note=0x40, velocity=0x40): + + """ + channel: 0-15 + note, velocity: 0-127 + """ + pass + + + def note_off(self, channel=0, note=0x40, velocity=0x40): + + """ + channel: 0-15 + note, velocity: 0-127 + """ + pass + + + def aftertouch(self, channel=0, note=0x40, velocity=0x40): + + """ + channel: 0-15 + note, velocity: 0-127 + """ + pass + + + def continuous_controller(self, channel, controller, value): + + """ + channel: 0-15 + controller, value: 0-127 + """ + pass + + + def patch_change(self, channel, patch): + + """ + channel: 0-15 + patch: 0-127 + """ + pass + + + def channel_pressure(self, channel, pressure): + + """ + channel: 0-15 + pressure: 0-127 + """ + pass + + + def pitch_bend(self, channel, value): + + """ + channel: 0-15 + value: 0-16383 + + """ + pass + + + + + ##################### + ## System Exclusive + + def system_exclusive(self, data): + + """ + data: list of values in range(128) + """ + pass + + + ##################### + ## Common events + + def song_position_pointer(self, value): + + """ + value: 0-16383 + """ + pass + + + def song_select(self, songNumber): + + """ + songNumber: 0-127 + """ + pass + + + def tuning_request(self): + + """ + No values passed + """ + pass + + + def midi_time_code(self, msg_type, values): + """ + msg_type: 0-7 + values: 0-15 + """ + pass + + + ######################### + # header does not really belong here. But anyhoo!!! + + def header(self, format=0, nTracks=1, division=96): + + """ + format: type of midi file in [1,2] + nTracks: number of tracks + division: timing division + """ + pass + + + def eof(self): + + """ + End of file. No more events to be processed. + """ + pass + + + ##################### + ## meta events + + + def meta_event(self, meta_type, data): + + """ + Handles any undefined meta events + """ + pass + + + def start_of_track(self, n_track=0): + + """ + n_track: number of track + """ + pass + + + def end_of_track(self): + + """ + n_track: number of track + """ + pass + + + def sequence_number(self, value): + + """ + value: 0-16383 + """ + pass + + + def text(self, text): + + """ + Text event + text: string + """ + pass + + + def copyright(self, text): + + """ + Copyright notice + text: string + """ + pass + + + def sequence_name(self, text): + + """ + Sequence/track name + text: string + """ + pass + + + def instrument_name(self, text): + + """ + text: string + """ + pass + + + def lyric(self, text): + + """ + text: string + """ + pass + + + def marker(self, text): + + """ + text: string + """ + pass + + + def cuepoint(self, text): + + """ + text: string + """ + pass + + + def midi_ch_prefix(self, channel): + + """ + channel: midi channel for subsequent data (deprecated in the spec) + """ + pass + + + def midi_port(self, value): + + """ + value: Midi port (deprecated in the spec) + """ + pass + + + def tempo(self, value): + + """ + value: 0-2097151 + tempo in us/quarternote + (to calculate value from bpm: int(60,000,000.00 / BPM)) + """ + pass + + + def smtp_offset(self, hour, minute, second, frame, framePart): + + """ + hour, + minute, + second: 3 bytes specifying the hour (0-23), minutes (0-59) and + seconds (0-59), respectively. The hour should be + encoded with the SMPTE format, just as it is in MIDI + Time Code. + frame: A byte specifying the number of frames per second (one + of : 24, 25, 29, 30). + framePart: A byte specifying the number of fractional frames, + in 100ths of a frame (even in SMPTE-based tracks + using a different frame subdivision, defined in the + MThd chunk). + """ + pass + + + + def time_signature(self, nn, dd, cc, bb): + + """ + nn: Numerator of the signature as notated on sheet music + dd: Denominator of the signature as notated on sheet music + The denominator is a negative power of 2: 2 = quarter + note, 3 = eighth, etc. + cc: The number of MIDI clocks in a metronome click + bb: The number of notated 32nd notes in a MIDI quarter note + (24 MIDI clocks) + """ + pass + + + + def key_signature(self, sf, mi): + + """ + sf: is a byte specifying the number of flats (-ve) or sharps + (+ve) that identifies the key signature (-7 = 7 flats, -1 + = 1 flat, 0 = key of C, 1 = 1 sharp, etc). + mi: is a byte specifying a major (0) or minor (1) key. + """ + pass + + + + def sequencer_specific(self, data): + + """ + data: The data as byte values + """ + pass + + + + + ##################### + ## realtime events + + def timing_clock(self): + + """ + No values passed + """ + pass + + + + def song_start(self): + + """ + No values passed + """ + pass + + + + def song_stop(self): + + """ + No values passed + """ + pass + + + + def song_continue(self): + + """ + No values passed + """ + pass + + + + def active_sensing(self): + + """ + No values passed + """ + pass + + + + def system_reset(self): + + """ + No values passed + """ + pass + + + +if __name__ == '__main__': + + midiOut = MidiOutStream() + midiOut.update_time(0,0) + midiOut.note_on(0, 63, 127) + midiOut.note_off(0, 63, 127) + + \ No newline at end of file diff --git a/src/mxmMidi/MidiOutStream.pyc b/src/mxmMidi/MidiOutStream.pyc new file mode 100644 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 index 0000000..7a35ff3 --- /dev/null +++ b/src/mxmMidi/MidiToText.py @@ -0,0 +1,184 @@ +# -*- coding: ISO-8859-1 -*- + +from MidiOutStream import MidiOutStream +class MidiToText(MidiOutStream): + + + """ + This class renders a midi file as text. It is mostly used for debugging + """ + + ############################# + # channel events + + def channel_message(self, message_type, channel, data): + """The default event handler for channel messages""" + print 'message_type:%X, channel:%X, data size:%X' % (message_type, channel, len(data)) + + + def note_on(self, channel=0, note=0x40, velocity=0x40): + print 'note_on - ch:%02X, note:%02X, vel:%02X time:%s' % (channel, note, velocity, self.timeInMs()) + + def note_off(self, channel=0, note=0x40, velocity=0x40): + print 'note_off - ch:%02X, note:%02X, vel:%02X time:%s' % (channel, note, velocity, self.abs_time()) + + def aftertouch(self, channel=0, note=0x40, velocity=0x40): + print 'aftertouch', channel, note, velocity + + + def continuous_controller(self, channel, controller, value): + print 'controller - ch: %02X, cont: #%02X, value: %02X' % (channel, controller, value) + + + def patch_change(self, channel, patch): + print 'patch_change - ch:%02X, patch:%02X' % (channel, patch) + + + def channel_pressure(self, channel, pressure): + print 'channel_pressure', channel, pressure + + + def pitch_bend(self, channel, value): + print 'pitch_bend ch:%s, value:%s' % (channel, value) + + + + ##################### + ## Common events + + + def system_exclusive(self, data): + print 'system_exclusive - data size: %s' % len(data) + + + def song_position_pointer(self, value): + print 'song_position_pointer: %s' % value + + + def song_select(self, songNumber): + print 'song_select: %s' % songNumber + + + def tuning_request(self): + print 'tuning_request' + + + def midi_time_code(self, msg_type, values): + print 'midi_time_code - msg_type: %s, values: %s' % (msg_type, values) + + + + ######################### + # header does not really belong here. But anyhoo!!! + + def header(self, format=0, nTracks=1, division=96): + print 'format: %s, nTracks: %s, division: %s' % (format, nTracks, division) + print '----------------------------------' + print '' + print division + self.division = division + + def eof(self): + print 'End of file' + + + def start_of_track(self, n_track=0): + print 'Start - track #%s' % n_track + + + def end_of_track(self): + print 'End of track' + print '' + + + + ############### + # sysex event + + def sysex_event(self, data): + print 'sysex_event - datasize: %X' % len(data) + + + ##################### + ## meta events + + def meta_event(self, meta_type, data): + print 'undefined_meta_event:', meta_type, len(data) + + + def sequence_number(self, value): + print 'sequence_number', value + + + def text(self, text): + print 'text', text + + + def copyright(self, text): + print 'copyright', text + + + def sequence_name(self, text): + print 'sequence_name:', text + + + def instrument_name(self, text): + print 'instrument_name:', text + + + def lyric(self, text): + print 'lyric', text + + + def marker(self, text): + print 'marker', text + + + def cuepoint(self, text): + print 'cuepoint', text + + + def midi_ch_prefix(self, channel): + print 'midi_ch_prefix', channel + + + def midi_port(self, value): + print 'midi_port:', value + + + def tempo(self, value): + print 'tempo:', value + self.tempo = value + + + def smtp_offset(self, hour, minute, second, frame, framePart): + print 'smtp_offset', hour, minute, second, frame, framePart + + + def time_signature(self, nn, dd, cc, bb): + print 'time_signature:', nn, dd, cc, bb + + + def key_signature(self, sf, mi): + print 'key_signature', sf, mi + + + def sequencer_specific(self, data): + print 'sequencer_specific', len(data) + + def timeInMs(self): + return(long(self.abs_time())*1000000000/(long(self.tempo)*long(self.division))) + + + +if __name__ == '__main__': + + # get data + test_file = '../songs/midis/test.mid' + f = open(test_file, 'rb') + + # do parsing + from MidiInFile import MidiInFile + midiIn = MidiInFile(MidiToText(), f) + midiIn.read() + f.close() diff --git a/src/mxmMidi/RawInstreamFile.py b/src/mxmMidi/RawInstreamFile.py new file mode 100644 index 0000000..0c2eba6 --- /dev/null +++ b/src/mxmMidi/RawInstreamFile.py @@ -0,0 +1,108 @@ +# -*- coding: ISO-8859-1 -*- + +# standard library imports +from types import StringType +from struct import unpack + +# custom import +from DataTypeConverters import readBew, readVar, varLen + + +class RawInstreamFile: + + """ + + It parses and reads data from an input file. It takes care of big + endianess, and keeps track of the cursor position. The midi parser + only reads from this object. Never directly from the file. + + """ + + def __init__(self, infile=''): + """ + If 'file' is a string we assume it is a path and read from + that file. + If it is a file descriptor we read from the file, but we don't + close it. + Midi files are usually pretty small, so it should be safe to + copy them into memory. + """ + if infile: + if isinstance(infile, StringType): + infile = open(infile, 'rb') + self.data = infile.read() + infile.close() + else: + # don't close the f + self.data = infile.read() + else: + self.data = '' + # start at beginning ;-) + self.cursor = 0 + + + # setting up data manually + + def setData(self, data=''): + "Sets the data from a string." + self.data = data + + # cursor operations + + def setCursor(self, position=0): + "Sets the absolute position if the cursor" + self.cursor = position + + + def getCursor(self): + "Returns the value of the cursor" + return self.cursor + + + def moveCursor(self, relative_position=0): + "Moves the cursor to a new relative position" + self.cursor += relative_position + + # native data reading functions + + def nextSlice(self, length, move_cursor=1): + "Reads the next text slice from the raw data, with length" + c = self.cursor + slc = self.data[c:c+length] + if move_cursor: + self.moveCursor(length) + return slc + + + def readBew(self, n_bytes=1, move_cursor=1): + """ + Reads n bytes of date from the current cursor position. + Moves cursor if move_cursor is true + """ + return readBew(self.nextSlice(n_bytes, move_cursor)) + + + def readVarLen(self): + """ + Reads a variable length value from the current cursor position. + Moves cursor if move_cursor is true + """ + MAX_VARLEN = 4 # Max value varlen can be + var = readVar(self.nextSlice(MAX_VARLEN, 0)) + # only move cursor the actual bytes in varlen + self.moveCursor(varLen(var)) + return var + + + +if __name__ == '__main__': + + test_file = 'test/midifiles/minimal.mid' + fis = RawInstreamFile(test_file) + print fis.nextSlice(len(fis.data)) + + test_file = 'test/midifiles/cubase-minimal.mid' + cubase_minimal = open(test_file, 'rb') + fis2 = RawInstreamFile(cubase_minimal) + print fis2.nextSlice(len(fis2.data)) + cubase_minimal.close() diff --git a/src/mxmMidi/RawInstreamFile.pyc b/src/mxmMidi/RawInstreamFile.pyc new file mode 100644 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 index 0000000..73eed31 --- /dev/null +++ b/src/mxmMidi/RawOutstreamFile.py @@ -0,0 +1,69 @@ +# -*- coding: ISO-8859-1 -*- + +# standard library imports +import sys +from types import StringType +from struct import unpack +from cStringIO import StringIO + +# custom import +from DataTypeConverters import writeBew, writeVar, fromBytes + +class RawOutstreamFile: + + """ + + Writes a midi file to disk. + + """ + + def __init__(self, outfile=''): + self.buffer = StringIO() + self.outfile = outfile + + + # native data reading functions + + + def writeSlice(self, str_slice): + "Writes the next text slice to the raw data" + self.buffer.write(str_slice) + + + def writeBew(self, value, length=1): + "Writes a value to the file as big endian word" + self.writeSlice(writeBew(value, length)) + + + def writeVarLen(self, value): + "Writes a variable length word to the file" + var = self.writeSlice(writeVar(value)) + + + def write(self): + "Writes to disc" + if self.outfile: + if isinstance(self.outfile, StringType): + outfile = open(self.outfile, 'wb') + outfile.write(self.getvalue()) + outfile.close() + else: + self.outfile.write(self.getvalue()) + else: + sys.stdout.write(self.getvalue()) + + def getvalue(self): + return self.buffer.getvalue() + + +if __name__ == '__main__': + + out_file = 'test/midifiles/midiout.mid' + out_file = '' + rawOut = RawOutstreamFile(out_file) + rawOut.writeSlice('MThd') + rawOut.writeBew(6, 4) + rawOut.writeBew(1, 2) + rawOut.writeBew(2, 2) + rawOut.writeBew(15360, 2) + rawOut.write() diff --git a/src/mxmMidi/__init__.py b/src/mxmMidi/__init__.py new file mode 100644 index 0000000..b2d2031 --- /dev/null +++ b/src/mxmMidi/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: ISO-8859-1 -*- + +#import MidiOutStream +#import MidiInStream +#import MidiInFile +#import MidiToText \ No newline at end of file diff --git a/src/mxmMidi/changes.txt b/src/mxmMidi/changes.txt new file mode 100644 index 0000000..8b6670a --- /dev/null +++ b/src/mxmMidi/changes.txt @@ -0,0 +1,45 @@ +------------------------------------------------------------------------ +r409 | maxm | 2006-01-05 16:37:29 +0100 (to, 05 jan 2006) | 1 line + +Made RawOutstreamFile.py write to std out if no outfile is given. +------------------------------------------------------------------------ +r403 | maxm | 2006-01-05 13:34:11 +0100 (to, 05 jan 2006) | 1 line + + +------------------------------------------------------------------------ +r402 | maxm | 2006-01-05 13:33:56 +0100 (to, 05 jan 2006) | 1 line + +- Fixed minor bugs, added coding headers +------------------------------------------------------------------------ +r401 | maxm | 2006-01-01 23:09:17 +0100 (s_, 01 jan 2006) | 1 line + +Fixed sysex dispathcer bug. +------------------------------------------------------------------------ +r268 | maxm | 2005-02-04 12:26:59 +0100 (fr, 04 feb 2005) | 1 line + + +------------------------------------------------------------------------ +r128 | maxm | 2004-12-18 14:05:27 +0100 (l_, 18 dec 2004) | 1 line + +Fixed bug when using relative time +------------------------------------------------------------------------ +r15 | maxm | 2004-03-09 15:01:41 +0100 (ti, 09 mar 2004) | 1 line + +made a copy to meta folder +------------------------------------------------------------------------ +r13 | maxm | 2004-03-09 09:17:23 +0100 (ti, 09 mar 2004) | 1 line + +Deleted .pyc files +------------------------------------------------------------------------ +r12 | maxm | 2004-03-09 09:15:54 +0100 (ti, 09 mar 2004) | 1 line + +Removed file/folder +------------------------------------------------------------------------ +r3 | maxm | 2004-03-08 23:16:25 +0100 (ma, 08 mar 2004) | 1 line + +Adde midi +------------------------------------------------------------------------ +r1 | maxm | 2004-03-08 22:49:23 +0100 (ma, 08 mar 2004) | 1 line + +Initial Import +------------------------------------------------------------------------ diff --git a/src/mxmMidi/constants.py b/src/mxmMidi/constants.py new file mode 100644 index 0000000..81b91bc --- /dev/null +++ b/src/mxmMidi/constants.py @@ -0,0 +1,210 @@ +# -*- coding: ISO-8859-1 -*- + +################################################### +## Definitions of the different midi events + + + +################################################### +## Midi channel events (The most usual events) +## also called "Channel Voice Messages" + +NOTE_OFF = 0x80 +# 1000cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity) + +NOTE_ON = 0x90 +# 1001cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity) + +AFTERTOUCH = 0xA0 +# 1010cccc 0nnnnnnn 0vvvvvvv (channel, note, velocity) + +CONTINUOUS_CONTROLLER = 0xB0 # see Channel Mode Messages!!! +# 1011cccc 0ccccccc 0vvvvvvv (channel, controller, value) + +PATCH_CHANGE = 0xC0 +# 1100cccc 0ppppppp (channel, program) + +CHANNEL_PRESSURE = 0xD0 +# 1101cccc 0ppppppp (channel, pressure) + +PITCH_BEND = 0xE0 +# 1110cccc 0vvvvvvv 0wwwwwww (channel, value-lo, value-hi) + + +################################################### +## Channel Mode Messages (Continuous Controller) +## They share a status byte. +## The controller makes the difference here + +# High resolution continuous controllers (MSB) + +BANK_SELECT = 0x00 +MODULATION_WHEEL = 0x01 +BREATH_CONTROLLER = 0x02 +FOOT_CONTROLLER = 0x04 +PORTAMENTO_TIME = 0x05 +DATA_ENTRY = 0x06 +CHANNEL_VOLUME = 0x07 +BALANCE = 0x08 +PAN = 0x0A +EXPRESSION_CONTROLLER = 0x0B +EFFECT_CONTROL_1 = 0x0C +EFFECT_CONTROL_2 = 0x0D +GEN_PURPOSE_CONTROLLER_1 = 0x10 +GEN_PURPOSE_CONTROLLER_2 = 0x11 +GEN_PURPOSE_CONTROLLER_3 = 0x12 +GEN_PURPOSE_CONTROLLER_4 = 0x13 + +# High resolution continuous controllers (LSB) + +BANK_SELECT = 0x20 +MODULATION_WHEEL = 0x21 +BREATH_CONTROLLER = 0x22 +FOOT_CONTROLLER = 0x24 +PORTAMENTO_TIME = 0x25 +DATA_ENTRY = 0x26 +CHANNEL_VOLUME = 0x27 +BALANCE = 0x28 +PAN = 0x2A +EXPRESSION_CONTROLLER = 0x2B +EFFECT_CONTROL_1 = 0x2C +EFFECT_CONTROL_2 = 0x2D +GENERAL_PURPOSE_CONTROLLER_1 = 0x30 +GENERAL_PURPOSE_CONTROLLER_2 = 0x31 +GENERAL_PURPOSE_CONTROLLER_3 = 0x32 +GENERAL_PURPOSE_CONTROLLER_4 = 0x33 + +# Switches + +SUSTAIN_ONOFF = 0x40 +PORTAMENTO_ONOFF = 0x41 +SOSTENUTO_ONOFF = 0x42 +SOFT_PEDAL_ONOFF = 0x43 +LEGATO_ONOFF = 0x44 +HOLD_2_ONOFF = 0x45 + +# Low resolution continuous controllers + +SOUND_CONTROLLER_1 = 0x46 # (TG: Sound Variation; FX: Exciter On/Off) +SOUND_CONTROLLER_2 = 0x47 # (TG: Harmonic Content; FX: Compressor On/Off) +SOUND_CONTROLLER_3 = 0x48 # (TG: Release Time; FX: Distortion On/Off) +SOUND_CONTROLLER_4 = 0x49 # (TG: Attack Time; FX: EQ On/Off) +SOUND_CONTROLLER_5 = 0x4A # (TG: Brightness; FX: Expander On/Off)75 SOUND_CONTROLLER_6 (TG: Undefined; FX: Reverb OnOff) +SOUND_CONTROLLER_7 = 0x4C # (TG: Undefined; FX: Delay OnOff) +SOUND_CONTROLLER_8 = 0x4D # (TG: Undefined; FX: Pitch Transpose OnOff) +SOUND_CONTROLLER_9 = 0x4E # (TG: Undefined; FX: Flange/Chorus OnOff) +SOUND_CONTROLLER_10 = 0x4F # (TG: Undefined; FX: Special Effects OnOff) +GENERAL_PURPOSE_CONTROLLER_5 = 0x50 +GENERAL_PURPOSE_CONTROLLER_6 = 0x51 +GENERAL_PURPOSE_CONTROLLER_7 = 0x52 +GENERAL_PURPOSE_CONTROLLER_8 = 0x53 +PORTAMENTO_CONTROL = 0x54 # (PTC) (0vvvvvvv is the source Note number) (Detail) +EFFECTS_1 = 0x5B # (Ext. Effects Depth) +EFFECTS_2 = 0x5C # (Tremelo Depth) +EFFECTS_3 = 0x5D # (Chorus Depth) +EFFECTS_4 = 0x5E # (Celeste Depth) +EFFECTS_5 = 0x5F # (Phaser Depth) +DATA_INCREMENT = 0x60 # (0vvvvvvv is n/a; use 0) +DATA_DECREMENT = 0x61 # (0vvvvvvv is n/a; use 0) +NON_REGISTERED_PARAMETER_NUMBER = 0x62 # (LSB) +NON_REGISTERED_PARAMETER_NUMBER = 0x63 # (MSB) +REGISTERED_PARAMETER_NUMBER = 0x64 # (LSB) +REGISTERED_PARAMETER_NUMBER = 0x65 # (MSB) + +# Channel Mode messages - (Detail) + +ALL_SOUND_OFF = 0x78 +RESET_ALL_CONTROLLERS = 0x79 +LOCAL_CONTROL_ONOFF = 0x7A +ALL_NOTES_OFF = 0x7B +OMNI_MODE_OFF = 0x7C # (also causes ANO) +OMNI_MODE_ON = 0x7D # (also causes ANO) +MONO_MODE_ON = 0x7E # (Poly Off; also causes ANO) +POLY_MODE_ON = 0x7F # (Mono Off; also causes ANO) + + + +################################################### +## System Common Messages, for all channels + +SYSTEM_EXCLUSIVE = 0xF0 +# 11110000 0iiiiiii 0ddddddd ... 11110111 + +MTC = 0xF1 # MIDI Time Code Quarter Frame +# 11110001 + +SONG_POSITION_POINTER = 0xF2 +# 11110010 0vvvvvvv 0wwwwwww (lo-position, hi-position) + +SONG_SELECT = 0xF3 +# 11110011 0sssssss (songnumber) + +#UNDEFINED = 0xF4 +## 11110100 + +#UNDEFINED = 0xF5 +## 11110101 + +TUNING_REQUEST = 0xF6 +# 11110110 + +END_OFF_EXCLUSIVE = 0xF7 # terminator +# 11110111 # End of system exclusive + + +################################################### +## Midifile meta-events + +SEQUENCE_NUMBER = 0x00 # 00 02 ss ss (seq-number) +TEXT = 0x01 # 01 len text... +COPYRIGHT = 0x02 # 02 len text... +SEQUENCE_NAME = 0x03 # 03 len text... +INSTRUMENT_NAME = 0x04 # 04 len text... +LYRIC = 0x05 # 05 len text... +MARKER = 0x06 # 06 len text... +CUEPOINT = 0x07 # 07 len text... +PROGRAM_NAME = 0x08 # 08 len text... +DEVICE_NAME = 0x09 # 09 len text... + +MIDI_CH_PREFIX = 0x20 # MIDI channel prefix assignment (unofficial) + +MIDI_PORT = 0x21 # 21 01 port, legacy stuff but still used +END_OF_TRACK = 0x2F # 2f 00 +TEMPO = 0x51 # 51 03 tt tt tt (tempo in us/quarternote) +SMTP_OFFSET = 0x54 # 54 05 hh mm ss ff xx +TIME_SIGNATURE = 0x58 # 58 04 nn dd cc bb +KEY_SIGNATURE = 0x59 # ??? len text... +SPECIFIC = 0x7F # Sequencer specific event + +FILE_HEADER = 'MThd' +TRACK_HEADER = 'MTrk' + +################################################### +## System Realtime messages +## I don't supose these are to be found in midi files?! + +TIMING_CLOCK = 0xF8 +# undefined = 0xF9 +SONG_START = 0xFA +SONG_CONTINUE = 0xFB +SONG_STOP = 0xFC +# undefined = 0xFD +ACTIVE_SENSING = 0xFE +SYSTEM_RESET = 0xFF + + +################################################### +## META EVENT, it is used only in midi files. +## In transmitted data it means system reset!!! + +META_EVENT = 0xFF +# 11111111 + + +################################################### +## Helper functions + +def is_status(byte): + return (byte & 0x80) == 0x80 # 1000 0000 + + diff --git a/src/mxmMidi/constants.pyc b/src/mxmMidi/constants.pyc new file mode 100644 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 index 0000000..d8d1842 --- /dev/null +++ b/src/mxmMidi/example_mimimal_type0.py @@ -0,0 +1,29 @@ +from MidiOutFile import MidiOutFile + +""" +This is an example of the smallest possible type 0 midi file, where +all the midi events are in the same track. +""" + +out_file = 'midiout/minimal_type0.mid' +midi = MidiOutFile(out_file) + +# non optional midi framework +midi.header() +midi.start_of_track() + + +# musical events + +midi.update_time(0) +midi.note_on(channel=0, note=0x40) + +midi.update_time(192) +midi.note_off(channel=0, note=0x40) + + +# non optional midi framework +midi.update_time(0) +midi.end_of_track() + +midi.eof() diff --git a/src/mxmMidi/example_print_channel_0.py b/src/mxmMidi/example_print_channel_0.py new file mode 100644 index 0000000..2dbe3e4 --- /dev/null +++ b/src/mxmMidi/example_print_channel_0.py @@ -0,0 +1,23 @@ +from MidiOutStream import MidiOutStream +from MidiInFile import MidiInFile + +""" +This prints all note on events on midi channel 0 +""" + + +class Transposer(MidiOutStream): + + "Transposes all notes by 1 octave" + + def note_on(self, channel=0, note=0x40, velocity=0x40): + if channel == 0: + print channel, note, velocity, self.rel_time() + + +event_handler = Transposer() + +in_file = 'midiout/minimal_type0.mid' +midi_in = MidiInFile(event_handler, in_file) +midi_in.read() + diff --git a/src/mxmMidi/example_print_events.py b/src/mxmMidi/example_print_events.py new file mode 100644 index 0000000..b1e27f9 --- /dev/null +++ b/src/mxmMidi/example_print_events.py @@ -0,0 +1,28 @@ +from MidiToText import MidiToText + +""" +This is an example that uses the MidiToText eventhandler. When an +event is triggered on it, it prints the event to the console. +""" + +midi = MidiToText() + +# non optional midi framework +midi.header() +midi.start_of_track() + + +# musical events + +midi.update_time(0) +midi.note_on(channel=0, note=0x40) + +midi.update_time(192) +midi.note_off(channel=0, note=0x40) + + +# non optional midi framework +midi.update_time(0) +midi.end_of_track() # not optional! + +midi.eof() diff --git a/src/mxmMidi/example_print_file.py b/src/mxmMidi/example_print_file.py new file mode 100644 index 0000000..4fcd531 --- /dev/null +++ b/src/mxmMidi/example_print_file.py @@ -0,0 +1,19 @@ +""" +This is an example that uses the MidiToText eventhandler. When an +event is triggered on it, it prints the event to the console. + +It gets the events from the MidiInFile. + +So it prints all the events from the infile to the console. great for +debugging :-s +""" + + +# get data +test_file = 'test/midifiles/minimal-cubase-type0.mid' + +# do parsing +from MidiInFile import MidiInFile +from MidiToText import MidiToText # the event handler +midiIn = MidiInFile(MidiToText(), test_file) +midiIn.read() diff --git a/src/mxmMidi/example_transpose_octave.py b/src/mxmMidi/example_transpose_octave.py new file mode 100644 index 0000000..57dbbff --- /dev/null +++ b/src/mxmMidi/example_transpose_octave.py @@ -0,0 +1,40 @@ +from MidiOutFile import MidiOutFile +from MidiInFile import MidiInFile + +""" +This is an example of the smallest possible type 0 midi file, where +all the midi events are in the same track. +""" + + +class Transposer(MidiOutFile): + + "Transposes all notes by 1 octave" + + def _transp(self, ch, note): + if ch != 9: # not the drums! + note += 12 + if note > 127: + note = 127 + return note + + + def note_on(self, channel=0, note=0x40, velocity=0x40): + note = self._transp(channel, note) + MidiOutFile.note_on(self, channel, note, velocity) + + + def note_off(self, channel=0, note=0x40, velocity=0x40): + note = self._transp(channel, note) + MidiOutFile.note_off(self, channel, note, velocity) + + +out_file = 'midiout/transposed.mid' +midi_out = Transposer(out_file) + +#in_file = 'midiout/minimal_type0.mid' +#in_file = 'test/midifiles/Lola.mid' +in_file = 'test/midifiles/tennessee_waltz.mid' +midi_in = MidiInFile(midi_out, in_file) +midi_in.read() + diff --git a/src/mxmMidi/experimental/EventDispatcherBase.py b/src/mxmMidi/experimental/EventDispatcherBase.py new file mode 100644 index 0000000..71bde62 --- /dev/null +++ b/src/mxmMidi/experimental/EventDispatcherBase.py @@ -0,0 +1,76 @@ +class EventDispatcherBase: + + + def __init__(self, outstream): + """ + The event dispatcher generates events on the outstream. This + is the base implementation. It is more like an interface for + how the EventDispatcher. It has the methods that are used by + the Midi Parser. + """ + # internal values, don't mess with 'em directly + self.outstream = outstream + + + def eof(self): + "End of file!" + self.outstream.eof() + + + def update_time(self, new_time=0, relative=1): + "Updates relative/absolute time." + self.outstream.update_time(new_time, relative) + + # 'official' midi events + + def header(self, format, nTracks, division): + "Triggers the header event" + self.outstream.header(format, nTracks, division) + + + def start_of_track(self, current_track): + "Triggers the start of track event" + + # I do this twice so that users can overwrite the + # start_of_track event handler without worrying whether the + # track number is updated correctly. + self.outstream.set_current_track(current_track) + self.outstream.start_of_track(current_track) + + # Event dispatchers for midi events + + def channel_messages(self, hi_nible, channel, data): + "Dispatches channel messages" + self.outstream.channel_message(hi_nible, channel, data) + + + def continuous_controllers(self, channel, controller, value): + "Dispatches channel messages" + self.outstream.continuous_controller(channel, controller, value) + + + def system_commons(self, common_type, common_data): + "Dispatches system common messages" + self.outstream.system_common(common_type, common_data) + + + def meta_event(self, meta_type, data): + "Dispatches meta events" + self.outstream.meta_event(meta_type, data) + + + def sysex_events(self, data): + "Dispatcher for sysex events" + self.outstream.sysex_event(data) + + + +if __name__ == '__main__': + + + from MidiToText import MidiToText + from constants import NOTE_ON + + outstream = MidiToText() + dispatcher = EventDispatcherBase(outstream) + 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 index 0000000..25ceed4 --- /dev/null +++ b/src/mxmMidi/experimental/MidiOutPassThrough.py @@ -0,0 +1,182 @@ +from MidiOutStream import MidiOutStream + +class MidiOutPassThrough(MidiOutStream): + + + """ + + This class i mainly used for testing the event dispatcher. The + methods just returns the passed parameters as a tupple. + + """ + + + ##################### + ## Midi channel events + + + def note_on(self, channel, note, velocity, time=None): + return channel, note, velocity, time + + + def note_off(self, channel, note, velocity, time=None): + return channel, note, velocity, time + + + def aftertouch(self, channel, note, velocity, time=None): + return channel, note, velocity, time + + + def continuous_controller(self, channel, controller, value, time=None): + return channel, controller, value, time + + + def patch_change(self, channel, patch, time=None): + return channel, patch, time + + + def channel_pressure(self, channel, pressure, time=None): + return channel, pressure, time + + + ##################### + ## defined continuous controller events + +# def cc_ + + ##################### + ## Common events + + def system_exclusive(self, data, time=None): + return data, time + + + def song_position_pointer(self, hiPos, loPos, time=None): + return hiPos, loPos, time + + + def song_select(self, songNumber, time=None): + return songNumber, time + + + def tuning_request(self, time=None): + return time + + + + ######################### + # header does not really belong here. But anyhoo!!! + + def header(self, format, nTracks, division): + return format, nTracks, division + + + def eof(self): + return 'eof' + + + ##################### + ## meta events + + def start_of_track(self, n_track=0): + return n_track + + + def end_of_track(self, n_track=0, time=None): + return n_track, time + + + def sequence_number(self, hiVal, loVal, time=None): + return hiVal, loVal, time + + + def text(self, text, time=None): + return text, time + + + def copyright(self, text, time=None): + return text, time + + + def sequence_name(self, text, time=None): + return text, time + + + def instrument_name(self, text, time=None): + return text, time + + + def lyric(self, text, time=None): + return text, time + + + def marker(self, text, time=None): + return text, time + + + def cuepoint(self, text, time=None): + return text, time + + + def midi_port(self, value, time=None): + return value, time + + + def tempo(self, value, time=None): + return value, time + + def smtp_offset(self, hour, minute, second, frame, framePart, time=None): + return hour, minute, second, frame, framePart, time + + + def time_signature(self, nn, dd, cc, bb, time=None): + return nn, dd, cc, bb, time + + + def key_signature(self, sf, mi, time=None): + return sf, mi, time + + + def sequencer_specific(self, data, time=None): + return data, time + + + + + ##################### + ## realtime events + + def timing_clock(self, time=None): + return time + + + def song_start(self, time=None): + return time + + + def song_stop(self, time=None): + return time + + + def song_continue(self, time=None): + return time + + + def active_sensing(self, time=None): + return time + + + def system_reset(self, time=None): + return time + + + + + +if __name__ == '__main__': + + midiOut = MidiOutStream() + midiOut.note_on(0, 63, 127, 0) + midiOut.note_off(0, 63, 127, 384) + + \ No newline at end of file diff --git a/src/mxmMidi/experimental/MidiOutStreamBase.py b/src/mxmMidi/experimental/MidiOutStreamBase.py new file mode 100644 index 0000000..1abada0 --- /dev/null +++ b/src/mxmMidi/experimental/MidiOutStreamBase.py @@ -0,0 +1,135 @@ +class MidiOutStreamBase: + + + """ + + MidiOutStreamBase is Basically an eventhandler. It is the most central + class in the Midi library. You use it both for writing events to + an output stream, and as an event handler for an input stream. + + This makes it extremely easy to take input from one stream and + send it to another. Ie. if you want to read a Midi file, do some + processing, and send it to a midiport. + + All time values are in absolute values from the opening of a + stream. To calculate time values, please use the MidiTime and + MidiDeltaTime classes. + + """ + + def __init__(self): + + # the time is rather global, so it needs to be stored + # here. Otherwise there would be no really simple way to + # calculate it. The alternative would be to have each event + # handler do it. That sucks even worse! + self._absolute_time = 0 + self._relative_time = 0 + self._current_track = 0 + + # time handling event handlers. They should overwritten with care + + def update_time(self, new_time=0, relative=1): + """ + Updates the time, if relative is true, new_time is relative, + else it's absolute. + """ + if relative: + self._relative_time = new_time + self._absolute_time += new_time + else: + self._absolute_time = new_time + self._relative_time = new_time - self._absolute_time + + def rel_time(self): + "Returns the relative time" + return self._relative_time + + def abs_time(self): + "Returns the absolute time" + return self._absolute_time + + # track handling event handlers + + def set_current_track(self, new_track): + "Sets the current track number" + self._current_track = new_track + + def get_current_track(self): + "Returns the current track number" + return self._current_track + + + ##################### + ## Midi events + + + def channel_message(self, message_type, channel, data): + """The default event handler for channel messages""" + pass + + + ##################### + ## Common events + + def system_exclusive(self, data): + + """The default event handler for system_exclusive messages""" + pass + + + def system_common(self, common_type, common_data): + + """The default event handler for system common messages""" + pass + + + ######################### + # header does not really belong here. But anyhoo!!! + + def header(self, format, nTracks, division): + + """ + format: type of midi file in [1,2] + nTracks: number of tracks + division: timing division + """ + pass + + + def start_of_track(self, n_track=0): + + """ + n_track: number of track + """ + pass + + + def eof(self): + + """ + End of file. No more events to be processed. + """ + pass + + + ##################### + ## meta events + + + def meta_event(self, meta_type, data, time): + + """The default event handler for meta_events""" + pass + + + + +if __name__ == '__main__': + + midiOut = MidiOutStreamBase() + midiOut.update_time(0,0) + midiOut.note_on(0, 63, 127) + midiOut.note_off(0, 63, 127) + + \ No newline at end of file diff --git a/src/mxmMidi/experimental/readme.txt b/src/mxmMidi/experimental/readme.txt new file mode 100644 index 0000000..4234118 --- /dev/null +++ b/src/mxmMidi/experimental/readme.txt @@ -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 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 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 index 0000000..d3c7431 --- /dev/null +++ b/src/mxmMidi/readme.txt @@ -0,0 +1,50 @@ +This is the documentation for the midi package +============================================== + + +The modules follows the following naming convention: + + +MidiIn.py +--------------------- + +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. + + +MidiOut.py +---------------------- + +The MidiOut modules are event handlers, that reacts to events generated by a a Midi in module. + + +MidiInBase.py +--------------- + +The base class for input streams. + + +MidiOutBase.py +---------------- + +The base class for the output streams. + + + + + + +Internal modules +================ + + +DataTypeConverters.py +--------------------- + +A collection of functions that converts the special data types used in midi files to and from strings. + + +constants.py +------------ + +A collection of constants from the midi spec. + diff --git a/src/mxmMidi/test/midifiles/midiout.mid b/src/mxmMidi/test/midifiles/midiout.mid new file mode 100644 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 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 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 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 index 0000000..dd9f61e --- /dev/null +++ b/src/mxmMidi/test/midifiles/minimal.txt @@ -0,0 +1,26 @@ +MThd | Format=1 | # of Tracks=2 | Division=15360 + + + + +Track #0 ****************************************** + Time Event + + 1: 1: 0 |Time Sig | 4/4 | MIDI-clocks\click=24 | 32nds\quarter=8 + |Tempo | BPM=120 | micros\quarter=500000 + 101: 1: 0 |End of track| + + + + + +Track #1 ****************************************** + Time Event + + 1: 1: 0 |Track Name | len=7 | + 0x53 0x79 0x6E 0x74 0x68 0x20 0x31 + |Instrument | len=7 | + 0x53 0x79 0x6E 0x74 0x68 0x20 0x31 + |On Note | chan= 1 | pitch=C 1 | vol=127 + 2: 1: 0 |Off Note | chan= 1 | pitch=c 1 | vol=0 + 101: 1: 0 |End of track| diff --git a/src/mxmMidi/test/midifiles/minimal_analyse.txt b/src/mxmMidi/test/midifiles/minimal_analyse.txt new file mode 100644 index 0000000..498dfe9 --- /dev/null +++ b/src/mxmMidi/test/midifiles/minimal_analyse.txt @@ -0,0 +1,54 @@ +4D 54 68 64 MThd +00 00 00 06 len: 6 +00 01 Type: 1 +00 02 tracks: 2 +3C 00 tempo: 15360 + + + +4D 54 72 6B MTrk +00 00 00 16 len: 22 + +00 time + FF 58 04 Time Signature + 04 02 18 08 4/4 24 8 + +00 time + FF 51 03 TEMPO: + 07 A1 20 500.000 mySec + +82 F7 80 00 time ## oh bugger, it's buggy. + FF 2F 00 End Of Track + + + +4D 54 72 6B MTrk +00 00 00 2C len: 44 + +00 time + FF 03 Sequence/Track Name + 07 len: 7 + 53 79 6E 74 + 68 20 31 'Synth 1' + +00 time + FF 04 Instrument + 07 len: 7 + 53 79 6E 74 + 68 20 31 'Synth 1' + +00 time + FF 21 01 Midi port + 04 Port #5 + +00 time + 90 24 7F Note ON + 83 E0 00 Note OFF + +00 time + 80 24 00 Note OFF + +00 82 F3 A0 + +00 + 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 index 0000000..f3f59cd --- /dev/null +++ b/src/mxmMidi/test/midifiles/readme.txt @@ -0,0 +1,7 @@ +These files are used for testing the midi package +================================================= + +minimal.mid +----------- + +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 index 0000000..c40adf3 --- /dev/null +++ b/src/mxmMidi/test/readme.txt @@ -0,0 +1,3 @@ +Embarrasingly empty. + +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 index 0000000..446ba66 --- /dev/null +++ b/src/mxmMidi/version.txt @@ -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 index 0000000..5cc6d1c --- /dev/null +++ b/src/pgu/__init__.py @@ -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 index 0000000..993eab0 --- /dev/null +++ b/src/pgu/algo.py @@ -0,0 +1,143 @@ +"""Some handy algorithms for use in games, etc. + +

please note that this file is alpha, and is subject to modification in +future versions of pgu!

+""" + +# 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 + +
astar(start,end,layer,dist): return [list of positions]
+ +
+
start
start position +
end
end position +
layer
a grid where zero cells are open and non-zero cells are walls +
dist
a distance function dist(a,b) - manhattan distance is used by default +
+ +

returns a list of positions from start to end

+ """ + + 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 + +
getline(a,b): return [list of points]
+ +
+
a
starting point +
b
ending point +
+ +

returns a list of points from a to b

+ """ + + 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 index 0000000..c33d380 --- /dev/null +++ b/src/pgu/ani.py @@ -0,0 +1,90 @@ +"""animation loading and manipulating functions. + +

please note that this file is alpha, and is subject to modification in +future versions of pgu!

+""" + +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 + +
ani_load(tv,name,image,size,shape,parts)
+ +
+
tv
vid to load into +
name
prefix name to give the images +
image
image to load anis from +
size
w,h size of image +
shape
shape of image (usually a subset of 0,0,w,h) used for collision detection +
parts
list of parts to divide the animation into +
for example parts = [4,5] would yield 4 animations 5 frames long, 20 total +
for example parts = [a,b,c] would yield ... images['name.a.b.c'] ..., a*b*c total +
+ + """ + 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 + +
image_rotate(tv,name,image,shape,angles,diff=0)
+ +
+
tv
vid to load into +
name
prefix name to give the images +
image
image to load anis from +
shape
shape fimage (usually a subset of 0,0,w,h) used for collision detection +
angles
a list of angles to render in degrees +
diff
a number to add to the angles, to correct for source image not actually being at 0 degrees +
+ """ + 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 index 0000000..76be583 --- /dev/null +++ b/src/pgu/engine.py @@ -0,0 +1,154 @@ +"""a state engine. +""" +import pygame +from pygame.locals import * + +class State: + """Template Class -- for a state. + +
State(game,value...)
+ +
+
game
The state engine. +
value
I usually pass in a custom value to a state +
+ +

For all of the template methods, they should return None unless they return + a new State to switch the engine to.

+ """ + 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. + +
State.init()
+ """ + return + def paint(self,screen): + """Template Method - Paint the screen. Called once after the state is selected. + +

State is responsible for calling pygame.display.flip() or whatever.

+ +
State.paint(screen)
+ """ + return + + def repaint(self): + """Template Method - Request a repaint of this state. + +
State.repaint()
+ """ + self._paint = 1 + def update(self,screen): + """Template Method - Update the screen. + +

State is responsible for calling pygame.display.update(updates) or whatever.

+ +
State.update(screen)
+ """ + return + def loop(self): + """Template Method - Run a logic loop, called once per frame. + +
State.loop()
+ """ + return + def event(self,e): + """Template Method - Recieve an event. + +
State.event(e)
+ """ + return + +class Quit(State): + """A state to quit the state engine. + +
Quit(game,value)
+ """ + + 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). + +
Game.run(state,screen=None)
+ +
+
game
a state engine +
screen
the screen +
+ """ + 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. + +
Game.init()
+ """ + return + + def tick(self): + """Template Method - called once per frame, usually for timer purposes. + +
Game.tick()
+ """ + pygame.time.wait(10) + + def event(self,e): + """Template Method - called with each event, so the engine can capture special events. + +
Game.event(e): return captured
+ +

return a True value if the event is captured and does not need to be passed onto the current + state

+ """ + 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 index 0000000..ab6f73d --- /dev/null +++ b/src/pgu/fonts.py @@ -0,0 +1,130 @@ +"""Some handy font-like objects. + +

please note that this file is alpha, and is subject to modification in +future versions of pgu!

+""" + +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 + +

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.

+ +
TileFont(fname,size,hints,scale=None,sensitive=False)
+ +
+
size
the dimensions of the characters +
hints
a string of hints "abcdefg..." +
scale
size to scale font to +
sensitive
case sensitivity +
+ """ + + 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. + +
BorderFont(font,size=1,color=(0,0,0))
+ +
+
size
width of border; defaults 0 +
color
color of border; default (0,0,0) +
+ """ + 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 index 0000000..256fb63 --- /dev/null +++ b/src/pgu/gui/__init__.py @@ -0,0 +1,32 @@ +import pygame +from pygame.locals import * + +from theme import Theme +from style import Style +from widget import Widget +from surface import subsurface, ProxySurface +from const import * + +from container import Container +from app import App, Desktop +from table import Table +from document import Document +#html +from area import SlideBox, ScrollArea, List + +from form import Form +from group import Group + +from basic import Spacer, Color, Label, Image, parse_color +from button import Icon, Button, Switch, Checkbox, Radio, Tool, Link +from input import Input, Password +from keysym import Keysym +from slider import VSlider, HSlider, VScrollBar, HScrollBar +from select import Select +from misc import ProgressBar + +from menus import Menus +from dialog import Dialog, FileDialog +from textarea import TextArea + +from deprecated import Toolbox, action_open, action_setvalue, action_quit, action_exec diff --git a/src/pgu/gui/app.py b/src/pgu/gui/app.py new file mode 100644 index 0000000..857af98 --- /dev/null +++ b/src/pgu/gui/app.py @@ -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. + +
App(theme=None)
+ +
+
theme
an instance of a Theme, optional as it will use the default Theme class. +
+ + Basic Example + + app = gui.App() + app.run(widget=widget,screen=screen) + + + Integrated Example + + app = gui.App() + gui.init(widget=widget) + while 1: + for e in pygame.event.get(): + app.event(e) + app.update(screen) + + + + + """ + 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. + +
App.init(widget=None,screen=None)
+ +
+
widget
main widget +
screen
pygame.Surface to render to +
+ """ + + 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. + +
App.event(e)
+ +
+
e
event +
+ """ + 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. + +
+
screen
pygame surface +
+ """ + 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. + +

Automatically calls App.init and then forever loops App.event and App.update

+ +
+
widget
main widget +
screen
pygame.Surface to render to +
+ """ + 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 desktop theme class. + +
Desktop()
+ """ + 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 index 0000000..64074d6 --- /dev/null +++ b/src/pgu/gui/area.py @@ -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. + +
SlideBox(widget,width,height)
+ +
+
widget
widget to be able to scroll around +
width, height
size of scrollable area +
+ + Example + + c = SlideBox(w,100,100) + c.offset = (10,10) + c.repaint() + + + """ + + 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. + +
ScrollArea(widget,width,height,hscrollbar=True)
+ +
+
widget
widget to be able to scroll around +
width, height
size of scrollable area. Set either to 0 to default to size of widget. +
hscrollbar
set to False if you do not wish to have a horizontal scrollbar +
vscrollbar
set to False if you do not wish to have a vertical scrollbar +
step
set to how far clicks on the icons will step +
+ """ + 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. + +

This widget can be a form element, it has a value set to whatever item is selected.

+ +
List(width,height)
+ """ + 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. + +
List.clear()
+ """ + 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. + +
List.add(label,image=None,value=None)
+ +
+
label
a label for the item +
image
an image for the item +
value
a value for the item +
+ """ + + def remove(self,value): + """Remove an item from the list. + +
List.remove(value)
+ +
+
value
a value of an item to remove from the list +
+ """ + + 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 index 0000000..3800440 --- /dev/null +++ b/src/pgu/gui/basic.py @@ -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. + +
Spacer(width,height)
+ + """ + 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. + +

The color can be changed at run-time.

+ +
Color(value=None)
+ + Example + + c = Color() + c.value = (255,0,0) + c.value = (0,255,0) + + """ + + + 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. + +
Label(value)
+ +
+
value
text to be displayed +
+ + Example + + w = Label(value="I own a rubber chicken!") + + w = Label("3 rubber chickens") + + """ + 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. + +
Image(value)
+ +
+
value
a file name or a pygame.Surface +
+ + """ + 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 index 0000000..90cdd1e --- /dev/null +++ b/src/pgu/gui/button.py @@ -0,0 +1,351 @@ +""" +""" + +from pygame.locals import * + +from const import * +import widget, surface +import basic + +class _button(widget.Widget): + def __init__(self,**params): + widget.Widget.__init__(self,**params) + self.state = 0 + + def event(self,e): + if e.type == ENTER: self.repaint() + elif e.type == EXIT: self.repaint() + elif e.type == FOCUS: self.repaint() + elif e.type == BLUR: self.repaint() + elif e.type == KEYDOWN: + if e.key == K_SPACE or e.key == K_RETURN: + self.state = 1 + self.repaint() + elif e.type == MOUSEBUTTONDOWN: + self.state = 1 + self.repaint() + elif e.type == KEYUP: + if self.state == 1: + sub = pygame.event.Event(CLICK,{'pos':(0,0),'button':1}) + #self.send(sub.type,sub) + self._event(sub) + + self.state = 0 + self.repaint() + elif e.type == MOUSEBUTTONUP: + self.state = 0 + self.repaint() + elif e.type == CLICK: + self.click() + + self.pcls = "" + if self.state == 0 and self.container.myhover is self: + self.pcls = "hover" + if self.state == 1 and self.container.myhover is self: + self.pcls = "down" + + def click(self): + pass + + +class Button(_button): + """A button, buttons can be clicked, they are usually used to set up callbacks. + +
Button(value=None)
+ +
+
value
either a widget or a string +
+ + Example + + w = gui.Button("Click Me") + w.connect(gui.CLICK,fnc,value) + + """ + 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. + +
Switch(value=False)
+ +
+
value
initial value, (True, False) +
+ + Example + + w = gui.Switch(True) + w.connect(gui.CHANGE,fnc,value) + + """ + 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. + +
Checkbox(group,value=None)
+ +
+
group
a gui.Group for the Checkbox to belong to +
value
the value +
+ + Example + + 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')) + + """ + + 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. + +
Radio(group,value=None)
+ +
+
group
a gui.Group for the Radio to belong to +
value
the value +
+ + Example + + 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')) + + """ + + + 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. + +
Tool(group,widget=None,value=None)
+ +
+
group
a gui.Group for the Tool to belong to +
widget
a widget to appear on the Tool (similar to a Button) +
value
the value +
+ + Example + + 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')) + + """ + + 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. + +
Link(value=None)
+ +
+
value
a string +
+ + Example + + w = gui.Link("Click Me") + w.connect(gui.CLICK,fnc,value) + + """ + 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 index 0000000..c865cd8 --- /dev/null +++ b/src/pgu/gui/const.py @@ -0,0 +1,45 @@ +"""Constants. +

+Event Types + +

from pygame

+
+
QUIT +
MOUSEBUTTONDOWN +
MOUSEBUTTONUP +
MOUSEMOTION +
KEYDOWN +
+ +

gui specific

+
+
ENTER +
EXIT +
BLUR +
FOCUS +
CLICK +
CHANGE +
OPEN +
CLOSE +
INIT +
+ +Other +
+
NOATTR +
+""" +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 index 0000000..73a8e68 --- /dev/null +++ b/src/pgu/gui/container.py @@ -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. + +
Container()
+ """ + 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. + +
Container.remove(w)
+ """ + self.blur(w) + self.widgets.remove(w) + #self.repaint() + self.chsize() + + def add(self,w,x,y): + """Add a widget to the container. + +
Container.add(w,x,y)
+ +
+
x, y
position of the widget +
+ """ + 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 index 0000000..8d53515 --- /dev/null +++ b/src/pgu/gui/deprecated.py @@ -0,0 +1,76 @@ +import pygame + +from const import * +import table +import group +import button, basic +import pguglobals + +def action_open(value): + print 'gui.action_open',"Scheduled to be deprecated." + value.setdefault('x',None) + value.setdefault('y',None) + value['container'].open(value['window'],value['x'],value['y']) + +def action_setvalue(value): + print 'gui.action_setvalue',"Scheduled to be deprecated." + a,b = value + b.value = a.value + +def action_quit(value): + print 'gui.action_quit',"Scheduled to be deprecated." + value.quit() + +def action_exec(value): + print 'gui.action_exec',"Scheduled to be deprecated." + exec(value['script'],globals(),value['dict']) + +class Toolbox(table.Table): + def __setattr__(self,k,v): + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.group.value = v + for w in self.group.widgets: + if w.value != v: w.pcls = "" + else: w.pcls = "down" + self.repaint() + + def _change(self,value): + self.value = self.group.value + self.send(CHANGE) + + def __init__(self,data,cols=0,rows=0,tool_cls='tool',value=None,**params): + print 'gui.Toolbox','Scheduled to be deprecated.' + params.setdefault('cls','toolbox') + table.Table.__init__(self,**params) + + if cols == 0 and rows == 0: cols = len(data) + if cols != 0 and rows != 0: rows = 0 + + self.tools = {} + + _value = value + + g = group.Group() + self.group = g + g.connect(CHANGE,self._change,None) + self.group.value = _value + + x,y,p,s = 0,0,None,1 + for ico,value in data: + #from __init__ import theme + img = pguglobals.app.theme.get(tool_cls+"."+ico,"","image") + if img: + i = basic.Image(img) + else: i = basic.Label(ico,cls=tool_cls+".label") + p = button.Tool(g,i,value,cls=tool_cls) + self.tools[ico] = p + #p.style.hexpand = 1 + #p.style.vexpand = 1 + self.add(p,x,y) + s = 0 + if cols != 0: x += 1 + if cols != 0 and x == cols: x,y = 0,y+1 + if rows != 0: y += 1 + if rows != 0 and y == rows: x,y = x+1,0 diff --git a/src/pgu/gui/dialog.py b/src/pgu/gui/dialog.py new file mode 100644 index 0000000..0d30b34 --- /dev/null +++ b/src/pgu/gui/dialog.py @@ -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. + +
Dialog(title,main)
+ +
+
title
title widget, usually a label +
main
main widget, usually a container +
+ + Example + + title = gui.Label("My Title") + main = gui.Container() + #add stuff to the container... + + d = gui.Dialog(title,main) + d.open() + + """ + 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. + +
FileDialog()
+

Some optional parameters:

+
+
title_txt
title text +
button_txt
button text +
path
initial path +
+ """ + + 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 index 0000000..1bd96df --- /dev/null +++ b/src/pgu/gui/document.py @@ -0,0 +1,112 @@ +""" +""" +import pygame + +import container +import layout + +class _document_widget: + def __init__(self,w,align=None): + #w.rect.w,w.rect.h = w.resize() + #self.rect = w.rect + self.widget = w + if align != None: self.align = align + +class Document(container.Container): + """A document container contains many widgets strung together in a document format. (How informative!) + +
Document()
+ + """ + 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. + +
Document.add(e,align=None)
+ +
+
e
widget +
align
alignment (None,-1,0,1) +
+ """ + 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. + +
Document.block(align)
+ +
+
align
alignment of block (-1,0,1) +
+ """ + self.layout.add(align) + + def space(self,e): + """Add a spacer. + +
Document.space(e)
+ +
+
e
a (w,h) size for the spacer +
+ """ + self.layout.add(e) + + def br(self,height): + """Add a line break. + +
Document.br(height)
+ +
+
height
height of line break +
+ """ + 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 index 0000000..9a874f9 --- /dev/null +++ b/src/pgu/gui/form.py @@ -0,0 +1,79 @@ +""" +""" +import widget + +class Form(widget.Widget): + """A form that automatically will contain all named widgets. + +

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.

+ +
Form()
+ + Example + + 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 + + """ + + 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. + +
Form.results(): return dict
+ """ + 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. + +
Form.items(): return list
+ """ + 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 index 0000000..bcb231a --- /dev/null +++ b/src/pgu/gui/group.py @@ -0,0 +1,43 @@ +""" +""" +from const import * +import widget + +class Group(widget.Widget): + """An object for grouping together Form elements. + +
Group(name=None,value=None)
+ +
+
name
name as used in the Form +
value
values that are currently selected in the group +
+ +

See [[gui-button]] for several examples.

+ +

When the value changes, an gui.CHANGE event is sent. + Although note, that when the value is a list, it may have to be sent by hand via + g.send(gui.CHANGE)

+ """ + + 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. + +
Group.add(w)
+ """ + 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 index 0000000..3f3f653 --- /dev/null +++ b/src/pgu/gui/input.py @@ -0,0 +1,169 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class Input(widget.Widget): + """A single line text input. + +
Input(value="",size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = Input(value="Cuzco the Goat",size=20) + + w = Input("Marbles") + + + """ + 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. + +
Password(value="",size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = Password(value="password",size=20) + + w = Password("53[r3+") + + + """ + + 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 index 0000000..cc24089 --- /dev/null +++ b/src/pgu/gui/keysym.py @@ -0,0 +1,72 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class Keysym(widget.Widget): + """A keysym input. + +

This widget records the keysym of the key pressed while this widget is in focus.

+ +
Keysym(value=None)
+ +
+
value
initial keysym, see pygame keysyms
+ + Example + + w = Input(value=pygame.locals.K_g) + + w = Input(pygame.locals.K_g) + + w = Input() + + + """ + + 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 index 0000000..01fe0cb --- /dev/null +++ b/src/pgu/gui/layout.py @@ -0,0 +1,172 @@ +"""document layout engine.""" +class Layout: + """the document layout engine + + .widgets -- elements are kept in this list. read-only, use add to add items to it. + """ + + def __init__(self,rect=None): + """initialize the object with the size of the box.""" + self._widgets = [] + self.rect = rect + + def add(self,e): + """add a document element to the layout. + + a document element may be + - a tuple (w,h) if it is a whitespace element + - a tuple (0,h) if it is a linebreak element + - an integer -1,0,1 if it is a command to start a new block of elements that are aligned either left,center, or right. + - an object with a .rect (for size) -- such as a word element + - an object with a .rect (for size) and .align -- such as an image element + """ + + self._widgets.append(e) + + + def resize(self): + """resize the layout + this method recalculates the position of all document elements + after they have been added to the document. .rect.x,y will be updated for all + objects. + """ + self.init() + self.widgets = [] + for e in self._widgets: + if type(e) is tuple and e[0] != 0: + self.do_space(e) + elif type(e) is tuple and e[0] == 0: + self.do_br(e[1]) + elif type(e) is int: + self.do_block(align=e) + elif hasattr(e,'align'): + self.do_align(e) + else: + self.do_item(e) + self.line() + self.rect.h = max(self.y,self.left_bottom,self.right_bottom) + + def init(self): + self.x,self.y = self.rect.x,self.rect.y + self.left = self.rect.left + self.right = self.rect.right + self.left_bottom = 0 + self.right_bottom = 0 + self.y = self.rect.y + self.x = self.rect.x + self.h = 0 + + self.items = [] + self.align = -1 + + def getleft(self): + if self.y > self.left_bottom: + self.left = self.rect.left + return self.left + + def getright(self): + if self.y > self.right_bottom: + self.right = self.rect.right + return self.right + + def do_br(self,h): + self.line() + self.h = h + + def do_block(self,align=-1): + self.line() + self.align = align + + def do_align(self,e): + align = e.align + ox,oy,oh = self.x,self.y,self.h + w,h = e.rect.w,e.rect.h + + if align == 0: + self.line() + self.x = self.rect.left + (self.rect.width-w)/2 + self.fit = 0 + elif align == -1: + self.line() + self.y = max(self.left_bottom,self.y + self.h) + self.h = 0 + self.x = self.rect.left + elif align == 1: + self.line() + self.y = max(self.right_bottom,self.y + self.h) + self.h = 0 + self.x = self.rect.left + (self.rect.width-w) + + e.rect.x,e.rect.y = self.x,self.y + + self.x = self.x + w + self.y = self.y + + if align == 0: + self.h = max(self.h,h) + self.y = self.y + self.h + self.x = self.getleft() + self.h = 0 + elif align == -1: + self.left = self.x + self.left_bottom = self.y + h + self.x,self.y,self.h = ox + w,oy,oh + elif align == 1: + self.right = self.x - w + self.right_bottom = self.y + h + self.x,self.y,self.h = ox,oy,oh + + self.widgets.append(e) + + def do_space(self,e): + w,h = e + if self.x+w >= self.getright(): + self.line() + else: + self.items.append(e) + self.h = max(self.h,h) + self.x += w + + def do_item(self,e): + w,h = e.rect.w,e.rect.h + if self.x+w >= self.getright(): + self.line() + self.items.append(e) + self.h = max(self.h,h) + self.x += w + + def line(self): + x1 = self.getleft() + x2 = self.getright() + align = self.align + y = self.y + + if len(self.items) != 0 and type(self.items[-1]) == tuple: + del self.items[-1] + + w = 0 + for e in self.items: + if type(e) == tuple: w += e[0] + else: w += e.rect.w + + if align == -1: x = x1 + elif align == 0: + x = x1 + ((x2-x1)-w)/2 + self.fit = 0 + elif align == 1: x = x2 - w + + for e in self.items: + if type(e) == tuple: x += e[0] + else: + e.rect.x,e.rect.y = x,y + self.widgets.append(e) + x += e.rect.w + + self.items = [] + self.y = self.y + self.h + self.x = self.getleft() + self.h = 0 + + + +# vim: set filetype=python sts=4 sw=4 noet si : diff --git a/src/pgu/gui/menus.py b/src/pgu/gui/menus.py new file mode 100644 index 0000000..b850b6c --- /dev/null +++ b/src/pgu/gui/menus.py @@ -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. + +
Menus(data)
+ +
+
data
Menu data, a list of (path,fnc,value), see example below +
+ + Example + + 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 index 0000000..afb10c5 --- /dev/null +++ b/src/pgu/gui/misc.py @@ -0,0 +1,43 @@ +from const import * +import widget +import pguglobals + +class ProgressBar(widget.Widget): + """A progress bar. + +
ProgressBar(value,min,max)
+ +
+
value
starting value +
min
minimum value rendered on the screen (usually 0) +
max
maximum value +
+ + Example + + w = gui.ProgressBar(0,0,100) + w.value = 25 + + """ + + 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 index 0000000..dc0e673 --- /dev/null +++ b/src/pgu/gui/pguglobals.py @@ -0,0 +1,7 @@ +# pguglobals.py - A place to stick global variables that need to be accessed +# from other modules. To avoid problems with circular imports +# this module should not import any other PGU module. + +# A global reference to the application instance (App class) +app = None + diff --git a/src/pgu/gui/select.py b/src/pgu/gui/select.py new file mode 100644 index 0000000..0ee39e9 --- /dev/null +++ b/src/pgu/gui/select.py @@ -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. + +
Select(value=None)
+ +
+
value
initial value +
+ + Example + + 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 + + + """ + + # 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. + +
Select.add(widget,value=None)
+ +
+
widget
Widget or string to represent the item +
value
value for this item +
+ + Example + + 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 + + """ + + 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 index 0000000..f4fa623 --- /dev/null +++ b/src/pgu/gui/slider.py @@ -0,0 +1,279 @@ +"""All sliders and scroll bar widgets have the same parameters. + +
Slider(value,min,max,size)
+
+
value
initial value +
min
minimum value +
max
maximum value +
size
size of bar in pixels +
+""" +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. + +
VSlider(value,min,max,size)
+ """ + 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. + +
HSlider(value,min,max,size)
+ """ + 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. + +
HScrollBar(value,min,max,size,step=1)
+ """ + 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. + +
VScrollBar(value,min,max,size,step=1)
+ """ + 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 index 0000000..3060928 --- /dev/null +++ b/src/pgu/gui/style.py @@ -0,0 +1,41 @@ +""" +""" + +import pguglobals + +class Style: + """The class used by widget for the widget.style + +

This object is used mainly as a dictionary, accessed via widget.style.attr, as opposed to + widget.style['attr']. It automatically grabs information from the theme via value = theme.get(widget.cls,widget.pcls,attr).

+ + """ + 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 index 0000000..9bd064d --- /dev/null +++ b/src/pgu/gui/surface.py @@ -0,0 +1,143 @@ +""" +""" +import pygame + +def subsurface(s,r): + """Return the subsurface of a surface, with some help, checks. + +
subsurface(s,r): return surface
+ """ + 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. + +
ProxySurface(parent, rect, real_surface=None, offset=(0, 0))
+ +

only one of parent and real_surface should be supplied (non None)

+
+
parent
a ProxySurface object +
real_surface
a pygame Surface object +
+ + Variables + +
+
mysubsurface
a real and valid pygame.Surface object to be used + for blitting. +
x, y
if the proxy surface is lefter or higher than the parent, + x, y hold the diffs. +
offset
an optional feature which let you scroll the whole blitted + content. +
+ """ + 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. + +
ProxySurface(parent, rect, real_surface=None, offset=(0, 0))
+ +

only one of parent and real_surface should be supplied (non None)

+
+
parent
a ProxySurface object +
real_surface
a pygame Surface object +
+ + Variables + +
+
mysubsurface
a real and valid pygame.Surface object to be used + for blitting. +
x, y
if the proxy surface is lefter or higher than the parent, + x, y hold the diffs. +
offset
an optional feature which let you scroll the whole blitted + content. +
+ """ + 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 index 0000000..6ff6c74 --- /dev/null +++ b/src/pgu/gui/table.py @@ -0,0 +1,331 @@ +""" +""" +from const import * +import container + +class Table(container.Container): + """A table style container. + +

If you know HTML, this should all work roughly how you would expect. If you are not + familiar with HTML, please read Tables in HTML Documents. Pay attention to TABLE, TR, TD related parts of the document.

+ +
Table()
+ + Example + + 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()) + + + """ + + + 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. + +
Table.td(w,col=None,row=None,colspan=1,rowspan=1,**params)
+ +
+
w
widget +
col
column +
row
row +
colspan
colspan +
rowspan
rowspan +
align
horizontal alignment (-1,0,1) +
valign
vertical alignment (-1,0,1) +
params
other params for the TD container, style information, etc +
+ """ + + 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. + +
Table.add(w,col=None,row=None,colspan=1,rowspan=1)
+ +

See Table.td for an explanation of the parameters.

+ """ + 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) 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 index 0000000..667076a --- /dev/null +++ b/src/pgu/gui/textarea.py @@ -0,0 +1,287 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class TextArea(widget.Widget): + """A multi-line text input. + +
TextArea(value="",width = 120, height = 30, size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = TextArea(value="Cuzco the Goat",size=20) + + w = TextArea("Marbles") + + w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12) + + + """ + 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 index 0000000..283c287 --- /dev/null +++ b/src/pgu/gui/theme.py @@ -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. + +

If you wish to create your own theme, create a class with this interface, and + pass it to gui.App via gui.App(theme=MyTheme()).

+ + Default Theme + +
Theme(dirs='default')
+
+
dirs
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. +
+ + Example + + + theme = gui.Theme("default") + theme = gui.Theme(["mytheme","mytheme2"]) + + """ + 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. + +
Theme.get(cls,pcls,attr): return value
+ +
+
cls
class, for example "checkbox", "button", etc. +
pcls
pseudo class, for example "hover", "down", etc. +
attr
attribute, for example "image", "background", "font", "color", etc. +
+ +

returns the value of the attribute.

+ +

This method is called from [[gui-style]].

+ """ + + 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. + +

The theme system is given the opportunity to decorate a widget methods at the + end of the Widget initializer.

+ +
Theme.decorate(widget,level)
+ +
+
widget
the widget to be decorated +
level
the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects. +
+ """ + + 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. + +
Theme.render(s,box,r)
+ +
+
s
pygame.Surface +
box
box data, a value returned from Theme.get, typically a pygame.Surface +
r
pygame.Rect with the size that the box data should be rendered +
+ + """ + + 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 index 0000000..f0f4b13 --- /dev/null +++ b/src/pgu/gui/widget.py @@ -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. + +
Widget(**params)
+ +

A number of optional params may be passed to the Widget initializer.

+ +
+
decorate
defaults to True. If true, will call theme.decorate(self) to allow the theme a chance to decorate the widget. +
style
a dict of style parameters. +
x, y, width, height
position and size parameters, passed along to style +
align, valign
alignment parameters, passed along to style +
font, color, background
other common parameters that are passed along to style +
cls
class name as used by Theme +
name
name of widget as used by Form. If set, will call form.add(self,name) to add the widget to the most recently created Form. +
focusable
True if this widget can receive focus via Tab, etc. Defaults to True. +
disabled
True of this widget is disabled. Defaults to False. +
value
initial value +
+ + Example - Creating your own Widget +

This example shows which methods are template methods.

+ + 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 + + """ + + # 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. + +
Widget.focus()
+ """ + if getattr(self,'container',None) != None: + if self.container.myfocus != self: ## by Gal Koren + self.container.focus(self) + + def blur(self): + """Blur this Widget. + +
Widget.blur()
+ """ + if getattr(self,'container',None) != None: self.container.blur(self) + + def open(self): + """Open this Widget as a modal dialog. + +
Widget.open()
+ """ + #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.) + +
Widget.close()
+ """ + #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. + +

Responsible for also resizing all sub-widgets.

+ +
Widget.resize(width,height): return width,height
+ +
+
width
suggested width +
height
suggested height +
+ +

If not overridden, will return self.style.width, self.style.height

+ """ + return self.style.width, self.style.height + + def chsize(self): + """Change the size of this widget. + +

Calling this method will cause a resize on all the widgets, + including this one.

+ +
Widget.chsize()
+ """ + + 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 + +
Widget.update(s): return list of pygame.Rect(s)
+ +
+
s
pygame.Surface to update +
+ +

return - a list of the updated areas as pygame.Rect(s).

+ """ + return + + def paint(self,s): + """Template method - paint the surface + +
Widget.paint(s)
+ +
+
s
pygame.Surface to paint +
+ """ + return + + def repaint(self): + """Request a repaint of this Widget. + +
Widget.repaint()
+ """ + if getattr(self,'container',None) != None: self.container.repaint(self) + def repaintall(self): + """Request a repaint of all Widgets. + +
Widget.repaintall()
+ """ + if getattr(self,'container',None) != None: self.container.repaintall() + def reupdate(self): + """Request a reupdate of this Widget + +
Widget.reupdate()
+ """ + if getattr(self,'container',None) != None: self.container.reupdate(self) + def next(self): + """Pass focus to next Widget. + +

Widget order determined by the order they were added to their container.

+ +
Widget.next()
+ """ + if getattr(self,'container',None) != None: self.container.next(self) + def previous(self): + """Pass focus to previous Widget. + +

Widget order determined by the order they were added to their container.

+ +
Widget.previous()
+ """ + + 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 + +
Widget.get_abs_rect(): return pygame.Rect
+ """ + 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. + +

There may be multiple callbacks per event code.

+ +
Object.connect(code,fnc,value)
+ +
+
code
event type [[gui-const]] +
fnc
callback function +
*values
values to pass to callback. Please note that callbacks may also have "magicaly" parameters. Such as: +
+
_event
receive the event +
_code
receive the event code +
_widget
receive the sending widget +
+
+ + Example + + def onclick(value): + print 'click',value + + w = Button("PGU!") + w.connect(gui.CLICK,onclick,'PGU Button Clicked') + + """ + 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. + +
Object.send(code,event=None)
+ +
+
code
event code +
event
event +
+ """ + 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. + +

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.)

+ +
+
e
event +
+ """ + + 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 index 0000000..2d4156d --- /dev/null +++ b/src/pgu/hexvid.py @@ -0,0 +1,127 @@ +"""Hexagonal tile engine. + +

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!

+ +

please note that this file is alpha, and is subject to modification in +future versions of pgu!

+ +""" +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 index 0000000..e05d22a --- /dev/null +++ b/src/pgu/high.py @@ -0,0 +1,154 @@ +"""Classes for handling high score tables. +""" + +import os + +def High(fname,limit=10): + """Create a Highs object and returns the default high score table. + +
High(fname,limit=10)
+ +
+
fname
filename to store high scores in +
limit
limit of scores to be recorded, defaults to 10 +
+ """ + 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. + +

You can iterate them:

+ + for e in myhigh: + print e.score,e.name,e.data + + +

You can modify them:

+ + myhigh[0].name = 'Cuzco' + + +

You can find out their length:

+ + print len(myhigh) + + """ + + def __init__(self,highs,limit=10): + self.highs = highs + self._list = [] + self.limit = limit + + def save(self): + """Save the high scores. + +
_High.save()
+ """ + self.highs.save() + + def submit(self,score,name,data=None): + """Submit a high score to this table. + +
_High.submit(score,name,data=None)
+ +

return -- the position in the table that the score attained. None if the score did not attain a position in the table.

+ """ + 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. + +
_High.check(score)
+ +

return -- the position the score will attain, else None

+ """ + 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. + +
Highs(fname,limit=10)
+
    +
    fname
    filename to store high scores in +
    limit
    limit of scores to be recorded, defaults to 10 +
+ +

You may access _High objects through this object:

+ + + my_easy_hs = highs['easy'] + my_hard_hs = highs['hard'] + + + """ + def __init__(self,fname,limit=10): + self.fname = fname + self.limit = limit + self.load() + + def load(self): + """Re-load the high scores. + +
Highs.load()
+ """ + + 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. + +
Highs.save()
+ """ + + 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 index 0000000..817c000 --- /dev/null +++ b/src/pgu/html.py @@ -0,0 +1,571 @@ +"""a html renderer +""" + +import sys +import htmllib +import re +import pygame +from pygame.locals import * + +from pgu import gui + +_amap = {'left':-1,'right':1,'center':0,None:None,'':None,} +_vamap = {'top':-1,'bottom':1,'center':0,'middle':0,None:None,'':None,} + +# Used by the HTML parser to load external resources (like images). This +# class loads content from the local file system. But you can pass your own +# resource loader to the HTML parser to find images by other means. +class ResourceLoader(object): + # Loads an image and returns it as a pygame image + def load_image(this, path): + return pygame.image.load(path) + +class _dummy: + pass + +class _flush: + def __init__(self): + self.style = _dummy() + self.style.font = None + self.style.color = None + self.cls = None + def add(self,w): pass + def space(self,v): pass + +class _hr(gui.Color): + def __init__(self,**params): + gui.Color.__init__(self,(0,0,0),**params) + def resize(self,width=None,height=None): + w,h = self.style.width,self.style.height + #if width != None: self.rect.w = width + #else: self.rect.w = 1 + + #xt,xr,xb,xl = self.getspacing() + + if width != None: w = max(w,width) + if height != None: h = max(h,height) + w = max(w,1) + h = max(h,1) + + return w,h #self.container.rect.w,h + + #self.rect.w = max(1,width,self.container.rect.w-(xl+xr)) + + #print self.rect + #self.rect.w = 1 + +class _html(htmllib.HTMLParser): + def init(self,doc,font,color,_globals,_locals,loader=None): + self.mystack = [] + self.document = doc + if (loader): + self.loader = loader + else: + # Use the default resource loader + self.loader = ResourceLoader() + self.myopen('document',self.document) + + self.myfont = self.font = font + self.mycolor = self.color = color + + self.form = None + + self._globals = _globals + self._locals = _locals + + def myopen(self,type_,w): + + self.mystack.append((type_,w)) + self.type,self.item = type_,w + + self.font = self.item.style.font + self.color = self.item.style.color + + if not self.font: self.font = self.myfont + if not self.color: self.color = self.mycolor + + def myclose(self,type_): + t = None + self.mydone() + while t != type_: + #if len(self.mystack)==0: return + t,w = self.mystack.pop() + t,w = self.mystack.pop() + self.myopen(t,w) + + def myback(self,type_): + if type(type_) == str: type_ = [type_,] + self.mydone() + #print 'myback',type_ + t = None + while t not in type_: + #if len(self.mystack)==0: return + t,w = self.mystack.pop() + self.myopen(t,w) + + def mydone(self): + #clearing out the last

+ 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)," ") #  + #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 + +
HTML(data,globals=None,locals=None)
+ +
+
data
html data +
globals
global variables (for scripting) +
locals
local variables (for scripting) +
loader
the resource loader +
+ +

you may access html elements that have an id via widget[id]

+ """ + 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 + +
render(font,rect,text,aa,color,bgcolor=(0,0,0,0))
+ """ + 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 index 0000000..d5048ec --- /dev/null +++ b/src/pgu/isovid.py @@ -0,0 +1,182 @@ +"""Isometric tile engine. + +

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!

+ +

please note that this file is alpha, and is subject to modification in +future versions of pgu!

+ +""" +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 index 0000000..75b6c9a --- /dev/null +++ b/src/pgu/layout.py @@ -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 index 0000000..1010a87 --- /dev/null +++ b/src/pgu/text.py @@ -0,0 +1,61 @@ +"""a collection of text rendering functions +""" +def write(s,font,pos,color,text,border=1): + """write text to a surface with a black border + +
write(s,font,pos,color,text,border=1)
+ """ + 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 + +
writec(s,font,color,text,border=1)
+ """ + 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 + +
writepre(s,font,rect,color,text)
+ """ + 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 + +
writewrap(s,font,rect,color,text)
+ """ + 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 index 0000000..00f730d --- /dev/null +++ b/src/pgu/tilevid.py @@ -0,0 +1,195 @@ +"""Square tile based engine.""" + +from pgu.vid import * +import pygame + +class Tilevid(Vid): + """Based on [[vid]] -- see for reference.""" + def paint(self,s): + sw,sh = s.get_width(),s.get_height() + self.view.w,self.view.h = sw,sh + + tiles = self.tiles + tw,th = tiles[0].image.get_width(),tiles[0].image.get_height() + w,h = self.size + + if self.bounds != None: self.view.clamp_ip(self.bounds) + + ox,oy = self.view.x,self.view.y + tlayer = self.tlayer + blayer = self.blayer + alayer = self.alayer + sprites = self.sprites + + blit = s.blit + yy = - (self.view.y%th) + my = (oy+sh)/th + if (oy+sh)%th: my += 1 + + if blayer != None: + for y in xrange(oy/th,my): + if y >=0 and y < h: + trow = tlayer[y] + brow = blayer[y] + arow = alayer[y] + xx= - (self.view.x%tw) + mx = (ox+sw)/tw + #if (ox+sh)%tw: mx += 1 + for x in xrange(ox/tw,mx+1): + if x >=0and x=0 and y=0 and xTimer(fps) + """ + + 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. + +
Timer.tick()
+ """ + 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. +
Speedometer()
+ + Attributes +
+
fps
always set to the current FPS +
+ """ + def __init__(self): + self.frames = 0 + self.st = pygame.time.get_ticks() + self.fps = 0 + + def tick(self): + """ Call this once per frame. + +
Speedometer.tick()
+ """ + 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 index 0000000..6890e5d --- /dev/null +++ b/src/pgu/vid.py @@ -0,0 +1,560 @@ +"""Sprite and tile engine. + +

[[tilevid]], [[isovid]], [[hexvid]] are all subclasses of +this interface.

+ +

Includes support for:

+ +
    +
  • Foreground Tiles +
  • Background Tiles +
  • Sprites +
  • Sprite-Sprite Collision handling +
  • Sprite-Tile Collision handling +
  • Scrolling +
  • Loading from PGU tile and sprite formats (optional) +
  • Set rate FPS (optional) +
+ +

This code was previously known as the King James Version (named after the +Bible of the same name for historical reasons.)

+""" + +import pygame +from pygame.rect import Rect +from pygame.locals import * +import math + +class Sprite: + """The object used for Sprites. + +
Sprite(ishape,pos)
+ +
+
ishape
an image, or an image, rectstyle. The rectstyle will + describe the shape of the image, used for collision + detection. +
pos
initial (x,y) position of the Sprite. +
+ + Attributes +
+
rect
the current position of the Sprite +
_rect
the previous position of the Sprite +
groups
the groups the Sprite is in +
agroups
the groups the Sprite can hit in a collision +
hit
the handler for hits -- hit(g,s,a) +
loop
the loop handler, called once a frame +
+ """ + 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. + +
Sprite.setimage(ishape)
+ +
+
ishape
an image, or an image, rectstyle. The rectstyle will + describe the shape of the image, used for collision detection. +
+ """ + 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. + +
Tile(image=None)
+
+
image
an image for the Tile. +
+ + Attributes +
+
agroups
the groups the Tile can hit in a collision +
hit
the handler for hits -- hit(g,t,a) +
+ """ + 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. + +
Vid()
+ + Attributes +
+
sprites
a list of the Sprites to be displayed. You may append and + remove Sprites from it. +
images
a dict for images to be put in. +
size
the width, height in Tiles of the layers. Do not modify. +
view
a pygame.Rect of the viewed area. You may change .x, .y, + etc to move the viewed area around. +
bounds
a pygame.Rect (set to None by default) that sets the bounds + of the viewable area. Useful for setting certain borders + as not viewable. +
tlayer
the foreground tiles layer +
clayer
the code layer (optional) +
blayer
the background tiles layer (optional) +
groups
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) +
+ """ + + 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. + +
Vid.resize(size,bg=0)
+ +
+
size
w,h in Tiles of the layers +
bg
set to 1 if you wish to use both a foreground layer and a + background layer +
+ """ + 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. + +

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() +

+ +
Vid.set(pos,v)
+ +
+
pos
(x,y) of tile +
v
value +
+ """ + 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. + +
Vid.get(pos): return value
+ +
+
pos
(x,y) of tile +
+ """ + return self.tlayer[pos[1]][pos[0]] + + def paint(self,s): + """Paint the screen. + +
Vid.paint(screen): return [updates]
+ +
+
screen
a pygame.Surface to paint to +
+ +

returns the updated portion of the screen (all of it)

+ """ + return [] + + def update(self,s): + """Update the screen. + +
Vid.update(screen): return [updates]
+ +
+
screen
a pygame.Rect to update +
+ +

returns a list of updated rectangles.

+ """ + self.updates = [] + return [] + + def tga_load_level(self,fname,bg=0): + """Load a TGA level. + +
Vid.tga_load_level(fname,bg=0)
+ +
+
g
a Tilevid instance +
fname
tga image to load +
bg
set to 1 if you wish to load the background layer +
+ """ + 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. + +
Vid.tga_save_level(fname)
+ +
+
fname
tga image to save to +
+ """ + 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. + +
Vid.tga_load_tiles(fname,size,tdata={})
+ +
+
g
a Tilevid instance +
fname
tga image to load +
size
(w,h) size of tiles in pixels +
tdata
tile data, a dict of tile:(agroups, hit handler, config) +
+ """ + 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. + +
Vid.load_images(idata)
+ +
+
idata
a list of (name, fname, shape) +
+ """ + for name,fname,shape in idata: + self.images[name] = pygame.image.load(fname).convert_alpha(),shape + + def run_codes(self,cdata,rect): + """Run codes. + +
Vid.run_codes(cdata,rect)
+ +
+
cdata
a dict of code:(handler function, value) +
rect
a tile rect of the parts of the layer that should have + their codes run +
+ """ + 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. + +
Vid.string2groups(str): return groups
+ """ + if str == None: return 0 + return self.list2groups(str.split(",")) + + def list2groups(self,igroups): + """Convert a list to groups. +
Vid.list2groups(igroups): return groups
+ """ + 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. +
Vid.groups2list(groups): return list
+ """ + 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. +
Vid.loop()
+ """ + 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<>= 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. +
Vid.screen_to_tile(pos): return pos
+ """ + return pos + + def tile_to_screen(self,pos): + """Convert a tile position to a screen position. +
Vid.tile_to_screen(pos): return pos
+ """ + 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 index 0000000..f85b06c --- /dev/null +++ b/src/songs/MidiToSong.py @@ -0,0 +1,60 @@ +''' +Created on 8 dec. 2009 + +@author: Samuel Benveniste +''' + +from mxmMidi.MidiOutStream import MidiOutStream + +class MidiToSong(MidiOutStream): + ''' + Creates songs from midi files using mxmMidi package + ''' + def __init__(self): + self.midiNoteNumbers = [] + self.noteLengths = [] + self.quarterNoteLength = 500 + self.firstNoteOn = True + self._lastNoteOnTime = 0 + + def header(self, format=0, nTracks=1, division=96): + print 'format: %s, nTracks: %s, division: %s' % (format, nTracks, division) + print '----------------------------------' + print '' + self.division = division + + def tempo(self,value): + self.quarterNoteLength = value/1000 + print "quarterNoteLength in ms :" + str(self.quarterNoteLength) + + def note_on(self, channel=0, note=0x40, velocity=0x40): + self.midiNoteNumbers.append(note) + # if it's the first note_on take note of time (the song begins here) + # from the second note_on and after, mark the length of the preceding note + if self.firstNoteOn : + self.firstNoteOn = False + self._lastNoteOnTime = self.abs_time() + else : + self.noteLengths.append(float(self.abs_time()-self._lastNoteOnTime)/float(self.division)) + self._lastNoteOnTime = self.abs_time() + + def eof(self): + self.noteLengths.append(4) + for i in range(len(self.midiNoteNumbers)): + print "note number :" + str(self.midiNoteNumbers[i]) + ", length in quarter Notes :" + str(self.noteLengths[i]) + print"--------------" + print "end of file" + +if __name__ == '__main__': + + # get data + test_file = '../songs/midis/test.mid' + f = open(test_file, 'rb') + + # do parsing + from mxmMidi.MidiInFile import MidiInFile + mts = MidiToSong() + midiIn = MidiInFile(mts, f) + midiIn.read() + f.close() + \ No newline at end of file diff --git a/src/songs/Song.py b/src/songs/Song.py new file mode 100755 index 0000000..c018eae --- /dev/null +++ b/src/songs/Song.py @@ -0,0 +1,137 @@ +''' +Created on 2 oct. 2009 + +@author: samsam +''' +import pickle +import os.path +from MidiToSong import MidiToSong +from mxmMidi.MidiInFile import MidiInFile + +class Song: + ''' + classdocs + ''' + + + 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"): + ''' + Constructor + ''' + self.name = name + self.scale = scale + self.notes = notesInExtendedScale + self.lyrics = lyrics + self.noteLengths = noteLengths + self.quarterNoteLength = quarterNoteLength + if midiNoteNumbers == None : + self.midiNoteNumbers = [self.scale[note] for note in self.notes] + else: + self.midiNoteNumbers = midiNoteNumbers + if self.notes == [] : + self.assignNotesFromMidiNoteNumbers() + self.requiresExtendedScale = requiresExtendedScale + if alterationIndexes != None and alterations != None : + self.alterNotes(alterationIndexes, alterations) + if modulationIndexes != None and modulationScales != None : + self.modulate(modulationIndexes, modulationScales) + + def getSongIterator(self): + while True: + for i in range(len(self.notes)): + if self.lyrics : + lyrics = self.lyrics[i] + else : + lyrics = None + + if self.noteLengths : + noteLength = self.noteLengths[i] + else : + noteLength = 1 + + yield [self.notes[i],self.midiNoteNumbers[i],lyrics,noteLength] + + def alterNotes(self,noteIndexes,alterations): + for i in range(len(noteIndexes)) : + self.midiNoteNumbers[noteIndexes[i]] = self.midiNoteNumbers[noteIndexes[i]] + alterations[i] + + def modulate(self,modulationIndexes,scales): + for i in range(len(scales)): + if i < len(scales)-1 : + bound = modulationIndexes[i+1] + else : + bound = len(self.notes) + for j in range(modulationIndexes[i],bound): + self.midiNoteNumbers[j]=scales[i][self.notes[j]] + + def assignNotesFromMidiNoteNumbers(self): + for i in range(len(self.midiNoteNumbers)): + noteInExtendedScale = 0 + while self.midiNoteNumbers[i] > self.scale[noteInExtendedScale] and noteInExtendedScale < len(self.scale)-1: + noteInExtendedScale += 1 + if self.midiNoteNumbers[i]