from sugar.activity import activity
import logging

from CsoundServer import *
from CsoundEventSequencer import *

import random
import sys, os
import gtk

import gobject


class EventSelection:
    def __init__(self, interface):
        self._dragOrigin = [0, 0]
        self._selection = set([])
        self.interface = interface
            
    def clear(self):            
        tracksToRedraw = set([])
        # see if we need to redraw any tracks based on what the selection was
        for event in self._selection:
            event.setResizing(False)
            tracksToRedraw.add(event.trackIndex())

        self._selection = set([])

        self.interface.redrawTracks(tracksToRedraw)    
        
    def addEvent(self, event):
        self._selection.add( event )

    def numSelected(self):
        return len(self._selection)
        
    def selection(self):
        return self._selection
        
    def setDragOrigin(self, x, y):
        self._dragOrigin = [x, y]
        
    def dragOrigin(self):    
        return self._dragOrigin




class CSSequencerActivity(activity.Activity):
    orchestra = """
        nchnls = 1

gisin1	ftgen	1, 0, 8192, 10, 1
gisin2	ftgen	2, 0, 4096, 10, 1, 0.5, 0.3, 0.7, 0.2, 0.8
gisin3	ftgen	3, 0, 4096, 10, 1, 0.35, 0.23, 0.17, 0.02, 0.18, 0.7


gkVol1 init 0.0
gkVol1 chnexport "track0vol", 1, 0, i(gkVol1), -36.0, 12.0
gkVol2 init 0.0
gkVol2 chnexport "track1vol", 1, 0, i(gkVol2), -36.0, 12.0
gkVol3 init 0.0
gkVol3 chnexport "track2vol", 1, 0, i(gkVol3), -36.0, 12.0
gkVol4 init 0.0
gkVol4 chnexport "track3vol", 1, 0, i(gkVol4), -36.0, 12.0

gkRevSend1 init -48.0
gkRevSend1 chnexport "reverb0Send", 1, 0, i(gkRevSend1), -60, 0
gkRevSend2 init -48.0
gkRevSend2 chnexport "reverb1Send", 1, 0, i(gkRevSend2), -60, 0
gkRevSend3 init -48.0
gkRevSend3 chnexport "reverb2Send", 1, 0, i(gkRevSend3), -60, 0
gkRevSend4 init -48.0
gkRevSend4 chnexport "reverb3Send", 1, 0, i(gkRevSend4), -60, 0

gaReverbIn init 0

turnon 100

        instr	10 ; uses f-table 1        

kSend = exp(gkRevSend1 * 0.11512925)
iPitch	= 	8.17579891564 * exp(0.0577622650 * p4)
kAmp = exp(gkVol1 * 0.11512925)

kAmp = kAmp * ampdbfs(-12)

  kCPS      expon   cpspch(p5), p3, cpspch(p6)
    
  ; Select the wave form.
  iWave = 2

  ; Set the pulse-width/saw-ramp character.
  kpw init 0.5

  ; Use Table #1.
  ifn = 1

aEnv        transeg       1.0, p3, 2, 0.00001

  ; Generate the waveform.
aOsc        vco         kAmp, kCPS, iWave, kpw, gisin1

aOut = aOsc * aEnv

    out aOut

gaReverbIn = gaReverbIn + aOut * kSend        
        endin

        instr	11 ; clap
kSend = exp(gkRevSend2 * 0.11512925)
        
iamp = 1000
kAmp = exp(gkVol2 * 0.11512925)

kamp1 expon 1.25, .03,  .0001
kamp2 expseg .001,.005,1, .35, .001
  
anoise rand 1

anoise1 = anoise*kamp1
anoise2 = anoise*kamp2

adel1     = anoise1
adel2 delay anoise1, .01
adel3 delay anoise1, .02
adel4 delay anoise2, .03

abp1 resonz adel1,  400,  1100
abp2 resonz adel2,  600,  1100
abp3 resonz adel3,  800,  1100
abp4 resonz adel4,  1100, 1100

aOut = iamp*2*(abp1+abp2+abp3+abp4)
aOut = aOut * kAmp

    out aOut

gaReverbIn = gaReverbIn + aOut * kSend        
        endin

        instr	12 ; uses f-table 3
kSend = exp(gkRevSend3 * 0.11512925)
iPitch	= 	8.17579891564 * exp(0.0577622650 * p4)
kAmp = exp(gkVol3 * 0.11512925)
aAmp    line    ampdbfs(-12), p3, 0
a1      oscil	aAmp, iPitch, 3
a1 = a1 * kAmp
        out	a1

gaReverbIn = gaReverbIn + a1 * kSend        
        endin


        instr	13 ; Hi Hat
iPitch	= 	8.17579891564 * exp(0.0577622650 * p4)

kSend = exp(gkRevSend4 * 0.11512925)
aAmp    expon   1.0, p3, 0.00001
kAmp = exp(gkVol4 * 0.11512925)
a1      rand	ampdbfs(-12)

abp1 resonz a1,  iPitch * 2.0,  500

abp1 = abp1 * aAmp * kAmp
        out	abp1
        
gaReverbIn = gaReverbIn + abp1 * kSend        
        endin


        instr 100 ; reverb

a1  nreverb gaReverbIn, 2.0, 0.3
        
        out a1

gaReverbIn = 0 ; clear the bus
        endin
    """



    def destroy(self, data=None):
        print "Destroy"
        self.eventSequencer.shutdownServer()
        return True

       
    def __init__(self, handle):        
        activity.Activity.__init__(self, handle)
        
        self.connect('destroy', self.destroy)

        server = CsoundServer()
        fd = server.launch(sampleRate=16000)
        #if fd != None:
        #    gobject.io_add_watch(fd.fileno(), gobject.IO_IN, self.serverMessages, fd)
        

        self.set_title('Sequencer')
        self.initializeParameters()
        
        self.lastmin = -1
        self.lastdur = -1
                
        # Creates the Toolbox. It contains the Activity Toolbar, which is the
        # bar that appears on every Sugar window and contains essential
        # functionalities, such as the 'Collaborate' and 'Close' buttons.
        toolbox = activity.ActivityToolbox(self)
        self.set_toolbox(toolbox)
        toolbox.show()
        
        
        self.setup()
        widgets = self.setupWidgets()
        # Set the button to be our canvas. The canvas is the main section of
        # every Sugar Window. It fills all the area below the toolbox.
        self.set_canvas(widgets)
        
        widgets.show_all()
                
        

    def setup(self):
        self.tempo = 60
        self.minTempo = 30
        self.maxTempo = 240
        
        self.grid = 4
        self.minGrid = 1
        self.maxGrid = 9

        # setup network sequencer (this will also setup an instance of Csound)
        self.eventSequencer = CsoundEventSequencer("Seq", self.orchestra)
        self.eventSequencer.setTrackAddedCallback(self.trackAdded)
        self.eventSequencer.setTrackRemovedCallback(self.trackRemoved)
        self.eventSequencer.setEventAddedCallback(self.eventAdded)
        self.eventSequencer.setEventUpdateCallback(self.eventUpdated)
        self.eventSequencer.setEventRemovedCallback(self.eventRemoved)
        self.eventSequencer.setTrackClearedCallback(self.trackCleared)
        
        self.eventSelection = EventSelection(self)
                        
        mainWidget = self.setupWidgets()        
        mainWidget.show_all()
                
        localConnection = self.eventSequencer.addConnection(sampleRate=16000, controlRate=160)
        if localConnection != None:
            # we only want to get time notifications from our local server
            localConnection.registerTime(self.timeUpdate, 0.25) # 4x per sec

        for i in range(4):
            self.eventSequencer.requestNewTrack()
        
        # use this to turn off csound messages
        #self.eventSequencer.setDisplayMessages(False)

        # initialize our tempo
        self.tempoSlider.set_value(120)
        self.eventSequencer.setLoopEnabled(True)
    

    def initializeParameters(self):
        """
        Initialize params
        """
        pass
        

    def setupWidgets(self):
        self.layout = gtk.Layout()
        self.layout.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#C0C0C0"))
                               
        # transport
        self.playButton = gtk.Button("play")
        self.playButton.set_size_request(50*2, 40)
        self.playButton.connect("clicked", self.playClicked, None)
        
        self.pauseButton = gtk.Button("pause")
        self.pauseButton.set_size_request(50*2, 40)
        self.pauseButton.connect("clicked", self.pauseClicked, None)
        
        self.stopButton = gtk.Button("stop")
        self.stopButton.set_size_request(50*2, 40)
        self.stopButton.connect("clicked", self.stopClicked, None)

        tempoLabel = gtk.Label('tempo')
        gridLabel = gtk.Label('grid')
        
        # value, lower, upper, step_increment, page_increment, page_size
        self.tempoSlider = gtk.HScale(gtk.Adjustment(self.tempo, self.minTempo, self.maxTempo, 1.0, 1.0, 1.0))
        self.tempoSlider.connect("value_changed", self.tempoSliderChanged, self.tempoSlider)
        self.tempoSlider.set_size_request(200, 50)
        self.tempoSlider.set_update_policy(gtk.UPDATE_CONTINUOUS)
        self.tempoSlider.set_digits(1)
        self.tempoSlider.set_value_pos(gtk.POS_BOTTOM)
        self.tempoSlider.set_draw_value(True)

        # value, lower, upper, step_increment, page_increment, page_size
        self.gridSlider = gtk.HScale(gtk.Adjustment(self.grid, self.minGrid, self.maxGrid, 1.0, 1.0, 1.0))
        self.gridSlider.connect("value_changed", self.gridSliderChanged, self.gridSlider)
        self.gridSlider.set_size_request(200, 50)
        self.gridSlider.set_update_policy(gtk.UPDATE_CONTINUOUS)
        self.gridSlider.set_digits(1)
        self.gridSlider.set_value_pos(gtk.POS_BOTTOM)
        self.gridSlider.set_draw_value(True)

    
        self.syncLocalButton = gtk.RadioButton(None, "local")
        self.syncLocalButton.connect("toggled", self.syncMode, "local")
        self.syncMasterButton = gtk.RadioButton(self.syncLocalButton, "master")
        self.syncMasterButton.connect("toggled", self.syncMode, "master")
        self.syncSlaveButton = gtk.RadioButton(self.syncLocalButton, "slave")
        self.syncSlaveButton.connect("toggled", self.syncMode, "slave")
   
        # pages
        self.pageButtons = [ gtk.Button("all"), gtk.Button("1"), 
            gtk.Button("2"), gtk.Button("3"), gtk.Button("4")]
        
        self.pageButtons[0].set_size_request(35*2, 40)
        self.pageButtons[0].connect("clicked", self.pageClicked, "0")

        self.pageButtons[1].set_size_request(35*2, 40)
        self.pageButtons[1].connect("clicked", self.pageClicked, "1")

        self.pageButtons[2].set_size_request(35*2, 40)
        self.pageButtons[2].connect("clicked", self.pageClicked, "2")

        self.pageButtons[3].set_size_request(35*2, 40)
        self.pageButtons[3].connect("clicked", self.pageClicked, "3")

        self.pageButtons[4].set_size_request(35*2, 40)
        self.pageButtons[4].connect("clicked", self.pageClicked, "4")

        self.pFieldsText = gtk.TextView()
        self.pFieldsText.set_size_request(300*2, 35)

        self.locatorView = LocatorView()
        self.locatorView.set_size_request(900, 20)

        self.updateButton = gtk.Button("update")
        self.updateButton.set_size_request(50*2, 40)
        self.updateButton.connect("clicked", self.updateClicked, None)
                        
        # tracks
        self.trackViews = [ TrackView(self), TrackView(self), 
            TrackView(self), TrackView(self) ]

        self.trackViews[0].set_size_request(900, 155)
        self.trackViews[0].setEventSelection(self.eventSelection)        
        self.trackViews[1].set_size_request(900, 155)
        self.trackViews[1].setEventSelection(self.eventSelection)
        self.trackViews[2].set_size_request(900, 155)
        self.trackViews[2].setEventSelection(self.eventSelection)
        self.trackViews[3].set_size_request(900, 155)
        self.trackViews[3].setEventSelection(self.eventSelection)
        
        # track info
        self.trackInfoViews = [ TrackInfoView(0, 10, self), TrackInfoView(1, 11, self), 
            TrackInfoView(2, 12, self), TrackInfoView(3, 13, self) ]
        
        # header layout
        self.layout.put(self.playButton, 10, 5)
        self.layout.put(self.pauseButton, 120, 5)
        self.layout.put(self.stopButton, 230, 5)

        #self.layout.put(self.syncLocalButton, 200*2, 10)
        #self.layout.put(self.syncMasterButton, 270*2, 10)
        #self.layout.put(self.syncSlaveButton, 340*2, 10)
                        
        self.layout.put(tempoLabel, 470, 10)
        self.layout.put(self.tempoSlider, 550, 10)
        self.layout.put(gridLabel, 800, 10)        
        self.layout.put(self.gridSlider, 880, 10)
                
        self.layout.put(self.pageButtons[0], 10, 70)
        self.layout.put(self.pageButtons[1], 45*2, 70)
        self.layout.put(self.pageButtons[2], 80*2, 70)
        self.layout.put(self.pageButtons[3], 115*2, 70)
        self.layout.put(self.pageButtons[4], 150*2, 70)


        self.layout.put(self.pFieldsText, 210*2, 70)
        self.layout.put(self.updateButton, 520*2, 70)
        
                
        self.layout.put(self.locatorView, 295, 125)
        
        # tracks layout
        self.trackInfoViews[0].put(self.layout, 5, 160)
        self.trackInfoViews[1].put(self.layout, 5, 160 + (165))
        self.trackInfoViews[2].put(self.layout, 5, 160 + (165*2))
        self.trackInfoViews[3].put(self.layout, 5, 160 + (165*3))
        
        self.layout.put(self.trackViews[0], 295, 160)
        self.layout.put(self.trackViews[1], 295, 160 + (165))
        self.layout.put(self.trackViews[2], 295, 160 + (165*2))
        self.layout.put(self.trackViews[3], 295, 160 + (165*3))
                        
        return self.layout



    def syncMode(self, widget, data = None):
        if data == "master":
            self.eventSequencer.setSyncMode(1) 
        elif data == "slave":
            self.eventSequencer.setSyncMode(2) 
        else:
            self.eventSequencer.setSyncMode(0) 

    def updatePFields(self):
        if self.eventSelection.numSelected() == 1:
            for event in self.eventSelection.selection():
                pfields = ''
                for pfield in event.pFields():
                    pfields += str(pfield) + " "
                                    
                buffer = gtk.TextBuffer()
                buffer.set_text(pfields)
                self.pFieldsText.set_buffer(buffer)
        

    def updateClicked(self, widget, data = None):
        buffer = self.pFieldsText.get_buffer()
        startiter, enditer = buffer.get_bounds()
        val = buffer.get_text(startiter, enditer)
        
        items = val.split(' ')
        testedItems = []
        for i in items:
            if i:
                testedItems.append(float(i))
                
        for event in self.eventSelection.selection():
            event.setPFields(testedItems)
            event.requestUpdate()
        
    def pageClicked(self, widget, data = None):
        page = int(data)
        
        if page == 0:
            min = 0
            dur = 16            
        else:
            min = (page-1) * 4
            dur = 4

        self.eventSequencer.gotoBeat(min)
        self.eventSequencer.setLoopPoints(min, dur)

            
        for i in range(4):
            view = self.trackViews[i]
            view.setTimeVisible(min, min+dur)
            self.locatorView.setTimeVisible(min, min+dur)
            
            if min != self.lastmin or dur != self.lastdur:
                view.redrawAll()
        
        self.lastmin = min
        self.lastdur = dur
        
    def playClicked(self, widget, data = None):
        self.eventSequencer.play()
        
    def pauseClicked(self, widget, data = None):
        self.eventSequencer.pause()
        
    def stopClicked(self, widget, data = None):
        self.eventSequencer.stop()

    def tempoSliderChanged(self, widget, data = None):
        val = int( widget.get_value() + 0.5 )
        self.eventSequencer.setTempo(val)
        
    def gridSliderChanged(self, widget, data = None):
        val = int( widget.get_value() + 0.5 )
        for i in range(4):
            self.trackViews[i].setQuantize( 1.0 / val )
        
        self.locatorView.setQuantize( 1.0 / val )
        
    def setupWindowWithMainWidget(self, mainWidget):
        """
        Setup our window with its main widget
        """
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)        
        self.window.connect("destroy", self.destroy)
        self.window.set_size_request(800, 600)
        self.window.add(mainWidget)
        self.window.show_all()


    def redrawTracks(self, trackIndexSet):
        for i in trackIndexSet:
            self.trackViews[i].redrawAll()

    ############################################################################
    # Callbacks from CsoundEventSequencer
    ############################################################################
    def trackAdded(self, eventSequencer, track, requestRef, userData = None):
        
        index = track.index()
        
        track.setInstrument( track.index() + 10 )
        
        self.trackViews[index].setTrack(track)        
        self.trackViews[index].redrawAll()
        
    def trackRemoved(self, eventSequencer, track, requestRef, userData = None):
        print "trackRemoved"
        

    def eventAdded(self, eventSequencer, track, event, requestRef, userData = None):
        self.eventSelection.addEvent(event)        
        
        self.updatePFields()
        
        index = track.index()
        self.trackViews[index].redrawAll()
        
    def eventUpdated(self, eventSequencer, track, event, requestRef, userData = None):
        index = track.index()
        self.trackViews[index].redrawAll()
                
    def eventRemoved(self, eventSequencer, track, requestRef, userData = None):
        index = track.index()
        self.trackViews[index].redrawAll()

    def timeUpdate(self, message, userData = None):
        loc = message.argument(0) # current sequencer time
        #print message.argument(1) # total beats elapsed
        
        self.locatorView.setLocation(loc)
        self.locatorView.redrawAll()
                
    def trackCleared(self, eventSequencer, track, userData = None):
        index = track.index()
        self.trackViews[index].redrawAll()





class TrackInfoView:
    instrumentNames = [ 'kick', 'clap', 'synth', 'hi-hat' ]
    
    def __init__(self, index, instr, interface):
        self.interface = interface
        self.index = index

        self.nameLabel = gtk.Label('name')
        
        self.vol = 0.0
        self.minVol = -36.0
        self.maxVol = 12.0

        self.minSend = -60.0
        self.maxSend = 0.0

        self.minInstrument = 10
        self.maxInstrument = 13

        adj = gtk.Adjustment(self.minInstrument, self.minInstrument, self.maxInstrument, 1.0, 1.0, 1.0)

        self.instrumentSpin = gtk.SpinButton(adj, 0, 0)
        self.instrumentSpin.connect("value_changed", self.instrChanged, index)        
        self.instrumentSpin.set_size_request(40*2, 20*2)
        self.instrumentSpin.set_wrap(True)

        self.instrumentSpin.set_value(instr)
        self.updateName()
        
        # value, lower, upper, step_increment, page_increment, page_size
        self.volSlider = gtk.HScale(gtk.Adjustment(self.vol, self.minVol, self.maxVol+1, 1.0, 1.0, 1.0))
        self.volSlider.connect("value_changed", self.volSliderChanged, index)
        self.volSlider.set_size_request(200, 30)
        self.volSlider.set_update_policy(gtk.UPDATE_CONTINUOUS)
        #self.volSlider.set_digits(1)
        #self.volSlider.set_value_pos(gtk.POS_BOTTOM)
        self.volSlider.set_draw_value(False)

        # value, lower, upper, step_increment, page_increment, page_size
        self.revSendSlider = gtk.HScale(gtk.Adjustment(-48, self.minSend, self.maxSend+1, 1.0, 1.0, 1.0))
        self.revSendSlider.connect("value_changed", self.revSliderChanged, index)
        self.revSendSlider.set_size_request(200, 30)
        self.revSendSlider.set_update_policy(gtk.UPDATE_CONTINUOUS)
        #self.revSendSlider.set_digits(1)
        #self.revSendSlider.set_value_pos(gtk.POS_BOTTOM)
        self.revSendSlider.set_draw_value(False)

        self.revSendSlider.set_value(-48)
        
        self.muteButton = gtk.ToggleButton("m")
        self.muteButton.set_size_request(50, 40)
        self.muteButton.connect("clicked", self.muteClicked, None)

        self.soloButton = gtk.ToggleButton("s")
        self.soloButton.set_size_request(50, 40)
        self.soloButton.connect("clicked", self.soloClicked, None)

        self.clearButton = gtk.Button("clear")
        self.clearButton.set_size_request(80, 40)
        self.clearButton.connect("clicked", self.clearClicked, None)

    def instrChanged(self, widget, data = None):
        try:
            self.interface.eventSequencer.track(self.index).setInstrument(widget.get_value())
            self.setVolume(self.volSlider.get_value())
            self.updateName()
        except Exception, msg:
            print msg

    def updateName(self):
        self.setName( self.instrumentNames[ int( self.instrumentSpin.get_value() - self.minInstrument ) ] )
    
    def setName(self, name):
        self.nameLabel.set_text(name)
        
    def soloClicked(self, widget, data = None):
        """
        solo or unsolo the track depending on the button state
        """        
        if widget.get_active() == True:
            self.interface.eventSequencer.soloTrack(self.index)
        else:
            self.interface.eventSequencer.unsoloTrack(self.index)
            
    def muteClicked(self, widget, data = None):
        """
        mute or unmute the track depending on the button state
        """        
        if widget.get_active() == True:
            self.interface.eventSequencer.muteTrack(self.index)
        else:
            self.interface.eventSequencer.unmuteTrack(self.index)

    def setVolume(self, val):
        """
        since each instrument has a hard wired track volume
        we need to change the name based on the instrument
        
        see the orchestra for how this relates
        """
        volIndex = int( self.instrumentSpin.get_value() - self.minInstrument )
        channelName = "track"+str(volIndex)+"vol"
        self.interface.eventSequencer.writeValueToChannel(channelName, val)

    def revSliderChanged(self, widget, data = None):
        volIndex = int( self.instrumentSpin.get_value() - self.minInstrument )
        channelName = "reverb"+str(volIndex)+"Send"
        self.interface.eventSequencer.writeValueToChannel(channelName, widget.get_value())

    def volSliderChanged(self, widget, data = None):
        self.setVolume(self.volSlider.get_value())            

    def clearClicked(self, widget, data = None):
        self.interface.eventSequencer.clearTrack(self.index)
        
    def put(self, layout, x, y):
        layout.put(self.instrumentSpin, x + 5, y)        
        layout.put(self.nameLabel, x + 95, y)        

        layout.put(gtk.Label("vol"), x + 5, y + 40)
        layout.put(self.volSlider, x + 55, y + 40)

        layout.put(gtk.Label("rev"), x + 5, y + 70)
        layout.put(self.revSendSlider, x + 55, y + 70)
        
        layout.put(self.muteButton, x + 5, y + 110)
        layout.put(self.soloButton, x + 55, y + 110)

        layout.put(self.clearButton, x + 115, y + 110)



        
class LocatorView(gtk.DrawingArea):
    def __init__(self):
        gtk.DrawingArea.__init__(self)
        
        self.setTimeVisible(0, 4)
        self.setQuantize(0.25)
        self.setLocation(0)
            
        # setup for expose events (drawing)
        self.connect("expose_event", self.expose)


    def setTimeVisible(self, min, max):
        self.minTimeVisible = min
        self.maxTimeVisible = max

    def setQuantize(self, quantize):
        self.quantize = quantize

    def setLocation(self, loc):
        self.location = loc
        
    ############################################################################
    # Drawing
    ############################################################################
    def expose(self, widget, event):
        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 drawGrid(self, context, visibleTime, rect):        
        top = 0
        bottom = rect.height
        xScale = rect.width / visibleTime        
                
        context.set_source_rgba(0.6, 0.6, 0.6, 0.75)
        
        numLines = int( (1.0 / self.quantize) * visibleTime )
        
        for i in range(numLines):
            x = i * xScale * self.quantize
            context.move_to(x, top)
            context.line_to(x, bottom)
            context.stroke()

        numLines = int( visibleTime )
        context.set_source_rgba(0.6, 0.6, 0.6, 1.0)        
        for i in range(numLines):
            x = i * xScale
            context.move_to(x, top)
            context.line_to(x, bottom)
            context.stroke()

    def draw(self, context):        
        rect = self.get_allocation()
           
        context.set_source_rgba(0, 0, 0, 1.0)
        context.rectangle(0, 0, rect.width, rect.height)
        context.fill()

        # calculate scales
        visibleTime = self.maxTimeVisible - self.minTimeVisible            
        xScale = rect.width / visibleTime        
        trackHeight = rect.height
                
        # draw the grid
        self.drawGrid(context, visibleTime, rect)

        # draw locator
        context.set_source_rgba(0.6, 0.8, 1.0, 1.0)
        context.rectangle( (self.location - self.minTimeVisible) * xScale, 0, 5, rect.height)
        context.fill()

    def redrawAll(self):        
        rect = self.get_allocation()
        self.queue_draw_area(0, 0, rect.width, rect.height)




class TrackView(gtk.DrawingArea):
    def __init__(self, interface):
        gtk.DrawingArea.__init__(self)
                
        self.interface = interface

        self.setTrack(None)
        
        # 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.resize = False
        
        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)


        # defaults
        self.minTimeVisible = 0.0
        self.maxTimeVisible = 4.0
        
        self.minPitch = 60
        self.maxPitch = 72
        self.pitchRange = self.maxPitch - self.minPitch

        self.useQuantize = True        
        self.setQuantize(0.25)
        self.location = 0

    def setTimeVisible(self, min, max):
        self.minTimeVisible = min
        self.maxTimeVisible = max
        self.redrawAll()
        
    def setEventSelection(self, eventSel):
        self.eventSelection = eventSel
        
    def setTrack(self, track):
        self._track = track
        
    def track(self):
        return self._track

    def setQuantize(self, quantize):
        self.quantize = quantize
        self.redrawAll()
                
    ############################################################################
    # Mouse
    ############################################################################
    def button_press_event(self, widget, event):
        if self._track == None:
            return
            
        self.mouseDown = True
        
        trackEvent, start, pitch = self.eventAtLocation(event.x, event.y)
        
        dur = self.quantize
        
        if event.state & gtk.gdk.SHIFT_MASK:
            shiftDown = True
        else:
            shiftDown = False

        if trackEvent == None:
            if shiftDown == False:
                self.eventSelection.clear()
            
            if self._track.index() == 0:    
                self._track.requestNewEvent(start, dur, pitch, [9.00, 6.00] )
            else:
                self._track.requestNewEvent(start, dur, pitch)
            
            self.eventSelection.setDragOrigin(event.x, event.y)

        else:
            if event.button == 1: # ADD AND/OR SELECT EVENT
                
                # handle multiple selection
                if shiftDown == False:
                    #print self.eventSelection.selection()
                    #print trackEvent
                    
                    if trackEvent in self.eventSelection.selection():
                        self.eventSelection.addEvent(trackEvent)
                        self.interface.updatePFields()
                    else:
                        self.eventSelection.clear()
                        self.eventSelection.addEvent(trackEvent)
                        self.interface.updatePFields()
                else:
                    self.eventSelection.addEvent(trackEvent)
                    self.interface.updatePFields()
                    
                # see if we clicked the right edge (past 80% of the event)
                fraction = (self.timeAtLocation(event.x) - trackEvent.startTime()) / trackEvent.duration()
                if fraction > 0.8:
                    self.resize = True
                    trackEvent.setResizing(True)
                else:
                    self.resize = False
                    trackEvent.setResizing(False)

                # save this location for reference when we release the mouse
                self.eventSelection.setDragOrigin(event.x, event.y)    
                
                # redraw if necessary
                self.redrawAll() 
                
            elif event.button == 2: # REMOVE EVENT

                self.eventSelection.clear()
                self.interface.updatePFields()
                self._track.requestRemoveEvent(trackEvent)
                
        return True

        
    def button_release_event(self, widget, event):        
        if self._track == None:
            return
            
        self.mouseDown = False

        if self.eventSelection.numSelected() > 0:

            originX, originY = self.eventSelection.dragOrigin()
            
            trackEventOrigin, startOrigin, pitchOrigin = self.eventAtLocation(originX, originY)
            
            trackEventDest, startDest, pitchDest = self.eventAtLocation(event.x, event.y)
            
            startDelta = startDest - startOrigin
            pitchDelta = pitchDest - pitchOrigin

            #print "startDelta", startDelta, "pitchDelta", pitchDelta
            
            if self.resize == True:
                for selEvent in self.eventSelection.selection():
                    newDur = selEvent.duration() + startDelta
                    if newDur < self.quantize:
                        newDur = self.quantize
                    selEvent.setDuration( newDur )
                    selEvent.requestUpdate()
                    
            else:
                for selEvent in self.eventSelection.selection():
                    if selEvent.startTime() != startDest or selEvent.noteNum() != pitchDest:
                        
                        selEvent.setStartTime( selEvent.startTime() + startDelta )
                        selEvent.setNoteNum( selEvent.noteNum() + pitchDelta )                                        
                        selEvent.requestUpdate()
                                            

        
    def motion_notify_event(self, widget, event):        
        pass


    def redrawAll(self):        
        rect = self.get_allocation()
        self.queue_draw_area(0, 0, rect.width, rect.height)

    ############################################################################
    # Working With Events (and their locations)
    ############################################################################
    def eventAtLocation(self, x, y):
        if self._track != None:            
            start = self.timeAtLocation(x)
            if self.useQuantize:
                # quantize
                start = int(start / self.quantize) * self.quantize

            pitch = self.pitchAtLocation(y)            
            return self._track.timeAndPitchIntersectsEvent(start, pitch), start, pitch
            
    def timeAtLocation(self, x):
        rect = self.get_allocation()        
        visibleDuration = self.maxTimeVisible - self.minTimeVisible        
        pixelsPerBeat = rect.width / float( visibleDuration )       
        return (x / pixelsPerBeat) + self.minTimeVisible
        
    def pitchAtLocation(self, y):
        rect = self.get_allocation()    
        trackHeight = rect.height
        eventHeight = trackHeight / (self.maxPitch - self.minPitch)
        eventIndex = y / eventHeight        
        
        note = self.minPitch + int(eventIndex)
        return (self.maxPitch - note) + self.minPitch
    
    def trackIndexAtLocation(self, x, y):
        rect = self.get_allocation()            
        trackHeight = rect.height
        trackIndex = int( y / trackHeight )
        return trackIndex
            

        
    ############################################################################
    # 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 drawGrid(self, context, visibleTime, rect):        
        top = 0
        bottom = rect.height
        xScale = rect.width / visibleTime        
                
        context.set_source_rgba(0.6, 0.6, 0.6, 0.75)
        
        numLines = int( (1.0 / self.quantize) * visibleTime )
        
        for i in range(numLines):
            x = i * xScale * self.quantize
            context.move_to(x, top)
            context.line_to(x, bottom)
            context.stroke()

        numLines = int( visibleTime )
        context.set_source_rgba(0.6, 0.6, 0.6, 1.0)        
        for i in range(numLines):
            x = i * xScale
            context.move_to(x, top)
            context.line_to(x, bottom)
            context.stroke()

    def draw(self, context):        
        rect = self.get_allocation()
           
        context.set_source_rgba(0.2, 0.2, 0.2, 1.0)
        context.rectangle(0, 0, rect.width, rect.height)
        context.fill()

        if self._track == None:
            return

        # calculate scales
        visibleTime = self.maxTimeVisible - self.minTimeVisible            
        xScale = rect.width / visibleTime        
        trackHeight = rect.height
        eventHeight = trackHeight / (self.pitchRange)
                
        # draw the grid
        self.drawGrid(context, visibleTime, rect)
        
        # draw the events
        #
        # note: this could and should be optimized
        for event in self._track.events():
            if event in self.eventSelection.selection():
                if event.resizing():                                    
                    context.set_source_rgba(0.8, 0.0, 0.0, 0.75)
                else:
                    context.set_source_rgba(0.2, 0.8, 0.2, 0.5)
            else:
                context.set_source_rgba(0.2, 0.4, 0.6, 0.5)
                
            x = (event.startTime() - self.minTimeVisible) * xScale
            eventWidth = event.duration() * xScale
  
            note = event.noteNum() - self.minPitch
            # flip the note for drawing
            note = self.pitchRange - note
            y = note * eventHeight
                            
            context.rectangle(x, y, eventWidth, eventHeight)
            context.fill()
            
            context.set_source_rgba(1.0, 1.0, 1.0, 1.0)
            context.rectangle(x, y, eventWidth, eventHeight)
            context.stroke()                


    def serverMessages(self, source, condition, userData = None):
        print userData.readline(),
        return True

