# GNU Solfege - free ear training software
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006, 2007, 2008  Tom Cato Amundsen
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import division

import gtk
import runtime
if runtime.use_cairo_widgets:
    import math

import mpd
import soundcard
import cfg

class CairoCommon(object):
    def __init__(self):
        self.m_marks = []
        self.m_clicked_tones = []
    def _redraw(self):
        self.window.invalidate_rect(gtk.gdk.Rectangle(0, 0,
            self.allocation.width, self.allocation.height), True)
        return False
    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 clear(self):
        self.m_marks = []
        self.m_clicked_tones = []
    def mark_note(self, midi_int):
        if midi_int not in self.m_marks:
            self.m_marks.append(midi_int)
        self._redraw()
    def set_first_note(self, note):
        self.clear()
        self.m_clicked_tones = [int(note)]
        self.mark_note(self.m_clicked_tones[-1])
    def grab_focus_first_sensitive_button(self):
        """
        Dummy function. Only buttons interface implement this.
        """
        pass
    def know_directions(self):
        """
        Return True because this is a inputwidget where the user say
        both interval type _and_ direction.
        """
        return True
    def forget_last_tone(self):
        self.m_clicked_tones.pop()
    def _handle_tone_clicked(self, midi_int, mouse_button):
        if self.m_clicked_tones:
            interval = midi_int - self.m_clicked_tones[-1]
            if mouse_button == 1:
                self.m_clicked_tones.append(midi_int)
            self.m_callback(mouse_button, interval, midi_int)

class PianoKeyboard(gtk.DrawingArea, CairoCommon):
    def __init__(self, num_octaves, lowest_c, key_w=15):
        gtk.DrawingArea.__init__(self)
        CairoCommon.__init__(self)
        self.connect("expose_event", self.expose)
        self.connect("button-press-event", self._on_button_press)
        self.set_events(gtk.gdk.BUTTON_PRESS_MASK)
        # Piano stuff
        self.m_num_octaves = num_octaves
        self.m_lowest_c = mpd.MusicalPitch.new_from_notename(lowest_c)
        self.m_lowest_tone = self.m_lowest_c.get_octave_notename()
        self.m_highest_tone = (self.m_lowest_c.clone() + 12 * num_octaves - 1).get_octave_notename()
        self.m_white_h = key_w * 3.4
        self.m_black_h = key_w * 2.0
        self.m_key_w = key_w
        self.m_last_redraw = 0
        self.m_black_w = 0.6
        self.set_size_request(int(num_octaves * 7 * key_w + 1), int(self.m_white_h + 1))
    def _on_button_press(self, drawingarea, event):
        assert event.x >= 0
        if event.x < self.m_pos_x or event.x > self.m_num_octaves * 7 * self.m_key_w + self.m_pos_x:
            # Click outside the keys are ignored.
            return
        click_x = event.x - self.m_pos_x
        x = click_x / self.m_key_w
        octave_w = 7 * self.m_key_w
        clicked_octave = int(click_x / octave_w)
        clicked_white = int((click_x % octave_w) / self.m_key_w)
        black_clicked = 0
        if event.y < self.m_black_h:
            if clicked_white in (0, 1, 3, 4, 5):
                # How many pixels into the white key does the black
                # key start:
                bpos1 = self.m_key_w - self.m_key_w * self.m_black_w / 2
                if click_x % self.m_key_w >= bpos1:
                    black_clicked = 1
            if clicked_white in (1, 2, 4, 5, 6):
                # How many pixels into the white key does the black
                # key end:
                bpos2 = self.m_key_w * self.m_black_w / 2
                if click_x % self.m_key_w <= bpos2:
                    black_clicked = -1
        clicked_on = self.m_lowest_c.clone()
        clicked_on.m_notename_i = clicked_white
        clicked_on.m_accidental_i = black_clicked
        clicked_on.m_octave_i += clicked_octave
        self.on_button_press_event(event, clicked_on.semitone_pitch())
    def draw(self, context):
        num_key = 7 * self.m_num_octaves
        width = num_key * self.m_key_w
        self.m_pos_x = pos_x = int((self.allocation.width - width) / 2) + 0.5
        pos_y = .5
        context.set_line_width(1.2)
        context.save()
        for n in range(num_key):
            context.rectangle(pos_x + n * self.m_key_w, pos_y,
                              self.m_key_w, self.m_white_h)
        context.set_source_rgb(1, 1, 1)
        context.fill_preserve()
        context.set_source_rgb(0, 0, 0)
        context.stroke()
        context.restore()

        # Draw black keys
        context.save()
        for oct in range(self.m_num_octaves):
            for n in (0, 1, 3, 4, 5):
                context.rectangle(pos_x + oct*7*self.m_key_w + (n+1-self.m_black_w/2) * self.m_key_w,
                            pos_y,
                            self.m_key_w * self.m_black_w, self.m_black_h)
                context.set_source_rgb(0, 0, 0)
                context.fill_preserve()
        context.stroke()
        context.restore()
        for midi_int in self.m_marks:
            n = mpd.MusicalPitch.new_from_int(midi_int)
            acc = n.m_accidental_i
            step = n.steps() - self.m_lowest_c.steps()
            if acc == 0:
                context.arc(pos_x + (step + 0.5) * self.m_key_w, self.m_white_h-(self.m_white_h - self.m_black_h) / 2,
                            self.m_key_w * 0.35, 0, 2 * math.pi)
            else:
                context.arc(pos_x + (step + 0.5 + 0.5 * acc) * self.m_key_w, self.m_black_h * 0.5,
                            self.m_key_w * 0.25, 0, 2 * math.pi)
            context.set_source_rgb(0, 1, 0)
            context.fill_preserve()
            context.stroke()


class PianoOctaveWithAccelName(PianoKeyboard):
    def __init__(self, callback, keys):
        PianoKeyboard.__init__(self, 1, "c", 40)
        self.m_callback = callback
        self.m_keys = keys
    def draw(self, context):
        PianoKeyboard.draw(self, context)
        context.save()
        context.select_font_face("Sans")
        context.set_font_size(24)
        text_h = context.text_extents('A')[3]
        for idx, n in enumerate((0, 2, 4, 5, 7, 9, 11)):
            context.new_path()
            text_w = context.text_extents(self.m_keys[n])[4]
            context.move_to(self.m_pos_x + idx*self.m_key_w + self.m_key_w / 2 - text_w / 2, self.m_black_h + (self.m_white_h - self.m_black_h) / 2 + text_h / 2)
            context.text_path(self.m_keys[n])
            context.fill()
            context.stroke()
        context.restore()
        context.save()
        context.select_font_face("Sans")
        context.set_font_size(24)
        for idx, n in enumerate((1, 3, None, 6, 8, 10)):
            if n:
                context.new_path()
                text_w = context.text_extents(self.m_keys[n])[4]
                context.move_to(self.m_pos_x + (idx + 1)*self.m_key_w - text_w/2, self.m_black_h * 0.8)
                context.text_path(self.m_keys[n])
                context.set_source_rgb(1, 1, 1)
                context.fill()
                context.stroke()
        context.restore()
    def on_button_press_event(self, event, midi_int):
        if event.button == 3:
            soundcard.play_note(cfg.get_int('config/preferred_instrument'),
                           4, midi_int,
                           cfg.get_int('config/preferred_instrument_velocity'))
        elif event.button == 1:
            self.m_callback(mpd.int_to_notename(midi_int))


class IntervalPianoWidget(PianoKeyboard):
    def __init__(self, callback):
        PianoKeyboard.__init__(self, 4, "c,", 18)
        self.m_callback = callback
    def on_button_press_event(self, event, midi_int):
        """
        The callback function is only called if we have an interval.
        """
        self._handle_tone_clicked(midi_int, event.button)


class IntervalButtonsWidget(gtk.Table, cfg.ConfigUtils):
    def __init__(self, exname, name, callback, sensicallback, vars_to_watch):
        gtk.Table.__init__(self, 1, 1, True)
        cfg.ConfigUtils.__init__(self, exname)
        self.m_name = name
        self.get_sensitive_buttons = sensicallback
        self.m_callback = callback
        self.m_buttons = {}
        self.new_int_button(_("Minor\nsecond"), 1, 0, 1, 0, 2)
        self.new_int_button(_("Major\nsecond"),  2, 0, 1, 2, 4)
        self.new_int_button(_("Minor\nthird"),   3, 0, 1, 4, 6)
        self.new_int_button(_("Major\nthird"),    4, 0, 1, 6, 8)
        self.new_int_button(_("Perfect\nfourth"),    5, 1, 2, 0, 2)
        self.new_int_button(_("Diminished\nfifth"),    6, 1, 2, 2, 4)
        self.new_int_button(_("Perfect\nfifth"),    7, 1, 2, 4, 6)
        self.new_int_button(_("Minor\nsixth"),  8, 1, 2, 6, 8)
        self.new_int_button(_("Major\nsixth"),   9, 2, 3, 0, 2)
        self.new_int_button(_("Minor\nseventh"),10, 2, 3, 2, 4)
        self.new_int_button(_("Major\nseventh"), 11, 2, 3, 4, 6)
        self.new_int_button(_("Perfect\noctave"),   12, 2, 3, 6, 8)
        self.new_int_button(_("Minor\nninth"),  13, 3, 4, 0, 2)
        self.new_int_button(_("Major\nninth"),   14, 3, 4, 2, 4)
        self.new_int_button(_("Minor\ndecim"), 15, 3, 4, 4, 6)
        self.new_int_button(_("Major\ndecim"),  16, 3, 4, 6, 8)
        self.m_lowest_tone = mpd.LOWEST_NOTENAME
        self.m_highest_tone = mpd.HIGHEST_NOTENAME
        self.add_watch('disable_unused_intervals', self.intervals_changed)
        for var in vars_to_watch:
            self.add_watch(var, self.intervals_changed)
        self.intervals_changed()
    def intervals_changed(self, s=None):
        if self.get_bool('disable_unused_intervals'):
            self.set_sensitivity(self.get_sensitive_buttons())
        else:
            for x in range(1, 17):
                self.m_buttons[x].set_sensitive(True)
    def set_sensitivity(self, make_active):
        for x in range(1, 17):
            self.m_buttons[x].set_sensitive(x in make_active)
    def new_int_button(self, txt, nr, x1, x2, y1, y2):
        # buttonwidget calls m_callback with None as midi_int because it
        # does not know if you mean interval up or down when you click
        # the buttons
        self.m_buttons[nr] = b = gtk.Button(txt)
        l=b.get_child().set_justify(gtk.JUSTIFY_CENTER)
        self.attach(b, x1, x2, y1, y2)
        b.connect('clicked',
                  lambda s, nr=nr, self=self:self.m_callback(1, nr, None))
        b.set_data('interval', nr)
        b.connect('event', self._abc)
    def _abc(self, button, event):
        if event.type == gtk.gdk.BUTTON_RELEASE and event.button == 3:
            self.m_callback(3, button.get_data('interval'), None)
    def set_first_note(self, note):
        self.m_first_note = int(note)
    def know_directions(self):
        return False
    def clear(self):
        pass
    def show(self):
        self.show_all()
    def grab_focus_first_sensitive_button(self):
        if self.get_bool('disable_unused_intervals'):
            self.m_buttons[self.get_sensitive_buttons()[0]].grab_focus()
        else:
            self.m_buttons[1].grab_focus()


class AbstractGuitarWidget(gtk.DrawingArea, CairoCommon):
    def __init__(self, callback, strings,
                string_thickness=(1, 1, 1, 1, 1, 1)):
        gtk.DrawingArea.__init__(self)
        CairoCommon.__init__(self)
        self.m_callback = callback
        self.m_strings = strings
        self.m_string_thickness = string_thickness
        self.m_fretdist = (20, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 30, 30)
        self.m_stringdist = 17
        self.m_numstring = len(self.m_strings)
        self.m_neckborder = 6
        self.m_neckl = 0
        self.m_xlist = []
        self.m_lowest_tone = mpd.int_to_notename(
            min(map(mpd.notename_to_int, self.m_strings)))
        self.m_highest_tone = mpd.int_to_notename(
            len(self.m_fretdist) - 1 + max(map(mpd.notename_to_int, self.m_strings)))
        self.m_stringtuning = map(mpd.notename_to_int, self.m_strings)
        tmp = 0
        for x in self.m_fretdist:
            tmp = tmp + x
            self.m_xlist.append(tmp)
        tmp = self.m_neckborder + self.m_stringdist/2
        self.m_ylist = [tmp]
        for y in range(self.m_numstring-1):
            tmp = tmp + self.m_stringdist
            self.m_ylist.append(tmp)
        for x in self.m_fretdist:
            self.m_neckl = self.m_neckl + x
        self.m_neckl = self.m_neckl + 2
        self.m_neckw = self.m_neckborder \
                       + (self.m_numstring-1)*self.m_stringdist \
                       + 1 + self.m_neckborder
        self.set_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK)
        self.connect('expose_event', self.expose)
        self.m_mouse_pos = None, None
        self.connect('motion-notify-event', self.on_motion_notify_event)
        self.connect('leave-notify-event', self.on_leave_notify_event)
        self.connect('button-press-event', self.on_button_press_event)
        self.set_size_request(self.m_neckl, self.m_neckw)
    def on_button_press_event(self, widget, event):
        x, y = self.event2xy(event)
        if x is not None and y is not None:
            midi_int = self.m_stringtuning[y] + x
            self._handle_tone_clicked(midi_int, event.button)
    def on_leave_notify_event(self, widget, event):
        self.m_mouse_pos = None, None
        self._redraw()
    def on_motion_notify_event(self, widget, event):
        x, y = self.event2xy(event)
        need_redraw = False
        if x is not None and y is not None:
            if (x, y) != self.m_mouse_pos:
                need_redraw = True
            self.m_mouse_pos = x, y
        else:
            if (x, y) != self.m_mouse_pos:
                need_redraw = True
            self.m_mouse_pos = None, None
        if need_redraw:
            self._redraw()
    def draw(self, context):
        self.m_posx = int((self.allocation.width - self.m_neckl) / 2) + 0.5
        self.m_posy = int((self.allocation.height - self.m_neckw) / 2) + 0.5
        context.set_line_width(1.2)
        context.save()
        LG = 0.75 # white grey
        DG = 0.66 # dark grey
        # fret board
        context.rectangle(self.m_posx, self.m_posy, self.m_neckl, self.m_neckw)
        context.fill_preserve()
        context.stroke()
        context.restore()
        # first fret
        context.save()
        px = self.m_fretdist[0] + self.m_posx
        context.rectangle(px, self.m_posy,
                          5, self.m_neckw)
        context.set_source_rgb(LG, LG, LG)
        context.fill_preserve()
        context.set_source_rgb(LG, LG, LG)
        context.stroke()
        #
        context.set_source_rgb(1, 1, 1)
        context.move_to(px, self.m_posy)
        context.rel_line_to(0, self.m_neckw)
        context.stroke()
        #
        context.set_source_rgb(DG, DG, DG)
        context.move_to(px + 5, self.m_posy)
        context.rel_line_to(0, self.m_neckw)
        context.stroke()
        context.restore()
        #
        #FRETS
        context.save()
        for w in self.m_fretdist[1:]:
            px = px + w
            context.set_source_rgb(1, 1, 1)
            context.move_to(px, self.m_posy)
            context.rel_line_to(0, self.m_neckw)
            context.stroke()
            context.set_source_rgb(LG, LG, LG)
            context.move_to(px + 1, self.m_posy)
            context.rel_line_to(0, self.m_neckw)
            context.stroke()
            context.set_source_rgb(DG, DG, DG)
            context.move_to(px + 2, self.m_posy)
            context.rel_line_to(0, self.m_neckw)
            context.stroke()
        context.restore()
        #
        # String
        context.save()
        for y in range(self.m_numstring):
            if self.m_string_thickness[y] == 1:
                context.set_line_width(self.m_string_thickness[y])
                context.set_source_rgb(LG, LG, LG)
                context.move_to(self.m_posx, self.m_posy + self.m_neckborder \
                                + y * self.m_stringdist)
                context.rel_line_to(self.m_neckl, 0)
                context.stroke()
            if self.m_string_thickness[y] == 2:
                context.set_source_rgb(1, 1, 1)
                context.move_to(self.m_posx, self.m_posy + self.m_neckborder \
                                + y * self.m_stringdist)
                context.rel_line_to(self.m_neckl, 0)
                context.stroke()
                context.set_source_rgb(LG, LG, LG)
                context.move_to(self.m_posx, self.m_posy + self.m_neckborder \
                                + y * self.m_stringdist + 1)
                context.rel_line_to(self.m_neckl, 0)
                context.stroke()
            if self.m_string_thickness[y] == 3:
                context.rectangle(self.m_posx, self.m_posy + self.m_neckborder \
                                + y * self.m_stringdist + 1,
                                self.m_neckl, 1)
                context.set_source_rgb(LG, LG, LG)
                context.fill_preserve()
                context.set_source_rgb(LG, LG, LG)
                context.stroke()
                context.set_source_rgb(1, 1, 1)
                context.move_to(self.m_posx, self.m_posy + self.m_neckborder \
                                + y * self.m_stringdist)
                context.rel_line_to(self.m_neckl, 0)
                context.stroke()
        context.restore()
        if isinstance(self, GuitarWidget):
            # The white dots on a guitar
            context.save()
            for x, y in ((3, 2), (5, 2), (7, 2), (9, 2), (12, 1), (12, 3)):
                context.arc(self.m_posx + self.m_xlist[x] - self.m_fretdist[x]/2,
                            self.m_posy + self.m_ylist[y],
                            4.5, 0, math.pi * 2)
                context.set_source_rgb(1, 1, 1)
                context.fill_preserve()
                context.stroke()
            context.restore()
        # Marks
        context.save()
        for note in self.m_marks:
            for idx in range(len(self.m_stringtuning)):
                if self.m_stringtuning[idx] <= note < self.m_stringtuning[idx] + len(self.m_fretdist):
                    x = note - self.m_stringtuning[idx]
                    context.arc(self.m_posx + self.m_xlist[x]-self.m_fretdist[x]/2,
                        self.m_posy + self.m_ylist[idx]-self.m_stringdist/2,
                        7, 0, math.pi * 2)
                    context.set_source_rgb(0, 1, 0)
                    context.fill_preserve()
                    context.set_source_rgb(0, 0, 0)
                    context.stroke()
        context.restore()
        # Dot following mouse cursor
        if self.m_mouse_pos != (None, None):
            context.save()
            context.arc(self.m_posx + self.m_xlist[self.m_mouse_pos[0]]-self.m_fretdist[self.m_mouse_pos[0]]/2,
                        self.m_posy + self.m_ylist[self.m_mouse_pos[1]]-self.m_stringdist/2,
                        5, 0, math.pi * 2)
            context.set_source_rgb(1, 0, 0)
            context.fill_preserve()
            context.set_source_rgb(0, 0, 0)
            context.stroke()
            context.restore()
    def event2xy(self, event):
        x = event.x - self.m_posx
        xp = yp = None
        for idx in range(len(self.m_xlist)):
            if 0 <= x < self.m_xlist[idx]:
                xp = idx
                break
        for idx in range(len(self.m_ylist)):
            if 0 <= event.y < self.m_ylist[idx]:
                yp = idx
                break
        return xp, yp


class GuitarWidget(AbstractGuitarWidget):
    def __init__(self, callback, strings, string_thickness):
        AbstractGuitarWidget.__init__(self, callback, strings, string_thickness)


class AccordionWidget(gtk.DrawingArea, CairoCommon):
    def __init__(self, callback, keyboard_system):
        gtk.DrawingArea.__init__(self)
        CairoCommon.__init__(self)
        self.m_callback = callback
        self.m_notenames_norwegian = (("g,", "bes,", "des", "e",
                 "g", "bes", "des'", "e'", "g'", "bes'", "des''", "e''",
                 "g''", "bes''", "des'''", "e'''", "g'''", "bes'''", "des''''"),
                ("f,", "aes,", "b,", "d",
                 "f", "aes", "b", "d'", "f'", "aes'", "b'", "d''", "f''",
                 "aes''", "b''", "d'''", "f'''", "aes'''", "b'''", "d''''"),
                ("fis,", "a,", "c", "ees",
                 "fis", "a", "c'", "ees'", "fis'", "a'", "c''", "ees''",
                 "fis''", "a''", "c'''", "ees'''", "fis'''", "a'''", "c''''"),
                ("e,", "g,", "bes,", "des",
                 "e", "g", "bes", "des'", "e'", "g'", "bes'", "des''", "e''",
                 "g''", "bes''", "des'''", "e'''", "g'''", "bes'''", "des''''"),
                ("f,", "aes,", "b,", "d",
                 "f", "aes", "b", "d'", "f'", "aes'", "b'", "d''", "f''",
                 "aes''", "b''", "d'''", "f'''", "aes'''", "b'''"))
        self.m_notenames_swedish = (
                ("e,", "g,", "bes,", "des",
                 "e", "g", "bes", "des'", "e'", "g'", "bes'", "des''", "e''",
                 "g''", "bes''", "des'''", "e'''", "g'''", "bes'''"),
                ("dis,", "fis,", "a,", "c", "ees", "fis", "a", "c'",
                 "ees'", "fis'", "a'", "c''", "ees''", "fis''", "a''",
                 "c'''", "ees'''", "fis'''", "a'''", "c''''"),
                ("f,", "aes,", "b,", "d",
                 "f", "aes", "b", "d'", "f'", "aes'", "b'", "d''", "f''",
                 "aes''", "b''", "d'''", "f'''", "aes'''", "b'''"),
                ("e,", "g,", "bes,", "des",
                 "e", "g", "bes", "des'", "e'", "g'", "bes'", "des''", "e''",
                 "g''", "bes''", "des'''", "e'''", "g'''", "bes'''", "des''''"),
                ("fis,", "a,", "c", "ees",
                 "fis", "a", "c'", "ees'", "fis'", "a'", "c''", "ees''",
                 "fis''", "a''", "c'''", "ees'''", "fis'''", "a'''", "c''''"),
                 )
        self.m_notenames_finnish = (
                ("dis,", "fis,", "a,", "c", "ees", "fis", "a", "c'",
                 "ees'", "fis'", "a'", "c''", "ees''", "fis''", "a''",
                 "c'''", "ees'''", "fis'''", "a'''", "c''''"),
                ("d,", "f,", "aes,", "b,", "d",
                 "f", "aes", "b", "d'", "f'", "aes'", "b'", "d''", "f''",
                 "aes''", "b''", "d'''", "f'''", "aes'''", "b'''"),
                ("e,", "g,", "bes,", "des",
                 "e", "g", "bes", "des'", "e'", "g'", "bes'", "des''", "e''",
                 "g''", "bes''", "des'''", "e'''", "g'''", "bes'''", "des''''"),
                ("dis,", "fis,", "a,", "c", "ees",
                 "fis", "a", "c'", "ees'", "fis'", "a'", "c''", "ees''",
                 "fis''", "a''", "c'''", "ees'''", "fis'''", "a'''", "c''''"),
                ("f,", "aes,", "b,", "d",
                 "f", "aes", "b", "d'", "f'", "aes'", "b'", "d''", "f''",
                 "aes''", "b''", "d'''", "f'''", "aes'''", "b'''", "d''''"),
                 )
        self.m_notenames = {'norwegian': self.m_notenames_norwegian,
                            'swedish': self.m_notenames_swedish,
                       'finnish': self.m_notenames_finnish}[keyboard_system]
        self.m_lowest_tone = mpd.HIGHEST_NOTENAME
        self.m_highest_tone = mpd.LOWEST_NOTENAME
        for v in self.m_notenames:
            if mpd.compare_notenames(self.m_lowest_tone, v[0]) > 0:
                self.m_lowest_tone = v[0]
            if mpd.compare_notenames(self.m_highest_tone, v[-1]) < 0:
                self.m_highest_tone = v[-1]
        self.m_button_radius = 9
        self.m_button_xdist = 20
        self.m_button_ydist = 18

        self.connect('expose_event', self.expose)
        self.connect('button-press-event', self._on_button_press)
        self.set_events(gtk.gdk.BUTTON_PRESS_MASK)
    def event_to_button_pos(self, event):
        """
        Return a tuple telling which button on the accordion that
        got clicked.
        Returns (Col. row)
        """
        for y in range(self.m_by):
          if self.m_bb + self.m_posy + y * self.m_button_ydist - self.m_button_radius < event.y < self.m_bb + self.m_posy + (y+1) * self.m_button_ydist - self.m_button_radius :
            for x in range(self.m_bx):
                # button center
                cx = self.m_bb + self.m_posx + x * self.m_button_xdist + (y + 1) % 2 * self.m_button_radius
                cy = self.m_bb + self.m_posy + y * self.m_button_ydist
                if math.sqrt((event.x-cx)**2+(event.y-cy)**2)<self.m_button_radius:
                    return x, y
        return None, None
    def _on_button_press(self, drawingarea, event):
        # This is only set if set_first_note is called
        x, y = self.event_to_button_pos(event)
        if x == None:
            return
        midi_int = mpd.notename_to_int(self.m_notenames[y][x])
        self._handle_tone_clicked(midi_int, event.button)
    def draw(self, context):
        # number of buttons, horiz and vertic.
        self.m_bx = bx = len(self.m_notenames[0])
        self.m_by = by = len(self.m_notenames)
        # how much space between buttons and border
        self.m_bb = bb = 16
        #
        w = bx * self.m_button_xdist + bb * 2
        h = by * self.m_button_ydist + bb * 2
        self.m_posx = posx = int((self.allocation.width - w) / 2) + 0.5
        self.m_posy = posy = int((self.allocation.height - h) / 2) + 0.5
        context.set_line_width(1.2)
        context.save()
        context.move_to(posx, posy)
        for x, y in ((w, 0),
                     (0, self.m_button_ydist * by), # right btn
                     (-self.m_button_xdist, self.m_button_ydist), #right btn
                     (-w + self.m_button_xdist * 2, 0),
                     (-self.m_button_xdist, -self.m_button_ydist),
                     (0, -self.m_button_ydist * by),
                     ):
            context.rel_line_to(x, y)
        context.close_path()
        context.set_source_rgb(0, 0, 0)
        context.fill_preserve()
        context.stroke()
        context.restore()

        # buttons
        context.save()
        for row_idx, row in enumerate(self.m_notenames):
            for col_idx, notename in enumerate(row):
                context.arc(bb + posx + col_idx * self.m_button_xdist + (row_idx+1) % 2 * self.m_button_radius,
                            bb + posy + row_idx * self.m_button_ydist,
                            self.m_button_radius, 0, 2 * math.pi)
                if notename[1:][:2] in ('is', 'es'):
                    context.set_source_rgb(0.4, 0.4, 0.4)
                else:
                    context.set_source_rgb(1, 1, 1)
                context.fill_preserve()
                context.stroke()
        context.restore()

        context.save()
        for row_idx, row in enumerate(self.m_notenames):
            for col_idx, notename in enumerate(row):
                if mpd.notename_to_int(notename) in self.m_marks:
                    context.arc(bb + posx + col_idx * self.m_button_xdist + (row_idx+1) % 2 * self.m_button_radius,
                            bb + posy + row_idx * self.m_button_ydist,
                            5, 0, 2 * math.pi)
                    context.set_source_rgb(0, 1, 0)
                    context.fill_preserve()
                    context.stroke()

        context.restore()
        self.set_size_request(500, self.m_by * self.m_button_ydist + 2 * self.m_bb)


inputwidget_names = [_("Buttons")]
if runtime.use_cairo_widgets:
    inputwidget_names.extend([_("Piano"), _("Guitar"), _("Bass"),
                 _("5 string bass"), _("6 string bass"),
                 _("Accordion B griff"), _("Accordion C griff"),
                 _("Accordion (system used in Finland)")])

def name_to_inputwidget(name, callback):
    if name == _("Piano"):
        w = IntervalPianoWidget(callback)
    elif name == _("Accordion B griff"):
        w = AccordionWidget(callback, 'norwegian')
    elif name == _("Accordion C griff"):
        w = AccordionWidget(callback, 'swedish')
    elif name == _("Accordion (system used in Finland)"):
        w = AccordionWidget(callback, 'finnish')
    elif name == _("Guitar"):
        w = GuitarWidget(callback, ["e'", "b", "g", "d", "a,", "e,"],
                                   (1, 1, 1, 2, 2, 3))
    elif name == _("Bass"):
        w = AbstractGuitarWidget(callback, ["g,", "d,", "a,,", "e,,"],
                                 (1, 1, 2, 3))
    elif name == _("5 string bass"):
        w = AbstractGuitarWidget(callback, ["g,", "d,", "a,,", "e,,", "b,,,"],
                                 (1, 1, 2, 3, 3))
    else:
        assert name == _("6 string bass")
        w = AbstractGuitarWidget(callback, ["c", "g,", "d,", "a,,", "e,,", "b,,,"],
                                 (1, 1, 2, 2, 3, 3))
    return w

