#!/usr/bin/python

if __name__ == '__main__':
    import testsetup

import gtk
import math
import weakref
import cairo
import pangocairo
import pango

up = 1
down = -1

class dim:
    line_spacing = 7
    rest_font_size = 27
    notehead_font_size = 27
    clef_font_size = 27
    font_name = "feta18"

class Staff(object):
    def __init__(self, x, y, length):
        """
        The parent of a Staff is always the toplevel sheet.
        x and y is the position on the toplevel sheet.
        """
        self.rx, self.ry, self.length = x, y, length
        self.children = []
    def obj_at(self, x, y):
        for child in self.children:
            o = child.obj_at(x, y)
            if o:
                return o
    def draw(self, ctx):
        ctx.save()
        for c in range(-2, 3):
            ctx.move_to(self.rx, self.ry + dim.line_spacing * c)
            ctx.rel_line_to(self.length, 0)
            ctx.stroke()
        ctx.restore()
        for child in self.children:
            child.draw(ctx)

class _ElemCommon(object):
    def __init__(self, parent, x, y):
        self.parent = weakref.ref(parent)
        self.parent().children.append(self)
        self.x, self.y = x, y
        self.rx = self.parent().rx + x
        if isinstance(self.parent(), Staff):
            self.ry = self.parent().ry - y * dim.line_spacing * 0.5
        else:
            self.ry = self.parent().ry
        self.children = []
    def move_rel(self, x, y):
        print "move rel", self.rx, self.ry, self.children[0].rx, x, y
        self.rx += x
        self.ry += y
        for child in self.children:
            child.rx += x
            child.ry += y
    def obj_at(self, x, y):
        pass
    def draw(self, ctx):
        for child in self.children:
            child.draw(ctx)

class NoteHead(_ElemCommon):
    def __init__(self, parent, x, y, t):
        """
        x and y is the position relative to its parent.
        t is the type: 1, 2 or 4
        """
        _ElemCommon.__init__(self, parent, x, y)
        self.m_head_type = t
    def draw(self, ctx):
        ctx.save()
        ctx.select_font_face(dim.font_name)
        ctx.set_font_size(dim.notehead_font_size)
        s = {4: u"\x26", 2: u"\x25", 1: u"\x24"}[self.m_head_type]
        w, h = ctx.text_extents(s)[2:4]
        ctx.move_to(self.rx - w/2, self.ry)
        ctx.text_path(s)
        ctx.fill()
        ctx.stroke()
        ctx.restore()
        for child in self.children:
            child.draw(ctx)
    def obj_at(self, x, y):
        d = math.sqrt((self.rx - x)**2 + (self.ry - y)**2)
        if d < 3:
            return self

class Stem(_ElemCommon):
    stem_len = 20
    def __init__(self, parent, x, y, dir):
        """
        dir: -1 for down, 1 for up
        """
        _ElemCommon.__init__(self, parent, x, y)
        self.dir = dir
    def draw(self, ctx):
        ctx.save()
        if self.dir == up:
            ctx.move_to(self.rx + 4, self.ry)
        else:
            ctx.move_to(self.rx - 5, self.ry)
        ctx.rel_line_to(0, -self.dir * self.stem_len)
        ctx.stroke()
        ctx.restore()

class QuarterNote(_ElemCommon):
    def __init__(self, parent, x, y, dir):
        _ElemCommon.__init__(self, parent, x, y)
        NoteHead(self, 0, y, 4)
        Stem(self, 0, y, dir)
    def obj_at(self, x, y):
        for child in self.children:
            return child.obj_at(x, y)

class Rest(_ElemCommon):
    def __init__(self, parent, x, y, rtype):
        """
        x and y is the position relative to its parent staff.
        x is in pixels
        y is in staff steps.
        """
        _ElemCommon.__init__(self, parent, x, y)
        self.m_rtype = rtype
    def draw(self, ctx):
        self.rx = int(self.rx)
        self.ry = int(self.ry) + int(bool(self.m_rtype == 1))

        ctx.save()
        ctx.select_font_face(dim.font_name)
        ctx.set_font_size(dim.rest_font_size)
        s = {1: u"\x03", 2: u"\x04", 4: u"\x08", 8: u"\x0a"}[self.m_rtype]
        w, h = ctx.text_extents(s)[2:4]
        ctx.move_to(self.rx - w/2, self.ry)
        ctx.text_path(s)
        ctx.fill()
        ctx.stroke()
        ctx.restore()
        for child in self.children:
            child.draw(ctx)

class Accidental(_ElemCommon):
    def __init__(self, parent, x, y, atype):
        """
        x and y is the position relative to its parent staff.
        x is in pixels
        y is in staff steps.
        atype: -2 = bb, -1 = b, 0 = natural sign, 1 = #, 2 = x
        """
        _ElemCommon.__init__(self, parent, x, y)
        self.m_type = atype
    def draw(self, ctx):
        #self.rx = int(self.rx)
        #self.ry = int(self.ry)
        ctx.save()
        ctx.select_font_face(dim.font_name)
        ctx.set_font_size(dim.rest_font_size)
        s = {-2: u"\x15", -1: u"\x13", 0: u"\x12", 1: u"\x0f", 2: u"\x17"}[self.m_type]
        w, h = ctx.text_extents(s)[2:4]
        ctx.move_to(self.rx - w/2, self.ry)
        ctx.text_path(s)
        ctx.fill()
        ctx.stroke()
        ctx.restore()
        for child in self.children:
            child.draw(ctx)

class Label(object):
    def __init__(self, parent, txt):
        self.parent = weakref.ref(parent)
        self.parent().children.append(self)
        self.rx = self.parent().rx
        self.ry = self.parent().ry + 40
        self.m_txt = txt
    def draw(self, ctx):
        self.rx = int(self.rx)
        self.ry = int(self.ry)
        ctx.save()
        ctx.select_font_face("Sans")
        ctx.set_font_size(12)
        w, h = ctx.text_extents(self.m_txt)[2:4]
        ctx.move_to(self.rx, self.ry)
        ctx.text_path(self.m_txt)
        ctx.fill()
        ctx.stroke()
        ctx.restore()

class Clef(_ElemCommon):
    def __init__(self, parent, x, cleftype, change=False):
        """
        change is True if the clef it for a clef change. This will display
        a slightly smaller clef.
        We call _ElemCommon.__init__ with y as 0 always.
        """
        _ElemCommon.__init__(self, parent, x, 0)
        self.m_cleftype = cleftype
        self.m_change = change
    def draw(self, ctx):
        ctx.save()
        ctx.select_font_face(dim.font_name)
        ctx.set_font_size(dim.clef_font_size)
        s, yoffset = {
            'violin': (0x96, 0),
            'french': (0x96, 1),
            'soprano': (0x92, 1),
            'mezzosoprano': (0x92, 0),
            'alto': (0x92, -1),
            'tenor': (0x92, -2),
            'baritone': (0x92, -3),
            'bass': (0x94, -2),
            'varbaritone': (0x94, -1),
            'subbass': (0x94, -3),
        }[self.m_cleftype]
        s = unichr(s + int(self.m_change))
        w, h = ctx.text_extents(s)[2:4]
        ctx.move_to(self.rx, self.ry+dim.line_spacing+yoffset * dim.line_spacing)
        ctx.text_path(s)
        ctx.fill()
        ctx.stroke()
        ctx.restore()
        super(Clef, self).draw(ctx)

class _CairoCommon(object):
    def __init__(self):
        pass
    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

class Sheet(gtk.DrawingArea, _CairoCommon):
    def __init__(self, width, height):
        gtk.DrawingArea.__init__(self)
        _CairoCommon.__init__(self)
        self.width = width
        self.height = height
        self.children = []
        self.set_events(gtk.gdk.BUTTON_PRESS_MASK)
        self.set_size_request(self.width, self.height)
        self.connect("expose_event", self.expose)
        self.connect("button-press-event", self.on_button_press_event)
    def on_button_press_event(self, widget, event):
        s = self.obj_at(event.x, event.y)
    def clear(self):#FIXME test
        self.children = []
    def add_staff(self, x, y, length):
        staff = Staff(x, y, length)
        self.children.append(staff)
        return staff
    def draw(self, ctx):
        ctx.save()
        ctx.set_source_rgb(1, 1, 1)
        ctx.rectangle(0, 0, self.width, self.height)
        ctx.fill_preserve()
        ctx.stroke()
        ctx.restore()
        ctx.set_line_width(1.0)
        for child in self.children:
            child.draw(ctx)
    def write_to_file(self):
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.sheet.width,
            self.sheet.height)
        ctx = gtk.gdk.CairoContext(cairo.Context(surface))
        self.sheet.draw(ctx)
        surface.write_to_png("ut.png")
    def obj_at(self, x, y):
        for child in self.children:
            o = child.obj_at(x, y)
            if o:
                print "Sheet.obj_at: found:", o


class TestWindow(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.connect('destroy', gtk.main_quit)
        vbox = gtk.VBox()
        self.add(vbox)
        bug_fix = False
        if bug_fix:
            xlabel = gtk.Label(u"")
            xlabel.modify_font(pango.FontDescription(dim.font_name))
            vbox.pack_start(xlabel)
        self.cnv = Sheet(500, 300)
        vbox.pack_start(self.cnv, False, False)
        self.cnv.show()
        self.m_idx = 0
        hbox = gtk.HBox()
        vbox.pack_start(hbox, False, False)
        idx = 0
        b = gtk.Button(stock="gtk-go-back")
        b.connect('clicked', self.go_back)
        hbox.pack_start(b)
        b = gtk.Button(stock="gtk-go-forward")
        b.connect('clicked', self.go_forward)
        hbox.pack_start(b)
        b = gtk.Button("Write to ut.png")
        b.connect('clicked', self.write_to_png)
        hbox.pack_start(b)
        self.show_all()
        if bug_fix:
            xlabel.destroy()
        self.change_sheet()
    def on_realize(self, w):
        self.change_sheet()
    def change_sheet(self):
        print "change_sheet:::"
        self.get_sheet(self.m_idx)
        self.queue_draw()
    def go_back(self, widget):
        if self.m_idx > 0:
            self.m_idx -= 1
        self.change_sheet()
    def go_forward(self, widget):
        self.m_idx += 1
        self.change_sheet()
    def get_sheet(self, idx):
        self.cnv.clear()
        if idx == 0 or idx == 1:
            staff = self.cnv.add_staff(1.5, 130.5, 500)
            x = 0
            space = 50
            for n in ("violin", "alto", "tenor", "bass", "soprano",
                    "mezzosoprano", "baritone", "varbaritone", 
                    "subbass", "french"):
                Label(Clef(staff, x, n, bool(idx)), n)
                x += space
        elif idx == 2:
            staff = self.cnv.add_staff(1.5, 130.5, 300)
            Clef(staff, 0.0, "violin")
            n = QuarterNote(staff, 40, 0, up)
        elif idx == 3:
            staffA = self.cnv.add_staff(1.5, 30.5, 300)
            Clef(staffA, 0, "violin")
            Label(QuarterNote(staffA, 50, 0, up), "violin")
            Label(QuarterNote(staffA, 80, 1, down), "violin")
            NoteHead(staffA, 100, 0, 4)
            NoteHead(staffA, 100, 2, 4)
            NoteHead(staffA, 115, 0, 2)
            NoteHead(staffA, 115, 2, 2)
            NoteHead(staffA, 130, 0, 1)
            NoteHead(staffA, 130, 2, 1)
            NoteHead(staffA, 145, 1, 4)
            NoteHead(staffA, 160, 1, 2)
            Accidental(staffA, 175, 1, 1)
            NoteHead(staffA, 185, 1, 1)
            staffB = self.cnv.add_staff(1.5, 100.5, 300)
            Clef(staffB, 0, "violin")
            Label(Clef(staffB, 50, "bass", True), "bass")
            Rest(staffB, 90, 0, 1)
            Rest(staffB, 110, 0, 2)
            Rest(staffB, 130, 0, 4)
            Rest(staffB, 150, 0, 8)
    def write_to_png(self, w):
        self.cnv.write_to_file()

if __name__ == '__main__':
    #
    w = TestWindow()
    gtk.main()
