#!/usr/bin/env python

# initial version inherited from python tutorial example scribblesimple.py
# musicpainter.py  Wu-Hsi Li, June 11, 2007

# mp1.2 - fix the multi-button_press_event problem by checking release
#       - add attribute _showFunctionCall, _showEvent, _showCSoundMsg
#       - now accepting system parameter: server ip and port
#       - fix bug 0.1
#                                                       June 15

# mp1.3 - click on blocks to select them, then click once to drag them
#       - implement select_same_color function
#         June 16

# mp1.4 - extend to 8 colors
#         June 18

# mp1.5 - in painting mode, click on a painted grid will unpaint it
#          (unless the system finds that the user actually intent to drag)
#       - click (without dragging) on selected object can switch between
#         selecting connected grids, or selecting same color
#       - show the selection region
#         June 19

# mp1.6 - clicking toolbar is now making sounds
#       - the note on/off problem is fixed (modification appeared in csoundagent.py)
#         June 20

# mp1.7 - to implement tabcontrol ()
#         abandon tabcontrol, I don't need that~~ July 1
#         same function is supported by clicking the active color block
#         then the user can switch between seeing all color grids / specific color grids
#         to have score_drawing_area and melody_drawing_area

# mp1.8 - the melody control - 4 clipboard to do copy/paste thing
#       - can dip into different ink before pasting
#       - play music of the main canvas/ the selected block on the main canvas / the selected melody clipboard
#       - 6 different scale system

# mp1.9 - flip/reverse/rotate the melody block
#       - load/save file

# mp2.1  - fix path problem in linux
#        - fix reverse then rotate bug
#        - add load/save button

# June 17 - to rethink the function of mouse clicking when painting
#         - detect user's intention

# bug 0.1 - when dragging an object, same color grid will be erased when the
#           object passes by

# bug 0.2 - after the program is started, if we dragging from somewhere out of the
#           score into the score, error happens due to lack of initialization

# to do list #1 - scroll
#            #2 - 3 mode for selecting grids: select connected grid, region, color
#            #3 - copy / paste
#            #4 - save to file
#            #5 - trackview
#            #6 - flexible pianoroll
#            #7 - dash line
#            #8 - help desk ?

# what is the difference between deep copy and shallow copy, I forgot.... Jun 16

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

from copy import copy
import sys
import os

# ======= for linux ========== 

#from Numeric import *
from Csound5Agent import *
path = "/home/olpc/Activities/Musicpainter.activity"
#path = "/root/sugar-jhbuild/build/share/activities/Musicpainter.activity"
#path = "."

# ======= for windows ========

#from numpy import *
#from FakeCsoundAgent import *
#Int = int
#path = ""

# ============================

_showFunctionCall = False
_showEvent = False
_showCSoundMsg = False

class Musicpainter:

    def zeros(self, n1, n2):
	array = [[0 for i in range(n2)] for j in range(n1)]
	return array

    def zeros3(self, n1, n2, n3):
	array = [[[0 for i in range(n3)] for j in range(n2)] for k in range(n1)]
	return array

    def main(self):
	toplevel_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
	self.init(toplevel_window)
	gtk.main()

	return 0

    def init_csound(self):
        print "Musicpainter.csound_init():"

        host = 'localhost'
        port = 7000

#        try:
        self.csound5 = CsoundAgent()
	self.csound5.setup()
	self.csound5.perform()
	self.csound = self.csound5.csound
#            self.csound.connect(host, port)
#            if self.csound.setup():
#               self.csound.perform()
#        except connectException:
#            print "Error connecting..."
                
#        except Timeout:
#            print "Request Timed Out..."

    def init(self, toplevel_window):

        self.init_data()
        self.init_graphics()
	self.init_csound()
        
        self.window = toplevel_window

        # remove any children of the window that Sugar may have added
        for widget in self.window.get_children():
            self.window.remove(widget)

	self.window.set_name ("Music Painter")
	self.window.set_resizable(False)
	self.window.modify_bg(gtk.STATE_NORMAL, self.bg_color)

	self.layout = gtk.Fixed()
        self.window.add(self.layout)

	self.window.connect("destroy", self.destroy)
        self.window.connect("delete_event", self.delete_event)
        self.window.connect("key_press_event", self.key_press_event)

	# Create the drawing area
	self.score_drawing_area = gtk.DrawingArea()
	self.melody_drawing_area = gtk.DrawingArea()

        # adjustment (value, low, high, step_icr, page_icr, page_size)
        adj = gtk.Adjustment(self.score_vstart, 0, self.score_len, 1, self.ngrid_h/2, self.ngrid_h)
        adj.connect("value_changed", self.hscroll_changed)
        self.hscrollbar = gtk.HScrollbar(adj)
        self.hscrollbar.set_size_request(self.ngrid_h*self.gridw, self.scroll_h)
        self.layout.put(self.hscrollbar, self.ix, self.main_areah-5)
        self.hscrollbar.show()

	# Signals used to handle backing pixmap
	self.score_drawing_area.connect("expose_event", self.expose_event)
	self.score_drawing_area.connect("configure_event", self.configure_event)

	# Event signals
	self.score_drawing_area.connect("leave_notify_event", self.leave_notify_event)
	self.score_drawing_area.connect("motion_notify_event", self.motion_notify_event)
	self.score_drawing_area.connect("button_press_event", self.button_press_event)
	self.score_drawing_area.connect("button_release_event", self.button_release_event)

	self.score_drawing_area.set_events(gtk.gdk.EXPOSURE_MASK
	                        | gtk.gdk.LEAVE_NOTIFY_MASK
	                        | gtk.gdk.BUTTON_PRESS_MASK
                                | gtk.gdk.BUTTON_RELEASE_MASK
	                        | gtk.gdk.POINTER_MOTION_MASK
	                        | gtk.gdk.POINTER_MOTION_HINT_MASK)

	# Signals used to handle backing pixmap
	self.melody_drawing_area.connect("expose_event", self.m_expose_event)
	self.melody_drawing_area.connect("configure_event", self.m_configure_event)

	# Event signals
#	self.melody_drawing_area.connect("motion_notify_event", self.motion_notify_event)
	self.melody_drawing_area.connect("button_press_event", self.m_button_press_event)
#	self.melody_drawing_area.connect("button_release_event", self.button_release_event)

	self.melody_drawing_area.set_events(gtk.gdk.EXPOSURE_MASK
	                        | gtk.gdk.LEAVE_NOTIFY_MASK
	                        | gtk.gdk.BUTTON_PRESS_MASK)
#                               | gtk.gdk.BUTTON_RELEASE_MASK
#	                        | gtk.gdk.POINTER_MOTION_MASK
#	                        | gtk.gdk.POINTER_MOTION_HINT_MASK)

	self.score_drawing_area.set_size_request(self.main_areaw, self.main_areah+1)
        self.layout.put(self.score_drawing_area, 0, 0)
	self.score_drawing_area.show()

	self.melody_drawing_area.set_size_request(self.main_areaw, self.melody_areah)
        self.layout.put(self.melody_drawing_area, 0, self.main_areah+1+self.scroll_h)
	self.melody_drawing_area.show()

	# Add four buttons	
	w = self.main_areaw / 8
	self.playButton = gtk.Button("Play")
	self.playButton.set_size_request(w-12, -1)
	self.layout.put(self.playButton, 0, self.main_areah + self.scroll_h + self.melody_areah)
	self.playButton.connect_object("clicked", self.play_music, self.window)
	self.playButton.show()

	self.stopButton = gtk.Button("Stop")
	self.stopButton.set_size_request(w-12, -1)
	self.layout.put(self.stopButton, w, self.main_areah + self.scroll_h + self.melody_areah)
	self.stopButton.connect_object("clicked", self.stop_music, self.window)
	self.stopButton.show()

	self.memorizeButton = gtk.Button("Copy")
	self.memorizeButton.set_size_request(w-12, -1)
	self.layout.put(self.memorizeButton, w*2, self.main_areah + self.scroll_h + self.melody_areah)
	self.memorizeButton.connect_object("clicked", self.memorize_music, self.window)
	self.memorizeButton.show()

	self.mixButton = gtk.Button("Paste")
	self.mixButton.set_size_request(w-12, -1)
	self.layout.put(self.mixButton, w*3, self.main_areah + self.scroll_h + self.melody_areah)
	self.mixButton.connect_object("clicked", self.mix_music, self.window)
	self.mixButton.show()

	self.loadButton = gtk.Button("Load")
	self.loadButton.set_size_request(w-12, -1)
	self.layout.put(self.loadButton, w*4, self.main_areah + self.scroll_h + self.melody_areah)
	self.loadButton.connect_object("clicked", self.load_music, self.window)
	self.loadButton.show()

	self.saveButton = gtk.Button("Save")
	self.saveButton.set_size_request(w-12, -1)
	self.layout.put(self.saveButton, w*5, self.main_areah + self.scroll_h + self.melody_areah)
	self.saveButton.connect_object("clicked", self.save_music, self.window)
	self.saveButton.show()

      	self.clearButton = gtk.Button("Clear")
	self.clearButton.set_size_request(w-12, -1)
	self.layout.put(self.clearButton, w*6, self.main_areah + self.scroll_h + self.melody_areah)
	self.clearButton.connect_object("clicked", self.clear_canvas, self.window)
	self.clearButton.show()

	self.quitButton = gtk.Button("Quit")
	self.quitButton.set_size_request(w-12, -1)
        self.layout.put(self.quitButton, w*7, self.main_areah + self.scroll_h + self.melody_areah)
	self.quitButton.connect_object("clicked", self.destroy, self.window)
	self.quitButton.show()

#	self.init_graphics()
	self.window.show_all()

	self.clear_canvas(self.score_drawing_area)
        self.toolbar_init(self.score_drawing_area)

    def init_graphics(self):
       	self.white = gtk.gdk.color_parse('white')
        self.black = gtk.gdk.color_parse('black')
        self.blue = gtk.gdk.Color(0,1280,57600)
        self.green = gtk.gdk.Color(9728,38912,4608)
        self.red = gtk.gdk.Color(65535,15000,15000)
        self.yellow = gtk.gdk.Color(64000, 65000, 500)
        self.purple = gtk.gdk.color_parse('purple')
        self.orange = gtk.gdk.color_parse('orange')
        self.lblue = gtk.gdk.Color(0, 65535, 65535)
        self.grass = gtk.gdk.Color(41728, 63744, 512)
#        self.score_bg_color = gtk.gdk.Color(65535,54200,54200)
        self.score_bg_color = gtk.gdk.Color(65535,57200,57200)
        self.bg_color = gtk.gdk.Color(51200,51200,65535)

    def init_data(self):
	# for Csound
	self.last_note = -1          # the last note played
        self.tag = -1		     # the id of timer
	self.grid_tdiv = 0.25

        #
        self.n_melody = 0
        self.melody_sel = -1         # -1: main window 0~3: melody window
        self.mix_melody_state = 0
        
        self.change_filter = 0
	self.n_scales = 6
	self.scale_mode = 0          # 0: major scale     1: minor scale
        self.filter_mode = 0         # 0: show all color  1: show one color
        self.toolbar_sel = 1         # -2: select region -1: select 0: erase 1:color 1, 2:color 2, ...
        self.color_sel = 1	     # 1, 2, 4, 8, 16 ....
        self.select_object_state = 0 # 0/1: (un)selected
        self.select_object_flag = 0  # 0: just selected  1: select connected 2: select same color
        self.move_flag = 0           # to record whether the user drag after clicking(1) or not(0)
        self.drag_object_state = 0   # 0/1: (not) dragging
        self.just_clean = 0          # 1: when the user click on painted grid, the system assumes 
				     #    he/she want to erase that grid
        self.fix_button = 0          # to fix multiple mouse_press_events, counting the event before release

        self.ncolor = 8
        self.nbtn_toolbar = self.ncolor + 3

        self.toolbarw = 36
        self.tix = 108
        self.ix = 54
        self.iy = 80
        self.gridw = 20
        self.gridh = 22
        self.ngrid_h = 48
        self.ngrid_v = 18
        self.scroll_h = 30
        self.melody_areah = 120
	self.melody_p = 4
	self.melody_w = self.melody_p * self.ngrid_h
	self.melody_h = self.melody_p * self.ngrid_v
	self.melody_div = 252

        self.score_len = self.ngrid_h
        self.score_vstart = 0
        self.score_vend = self.ngrid_h 

	self.main_areaw = self.ix + self.gridw * self.ngrid_h + 24
#	self.main_areah = 350
	self.main_areah = self.iy + self.gridh * self.ngrid_v

        self.data = self.zeros(self.ngrid_h+1, self.ngrid_v)
        self.screen = self.zeros(self.ngrid_h+1, self.ngrid_v)

        self.clipboard = self.zeros3(4, self.ngrid_h+1, self.ngrid_v)
        self.file_sel = -1



#    def draw_mynotebook(self, widget):
#        for i in range(self.ncolor+1):
#            self.pixmap.draw_rectangle(widget.get_style().black_gc, False, self.ix-24, self.iy + i*24, 24, 24)
#        widget.queue_draw_area(self.ix-24, self.iy, 24, 24*(self.ncolor+1))

    def clear_canvas(self, widget, data=None):

        self.select_object_state = self.drag_object_state = 0
        self.draw_melody_selection(widget, -1, self.melody_sel)
        self.melody_sel = -1
        # do I have to use NumericPY !? go figure that out some day
        self.data = self.zeros(self.ngrid_h+1, self.ngrid_v)
        self.screen = self.zeros(self.ngrid_h+1, self.ngrid_v)
        self.screen_buffer = self.zeros(self.ngrid_h+1, self.ngrid_v)
        
      	x, y, width, height = widget.get_allocation()
      	
        gc2 = self.pixmap.new_gc()
        gc2.set_rgb_fg_color(self.score_bg_color)
	self.pixmap.draw_rectangle(gc2, True, self.ix, self.iy, self.ngrid_h * self.gridw, self.ngrid_v * self.gridh)

        for i in range(self.ngrid_v):
	    self.pixmap.draw_rectangle(widget.get_style().black_gc,False,self.ix-1,self.iy+i*self.gridh,self.gridw*self.ngrid_h+1,self.gridh)
	    
	widget.queue_draw_area(self.ix, self.iy, self.gridw*self.ngrid_h, self.gridh*self.ngrid_v)
	#self.draw_mynotebook(widget)

	return

    def destroy(self, widget, data=None):
        if _showFunctionCall:
            print "music_painter::destroy()"
        if self.tag !=-1 :
           gtk.timeout_remove(self.tag)
	if self.last_note !=-1 :
           self.note_off(self.last_note)
           self.last_note = -1
	self.csound.stop()
	self.csound.disconnect()
        gtk.main_quit()

    def delete_event(self, widget, event, data=None):
        if _showEvent:
            print "music_painter::delete_event()"
        
        return False

    def get_unused_filename(self):
        filelist = os.listdir(path)
        max_id = -1
        for filename in filelist:
            if filename.endswith(".mps"):  # mps = music painter score
                fid = int(filename.replace("myscore","").replace(".mps",""))
                if fid > max_id:
                    max_id = fid
        return "myscore" + str(max_id+1) + ".mps"
                
    def load_music(self, widget):
        if self.file_sel == -1 or self.file_sel == 0: # first time loading score, create file list
            self.filelist = os.listdir(path)
            self.filelist = filter(lambda name: name.endswith(".mps"), self.filelist)
            self.file_sel = len(self.filelist) - 1
        else:
            self.file_sel = self.file_sel - 1
        filename = path + "/" + self.filelist[self.file_sel]
#        print filename
        if self.melody_sel == -1:
            self.read_score(self.data, filename)
            self.force_redraw_all(widget)
        else:
            self.read_score(self.clipboard[self.melody_sel], filename)
            self.draw_mini_melody(widget, self.melody_sel)

    def read_score(self, src, filename):
        stream = open(filename, 'r')
        try:
            cnt = 0
            for line in stream:
                n = line.split(" ")
                for i in range(int(n[1])):
                    i1 = (i+cnt)%self.ngrid_h
                    i2 = (i+cnt)/self.ngrid_h
                    src[i1][i2] = int(n[0])
                cnt = cnt + int(n[1])
        finally:
            stream.close()
    
    def save_music(self, widget):
        filename = path + "/" + self.get_unused_filename()
#        print filename
        if self.melody_sel == -1:
            self.write_score(self.data, filename)
        else:
            self.write_score(self.clipboard[self.melody_sel], filename)

    def write_score(self, src, filename):
        stream = open(filename, 'w')
        l = -1
        cnt = 0
	for i in range(self.ngrid_v):
	    for j in range(self.ngrid_h):
                if src[j][i] != l:
                    if l != -1:
                        stream.writelines(str(l)+" "+str(cnt)+"\n")
                    l = src[j][i]
                    cnt = 1
                else:
                    cnt = cnt + 1
        stream.writelines(str(l)+" "+str(cnt)+"\n")
        stream.close()
        return

    def memorize_music(self, widget):
        if _showFunctionCall:
            print "music_painter::memorize_music()"

        target = self.melody_sel
        if target == -1:
            target = 0
            
	for i in range(self.ngrid_h):
	    for j in range(self.ngrid_v):
                if self.select_object_state == 0:
                    self.clipboard[target][i][j] = self.data[i][j]
                else:
                    if self.mask[i][j]:
                        self.clipboard[target][i][j] = self.drag_object_color
                    else:
                        self.clipboard[target][i][j] = 0
        # ignore blank copy
        #flag = True
	#for i in range(self.ngrid_h):
	#    for j in range(self.ngrid_v):
        #        if self.clipboard[i, j] != 0:
        #            flag = False
        #if flag:
        #    return

        self.draw_mini_melody(widget, target)
#        self.n_melody = self.n_melody + 1


    def draw_mini_melody(self, widget, target):
        if _showFunctionCall:
            print "music_painter::draw_mini_melody()"
        colors = [self.score_bg_color, self.red, self.orange, self.yellow, self.grass, self.green, self.lblue, self.blue, self.purple]        
        gc = self.m_pixmap.new_gc()
        gc.set_rgb_fg_color(self.score_bg_color)
        sx = self.ix + target*self.melody_div
        sy = 4
	p = self.melody_p
        self.m_pixmap.draw_rectangle(gc, True, sx, sy, self.melody_w, self.melody_h)
        for i in range(self.ngrid_h):
	    for j in range(self.ngrid_v):
                if self.clipboard[target][i][j] != 0:
                    color = self.first_color(self.clipboard[target][i][j])
                    gc.set_rgb_fg_color(colors[color])
                    self.m_pixmap.draw_rectangle(gc, True, sx + i*p, sy + j*p, p, p)
        self.melody_drawing_area.queue_draw_area(sx, sy, self.melody_w, self.melody_h)
                
    def mix_music(self, widget):
        if self.melody_sel == -1:
            return

        self.mask = self.zeros(self.ngrid_h+1, self.ngrid_h+1)
        (self.min_maskx, self.min_masky) = (self.ngrid_h, self.ngrid_v)
        (self.max_maskx, self.max_masky) = (-1, -1)
        m = self.melody_sel
        for i in range(self.ngrid_h):
            for j in range(self.ngrid_v):
                if self.clipboard[m][i][j] != 0:
                    self.mask[i][j] = self.clipboard[m][i][j]
                    if i < self.min_maskx:
                        self.min_maskx = i
                    if i > self.max_maskx:
                        self.max_maskx = i
                    if j < self.min_masky:
                        self.min_masky = j
                    if j > self.max_masky:
                       self.max_masky = j
        if self.max_maskx == -1: # nothing in the clipboard, ignore
            return
        x = (int)((self.min_maskx+self.max_maskx)/2)
        y = (int)((self.min_masky+self.max_masky)/2)
        (self.init_drag_gx,self.init_drag_gy) = (self.last_drag_gx,self.last_drag_gy) = (x,y)
        self.mix_melody_state = 1

    def m_button_press_event(self, widget, event):
        sel = -1
        if event.y >= 4 and event.y < (4 + self.melody_h):            
            for i in range(4):
                if event.x >= (self.ix + i*self.melody_div) and event.x < (self.ix + i*self.melody_div + self.melody_w):
                    sel = i
        if sel != self.melody_sel:
            self.draw_melody_selection(widget, sel, self.melody_sel)
            self.melody_sel = sel
        elif sel != -1 and self.toolbar_sel > 0: # color the clipboard
            self.color_clipboard(sel)
            self.draw_mini_melody(widget,sel)

        if self.mix_melody_state != 0: #
            self.mix_melody_state = 0
        return

    def color_clipboard(self, target):
	for i in range(self.ngrid_h):
	    for j in range(self.ngrid_v):
                if self.clipboard[target][i][j] != 0:
                    self.clipboard[target][i][j] = self.color_sel

    def color_mask(self):
	for i in range(self.ngrid_h):
	    for j in range(self.ngrid_v):
                if self.mask[i][j] != 0:
                    self.mask[i][j] = self.color_sel

    def draw_melody_selection(self, widget, new_selection, old_selection):
        gc = self.m_pixmap.new_gc()
        if old_selection != -1:
            gc.set_rgb_fg_color(self.bg_color)
            ox = self.ix + old_selection*self.melody_div
            self.m_pixmap.draw_rectangle(gc, False, ox - 3, 4-3, self.melody_w+6, self.melody_h+6)
            self.m_pixmap.draw_rectangle(gc, False, ox - 2, 4-2, self.melody_w+4, self.melody_h+4)
            self.melody_drawing_area.queue_draw_area(ox-3, 4-3, self.melody_w+7, self.melody_h+7)
        else:
            gc.set_rgb_fg_color(self.bg_color)
            w = self.ngrid_h*self.gridw
            h = self.ngrid_v*self.gridh
            self.pixmap.draw_rectangle(gc, False, self.ix - 3, self.iy - 2, w + 5, h + 4)
            self.pixmap.draw_rectangle(gc, False, self.ix - 2, self.iy - 1, w + 3, h + 2)
            self.score_drawing_area.queue_draw_area(self.ix - 3, self.iy - 2, w + 6, h + 4) 

        if new_selection != -1:    
            gc.set_rgb_fg_color(self.yellow)
            nx = self.ix + new_selection*self.melody_div
            self.m_pixmap.draw_rectangle(gc, False, nx - 3, 4-3, self.melody_w+6, self.melody_h+6)
            self.m_pixmap.draw_rectangle(gc, False, nx - 2, 4-2, self.melody_w+4, self.melody_h+4)
            self.melody_drawing_area.queue_draw_area(nx-3, 4-3, self.melody_w+7, self.melody_h+7)
        else:
            gc.set_rgb_fg_color(self.yellow)
            w = self.ngrid_h*self.gridw
            h = self.ngrid_v*self.gridh
            self.pixmap.draw_rectangle(gc, False, self.ix - 3, self.iy - 2, w + 5, h + 4)
            self.pixmap.draw_rectangle(gc, False, self.ix - 2, self.iy - 1, w + 3, h + 2)
            self.score_drawing_area.queue_draw_area(self.ix - 3, self.iy - 2, w + 6, h + 4)

    # Redraw the screen from the backing pixmap
    def m_expose_event(self, widget, event):
        x , y, width, height = event.area
	widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
	                                self.m_pixmap, x, y, x, y, width, height)
	return False

    def key_press_event(self, widget, event):
        if self.mix_melody_state == 1:
            if event.string == 'R' or event.string == 'r':
                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy, 1)
                self.reverse_mask()
                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy)
            elif event.string == 'F' or event.string == 'f':
                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy, 1)
                self.flip_mask()
                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy)
#            elif event.string == 'F' or event.string == 'f':
#                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy, 1)
#                self.flip_mask()
#                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy)
            elif event.string == 'T' or event.string == 't':
                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy, 1)
                self.rotate_mask_clockwise()
                self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy)
        else:
            if event.string == 'S' or event.string == 's':
                self.save_music(widget)
            elif event.string == 'L' or event.string == 'l':
                self.load_music(widget)
            

    def flip_mask(self):
        self.mask_buffer = self.zeros(self.ngrid_h+1, self.ngrid_h+1)
        self.get_mask_region(True)

        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] != 0:
                    self.mask_buffer[i][self.max_masky -j + self.min_masky] = self.mask[i][j]
        self.mask = copy(self.mask_buffer)
        self.get_mask_region()
        
    def reverse_mask(self):
        self.mask_buffer = self.zeros(self.ngrid_h+1, self.ngrid_h+1)
        self.get_mask_region(True)

        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] != 0:
                    self.mask_buffer[self.max_maskx -i + self.min_maskx][j] = self.mask[i][j]
        self.mask = copy(self.mask_buffer)
        self.get_mask_region()

    def rotate_mask_clockwise(self):
        self.mask_buffer = self.zeros(self.ngrid_h+1, self.ngrid_h+1)
        self.get_mask_region(True)

        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
#                if self.mask[i,j] != 0 and self.is_grid_valid(-j + self.max_masky, i-self.min_maskx):
                if self.mask[i][j] != 0:
                    self.mask_buffer[-j + self.max_masky][i - self.min_maskx] = self.mask[i][j]
#                    self.mask_buffer[self.max_maskx -j + self.min_masky, self.min_masky + i - self.min_maskx] = self.mask[i,j]

        self.mask = copy(self.mask_buffer)
        self.get_mask_region()
        x = (int)((self.min_maskx+self.max_maskx)/2)
        y = (int)((self.min_masky+self.max_masky)/2)
        (self.init_drag_gx,self.init_drag_gy) = (x,y)
#        (self.last_drag_gx,self.last_drag_gy) = 
        
    def get_mask_region(self, flag = False):
        (self.min_maskx, self.min_masky) = (self.ngrid_h, self.ngrid_v)
        (self.max_maskx, self.max_masky) = (-1, -1)
        v = self.ngrid_v
        if flag:
            v = self.ngrid_h
        for i in range(self.ngrid_h):
            for j in range(v):
                if self.mask[i][j] != 0:
                    if i < self.min_maskx:
                        self.min_maskx = i
                    if i > self.max_maskx:
                        self.max_maskx = i
                    if j < self.min_masky:
                        self.min_masky = j
                    if j > self.max_masky:
                       self.max_masky = j     
        
    # Create a new backing pixmap of the appropriate size
    def m_configure_event(self, widget, event):
        if _showFunctionCall:
            print "music_painter::m_configure_event()"

	x, y, width, height = widget.get_allocation()	    
        self.m_pixmap = gtk.gdk.Pixmap(widget.window, width, height)

        gc = self.m_pixmap.new_gc()
        gc.set_rgb_fg_color(self.bg_color)
	self.m_pixmap.draw_rectangle(gc, True, 0, 0, width, height)

#        gc.set_rgb_fg_color(self.score_bg_color)
        for i in range(4):
	    self.m_pixmap.draw_rectangle(widget.get_style().black_gc, False, self.ix + i*self.melody_div-1, 4-1, self.melody_w+2, self.melody_h+2)
	    self.m_pixmap.draw_rectangle(widget.get_style().white_gc, True, self.ix + i*self.melody_div, 4, self.melody_w, self.melody_h)

	return True
    
    # Redraw the screen from the backing pixmap
    def expose_event(self, widget, event):
        x , y, width, height = event.area
	widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL],
	                                self.pixmap, x, y, x, y, width, height)
	return False
        
    # Create a new backing pixmap of the appropriate size
    def configure_event(self, widget, event):

	x, y, width, height = widget.get_allocation()	    
        self.pixmap = gtk.gdk.Pixmap(widget.window, width, height)

# ================= try again if I have time, to reset the background color
#        self.score_drawing_area.modify_bg(gtk.STATE_NORMAL, self.bg_color)

        gc = self.pixmap.new_gc()
        gc.set_rgb_fg_color(self.bg_color)
	self.pixmap.draw_rectangle(gc, True, 0, 0, width, height)

	return True

    def hscroll_changed(self, widget):
        if _showFunctionCall:
            print "music_painter::hscroll_changed()"
        t = (int)(self.hscrollbar.get_value())
        if t != self.score_vstart:
            self.score_vstart = t
            print "Score Position: " + str(t)

#    def update_score_scrolling(self, widget):
                

    def draw_grid(self, widget, gx, gy, color_id, selected = 0):
	rect = (self.ix+ gx*self.gridw, self.iy+gy*self.gridh+1, self.gridw, self.gridh-1)
        self.screen[gx][gy] = color_id

        gc = self.pixmap.new_gc()
        if self.filter_mode == 0:
            color = self.first_color(color_id)
        else:
            if (color_id & self.color_sel) != 0:
                color = self.toolbar_sel
            else:
                color = 0
        colors = [self.score_bg_color, self.red, self.orange, self.yellow, self.grass, self.green, self.lblue, self.blue, self.purple]        
        gc.set_rgb_fg_color(colors[color])
	if selected >= 1:
            if selected == 2 or color == self.drag_object_color_id:
     	        self.pixmap.draw_rectangle(widget.get_style().white_gc, True, rect[0], rect[1], rect[2], rect[3])
 	        self.pixmap.draw_rectangle(gc, True, rect[0]+2, rect[1]+2, rect[2]-4, rect[3]-4)
 	    else:
                gc2 = self.pixmap.new_gc()
                gc2.set_rgb_fg_color(colors[self.drag_object_color_id])
 	        self.pixmap.draw_rectangle(widget.get_style().white_gc, True, rect[0], rect[1], rect[2], rect[3])
 	        self.pixmap.draw_rectangle(gc2, True, rect[0]+2, rect[1]+2, rect[2]-4, rect[3]-4)
 	        self.pixmap.draw_rectangle(gc, True, rect[0]+4, rect[1]+4, rect[2]-8, rect[3]-8)
	else:
 	    self.pixmap.draw_rectangle(gc, True, rect[0], rect[1], rect[2], rect[3])
	widget.queue_draw_area(rect[0], rect[1], rect[2], rect[3])        

    # Draw a rectangle on the screen
    def draw_brush(self,widget, gx, gy):
        if _showFunctionCall:
            print "music_painter::draw_brush(%d,%d)"%(gx,gy)
        if self.color_sel == 0 and self.data[gx][gy] != 0: # erase the topmost color
            self.data[gx][gy] = self.data[gx][gy] ^ self.first_color_bin(self.data[gx][gy])
        else:
            if gx == self.last_drag_gx and gy == self.last_drag_gy: # condition stands when call by button_press
                if (self.data[gx][gy] & self.color_sel) != 0: # start with an already painted grid
                    self.data[gx][gy] = self.data[gx][gy] ^ self.color_sel # assume the user intent to clean it
                    self.just_clean = 1
                else:                                        # start with an unpainted grid
                    self.data[gx][gy] = self.data[gx][gy] | self.color_sel
            else:                                                   # call by button_motion
                if self.just_clean == 1: # undo the assumption
                    self.just_clean = 0
                    self.data[self.last_drag_gx][self.last_drag_gy] = self.data[self.last_drag_gx][self.last_drag_gy] | self.color_sel
                    self.draw_grid(widget, self.last_drag_gx, self.last_drag_gy, self.data[self.last_drag_gx][self.last_drag_gy])
                self.data[gx][gy] = self.data[gx][gy] | self.color_sel
        self.draw_grid(widget, gx, gy, self.data[gx][gy])
	note = self.map_scale(gy)

	if note != self.last_note:  # to be fix, note != 0
#	   print "Note = %d, Last_Note = %d" % (note, self.last_note)
	   if self.last_note !=-1 :
              self.note_off(self.last_note)
           self.last_note = note
           self.note_on(note)

    def tb2color(self):
        if self.toolbar_sel < 0:
            return -1
        else:
            if self.toolbar_sel == 0:
                return 0
            else:
                return 1 << (self.toolbar_sel-1) # binary representation

    # determine if the input location is within the score_area
    # if not, return (-1, -1)
    # if  so, return grid index (gx, gy)
    def within_score(self, x, y):
        
        if x < self.ix or y < self.iy or x >= self.ix+self.gridw*self.ngrid_h or y >= self.iy+self.gridh*self.ngrid_v:
            return -1, -1
        return self.xy2gridxy(x, y)

    def xy2gridxy(self, x, y):
	gx = (int)((x-self.ix)/self.gridw)
	gy = (int)((y-self.iy)/self.gridh)
        return gx, gy

    # for debug purpose, redraw all grid
    def force_redraw_all(self, widget):
	for i in range(self.ngrid_h):
	    for j in range(self.ngrid_v):
                self.draw_grid(widget, i, j, self.data[i][j])

    def first_color_bin(self, bin):
        t = 1
        for i in range(self.ncolor):
            if bin & t != 0:
                return t
            t = t << 1
        return 0

    def first_color(self, bin):
        t = 1
        for i in range(self.ncolor):
            if bin & t != 0:
                return i + 1
            t = t << 1
        return 0

    def select_object_same_color(self, gx, gy):

#        self.drag_object_state = 1
        self.select_object_state = 1
	self.drag_object_color = self.first_color_bin(self.data[gx][gy])
	self.drag_object_color_id = self.first_color(self.data[gx][gy])
#	(self.init_drag_gx,self.init_drag_gy) = (self.last_drag_gx,self.last_drag_gy) = (gx,gy)

        self.mask = self.zeros(self.ngrid_h+1, self.ngrid_v)
        self.screen_buffer = copy(self.screen)

        (self.min_maskx, self.min_masky) = (self.max_maskx, self.max_masky) = (gx,gy)
        for i in range(self.ngrid_h):
            for j in range(self.ngrid_v):
                if self.data[i][j] & self.drag_object_color != 0:
#                    self.data[i, j] = self.data[i, j] ^ self.drag_object_color
                    self.mask[i][j] = 1
        self.get_mask_region()           
    
    # run breadth-first search to extract a connected object
    def select_object_connected(self, gx, gy):

#        self.drag_object_state = 1
        self.select_object_state = 1
	self.drag_object_color = self.first_color_bin(self.data[gx][gy])
	self.drag_object_color_id = self.first_color(self.data[gx][gy])
#	(self.init_drag_gx,self.init_drag_gy) = (self.last_drag_gx,self.last_drag_gy) = (gx,gy)

        self.mask = self.zeros(self.ngrid_h+1, self.ngrid_v)
        self.screen_buffer = copy(self.screen)

        # initialize the queue
        queue = [[gx,gy]]
        self.mask[gx][gy] = 1
        #self.data[gx,gy] = self.data[gx,gy] ^ self.drag_object_color # perform XOR to remove bit

        # the definition of "connected" here is 8-way, rather than 4-way
        ex = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]

        # these variables specify the used mask region
        (self.min_maskx, self.min_masky) = (self.max_maskx, self.max_masky) = (gx,gy)

        # execute until the queue is empty
        while len(queue) > 0:
            x, y = queue[0]
            del queue[0] #de-queue

            if x < self.min_maskx:
               self.min_maskx = x
            if x > self.max_maskx:
               self.max_maskx = x
            if y < self.min_masky:
               self.min_masky = y
            if y > self.max_masky:
               self.max_masky = y

            for i in range(len(ex)):
                nx = x+ex[i][0]
                ny = y+ex[i][1]
                if self.is_grid_valid(nx,ny) and (self.data[nx][ny] & self.drag_object_color) != 0 and self.mask[nx][ny] == 0:
                   self.mask[nx][ny] = 1
                   #self.data[nx,ny] = self.data[nx,ny] ^ self.drag_object_color # perform XOR to remove bit
                   queue.append([nx,ny]) #add node to queue
        return

    def draw_selected_grid(self, widget):
        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] == 1:
                    self.draw_grid(widget, i, j, self.data[i][j], 1)
                                            
    def draw_deselected_grid(self, widget):
        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] == 1:
                    self.draw_grid(widget, i, j, self.data[i][j])

    def button_press_event(self, widget, event):
        if _showFunctionCall:
            print "music_painter::button_press_event()"
        self.fix_button = self.fix_button + 1
	if self.fix_button != 1:
	    return
	if event.button == 1 and self.pixmap != None:
            tb_result = self.toolbar_detect(widget, event.x, event.y)
            if tb_result != 0:
                if self.fix_button == 1 and self.change_filter == 1:
                    self.filter_mode = (self.filter_mode+1)%2
                    self.change_filter = 0
                    self.force_redraw_all(widget)
                    return True
		if tb_result == 1:
                    if self.fix_button == 1 and self.select_object_state == 1:
                        self.draw_deselected_grid(widget)
                        self.select_object_state = 0
		    if self.toolbar_sel > 0:
		        self.note_on(72, 1)
		        if self.filter_mode == 1:
		            self.force_redraw_all(widget)
		        if self.mix_melody_state == 1:
                            self.color_mask()
                            self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy)
                    elif self.mix_melody_state == 1:
                        self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy, 1)
                        self.mix_melody_state = 0
                        self.draw_melody_selection(widget, -1, self.melody_sel)
                        self.melody_sel = -1
                    return True
                else:
                    return True
            gx, gy = self.within_score(event.x, event.y)
            if gx==-1 and self.fix_button == 1 and self.select_object_state == 1:
                self.draw_deselected_grid(widget)
                self.select_object_state = 0
            if gx!=-1 and self.fix_button == 1:  # if the cursor is within the score area
                if self.mix_melody_state != 1:
                    self.draw_melody_selection(widget, -1, self.melody_sel)
                    self.melody_sel = -1
                if self.mix_melody_state == 1:   # put the melody to be mixed down
                    self.update_mixing(widget, self.last_drag_gx, self.last_drag_gy, 0)
                    for i in range(self.ngrid_h):
                        for j in range(self.ngrid_v):
                            self.data[i][j] = self.screen[i][j]
                    self.mix_melody_state = 0
                elif self.toolbar_sel >= 0:
                    if _showEvent:
                        print "call draw_brush from function button_press_event"
                    self.last_drag_gx = gx
                    self.last_drag_gy = gy
                    if not (self.toolbar_sel == 0 and self.fix_button != 1):
                        self.just_clean = 0
                        self.draw_brush(widget, gx, gy)
                    else:
                        if _showEvent:
                            print "ignore button_press"
                else:  # to drag an object
                    if self.select_object_state == 0:
                        if self.data[gx][gy] > 0:
                            #print "select"
                            self.select_object_flag = 0
                            self.move_flag = 0
                            self.select_object_connected(gx, gy)
                            self.draw_selected_grid(widget)
#                           self.select_object_same_color(gx, gy)
                            #print "start dragging"
                            self.drag_object_state = 1
                            (self.init_drag_gx,self.init_drag_gy) = (self.last_drag_gx,self.last_drag_gy) = (gx,gy)
                            for i in range(self.min_maskx, self.max_maskx+1):
                                for j in range(self.min_masky, self.max_masky+1):
                                    if self.mask[i][j] == 1:
                                        self.data[i][j] = self.data[i][j] ^ self.drag_object_color
                    else:
                        if self.data[gx][gy] == 0:
                            #print "unselect"
                            self.draw_deselected_grid(widget)
                            self.select_object_state = 0
                        else:
                            if self.mask[gx][gy] == 0 or self.first_color_bin(self.data[gx][gy]) != self.drag_object_color:
                                #print "reselect"
                                self.draw_deselected_grid(widget)
                                self.select_object_flag = 0
                                self.move_flag = 0
                                self.select_object_connected(gx, gy)
                                self.draw_selected_grid(widget)
#                               self.select_object_same_color(gx, gy)
                                #print "start dragging"
                                self.drag_object_state = 1
                                (self.init_drag_gx,self.init_drag_gy) = (self.last_drag_gx,self.last_drag_gy) = (gx,gy)
                                for i in range(self.min_maskx, self.max_maskx+1):
                                    for j in range(self.min_masky, self.max_masky+1):
                                        if self.mask[i][j] == 1:
                                            self.data[i][j] = self.data[i][j] ^ self.drag_object_color
                            else:
                                #print "start dragging"
                                self.move_flag = 0
                                self.drag_object_state = 1
                                (self.init_drag_gx,self.init_drag_gy) = (self.last_drag_gx,self.last_drag_gy) = (gx,gy)
                                for i in range(self.min_maskx, self.max_maskx+1):
                                    for j in range(self.min_masky, self.max_masky+1):
                                        if self.mask[i][j] == 1:
                                            self.data[i][j] = self.data[i][j] ^ self.drag_object_color
                    #print "select = %d, drag = %d, drag_color = %d, data = %d" % (self.select_object_state, self.drag_object_state, self.drag_object_color, self.data[gx, gy])
        #print "..select = %d, drag = %d" % (self.select_object_state, self.drag_object_state)
                        
	return True

    def button_release_event(self, widget, event):
        if _showFunctionCall:        
            print "music_painter::button_release_event()"
        self.fix_button = 0
        self.just_clean = 0
	if self.drag_object_state == 1:
            gx, gy = self.xy2gridxy(event.x, event.y)
            if not (gx == self.last_drag_gx and gy == self.last_drag_gy):
                self.update_dragging(widget, gx, gy)
	    # drop the object down
            self.mask_buffer = self.zeros(self.ngrid_h+1, self.ngrid_v)
            for i in range(self.min_maskx, self.max_maskx+1):
                for j in range(self.min_masky, self.max_masky+1):
                    if self.mask[i][j] == 1:
                       dx = gx - self.init_drag_gx
                       dy = gy - self.init_drag_gy
                       if self.is_grid_valid(i+dx, j+dy):
                          self.data[i+dx][j+dy] = self.data[i+dx][j+dy] | self.drag_object_color
                          self.mask_buffer[i+dx][j+dy] = 1
            self.mask = copy(self.mask_buffer)
            self.get_mask_region()
            self.drag_object_state = 0
            if self.move_flag == 0:
                if self.select_object_flag == 0:
                    self.select_object_flag = 1
                elif self.select_object_flag == 1:
                    self.select_object_flag = 2
                    self.draw_deselected_grid(widget)
                    self.select_object_same_color(gx, gy)
                    self.draw_selected_grid(widget)
                elif self.select_object_flag == 2:
                    self.select_object_flag = 1
                    self.draw_deselected_grid(widget)
                    self.select_object_connected(gx, gy)
                    self.draw_selected_grid(widget)
            else:
                if not (gx == self.init_drag_gx and gy == self.init_drag_gy):
                    self.select_object_flag = 2
        else:
           if self.last_note !=-1 :
               self.note_off(self.last_note)
#	       self.note_on(self.last_note, 1)
               self.last_note = -1
#        print "after release, drag = %d, sflag = %d, sstate = %d"% (self.drag_object_state, self.select_object_flag, self.select_object_state)
        return True

    def leave_notify_event(self, widget, event):
        if self.mix_melody_state == 1:
            gx, gy = self.xy2gridxy(event.x, event.y)
            self.update_mixing(widget, gx, gy, 1)

    def motion_notify_event(self, widget, event):
	if event.is_hint:
	    x, y, state = event.window.get_pointer()
	else:
	    x = event.x
	    y = event.y
	    state = event.state
	    
	if state & gtk.gdk.BUTTON1_MASK and self.pixmap != None:
            gx, gy = self.within_score(event.x, event.y)
            if gx!=-1 and self.toolbar_sel >= 0 and not (gx == self.last_drag_gx and gy == self.last_drag_gy):
                if _showEvent:
                    print "call draw_brush from function motion_notify_event"        
	        self.draw_brush(widget, gx, gy)
	        self.last_drag_gx = gx
	        self.last_drag_gy = gy
            if self.drag_object_state == 1:
                gx, gy = self.xy2gridxy(event.x, event.y) # ignore out of ground grid x,y
                if not (gx == self.last_drag_gx and gy == self.last_drag_gy):
                    self.update_dragging(widget, gx, gy)
                    self.move_flag = 1 # the object has been moved
                
	if self.pixmap != None and self.mix_melody_state == 1:
            gx, gy = self.xy2gridxy(event.x, event.y) # ignore out of ground grid x,y
            if not (gx == self.last_drag_gx and gy == self.last_drag_gy):
                self.update_mixing(widget, gx, gy)
	return True

    def is_grid_valid(self, x, y):
        if x < 0 or y < 0 or x >= self.ngrid_h or y >= self.ngrid_v:
            return False
        return True

    def update_mixing(self, widget, gx, gy, mode = 2):
        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] != 0:
                    (dx, dy) = (self.last_drag_gx - self.init_drag_gx, self.last_drag_gy - self.init_drag_gy)
                    if self.is_grid_valid(i+dx, j+dy):
                        self.screen_buffer[i+dx][j+dy] = self.data[i+dx][j+dy]
                        self.draw_grid(widget, i + dx, j + dy, self.data[i+dx][j+dy])
        if mode != 1:                
            for i in range(self.min_maskx, self.max_maskx+1):
                for j in range(self.min_masky, self.max_masky+1):
                    if self.mask[i][j] != 0:
                        (dx, dy) = (gx - self.init_drag_gx, gy - self.init_drag_gy)
                        if self.is_grid_valid(i+dx, j+dy):
                            self.screen_buffer[i+dx][j+dy] = (self.screen_buffer[i+dx][j+dy] | self.mask[i][j])
                            self.draw_grid(widget, i + dx, j + dy, self.screen_buffer[i+dx][j+dy], mode)
        (self.last_drag_gx, self.last_drag_gy) = (gx, gy)

    def update_dragging(self, widget, gx, gy):
        cnt = 0 # number of grids drawed
        # precalculate screen_buffer, so that we won't waste time updating identical grids
        
        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] == 1:
                    (dx, dy) = (self.last_drag_gx - self.init_drag_gx, self.last_drag_gy - self.init_drag_gy)
                    if self.is_grid_valid(i+dx, j+dy):
                        self.screen_buffer[i+dx][j+dy] = self.data[i+dx][j+dy]
                
        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] == 1:
                    (dx, dy) = (gx - self.init_drag_gx, gy - self.init_drag_gy)
                    if self.is_grid_valid(i+dx, j+dy):
                        self.screen_buffer[i+dx][j+dy] = (self.screen_buffer[i+dx][j+dy] | self.drag_object_color)

        # draw the grid only for the difference between screen_buffer and screen 

        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] == 1:
                    (dx, dy) = (self.last_drag_gx - self.init_drag_gx, self.last_drag_gy - self.init_drag_gy)
#                    if self.is_grid_valid(i+dx, j+dy) and self.screen_buffer[i+dx, j+dy] != self.screen[i+dx, j+dy]:
                    if self.is_grid_valid(i+dx, j+dy):
                        self.draw_grid(widget, i + dx, j + dy, self.screen_buffer[i+dx][j+dy])
                        cnt = cnt + 1
                
        for i in range(self.min_maskx, self.max_maskx+1):
            for j in range(self.min_masky, self.max_masky+1):
                if self.mask[i][j] == 1:
                    (dx, dy) = (gx - self.init_drag_gx, gy - self.init_drag_gy)
#                    if self.is_grid_valid(i+dx, j+dy) and self.screen_buffer[i+dx, j+dy] != self.screen[i+dx, j+dy]:
                    if self.is_grid_valid(i+dx, j+dy):
                        self.draw_grid(widget, i + dx, j + dy, self.screen_buffer[i+dx][j+dy],1)
                        cnt = cnt + 1
#       if _showEvent:
#           print "%d grid(s) printed"%cnt
        (self.last_drag_gx, self.last_drag_gy) = (gx, gy)
                
        return

#   ================================================== music function ==============================================

    def play_music(self, widget, data=None):
#        cnt = 1
#        self.last_linex = -1
#        self.last_grid = -1
#        self.end_grid = 0
#        self.start_time = self.csound.current_kperiod()
#        self.tag = gtk.timeout_add(80, self.timeout, widget)

        if _showCSoundMsg:
            print "Playing CSound Music..."
	first_note_time = -1
        if self.melody_sel == -1:             # play music painted in the main canvas
            for i in range(self.ngrid_h):     # if specific block is selected, play the block only
                for j in range(self.ngrid_v):
                    t = 1
                    for k in range(self.ncolor):
                        if (self.select_object_state == 0 or (self.drag_object_color == t and self.mask[i][j] != 0)) and (self.data[i][j] & t) != 0 and (i == 0 or self.data[i-1][j] & t == 0):
                            if first_note_time == -1:
			        first_note_time = self.grid_tdiv*i
			    len = 1
                            while (self.data[i+len][j] & t) != 0:
                                len = len + 1
                            n = self.note2str(self.map_scale(j))
                            eventStr = "i" + str(k+1)+ " " + str(self.grid_tdiv*i - first_note_time) + " " + str(self.grid_tdiv*len) + " 1 " + n
                            if _showCSoundMsg:
                                print "CSound Event: " + eventStr
                            self.csound.playEventAsString(eventStr)
                        t = t << 1
        else:
            for i in range(self.ngrid_h):   # play music in the melody clipboard
                for j in range(self.ngrid_v):
                    t = 1
                    for k in range(self.ncolor):
                        if (self.clipboard[self.melody_sel][i][j] & t) != 0 and (i == 0 or self.clipboard[self.melody_sel][i-1][j] & t == 0):
                            if first_note_time == -1:
			        first_note_time = self.grid_tdiv*i
                            len = 1
                            while (self.clipboard[self.melody_sel][i+len][j] & t) != 0:
                                len = len + 1
                            n = self.note2str(self.map_scale(j))
                            eventStr = "i" + str(k+1)+ " " + str(self.grid_tdiv*i - first_note_time) + " " + str(self.grid_tdiv*len) + " 1 " + n
                            if _showCSoundMsg:
                                print "CSound Event: " + eventStr
                            self.csound.playEventAsString(eventStr)
                        t = t << 1            
	return

    def stop_music(self, widget, data=None):
        self.force_redraw_all(widget)
        self.csound.stop()
	time.sleep(1)
	self.csound.setup()
	self.csound.perform()
#	self.csound.quit()

#	time.sleep(1)

#	if self.csound.setup():
#	    self.csound.perform()
	return

    def map_scale(self,v):

#        scale = [0,2,4,5,7,9,11,12,14,16,17,19,21,23,24];
        major_scale = [0,2,4,5,7,9,10,12,14,16,17,19,21,22,24,26,28,29];
        minor_scale = [0,1,4,5,7,8,10,12,13,16,17,19,20,22,24,25,28,29];
        chromatic_scale = range(self.ngrid_v);
	whole_tone_scale = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34]
	phrygian_dominant_scale = [0, 1, 3, 5, 6, 9, 10, 12, 13, 15, 17, 18, 21, 22, 24, 25, 27, 29]
	blue_scale = [0, 1, 4, 6, 9, 11, 12, 13, 16, 18, 21, 23, 24, 25, 28, 30, 33, 35]
	pentatonic_scale = [0, 3, 5, 8, 10, 12, 15, 17, 20, 22, 24, 27, 29, 32, 34, 36, 39, 41]

	if self.scale_mode == 0:
            return 55 + major_scale[self.ngrid_v-v-1];
	elif self.scale_mode == 1:
            return 55 + minor_scale[self.ngrid_v-v-1];
	elif self.scale_mode == 2:
	    return 57 + chromatic_scale[self.ngrid_v-v-1];
	elif self.scale_mode == 3:
	    return 54 + whole_tone_scale[self.ngrid_v-v-1];
	elif self.scale_mode == 4:
	    return 54 + pentatonic_scale[self.ngrid_v-v-1];
#	    return 55 + phrygian_dominant_scale[self.ngrid_v-v-1];
	elif self.scale_mode == 5:
	    return 54 + blue_scale[self.ngrid_v-v-1];

    def note2str(self,note):
        rstr = str((int)((note+36)/12)) + "."
	if note%12 < 10:
           rstr += "0"
        rstr += str(note%12)
	return rstr

    def note_on(self,note,toolbar_sound = 0):
	if self.toolbar_sel==0:
	    return
	if toolbar_sound == 0:
	    eventStr = "i5" + str(self.toolbar_sel) + ".1 0 -1 1 " + self.note2str(note)
	else:
	    eventStr = "i" + str(self.toolbar_sel) + " 0 0.8 1 " + self.note2str(note)
	self.csound.playEventAsString(eventStr)

        if _showCSoundMsg:
            print "CSound Event: " + eventStr

    def note_off(self,note):
	if self.toolbar_sel == 0:
	    return
        eventStr = "i5" + str(self.toolbar_sel) + ".1 0 0 1 " +  self.note2str(note)
	self.csound.playEventAsString(eventStr)

        if _showCSoundMsg:
            print "CSound Event: " + eventStr

# ======================================== toolbar ==============================================

    def toolbar_draw_colorgrid (self, widget, x, y, color_id):
        self.pixmap.draw_rectangle(widget.get_style().white_gc,False,x,y,self.toolbarw,self.toolbarw)
        gc = self.pixmap.new_gc()
	gc.set_rgb_fg_color(self.dark_grey)
	self.pixmap.draw_line(gc, x+1, y+1, x+self.toolbarw-1, y+1)
	self.pixmap.draw_line(gc, x+1, y+1, x+1, y+self.toolbarw-1)
	gc.set_rgb_fg_color(self.light_grey)
	self.pixmap.draw_line(gc, x+self.toolbarw-1, y+2, x+self.toolbarw-1, y+self.toolbarw-1)
	self.pixmap.draw_line(gc, x+2, y+self.toolbarw-1, x+self.toolbarw-1, y+self.toolbarw-1)
	self.pixmap.draw_line(widget.get_style().black_gc, x+2, y+2, x+self.toolbarw-2, y+2)
	self.pixmap.draw_line(widget.get_style().black_gc, x+2, y+2, x+2, y+self.toolbarw-2)
	gc.set_rgb_fg_color(self.tb_colors[color_id])  #
	self.pixmap.draw_rectangle(gc,True,x+3,y+3,self.toolbarw-4,self.toolbarw-4)
        widget.queue_draw_area(x, y, self.toolbarw, self.toolbarw)

    def toolbar_init (self, widget):

        self.tiy = (int)((self.iy-self.toolbarw)/2)
        self.light_grey = gtk.gdk.Color(58112,58112,58112)
        self.dark_grey = gtk.gdk.Color(40960,40960,40960)
        self.tb_colors = [self.black,self.black,self.white,self.red,self.orange,self.yellow,self.grass,self.green,self.lblue,self.blue,self.purple]

        # active selection
        self.toolbar_draw_colorgrid(widget, self.ix, self.tiy, self.toolbar_sel+2)

	# scale selection
        self.toolbar_draw_colorgrid(widget, self.ix - self.toolbarw - 12, self.iy, self.scale_mode+4)

        # toolbar
	for i in range(self.nbtn_toolbar):
            self.toolbar_draw_colorgrid(widget, self.tix+i*self.toolbarw, self.tiy, i)

    def toolbar_update(self, widget, color_id):
        gc = self.pixmap.new_gc()
        gc.set_rgb_fg_color(self.tb_colors[color_id])
	self.pixmap.draw_rectangle(gc,True,self.ix+3,self.tiy+3,self.toolbarw-4,self.toolbarw-4)
        widget.queue_draw_area(self.ix+3,self.tiy+3,self.toolbarw-4,self.toolbarw-4)

    def toolbar_detect(self, widget, x, y):
	if x >= self.ix - 42 and x < self.ix - 42 + self.toolbarw and y >= self.iy and y < self.iy + self.toolbarw:
	    self.scale_mode = (self.scale_mode + 1) % self.n_scales
            self.toolbar_draw_colorgrid(widget, self.ix - self.toolbarw - 12, self.iy, self.scale_mode+4)
#            self.pixmap.draw_string(gtk.GdkFont, widget.get_style().black_gc, self.ix - 40, self.iy, "M")
            return 3
        if x >= self.ix and x < self.ix + self.toolbarw and y >= self.tiy and y < self.tiy + self.toolbarw:
            self.change_filter = 1
            return 2
        if x < self.tix or y < self.tiy or y >= self.tiy + self.toolbarw or x >= self.tix+self.toolbarw*(self.nbtn_toolbar):
            return 0
        self.toolbar_sel = (int)((x - self.tix)/self.toolbarw) - 2
        self.color_sel = self.tb2color()
        self.toolbar_update(widget, self.toolbar_sel+2)
        
        return 1

# ======================================== end of toolbar ==============================================

    def timeout(self,widget):
        nowk = self.csound.current_kperiod() - self.start_time
        now = 1.0 * nowk / 100
        print "Time = " + str(now-self.start_time)
 	now_grid = (int)(now / self.grid_tdiv)

	if now_grid == self.end_grid and self.last_linex != -1:
            self.bg_color = gtk.gdk.Color(51200,51200,65535)
            gc = self.pixmap.new_gc()
            gc.set_rgb_fg_color(self.bg_color)
            by = self.iy+self.ngrid_v*self.gridh + 1
            self.pixmap.draw_line(gc,self.last_linex + self.ix, 
                              by, self.last_linex + self.ix, by+7)
	    widget.queue_draw_area(self.last_linex + self.ix, by, 1, 12)
            return False

#       draw current time line =======================================================

        x = (int)(now / self.grid_tdiv * self.gridw)
        by = self.iy+self.ngrid_v*self.gridh + 1
        if self.last_linex != x:
            if self.last_linex != -1:
                self.bg_color = gtk.gdk.Color(51200,51200,65535)
                gc = self.pixmap.new_gc()
                gc.set_rgb_fg_color(self.bg_color)
                self.pixmap.draw_line(gc,self.last_linex + self.ix, 
                              by, self.last_linex + self.ix, by+7)
	        widget.queue_draw_area(self.last_linex + self.ix, by, 1, 12)
                
            print str(now) + " " + str(x)
            self.pixmap.draw_line(widget.get_style().black_gc,x+self.ix, 
                                  by, x+self.ix, by+7)
	    widget.queue_draw_area(x+self.ix, by, 1, 12)
            self.last_linex = x

        return True

if __name__ == "__main__":
    musicpainter = Musicpainter()
    musicpainter.main()
