import pygtk
pygtk.require('2.0')
import gtk

class Key:
    def __init__(self, noteNumber):
        self._noteNumber = noteNumber
        self._rect = gtk.gdk.Rectangle()
        self.setActive(False)
        self.setVelocity(127)
        
    def setRect(self, rect):
        self._rect.x = rect.x
        self._rect.y = rect.y
        self._rect.width = rect.width
        self._rect.height = rect.height
                        
    def rect(self):
        return self._rect
        
    def noteNumber(self):
        return self._noteNumber

    def setActive(self, val):
        self._active = val

    def isActive(self):
        return self._active

    def setVelocity(self, vel):
        self._vel = vel
        
    def velocity(self):
        return self._vel
        
class KeyboardView(gtk.DrawingArea):
    
    blackKeys = [ 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 ]
    
    def __init__(self):
        gtk.DrawingArea.__init__(self)

        # setup for expose events (drawing)
        self.connect("expose_event", self.expose)
                
        # track the mouse
        self.connect("motion_notify_event", self.motion_notify_event)
        self.connect("button_press_event", self.button_press_event)
        self.connect("button_release_event", self.button_release_event)
        
        self.mouseDown = False

        self.noteOn = None
        self.noteOnUserData = None
        self.noteOff = None
        self.noteOffUserData = None
        
        self.keys = []
        self.setKeyRange(60 - 12, 24) 
        self.lastKeyDown = None
        
        self.set_events(gtk.gdk.EXPOSURE_MASK
                            | gtk.gdk.LEAVE_NOTIFY_MASK
                            | gtk.gdk.BUTTON_PRESS_MASK
                            | gtk.gdk.POINTER_MOTION_MASK
#                            | gtk.gdk.POINTER_MOTION_HINT_MASK
                            | gtk.gdk.BUTTON_RELEASE_MASK)

    def setKeyRange(self, min, length):
        self.keys = []
            
        self.rangeMin = min
        self.rangeLength = length
        for i in range(self.rangeLength):
            self.keys.append( Key(i + min) )
        
    ############################################################################
    # Mouse
    ############################################################################
    def pointInRect(self, x, y, rect):
        #print x, y, rect.x, rect.y, rect.width, rect.height
        if x >= rect.x and y >= rect.y and x <= (rect.x+rect.width) and y <= (rect.y+rect.height):
            return 1
        else:
            return 0

    def noteAtPoint(self, x, y):
        # test black first
        for key in self.keys:
            noteNum = key.noteNumber()            
            if self.blackKeys[ noteNum % 12 ]:
                rect = key.rect()
                if self.pointInRect(x, y, rect):
                    vel = ((y - rect.y) / rect.height) * 127
                    vel = int(127 - vel)
                    
                    return (key, vel)

        # test white
        for key in self.keys:
            noteNum = key.noteNumber()            
            if not self.blackKeys[ noteNum % 12 ]:
                rect = key.rect()
                if self.pointInRect(x, y, rect):
                    vel = ((y - rect.y) / rect.height) * 127
                    vel = int(127 - vel)
                    return (key, vel)

        return (None, None)
            
    def button_press_event(self, widget, event):
        if not self.mouseDown:
            #print "press", event.x, event.y
            self.mouseDown = True
            
            key, vel = self.noteAtPoint(event.x, event.y)
            if key != None:
                key.setVelocity(vel)
                self.turnNoteOn(key)

                
        return True
        
    def button_release_event(self, widget, event):        
        #print "release", event.x, event.y
        self.mouseDown = False
        
        if self.lastKeyDown != None:
            self.turnNoteOff(self.lastKeyDown)
            
        return True

    def motion_notify_event(self, widget, event):        
        #print "motion", event.x, event.y
        if self.mouseDown == True:

            key, vel = self.noteAtPoint(event.x, event.y)
            if key != None and self.lastKeyDown != None:
                if key != self.lastKeyDown:                            
                    self.turnNoteOff(self.lastKeyDown)                    
                    key.setVelocity(vel)
                    self.turnNoteOn(key)

                    
                self.redraw()
        
        return True



    def turnNoteOn(self, key):
        key.setActive(True)
        self.redraw()        
        if self.noteOn != None:
            self.noteOn(key.noteNumber(), key.velocity(), self.noteOnUserData)
        self.lastKeyDown = key

    def turnNoteOff(self, key):
        key.setActive(False)
        self.redraw()
        if self.noteOff != None:
            self.noteOff(key.noteNumber(), key.velocity(), self.noteOffUserData)
                        

        

    def setNoteOnCallback(self, callback, userData = None):
        self.noteOn = callback
        self.noteOnUserData = userData

    def setNoteOffCallback(self, callback, userData = None):
        self.noteOff = callback
        self.noteOffUserData = userData
        
    def redraw(self):
        rect = self.get_allocation()
        self.queue_draw_area(0, 0, rect.width, rect.height)

    def determineNumWhiteNotesInRange(self):
        count = 0;
        
        for i in range(self.rangeLength):
            if not self.blackKeys[(i + self.rangeMin) % 12]:
                count = count + 1
        
        return count
	

    ############################################################################
    # Drawing
    ############################################################################
    def expose(self, widget, event):
        #print "expose", event.area.x, event.area.y, event.area.width, event.area.height        
        self.context = widget.window.cairo_create()        
        # set a clip region for the expose event
        self.context.rectangle(event.area.x, event.area.y,
                               event.area.width, event.area.height)
        self.context.clip()        
        self.draw(self.context)        
        return False
    
    def draw(self, context):        
        rect = self.get_allocation()

        numWhiteNotes = self.determineNumWhiteNotesInRange()
                
        whiteNoteRect = gtk.gdk.Rectangle(0, 0, rect.width / numWhiteNotes, rect.height)
        blackNoteRect = gtk.gdk.Rectangle(0, 0, int(whiteNoteRect.width * 0.5), int(whiteNoteRect.height * (5.0 / 8.0)))
        
        for key in self.keys:
            noteNum = key.noteNumber()
            
            if self.blackKeys[ noteNum % 12 ] == 0:
                # its white
                
                key.setRect(whiteNoteRect)
                
                if key.isActive():
                    context.set_source_rgba(0.4, 0.6, 0.8, key.velocity() / 127.0)
                    context.rectangle(whiteNoteRect.x, whiteNoteRect.y, whiteNoteRect.width, whiteNoteRect.height)
                    context.fill()

                else:
                    context.set_source_rgba(0.9, 0.9, 0.9, 1.0)
                    context.rectangle(whiteNoteRect.x, whiteNoteRect.y, whiteNoteRect.width, whiteNoteRect.height)
                    context.fill()

                context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
                context.rectangle(whiteNoteRect.x, whiteNoteRect.y, whiteNoteRect.width, whiteNoteRect.height)
                context.stroke()
                
                whiteNoteRect.x = whiteNoteRect.x + whiteNoteRect.width

	
        if not self.blackKeys[ self.rangeMin % 12 ]:
            # first note is white
            # is next note white?
            if not self.blackKeys[ (self.rangeMin+1) % 12 ]:
                # yes
                blackNoteRect.x = whiteNoteRect.width * 1.75;
            else:
                blackNoteRect.x = whiteNoteRect.width * 0.75;
        else:
            blackNoteRect.x = - (whiteNoteRect.width * 0.25);

        
        whiteNoteRect.x = 0
        
        for key in self.keys:
            noteNum = key.noteNumber()
            
            if self.blackKeys[ noteNum % 12 ] == 0:
                whiteNoteRect.x = whiteNoteRect.x + whiteNoteRect.width
            else:

                key.setRect(blackNoteRect)

                if key.isActive():
                    context.set_source_rgba(0.2, 0.2, 0.2, 1.0)
                    context.rectangle(blackNoteRect.x, blackNoteRect.y, blackNoteRect.width, blackNoteRect.height)                
                    context.fill()

                    context.set_source_rgba(0.4, 0.6, 0.8, key.velocity() / 127.0)
                    context.rectangle(blackNoteRect.x, blackNoteRect.y, blackNoteRect.width, blackNoteRect.height)
                    context.fill()
                else:
                    context.set_source_rgba(0.2, 0.2, 0.2, 1.0)
                    context.rectangle(blackNoteRect.x, blackNoteRect.y, blackNoteRect.width, blackNoteRect.height)                
                    context.fill()

                context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
                context.rectangle(blackNoteRect.x, blackNoteRect.y, blackNoteRect.width, blackNoteRect.height)                
                context.stroke()

                # is the note after the following note white?
                # in other words, are we at A# or D#?
                if self.blackKeys[ (noteNum+2) % 12 ] == 0:
                    blackNoteRect.x = blackNoteRect.x + (whiteNoteRect.width * 2.0)
                else:
                    blackNoteRect.x = blackNoteRect.x + whiteNoteRect.width
