# -*- coding: utf-8 -*-

# Songwrite 2
# Copyright (C) 2007 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# 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 verson.
#
# 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 os, os.path, sys, math, bisect, locale, codecs
from cStringIO import StringIO

import qt

from editobj2.introsp  import *
from editobj2.observe  import *
from editobj2.undoredo import *

import songwrite2.globdef as globdef
import songwrite2.model   as model
import songwrite2.stemml  as stemml
import songwrite2.player  as player
import songwrite2.__editobj2__

CLIPBOARD        = None
CLIPBOARD_SOURCE = None
CLIPBOARD_X1     = 0
CLIPBOARD_Y1     = 0
CLIPBOARD_X2     = 0
CLIPBOARD_Y2     = 0

def find_view_children(o):
  if isinstance(o, model.View) and hasattr(o, "strings"):
    return o.strings
  return []


class Canvas(qt.QScrollView):
  is_gui_interface = 1
  def __init__(self, main, song, parent, zoom = 1.0):
    qt.QScrollView.__init__(self, parent, "", qt.QWidget.WResizeNoErase | qt.QWidget.WRepaintNoErase)
    
    self.x                   = 0
    self.y                   = 0
    self.song                = song
    self.start_x             = 58
    self.zoom                = zoom
    self.selection_x1        = 1000000
    self.selection_y1        = 1000000
    self.selection_x2        = -1
    self.selection_y2        = -1
    self.default_font_size   = 16.0
    self.default_line_height = 0
    self.cursor              = None
    self.cursor_drawer       = None
    self.canvas              = None
    self.drawers             = None
    self.partition_2_drawer  = {}

    self.main              = main
    self.song              = song
    self.selections        = set()
    self.last_selections   = set()
    self.drag_selections   = set()
    self.click_x           = 0
    self.click_y           = 0
    #self.drag_start_x      = 0
    #self.drag_start_y      = 0
    self.last_selection_x1 = 1000000
    self.last_selection_y1 = 1000000
    self.last_selection_x2 = -1
    self.last_selection_y2 = -1
    self.need_update_selection = 0
    self.current_duration  = 96
    self.previous_text     = "" # Previously typed text
    self.edit_timeout      = 0
    self.first_time        = 1
    self.x_before_tracking = -1
    
    self.default_line_height = 0
    
    self.update_scroll_timer = qt.QTimer(self)
    self.update_scroll_timer.connect(self.update_scroll_timer, qt.SIGNAL("timeout()"), self.on_update_scroll_timer)
    
    self.setResizePolicy(qt.QScrollView.Manual)
    self.setFrameStyle(qt.QFrame.NoFrame)
    
    from songwrite2.main_qt import app

    self.white_color = app.white
    self.back  = app.black
    
    self.default_font = qt.QFont()
    #self.default_font = qt.QFont("SansSerif", 16, qt.QFont.Bold)
    metrics = qt.QFontMetrics(self.default_font)
    self.default_ascent      = metrics.ascent ()
    self.default_descent     = metrics.descent()
    self.default_line_height = (metrics.height () - 1)
    self.char_h_size = self.default_line_height // 2 # XXX
    
    self.small_font = qt.QFont("SansSerif", self.default_font.pointSize() // 2)
    
    self.selection_pen   = qt.QPen(qt.QColor(255, 0, 0), 0)
    
    self.none_pen        = qt.QPen(self.black, 0, qt.QPen.NoPen)
    self.default_pen     = qt.QPen()
    self.accentuated_pen = qt.QPen(qt.QColor(217, 0, 0))
    self.bold_pen        = qt.QPen(self.black, 2)
    self.string_type_2_pen = {
      -1 : None,
       0 : qt.QPen(qt.QColor(  0,   0,   0), 0),
       1 : qt.QPen(qt.QColor( 50,  50,  50), 0),
       2 : qt.QPen(qt.QColor(100, 100, 100), 0),
       3 : qt.QPen(qt.QColor(120, 120, 120), 0),
       4 : qt.QPen(qt.QColor(140, 140, 140), 0),
       5 : qt.QPen(qt.QColor(153, 153, 153), 0),
       6 : qt.QPen(qt.QColor(160, 160, 160), 0),
       7 : qt.QPen(qt.QColor(170, 170, 170), 0),
       8 : qt.QPen(qt.QColor(150, 150, 150), 0, qt.QPen.DashLine),
       9 : self.default_pen,
      }
    
    self.default_brush     = qt.QBrush(qt.QColor(  0, 0, 0))
    self.accentuated_brush = qt.QBrush(qt.QColor(217, 0, 0))
    self.none_brush        = qt.QBrush(qt.QBrush.NoBrush)
    
    observe(song                        , self.song_listener)
    observe(song.playlist               , self.playlist_listener)
    observe(song.playlist.playlist_items, self.playlist_listener)
    observe(song.mesures                , self.mesures_listener)
    for mesure in song.mesures:
      observe(mesure, self.mesure_listener)
      
    observe(song.partitions, self.partitions_listener)
    for partition in song.partitions:
      observe(partition      , self.partition_listener)
      if isinstance(partition, model.Partition):
        observe_tree(partition.view , self.view_listener, find_view_children)
        observe     (partition.notes, self.notes_listener)
        for note in partition.notes:
          observe(note, self.note_listener)
          
    
  def reset(self):
    for drawer in self.drawers: drawer.destroy()
    self.drawers = []
    
  def note_string_id(self, note):
    return self.partition_2_drawer[note.partition].note_string_id(note)
  
  def y_2_drawer(self, y):
    for drawer in self.drawers:
      if y < drawer.y + drawer.height: return drawer
    return None
    
  def x_2_time(self, x):
    return (x - self.start_x) / self.zoom
  
  def time_2_x(self, time):
    return self.start_x + time * self.zoom

  def round_time_to_current_duration(self, time, delta = 0):
    duration = self.current_duration
    
    if duration ==  96:
      if (self.song.mesure_at(time) or self.song.mesures[-1]).rythm2 == 8: duration /= 2
    if duration == 144:
      if (self.song.mesure_at(time) or self.song.mesures[-1]).rythm2 == 4: duration /= 3
    else:
      if duration / 1.5 in model.DURATIONS.keys(): duration /= 3
      
    return max(0, int((delta + (time // duration)) * duration))
    
  def play_tracker(self, time):
    if time == -1:
      self.render_selection()
      self.selection_x1 = self.last_selection_x1
      self.selection_y1 = self.last_selection_y1
      self.selection_x2 = self.last_selection_x2
      self.selection_y2 = self.last_selection_y2
      self.setContentsPos(self.x_before_tracking, self.contentsY())
      self.render_selection()
      self.x_before_tracking = -1
      
    else:
      if self.x_before_tracking == -1: self.x_before_tracking = max(0, self.contentsX())
      self.render_selection()
      self.selection_x1 = self.time_2_x(time)
      self.selection_y1 = self.drawers[0].y
      self.selection_x2 = self.selection_x1 + 2 * self.char_h_size
      self.selection_y2 = self.drawers[-1].y + self.drawers[-1].height
      
      self.render_selection()
      if   self.selection_x2 > self.contentsX() + self.visibleWidth() * 0.75:
        self.setContentsPos(self.selection_x2 - self.visibleWidth() * 0.1, self.contentsY())
        self.render_selection()
      elif self.selection_x1 < self.contentsX():
        self.setContentsPos(self.selection_x2 - self.visibleWidth() * 0.1, self.contentsY())
        self.render_selection()
        
  def destroy(self, destroyWindow = 1, destroySubWindows = 1):
    for drawer in self.drawers: drawer.destroy()
    unobserve(self.cursor)
    unobserve(self.song                        , self.song_listener)
    unobserve(self.song.playlist               , self.playlist_listener)
    unobserve(self.song.playlist.playlist_items, self.playlist_listener)
    unobserve(self.song.mesures                , self.mesures_listener)
    unobserve(self.song.partitions             , self.partitions_listener)
    
    for mesure in self.song.mesures:
      unobserve(mesure, self.mesure_listener)
      
    for partition in self.song.partitions:
      unobserve(partition, self.partition_listener)
      if isinstance(partition, model.Partition):
        unobserve_tree(partition.view, self.view_listener, find_view_children)
        unobserve(partition.notes, self.notes_listener)
        for note in partition.notes:
          unobserve(note, self.note_listener)
          
    qt.QScrollView.destroy(self, destroyWindow, destroySubWindows)
    
  def update_time(self):
    for drawer in self.drawers:
      if isinstance(drawer, LyricsDrawer): drawer.update_melody()
        
  def get_selected_time(self):
    if self.selections     : return list(self.selections     )[0].time
    if self.last_selections: return list(self.last_selections)[0].time
    return 0
  
  def get_selected_mesures(self):
    return list(set([self.song.mesure_at(note.time) for note in self.selections]))
  
  def get_selected_partitions(self):
    return list(set([note.partition for note in self.selections]))
  
  def song_listener(self, song, type, new, old):
    self.main.set_title(u"Songwrite2 -- %s" % song.title)
    self.render_all()
    
  def playlist_listener(self, playlist, type, new, old):
    self.render_all()
  
  def mesures_listener(self, obj, type, new, old):
    new = set(new)
    old = set(old)
    for mesure in new - old: observe  (mesure, self.mesure_listener)
    for mesure in old - new: unobserve(mesure, self.mesure_listener)
    
  def mesure_listener(self, mesure, type, new, old):
    self.render_all()
    
  def partitions_listener(self, obj, type, new, old):
    new = set(new)
    old = set(old)
    for partition in new - old:
      observe  (partition, self.partition_listener)
      if isinstance(partition, model.Partition):
        observe_tree(partition.view , self.view_listener, find_view_children)
        observe(partition.notes, self.notes_listener)
        for note in partition.notes: observe(note, self.note_listener)
        
    for partition in old - new:
      unobserve(partition, self.partition_listener)
      if isinstance(partition, model.Partition):
        unobserve_tree(partition.view , self.view_listener, find_view_children)
        unobserve(partition.notes, self.notes_listener)
        for note in partition.notes: unobserve(note, self.note_listener)
        
    self.deselect_all()
    self.reset()
    self.render_all()
    
  def partition_listener(self, partition, type, new, old):
    if type is object:
      if   (new.get("view") != old.get("view")):
        unobserve_tree(old.get("view"), self.view_listener, find_view_children)
        observe_tree  (new.get("view"), self.view_listener, find_view_children)
        
        self.deselect_all()
        self.reset()
        self.render_all()
      if   (new.get("g8") != old.get("g8")) or (new.get("tonality") != old.get("tonality")):
        self.deselect_all()
        self.reset()
        self.render_all()
      elif (new.get("instrument") != old.get("instrument")) or (new.get("header") != old.get("header")) or (new.get("visible") != old.get("visible")):
        self.render_all()
        
    self.partition_2_drawer[partition].partition_listener(partition, type, new, old)
    
  def view_listener(self, view, type, new, old):
    self.render_all()
    
  def strings_listener(self, view, type, new, old):
    self.render_all()
    
  def notes_listener(self, obj, type, new, old):
    new = set(new)
    old = set(old)
    for note in new - old: observe  (note, self.note_listener)
    for note in old - new: unobserve(note, self.note_listener)
    
  def note_listener(self, note, type, new, old):
    if note in self.selections:
      self.need_update_selection = 1
      if (note is self.cursor) and (note.value > 0):
        self.cursor = None
      self.render_selection()
      
    self.current_duration = note.duration
    self.partition_2_drawer[note.partition].render_note(note)
    
  def set_cursor(self, cursor, cursor_drawer):
    self.cursor        = cursor
    self.cursor_drawer = cursor_drawer
    
  def set_zoom(self, zoom):
    self.selection_x1 = (self.selection_x1 - self.start_x) * zoom / self.zoom + self.start_x
    self.selection_x2 = (self.selection_x2 - self.start_x) * zoom / self.zoom + self.start_x
    self.setContentsPos(self.contentsX() / self.zoom * zoom, self.contentsY())
    self.zoom = zoom
    self.render_all()
    self.update_time()
    self.main.menubar.changeItem(self.main.menubar.idAt(self.main.zoom_menu_index), u"%s%%" % int(zoom * 100.0))
    
  def arrange_selection_at_fret(self, fret):
    old_string_ids = dict([(note, note.string_id) for note in self.selections if hasattr(note, "string_id")])
    def do_it():
      for note, string_id in old_string_ids.iteritems():
        if isinstance(note.partition.view, model.TablatureView):
          strings = note.partition.view.strings
          if note.value - strings[string_id].base_note < fret:
            while (string_id < len(strings) - 1) and (note.value - strings[string_id].base_note < fret):
              string_id += 1
          else:
            while (string_id > 0) and (note.value - strings[string_id - 1].base_note >= fret):
              string_id -= 1
          note.string_id = string_id
      self.render_all()
      
    def undo_it():
      for note, string_id in old_string_ids.iteritems():
        note.string_id = string_id
      self.render_all()
    UndoableOperation(do_it, undo_it, _(u"arrange at fret #%s" % fret), self.main.undo_stack)
    
    
  def select_all(self):
    self.deselect_all()
    for partition in self.song.partitions:
      if isinstance(partition, model.Partition):
        drawer = self.partition_2_drawer[partition]
        for note in partition.notes:
          self.add_selection(note, render = 0, edit = 1, *drawer.note_dimensions(note))
    self.render_selection()
    
  def deselect_all(self):
    if self.cursor: self.cursor_drawer.destroy_cursor()
    if self.selection_x2 != -1:
      self.render_selection()
      self.selection_x1     = 1000000
      self.selection_y1     = 1000000
      self.selection_x2     = -1
      self.selection_y2     = -1
    self.selections      = set()
    self.cursor          = None
    self.previous_text   = ""

  def extend_selection(self, x1, y1, x2, y2):
    if self.selection_x1 > x1: self.selection_x1 = x1
    if self.selection_y1 > y1: self.selection_y1 = y1
    if self.selection_x2 < x2: self.selection_x2 = x2
    if self.selection_y2 < y2: self.selection_y2 = y2
    
    # Do it asynchronously, because Qt perform an IMMEDIATE repaint on scrolling !
    self.update_scroll_timer.stop()
    self.update_scroll_timer.start(0, 1)
    
  def on_update_scroll_timer(self):
    self.ensureVisible(self.selection_x1, self.selection_y1, 3, 3)
    self.ensureVisible(self.selection_x2, self.selection_y2, 3, 3)
    
  def add_selection(self, note, x1, y1, x2, y2, render = 1, edit = 1):
    self.selections.add(note)
    self.extend_selection(x1 + self.x, y1 + self.y, x2 + self.x, y2 + self.y)
    if not note is self.cursor:
      global CLIPBOARD, CLIPBOARD_SOURCE, CLIPBOARD_X1, CLIPBOARD_Y1, CLIPBOARD_X2, CLIPBOARD_Y2
      CLIPBOARD_SOURCE = self
      CLIPBOARD        = self.last_selections   = self.selections
      CLIPBOARD_X1     = self.last_selection_x1 = self.selection_x1
      CLIPBOARD_Y1     = self.last_selection_y1 = self.selection_y1
      CLIPBOARD_X2     = self.last_selection_x2 = self.selection_x2
      CLIPBOARD_Y2     = self.last_selection_y2 = self.selection_y2
      
    if render: self.render_selection()
    
  def clipboard_get(self):
    _xml    = StringIO()
    xml     = codecs.lookup("utf8")[3](_xml)
    context = model._XMLContext()
    xml.write("""<?xml version="1.0" encoding="utf-8"?>\n<notes click_x="0">""")
    
    min_y = 10000.0
    for note in CLIPBOARD:
      drawer = self.partition_2_drawer[note.partition]
      x1, y1, x2, y2 = drawer.note_dimensions(note)
      y = (y1 + y2) // 2
      if y < min_y: min_y = y
      
    for note in CLIPBOARD:
      drawer = self.partition_2_drawer[note.partition]
      x1, y1, x2, y2 = drawer.note_dimensions(note)
      nb_char = len(drawer.note_text(note))
      x = x1 + (self.char_h_size * nb_char) // 2 - CLIPBOARD_X1
      y = (y1 + y2) // 2 - min_y
      note.__xml__(xml, context, ' x="%r" y="%r" string_pitch="%s" view_type="%s"' % (x, y, drawer.strings[drawer.note_string_id(note)].base_note, note.partition.view.__class__.__name__[:-4].lower()))
    xml.write("</notes>")
    return xml.getvalue()
    
  def contentsMousePressEvent(self, event):
    try: event.accept()
    except: pass # QTopia has not accept()
    self.setFocus()
    
    self.click_x = event.x()
    self.click_y = event.y()
    
    button = event.button()
    if   button == qt.QWidget.LeftButton:
      if self.selections and (self.selection_x1 <= self.click_x <= self.selection_x2) and (self.selection_y1 <= self.click_y <= self.selection_y2):
        self.deselect_all()
        if self.click_x > self.start_x:
          for drawer in self.drawers:
            if drawer.y <= self.click_y <= drawer.y + drawer.height: drawer.on_button_press(event, self.click_x, self.click_y, 1)
            
        notes      = list(self.selections)
        if notes:
          old_values = [note.value for note in notes]
          
          drawer = self.partition_2_drawer[notes[0].partition]
          player.play_note(notes[0].partition.instrument, abs(notes[0].value))
          
          def do_it(notes = notes):
            for note in notes:
              drawer = self.partition_2_drawer[note.partition]
              if note.value < 0: note.partition.add_note(note)
              note.value = abs(note.value)
          def undo_it(notes = notes, old_values = old_values):
            for i in range(len(notes)):
              notes[i].value = old_values[i]
              if notes[i].value < 0: notes[i].partition.remove(notes[i])
              
          UndoableOperation(do_it, undo_it, _(u"note change"), self.main.undo_stack)
          
      else:
        self.deselect_all()
        if self.click_x > self.start_x:
          for drawer in self.drawers:
            if drawer.y <= self.click_y <= drawer.y + drawer.height: drawer.on_button_press(event, self.click_x, self.click_y, 0)
            
  def contentsMouseReleaseEvent(self, event):
    try: event.accept()
    except: pass # QTopia has not accept()
    
    if (not self.selections) and (self.selection_x2 != -1):
      time1 = self.x_2_time(self.selection_x1)
      time2 = self.x_2_time(self.selection_x2)
      y1 = self.selection_y1
      y2 = self.selection_y2
      
      self.deselect_all()
      for partition in self.song.partitions:
        if not isinstance(partition, model.Partition): continue
        drawer = self.partition_2_drawer[partition]
        string_id1 = drawer.y_2_string_id(y1 + drawer.string_height // 2, 1)
        string_id2 = drawer.y_2_string_id(y2 - drawer.string_height // 2, 1)
        
        for note in partition.notes_at(time1, time2):
          if string_id1 <= drawer.note_string_id(note) <= string_id2:
            self.add_selection(note, render = 0, edit = 0, *drawer.note_dimensions(note))
            
      if self.selections:
        self.render_selection()
          
  def contentsMouseMoveEvent(self, event):
    try: event.accept()
    except: pass # QTopia has not accept()
    
    x = event.x()
    y = event.y()
    
    if abs(self.click_x - x) + abs(self.click_y - y) > 6:
        if self.selections: self.deselect_all()
        old_sel_x1 = self.selection_x1
        old_sel_y1 = self.selection_y1
        old_sel_x2 = self.selection_x2
        old_sel_y2 = self.selection_y2
        self.ensureVisible(x, y, 3, 3)
        self.selection_x1 = max(min(self.click_x, x), self.start_x)
        self.selection_y1 = min(self.click_y, y + self.y)
        self.selection_x2 = max(self.click_x, x)
        self.selection_y2 = max(self.click_y, y + self.y)
        self.render(min(old_sel_x1, self.selection_x1) - 1,
                    min(old_sel_y1, self.selection_y1) - 1,
                    max(old_sel_x2, self.selection_x2) - min(old_sel_x1, self.selection_x1) + 2,
                    max(old_sel_y2, self.selection_y2) - min(old_sel_y1, self.selection_y1) + 2)
        
  def on_note_paste(self):
    xml = CLIPBOARD_SOURCE.clipboard_get()
    
    from cStringIO import StringIO
    click_x, notes = stemml.parse(StringIO(xml))
    
    if not self.selections: return
    note      = tuple(self.selections)[0]
    drawer    = self.partition_2_drawer[note.partition]
    y0        = drawer.string_id_2_y(drawer.note_string_id(note))
    dest_time = note.time
    orig_time = 0
    
    self.paste_notes(notes, dest_time, orig_time, y0)
    
  def paste_notes(self, notes, dest_time, orig_time, y0):
    orig_time += min([note.time for note in notes])
      
    dt = int(0.4 + float(abs(dest_time - orig_time)) / self.current_duration) * self.current_duration
    if dest_time < orig_time: dt = -dt
    
    notes_data     = []
    previous_notes = {}
    for note in notes:
      y = y0 + note.y
      drawer = self.y_2_drawer(y)
      if not isinstance(drawer, PartitionDrawer): continue
      time = note.time + dt
      if time < 0: continue
      
      if (note.view_type == drawer.partition.view.__class__.__name__[:-4].lower()) and (isinstance(drawer, TablatureDrawer) or isinstance(drawer, DrumsDrawer)):
        # Paste by string
        string_id = drawer.y_2_string_id(y)
        if string_id is None: continue
        paste_by_string = 1
      else:
        # Paste by note
        string_id = drawer.default_string_id(note.value)
        paste_by_string = 0
        
      notes_data.append((note, drawer, time, string_id, paste_by_string))
      for previous_note in drawer.partition.notes_at(time, time):
        if string_id == drawer.note_string_id(previous_note):
          drawer_previous_notes = previous_notes.get(drawer)
          if not drawer_previous_notes: drawer_previous_notes = previous_notes[drawer] = []
          drawer_previous_notes.append(previous_note)
          break
        
    def do_it(notes_data = notes_data):
      for drawer, notes in previous_notes.items():
        drawer.partition.remove(*notes)
        
      new_notes = {}
      for note, drawer, time, string_id, paste_by_string in notes_data:
        new_note = model.Note(drawer.partition, time, note.duration, note.value, note.volume, note.appoggiatura)
        new_note.fx = note.fx
        if note.bend_pitch: new_note.bend_pitch = note.bend_pitch
        
        if isinstance(drawer, TablatureDrawer) or isinstance(drawer, DrumsDrawer):
          new_note.string_id = string_id
        if paste_by_string:
          new_note.value = note.value - note.string_pitch + drawer.strings[string_id].base_note
          
        drawer_new_notes = new_notes.get(drawer)
        if not drawer_new_notes: drawer_new_notes = new_notes[drawer] = []
        drawer_new_notes.append(new_note)
        
      self.deselect_all()
      
      for drawer, notes in new_notes.items():
        drawer.partition.add_note(*notes)
        
        for note in notes:
          self.add_selection(note, render = 0, edit = 0, *drawer.note_dimensions(note))
          
      self.render_all()

    def undo_it(notes = notes):
      new_notes = {}
      for note, drawer, time, string_id, paste_by_string in notes_data:
        drawer_new_notes = new_notes.get(drawer)
        if not drawer_new_notes: drawer_new_notes = new_notes[drawer] = []
        for new_note in drawer.partition.notes_at(time, time):
          if paste_by_string:
            if string_id == drawer.note_string_id(new_note):
              drawer_new_notes.append(new_note)
              break
          else:
            if new_note.value == note.value:
              drawer_new_notes.append(new_note)
              break
          
      for drawer, notes in new_notes.items():
        drawer.partition.remove(*notes)
        
      for drawer, notes in previous_notes.items():
        drawer.partition.add_note(*notes)
        
      self.render_all()
      
    UndoableOperation(do_it, undo_it, _(u"add %s note(s)") % len(notes), self.main.undo_stack)




  def keyPressEvent(self, event):
    event.accept()
    
    keyval = event.key()

    if   keyval == qt.QWidget.Key_Left:
      if self.selections:
        sel = tuple(self.selections)[0]
        time = sel.time
        string_id = self.note_string_id(sel)
        note = sel.partition.note_before_pred(sel, lambda a: self.note_string_id(a) == string_id)
        time0 = self.round_time_to_current_duration(time, -1)
        if note and note.time >= time0:
          self.deselect_all()
          self.partition_2_drawer[sel.partition].select_note(note)
        elif time > 0:
          self.deselect_all()
          self.partition_2_drawer[sel.partition].select_at(time0, string_id)
          
    elif keyval == qt.QWidget.Key_Right:
      if self.selections:
        sel = tuple(self.selections)[0]
        time = sel.time
        string_id = self.note_string_id(sel)
        note = sel.partition.note_after_pred(sel, lambda a: self.note_string_id(a) == string_id)
        time0 = self.round_time_to_current_duration(time, 1)
        if note and note.time <= time0:
          self.deselect_all()
          self.partition_2_drawer[sel.partition].select_note(note)
        else:
          self.deselect_all()
          self.partition_2_drawer[sel.partition].select_at(time0, string_id)
          
    elif keyval == qt.QWidget.Key_Up:
      if self.selections:
        sel = tuple(self.selections)[0]
        string_id = self.note_string_id(sel)
        if string_id > 0:
          self.deselect_all()
          self.partition_2_drawer[sel.partition].select_at(sel.time, string_id - 1)
        else:
          i = self.drawers.index(self.partition_2_drawer[sel.partition])
          if i > 0:
            drawer = self.drawers[i - 1]
            if isinstance(drawer, PartitionDrawer):
              self.deselect_all()
              drawer.select_at(sel.time, len(drawer.strings) - 1)
              
    elif keyval == qt.QWidget.Key_Down:
      if self.selections:
        sel = tuple(self.selections)[0]
        string_id = self.note_string_id(sel)
        if string_id < len(self.partition_2_drawer[sel.partition].strings) - 1:
          self.deselect_all()
          self.partition_2_drawer[sel.partition].select_at(sel.time, string_id + 1)
        else:
          i = self.drawers.index(self.partition_2_drawer[sel.partition])
          if i < len(self.drawers) - 1:
            drawer = self.drawers[i + 1]
            if isinstance(drawer, PartitionDrawer):
              self.deselect_all()
              drawer.select_at(sel.time, 0)
              
    elif keyval == qt.QWidget.Key_Home:
      if self.selections:
        note   = list(self.selections)[0]
        drawer = self.partition_2_drawer[note.partition]
        string_id = drawer.note_string_id(note)
        self.deselect_all()
        drawer.select_at(0, string_id)
        self.setContentsPos(0, self.contentsY())
        
    elif keyval == qt.QWidget.Key_End:
      if self.selections:
        note   = list(self.selections)[0]
        drawer = self.partition_2_drawer[note.partition]
        string_id = drawer.note_string_id(note)
        self.deselect_all()
        drawer.select_at(self.song.mesures[-1].end_time() - self.current_duration, string_id)
        
    elif (keyval == qt.QWidget.Key_Comma) or (keyval == qt.QWidget.Key_Slash) or (keyval == qt.QWidget.Key_Asterisk): # / or *
      if keyval == qt.QWidget.Key_Asterisk: notes = [note for note in self.selections if note.base_duration() < 384]
      else:                                 notes = [note for note in self.selections if note.base_duration() > 12 ]
      def decrease_duration(notes = notes):
        for note in notes: self.current_duration = note.duration = note.duration // 2
      def increase_duration(notes = notes):
        for note in notes: self.current_duration = note.duration = note.duration *  2
        
      if keyval == qt.QWidget.Key_Asterisk: UndoableOperation(increase_duration, decrease_duration, _(u"increase note duration"), self.main.undo_stack)
      else:                                 UndoableOperation(decrease_duration, increase_duration, _(u"decrease note duration"), self.main.undo_stack)
      
    elif keyval == qt.QWidget.Key_Period: # .
      self.toggle_dotted()
      
    elif (keyval == qt.QWidget.Key_Plus) or (keyval == qt.QWidget.Key_Equal): # + ou =
      self.shift_selections_values(1)
      
    elif keyval == qt.QWidget.Key_Minus: # -
      self.shift_selections_values(-1)
      
    elif (qt.QWidget.Key_0 <= keyval <= qt.QWidget.Key_9): # Numbers
      nb = keyval - qt.QWidget.Key_0
      self.previous_text += str(nb)
      notes      = list(self.selections)
      if notes:
        text       = self.previous_text
        old_values = [note.value for note in notes]
        
        drawer = self.partition_2_drawer[notes[0].partition]
        player.play_note(notes[0].partition.instrument,
                         drawer.strings[drawer.note_string_id(notes[0])].text_2_value(text))
        
        def do_it(notes = notes, text = text):
          for note in notes:
            drawer = self.partition_2_drawer[note.partition]
            if note.value < 0: note.partition.add_note(note)
            note.value = drawer.strings[drawer.note_string_id(note)].text_2_value(text)
        def undo_it(notes = notes, old_values = old_values):
          for i in range(len(notes)):
            notes[i].value = old_values[i]
            if notes[i].value < 0: notes[i].partition.remove(notes[i])

        UndoableOperation(do_it, undo_it, _(u"note change"), self.main.undo_stack)
      
    elif (keyval == qt.QWidget.Key_Backspace) or (keyval == qt.QWidget.Key_Delete):
      if self.selections: self.delete_notes(self.selections)
      
    elif (keyval == qt.QWidget.Key_H) or (keyval == qt.QWidget.Key_P) or (keyval == qt.QWidget.Key_L):
      if self.selections: self.set_selections_fx(u"link")
      
    elif keyval == qt.QWidget.Key_D:
      if self.selections: self.set_selections_fx(u"dead")
      
    elif keyval == qt.QWidget.Key_T:
      if self.selections: self.set_selections_fx(u"tremolo")
      
    elif keyval == qt.QWidget.Key_B:
      if self.selections: self.set_selections_fx(u"bend")
      
    elif keyval == qt.QWidget.Key_S:
      if self.selections: self.set_selections_fx(u"slide")
      
    elif keyval == qt.QWidget.Key_R:
      if self.selections: self.set_selections_fx(u"roll")
      
    elif keyval == qt.QWidget.Key_N:
      if self.selections: self.set_selections_fx(u"")
      
    elif (keyval == qt.QWidget.Key_Return) or (keyval == qt.QWidget.Key_Enter):
      if self.selections:
        notes = list(self.selections)
        def stress(notes = notes):
          for note in notes: note.volume = 255
        def unstress(notes = notes):
          for note in notes: note.volume = 204
        if notes[0].volume == 255: UndoableOperation(unstress, stress, _(u"unstress note"), self.main.undo_stack)
        else:                      UndoableOperation(stress, unstress, _(u"stress note"), self.main.undo_stack)

    elif keyval == qt.QWidget.Key_Q:
      self.main.on_close()
      
    elif keyval == qt.QWidget.Key_Space:
      if player.is_playing(): player.stop()
      else: self.main.on_play_from_here()
      
    elif (keyval == qt.QWidget.Key_A) and (event.state() & qt.QWidget.ControlButton):
      self.select_all()
      
    elif (keyval == qt.QWidget.Key_V) and (event.state() & qt.QWidget.ControlButton):
      self.on_note_paste()
      
    elif keyval == qt.QWidget.Key_A: # a
      self.toggle_appo()
      
    else:
      event.ignore()
      
  def set_selections_volume(self, volume):
    if self.selections:
      notes      = list(self.selections)
      old_values = [note.volume for note in notes]
      def do_it(notes = notes):
        for note in notes: note.volume = volume
      def undo_it(notes = notes):
        for i in range(len(notes)): notes[i].volume = old_values[i]
      UndoableOperation(do_it, undo_it, _(u"change of %s") % _(u"volume"), self.main.undo_stack)
      
  def toggle_dotted(self, *args):
    notes      = list(self.selections)
    old_values = [note.duration for note in notes]
    def do_it(notes = notes):
      dotted = not notes[0].is_dotted()
      for note in notes:
        if note.is_dotted() != dotted:
          if dotted: self.current_duration = note.duration = int(note.duration * 1.5)
          else:      self.current_duration = note.duration = int(note.duration / 1.5)
    def undo_it(notes = notes, old_values = old_values):
      for i in range(len(notes)): notes[i].duration = old_values[i]

    UndoableOperation(do_it, undo_it, _(u"dotted note"), self.main.undo_stack)
    
  def toggle_triplet(self, *args):
    notes      = list(self.selections)
    old_values = [note.duration for note in notes]
    def do_it(notes = notes):
      triplet = not notes[0].is_triplet()
      for note in notes:
        if note.is_triplet() != triplet:
          if triplet: self.current_duration = note.duration = int(note.duration * 2 / 3)
          else:       self.current_duration = note.duration = int(note.duration * 3 / 2)
    def undo_it(notes = notes, old_values = old_values):
      for i in range(len(notes)): notes[i].duration = old_values[i]

    UndoableOperation(do_it, undo_it, _(u"triplet note"), self.main.undo_stack)
    
  def toggle_appo(self, *args):
    notes      = list(self.selections)
    old_values = [note.appoggiatura for note in notes]
    def do_it(notes = notes):
      appoggiatura = not notes[0].appoggiatura
      for note in notes:
        if note.appoggiatura != appoggiatura:
          note.appoggiatura = appoggiatura
    def undo_it(notes = notes, old_values = old_values):
      for i in range(len(notes)): notes[i].appoggiatura = old_values[i]
      
    UndoableOperation(do_it, undo_it, _(u"appoggiatura"), self.main.undo_stack)
    
  def shift_selections_values(self, d):
      notes      = list(self.selections)
      old_values = [note.value for note in notes]
      def do_it(notes = notes):
        for note in notes:
          drawer = self.partition_2_drawer[note.partition]
          if note.value < 0: note.partition.add_note(note)

          note.value = abs(note.value) + d
          while not drawer.note_value_possible(note.value):
            note.value += d
            
          if isinstance(drawer, TablatureDrawer) and (note.value < drawer.strings[drawer.note_string_id(note)].base_note):
            note.value = drawer.strings[drawer.note_string_id(note)].base_note
            
      def undo_it(notes = notes, old_values = old_values):
        for i in range(len(notes)):
          notes[i].value = old_values[i]
          if notes[i].value < 0: notes[i].partition.remove(notes[i])
          
      if d > 0: UndoableOperation(do_it, undo_it, _(u"increase notes pitch"), self.main.undo_stack)
      else:     UndoableOperation(do_it, undo_it, _(u"decrease notes pitch"), self.main.undo_stack)
      
  def delete_notes(self, notes):
    if not notes: return
    notes = list(notes)
    if notes == [self.cursor]: return
    
    def do_it(notes = notes):
      sel = notes[0]
      drawer = self.partition_2_drawer[sel.partition]
      partition_2_notes = {}
      for note in notes:
        l = partition_2_notes.get(note.partition)
        if not l: partition_2_notes[note.partition] = [note]
        else: l.append(note)
        drawer.render_note(note)
      for partition, notes in partition_2_notes.items(): partition.remove(*notes)
      if set(notes) & self.selections:
        self.deselect_all()
        drawer.select_at(sel.time, drawer.note_string_id(sel), 0)
    def undo_it(notes = notes):
      self.deselect_all()
      partition_2_notes = {}
      for note in notes:
        l = partition_2_notes.get(note.partition)
        if not l: partition_2_notes[note.partition] = [note]
        else: l.append(note)
      for partition, notes in partition_2_notes.items():
        partition.add_note(*notes)
        drawer = self.partition_2_drawer[partition]
        for note in notes:
          self.add_selection(note, render = 0, edit = 0, *drawer.note_dimensions(note))
          drawer.render_note(note)
          
    UndoableOperation(do_it, undo_it, _(u"delete %s note(s)") % len(notes), self.main.undo_stack)

  def set_selections_fx(self, fx):
    notes      = list(self.selections)
    old_values = [note.fx for note in notes]
    def do_it():
      for note in notes:
        note.set_fx(fx)
    def undo_it():
      for i in range(len(notes)):
        notes[i].set_fx(old_values[i])
    UndoableOperation(do_it, undo_it, _(u"set special effect %s") % fx, self.main.undo_stack)
    
  def render_pixel(self, x, y, width, height):
    self.updateContents(int(x), int(y), int(width), int(height))
  
  def render_all(self):
    # QTopia's updateContents requires arguments
    self.updateContents(self.contentsX(), self.contentsY(), self.visibleWidth(), self.visibleHeight())
    
  def render(self, x, y, width, height):
    self.updateContents(int(x), int(y), int(width), int(height))
    
  def render_selection(self):
    self.updateContents(int(self.selection_x1) - 1, int(self.selection_y1) - 1, int(self.selection_x2 - self.selection_x1) + 3, int(self.selection_y2 - self.selection_y1) + 3)
    
  def drawContents(self, ctx, x = -1, y = 0, width = 0, height = 0):
    #print x, y, width, height
    if x == -1: return
    
    #if width  == 0: width  = self.visibleWidth ()
    #if height == 0: height = self.visibleHeight()
    
    ctx.setFont(self.default_font)
    
    if self.need_update_selection:
      self.selection_x1     = 1000000
      self.selection_y1     = 1000000
      self.selection_x2     = -1
      self.selection_y2     = -1
      for note in self.selections: self.add_selection(note, render = 0, edit = 0, *self.partition_2_drawer[note.partition].note_dimensions(note))
      self.need_update_selection = 0
    
    first_drawer = None
    if not self.drawers: # first time
      self.drawers = []
      for partition in self.song.partitions:
        if   isinstance(partition     , model.Lyrics       ): drawer = LyricsDrawer   (self, partition)
        elif hasattr(partition.view, "get_drawer"):           drawer = partition.view.get_drawer(self)
        elif isinstance(partition.view, model.TablatureView): drawer = TablatureDrawer(self, partition)
        elif isinstance(partition.view, model.StaffView    ): drawer = StaffDrawer    (self, partition)
        elif isinstance(partition.view, model.DrumsView    ): drawer = DrumsDrawer    (self, partition)
        if drawer:
          self.drawers.append(drawer)
          if (not first_drawer) and self.first_time:
            first_drawer = drawer
            self.first_time = 0
            
    #ctx.setPen(self.none_pen)
    #ctx.setBrush(self.white_color)
    #ctx.drawRect(x, y, width, height)
    
    if self.selection_x2 != -1:
      ctx.setPen(self.none_pen)
      ctx.setBrush(self.white_color)
      if x < self.selection_x1:
        ctx.drawRect(x, y, self.selection_x1 - x, height)
        
      if x + width > self.selection_x2:
        ctx.drawRect(self.selection_x2, y, x + width - self.selection_x2, height)

      bgx = max(x, self.selection_x1)
      bgw = min(x + width, self.selection_x2) - bgx
      if y < self.selection_y1:
        ctx.drawRect(bgx, y, bgw, self.selection_y1 - y)
        
      if y + height > self.selection_y2:
        ctx.drawRect(bgx, self.selection_y2, bgw, y + height - self.selection_y2)
        
      #ctx.setPen  (qt.QColor(142, 199, 255))
      #ctx.setBrush(qt.QColor(215, 235, 255))
      ctx.setPen  (qt.QColor( 71, 100, 255))
      ctx.setBrush(qt.QColor(142, 199, 255))
      ctx.drawRect(self.selection_x1, self.selection_y1, self.selection_x2 - self.selection_x1, self.selection_y2 - self.selection_y1)
    else:
      ctx.setPen(self.none_pen)
      ctx.setBrush(self.white_color)
      ctx.drawRect(x, y, width, height)
      
    ctx.setPen(self.default_pen)
    
    dy = -self.y
    total_height = 0
    for drawer in self.drawers:
      drawer.y = dy
      drawer.draw(ctx, x, y, width, height)
      dy           += drawer.height + 10
      total_height += drawer.height + 10
      
    total_width = (self.song.mesures[-1].end_time() + self.song.mesures[-1].duration) * self.zoom
    if (total_width != self.contentsWidth()) or (total_height != self.contentsHeight()): self.resizeContents(total_width, total_height)
    
    if first_drawer:
      first_drawer.select_at(0, 0, 0)
      self.setFocus()
      
    qt.QScrollView.drawContents(self, ctx, x, y, width, height)
    
    
class Drawer(object):
  def __init__(self, canvas):
    self.canvas = canvas
    self.y      = 0
    self.height = 0
    
  def draw(self, ctx, x, y, width, height, drag = 0):
    return (y < self.y + self.height) and (self.y < y + height)
  
  def destroy(self): pass

  
class PartitionDrawer(Drawer):
  show_header = 1
  def __init__(self, canvas, partition):
    Drawer.__init__(self, canvas)
    self.partition = partition
    self.start_y   = 15
    self.stem_extra_height = 0
    self.stem_extra_height_bottom = 0
    self.up_stem_ratio = 0.5
    self.canvas.partition_2_drawer[partition] = self
    
  def default_string_id(self, value): return 0
  
  def note_value_possible(self, value): return 1
  
  def partition_listener(self, partition, type, new, old): pass
  
  def y_2_string_id(self, y, force = 0):
    string_id = int((y - self.y - self.stem_extra_height - self.start_y) // self.string_height)
    if not force:
      if   string_id < 0:                     return None
      elif string_id > len(self.strings) - 1: return None
    return string_id
  
  def string_id_2_y(self, string_id):
    return self.y + self.start_y + self.stem_extra_height + self.string_height * (string_id + 0.5)
  
  def on_button_press(self, event, x, y, in_selection):
    string_id = self.y_2_string_id(y)
    if not string_id is None:
      time = self.canvas.x_2_time(x)
      self.select_at(time, string_id)
      
  def select_at(self, time, string_id, fix_time = 1):
    if fix_time: time0 = self.canvas.round_time_to_current_duration(time)
    else:        time0 = time
    notes = self.partition.notes_at(time0, time)
    notes = [note for note in notes if self.note_string_id(note) == string_id]
    for note in notes:
      if note.end_time() > time:
        self.select_note(note)
        break
    else:
      for note in notes:
        if note.time == time0:
          self.select_note(note)
          break
      else:
        cursor          = model.Note(self.partition)
        cursor.time     = time0
        cursor.duration = self.canvas.current_duration
        if isinstance(self, TablatureDrawer):
          cursor.string_id = string_id
        if isinstance(self, StaffDrawer):
          cursor.value = -(self.strings[string_id].base_note + self.strings[string_id].alteration)
        else:
          cursor.value = -self.strings[string_id].base_note
        while cursor.time >= self.canvas.song.mesures[-1].end_time():
          mesure = self.canvas.song.add_mesure()
          self.canvas.render(self.canvas.time_2_x(mesure.time) - 1, 0, mesure.duration * self.canvas.zoom + 2, self.canvas.height())
        observe(cursor, self.canvas.note_listener)
        self.canvas.set_cursor(cursor, self)
        self.select_note(cursor)
        self.render_note(cursor)
        
  def destroy(self):
    self.destroy_cursor(render = 0)
    Drawer.destroy(self)
      
  def destroy_cursor(self, render = 1):
    cursor = self.canvas.cursor
    unobserve(cursor)
    self.canvas.cursor        = None
    self.canvas.cursor_drawer = None
    if render: self.render_note(cursor)
    
  def select_note(self, note):
    self.canvas.add_selection(note, *self.note_dimensions(note))
    
  def render_note(self, note):
    mesure = self.canvas.song.mesure_at(note)
    if mesure.rythm2 == 8: time1 = (note.link_start() // 144) * 144; time2 = max(note.end_time(), time1 + 144)
    else:                  time1 = (note.link_start() //  96) *  96; time2 = max(note.end_time(), time1 +  96)
    self.canvas.render_pixel(
      self.canvas.time_2_x(time1),
      self.y,
      (time2 - time1) * self.canvas.zoom + 2,
      self.height + self.canvas.default_line_height // 2 + self.canvas.default_ascent // 2,
      )
    
  def note_dimensions(self, note):
    x = self.canvas.time_2_x(note.time)
    y = self.string_id_2_y(self.note_string_id(note)) - self.string_height // 2
    return (
      x,
      y,
      x + note.duration * self.canvas.zoom,
      y + self.string_height,
      )
  
  def draw_mesure(self, ctx, time, mesure, y, height):
    x       = self.canvas.time_2_x(time)
    ny      = y
    nheight = height
    symbols = self.partition.song.playlist.symbols.get(mesure)
    if symbols:
      bold = 1
      for symbol in symbols:
        if symbol.startswith(r"\repeat"):
          ctx.setBrush(self.canvas.default_brush)
          ctx.drawEllipse(x + 3, y + 0.30 * height, 4, 4)
          ctx.drawEllipse(x + 3, y + 0.70 * height, 4, 4)
          
        elif (symbol == "} % end repeat") or (symbol == "} % end alternative"):
          ctx.setBrush(self.canvas.default_brush)
          ctx.drawEllipse(x - 7, y + 0.30 * height, 4, 4)
          ctx.drawEllipse(x - 7, y + 0.70 * height, 4, 4)
          
        elif symbol.startswith(r"{ % start alternative"):
          ny      = self.y
          nheight = height + y - ny
          alt = symbol[22:]
          
          ctx.drawText(x + 5, ny + self.canvas.default_line_height * 0.85, alt)
          ctx.drawLine(x, ny, x + 60, ny)
    else:
      bold = (mesure is None) or (time == 0)
      
    if bold:
      ctx.setPen(self.canvas.bold_pen)
      ctx.drawLine(x, ny, x, ny + nheight)
      ctx.setPen(self.canvas.default_pen)
    else:
      ctx.drawLine(x, ny, x, ny + nheight)
      
  def draw_strings(self, ctx, x, y, width, height):
    string_x  = x
    string_x2 = x + width
    
    pen = ctx.pen()
    
    if (string_x < string_x2):
      i = 0
      for string in self.strings:
        string_y = self.string_id_2_y(i)
        i += 1
        if y - 2 < string_y < y + height + 2:
          pen = self.canvas.string_type_2_pen[string.width()]
          if pen:
            ctx.setPen(pen)
            ctx.drawLine(string_x , string_y, string_x2, string_y)
      ctx.setPen(self.canvas.default_pen)
    self.height = int(self.start_y + self.stem_extra_height + self.string_height * len(self.strings) + self.stem_extra_height_bottom + 1)
    
  def draw(self, ctx, x, y, width, height, drag = 0, draw_icon = 1):
    if x < self.canvas.start_x:
      width = width + x - self.canvas.start_x
      x = self.canvas.start_x
    if not drag:
      if self.show_header:
        ctx.drawText(0, self.y + self.canvas.default_ascent, self.partition.header.split("\n", 1)[0])
        self.start_y = 15 + self.canvas.default_line_height
      else:
        self.start_y = 15
        
    if Drawer.draw(self, ctx, x, y, width, height, drag):
      if not drag:
        self.draw_strings(ctx, max(self.canvas.start_x, self.canvas.contentsX()), y, self.canvas.contentsX() + self.canvas.visibleWidth(), height)
        
        bar_y      = self.y + self.start_y + self.stem_extra_height + self.string_height * 0.5
        bar_height = self.string_height * (len(self.strings) - 1)
        for mesure in self.partition.song.mesures:
          if x - 60 <= self.canvas.time_2_x(mesure.time) <= x + width:
            self.draw_mesure(ctx, mesure.time, mesure, bar_y, bar_height)
        self.draw_mesure(ctx, mesure.end_time(), None, bar_y, bar_height)
        
      time1  = self.canvas.x_2_time(x - self.canvas.start_x)
      time2  = self.canvas.x_2_time(x + width)
      
      mesure = self.canvas.song.mesure_at(time2)
      if mesure:
        if mesure.rythm2 == 8:
          time1 = (    time1 // 144) * 144
          time2 = (1 + time2 // 144) * 144
        else:
          time1 = (    time1 //  96) *  96
          time2 = (1 + time2 //  96) *  96
          
      notes = self.partition.notes_at(time1, time2)
      if self.canvas.cursor and (self.canvas.cursor.partition is self.partition):
        if time1 <= self.canvas.cursor <= time2:
          bisect.insort(notes, self.canvas.cursor)
          
      current_time = -1
      previous     = None
      notes_y = {}
      
      chords = []
      if drag:
        for note in notes:
          string_id = self.note_string_id(note)
          note_y = notes_y[note] = self.string_id_2_y(string_id)
          self.draw_note(ctx, note, string_id, note_y)
          
      else:
        for note in notes:
          if note.appoggiatura:
            string_id = self.note_string_id(note)
            note_y = notes_y[note] = self.string_id_2_y(string_id)
            #ctx.set_source_rgb(0.0, 0.0, 0.0)
            self.draw_note(ctx, note, string_id, note_y)
            self.draw_stem_and_beam_for_chord(ctx, [note], { note : note_y }, None, None, 1)
            continue
          
          string_id = self.note_string_id(note)
          note_y = notes_y[note] = self.string_id_2_y(string_id)
          
          if current_time != note.time:
            if (current_time != -1): chords.append(notes_at_time)
            current_time  = note.time
            notes_at_time = []
            
          notes_at_time.append(note)
          
          self.draw_note(ctx, note, string_id, note_y)
          
        if current_time != -1: chords.append(notes_at_time)
        
        for i in range(0, len(chords)):
          if   i == 0: previous = None
          elif len(chords[i - 1]) == 1: previous = chords[i - 1][0]
          else:
            min_duration = min([note.duration for note in chords[i - 1]])
            for previous in chords[i - 1]:
              if previous.duration == min_duration: break
          if   i == len(chords) - 1: next = None
          elif len(chords[i + 1]) == 1: next = chords[i + 1][0]
          else:
            min_duration = min([note.duration for note in chords[i + 1]])
            for next in chords[i + 1]:
              if next.duration == min_duration: break
          self.draw_stem_and_beam_for_chord(ctx, chords[i], notes_y, previous, next)

      
      if (not drag) and draw_icon:
        # Draws the partition icon
        draw_bitmap(self.canvas, ctx,
                    description(self.partition.__class__).icon_filename_for(self.partition),
                    4,
                    int(self.y + self.start_y + self.stem_extra_height + (self.height - self.start_y - self.stem_extra_height - self.stem_extra_height_bottom) // 2 - 48 // 2),
                    0.5)
        
  def draw_stem_and_beam_for_chord(self, ctx, notes, notes_y, previous, next, appo = 0):
    if len(notes) == 1:
      l = notes
    else:
      l = {}
      for note in notes: l[self.note_string_id(note)] = note
      l = l.items()
      l.sort()
      l = [note for (string_id, note) in l]
      
    x = self.canvas.time_2_x(l[0].time) + 0.5
    
    mesure = self.canvas.song.mesure_at(l[0].time)
    
    if appo:
      y = notes_y[l[ 0]] - self.string_height // 2
      self.draw_stem_and_beam(ctx, x, l[ 0], y - 14, y, mesure, previous, next)
    else:
      self.draw_stem_and_beam(ctx, x, l[ 0], self.y + self.start_y, notes_y[l[ 0]] - self.string_height // 2, mesure, previous, next)
    
  def note_color(self, ctx, note):
    if note.volume == 255:
      ctx.setPen(self.canvas.accentuated_pen)
    else:
      ctx.setPen(self.canvas.default_pen)
      
  def draw_note(self, ctx, note, string_id, y):
    if note.volume == 255: ctx.setPen(self.canvas.accentuated_pen)
    x = self.canvas.time_2_x(note.time)
    
    if note.appoggiatura: ctx.setFont(self.canvas.small_font)
    
    if note.is_dotted(): ctx.drawText(x + 3, y + (self.canvas.default_ascent - self.canvas.default_descent) // 2, self.note_text(note) + ".")
    else:                ctx.drawText(x + 3, y + (self.canvas.default_ascent - self.canvas.default_descent) // 2, self.note_text(note))
    if note.appoggiatura: ctx.setFont(self.canvas.default_font)
    
    if note.fx: getattr(self, "draw_note_fx_%s" % note.fx)(ctx, note, string_id)
    if note.volume == 255: ctx.setPen(self.canvas.default_pen)
    
  def draw_note_fx_link(self, ctx, note, string_id):
    ax1, ay1, ax2, ay2 = self.note_dimensions(note)
    if note.linked_to: bx1, by1, bx2, by2 = self.note_dimensions(note.linked_to)
    else:              bx1, by1, bx2, by2 = ax1 + note.duration, ay1, ax2 + note.duration, ay2
    ctx.drawLine(
      ax1 + 3 + self.canvas.char_h_size // 2, ay2 - self.canvas.default_descent,
      (ax1 + bx1) // 2 + 3 + self.canvas.char_h_size // 2, (ay2 + by2) // 2 + 3 - self.canvas.default_descent,
      )
    ctx.drawLine(
      (ax1 + bx1) // 2 + 3 + self.canvas.char_h_size // 2, (ay2 + by2) // 2 + 3 - self.canvas.default_descent,
      bx1 + 3 + self.canvas.char_h_size // 2, by2 - self.canvas.default_descent,
      )
    
  def draw_note_fx_slide(self, ctx, note, string_id):
    ax1, ay1, ax2, ay2 = self.note_dimensions(note)
    if note.linked_to: bx1, by1, bx2, by2 = self.note_dimensions(note.linked_to)
    else:              bx1, by1, bx2, by2 = ax1 + note.duration, ay1, ax2 + note.duration, ay2
    bx1 -= 3
    anb_char = len(self.note_text(note))
    yy= (by1 + by2) // 2 + self.canvas.default_descent
    ctx.drawLine(
      ax1 + 5 + self.canvas.char_h_size * anb_char, (ay1 + ay2) // 2 + self.canvas.default_descent,
      bx1 + 2, yy,
      )
    ctx.drawLine(bx1 + 2, yy, bx1 - 3, yy - 5)
    ctx.drawLine(bx1 + 2, yy, bx1 - 3, yy + 5)
    
  def draw_note_fx_bend(self, ctx, note, string_id):
    x1, y1, x2, y2 = self.note_dimensions(note)
    x2 -= 3
    anb_char = len(self.note_text(note))
    dx       = min(x2 - x1, self.canvas.char_h_size * 3) + x1 + 5 + self.canvas.char_h_size * anb_char
    yy       = (y1 + y2) // 2 + self.canvas.default_descent - self.string_height // 3
    
    ctx.drawLine(x1 + 5 + self.canvas.char_h_size * anb_char, (y1 + y2) // 2 + self.canvas.default_descent + self.string_height // 4,
                 dx, yy)
    ctx.drawLine(dx, yy,
                 dx - 5, yy - 1)
    ctx.drawLine(dx, yy,
                 dx - 1, yy + 5)
    ctx.setFont(self.canvas.small_font)
    ctx.drawText(x1 + dx * 0.1 + anb_char * self.canvas.char_h_size, (y1 + y2) // 2, locale.str(note.bend_pitch))
    ctx.setFont(self.canvas.default_font)
    
  def draw_note_fx_tremolo(self, ctx, note, string_id):
    x1, y1, x2, ty2 = self.note_dimensions(note)
    anb_char = len(self.note_text(note))
    tx = x1 + 5 + anb_char * self.canvas.char_h_size
    ty1 = (y1 + ty2) // 2
    while tx < x2 - 20:
      ctx.drawLine(
        tx     , ty1,
        tx + 10, ty1 - 5,
        )
      ctx.drawLine(
        tx + 10, ty1 - 5,
        tx + 20, ty1,
        )
      tx += 20
    
  def draw_note_fx_dead(self, ctx, note, string_id):
    x1, cy1, x2, cy2 = self.note_dimensions(note)
    anb_char = len(self.note_text(note))
    cx1 = x1 + 3
    cx2 = cx1 + anb_char * self.canvas.char_h_size
    ctx.drawLine(cx1, cy1, cx2, cy2)
    ctx.drawLine(cx2, cy1, cx1, cy2)
    
  def draw_note_fx_roll(self, ctx, note, string_id):
    notes = note.chord_notes()
    min_string_id = 99999
    if notes:
      for other in notes:
        other_string_id = self.note_string_id(other)
        if other_string_id >     string_id: return
        if other_string_id < min_string_id: min_string_id = other_string_id
    else:
      min_string_id = self.note_string_id(note) - 1
    x1, y1, x2, y2 = self.note_dimensions(note)
    anb_char = len(self.note_text(note))
    xx = x1 + 10 + anb_char * self.canvas.char_h_size
    yy = (y1 + y2) // 2 - (string_id - min_string_id) * self.string_height
    ctx.drawLine(xx, (y1 + y2) // 2,
                 xx, yy)
    ctx.drawLine(xx, yy,
                 xx - 5, yy + 5)
    ctx.drawLine(xx, yy,
                 xx + 5, yy + 5)
    
    
  def _nothing(self, ctx, x, y1, y2, note, previous, next): pass
  _round = _nothing
  
  def _white(self, ctx, x, y1, y2, note, previous, next):
    ctx.setPen(self.canvas.string_type_2_pen[5])
    ctx.drawLine(x, y2, x, y1)
    ctx.setPen(self.canvas.default_pen)
  
  def _black(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
  def _quarter_appo(self, ctx, x, y1, y2, note, previous, next):
    self._quarter(ctx, x, y1, y2, note, previous, next)
    ctx.drawLine(x - 4, y1 + 12, x + 7, y1)
    
  def _quarter(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    dt           = note.time % 96
    has_next     = next     and (next.time + next.duration <= note.time - dt + 96) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.time >= note.time - dt) and (previous.time + previous.duration >= note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 96:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      if previous.duration > 48 * 1.5:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x - (note.time - previous.time) * self.canvas.zoom, y1)
        ctx.setPen(self.canvas.default_pen)
      return
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1, x + 10, y1)
    ctx.setPen(self.canvas.default_pen)
    
  def _hquarter(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    dt   = note.time % 96
    sens = cmp(y2, y1)
    has_next     = next     and (next.time + next.duration <= note.time - dt + 96) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 96:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        if   (next.duration <= 24 * 1.5):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration * self.canvas.zoom, y1 + 4 * sens)
        elif not (has_previous and (previous.duration <= 24 * 1.5)):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      if (previous.duration > 24 * 1.5): # Else, previous will draw the stem
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1 + 4 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1           , x + 10, y1           )
    ctx.drawLine(x, y1 + 4 * sens, x + 10, y1 + 4 * sens)
    ctx.setPen(self.canvas.default_pen)
    
  def _hhquarter(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    dt   = note.time % 96
    sens = cmp(y2, y1)
    has_next     = next     and (next.time + next.duration <= note.time - dt + 96) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 96:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        if (next.duration <= 24 * 1.5):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration * self.canvas.zoom, y1 + 4 * sens)
        elif not (has_previous and (previous.duration <= 24 * 1.5)):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        if (next.duration <= 12 * 1.5):
          ctx.drawLine(x, y1 + 8 * sens, x + note.duration * self.canvas.zoom, y1 + 8 * sens)
        elif not (has_previous and (previous.duration <= 12 * 1.5)):
          ctx.drawLine(x, y1 + 8 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 8 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      ctx.setPen(self.canvas.bold_pen)
      if (previous.duration > 24 * 1.5): # Else, previous will draw the stem
        ctx.drawLine(x, y1 + 4 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
      if (previous.duration > 12 * 1.5): # Else, previous will draw the stem
        ctx.drawLine(x, y1 + 8 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 8 * sens)
      ctx.setPen(self.canvas.default_pen)
      return

    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1           , x + 10, y1           )
    ctx.drawLine(x, y1 + 4 * sens, x + 10, y1 + 4 * sens)
    ctx.drawLine(x, y1 + 8 * sens, x + 10, y1 + 8 * sens)
    ctx.setPen(self.canvas.default_pen)
    
  def _black_triplet(self, ctx, x, y1, y2, note, previous, next):
    sens = cmp(y2, y1)
    ctx.drawLine(x, y2, x, y1 + 3 * sens)
    
    dt = note.time % 192
    
    if dt == 64:
      ctx.setFont(self.canvas.small_font)
      if    y2 > y1: ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_descent // 2, "3")
      else:          ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_ascent  // 2, "3")
      ctx.setFont(self.canvas.default_font)
      
      ctx.drawLine(x - 64 * self.canvas.zoom, y1 + 0.5, x + 64 * self.canvas.zoom, y1 + 0.5)
      
  def _quarter_triplet(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    dt           = note.time % 96
    has_next     = next     and (next.time + next.duration <= note.time - dt + 96) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    
    if dt == 32:
      ctx.setFont(self.canvas.small_font)
      if    y2 > y1: ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_descent // 2, "3")
      else:          ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_ascent  // 2, "3")
      ctx.setFont(self.canvas.default_font)
      
    if has_next:
      if note.time + note.duration < note.time - dt + 96:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      return
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1, x + 10, y1)
    ctx.setPen(self.canvas.default_pen)
     
  def _hquarter_triplet(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
   
    if (note.time % 48) == 16:
      ctx.setFont(self.canvas.small_font)
      if    y2 > y1: ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_descent // 2, "3")
      else:          ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_ascent  // 2, "3")
      ctx.setFont(self.canvas.default_font)
      
    dt   = note.time % 96
    sens = cmp(y2, y1)
    has_next     = next     and (next.time + next.duration <= note.time - dt + 96) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 96:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        if   (next.duration <= 24 * 1.5):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration * self.canvas.zoom, y1 + 4 * sens)
        elif not (has_previous and (previous.duration <= 24 * 1.5)):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      if (previous.duration > 24 * 1.5): # Else, previous will draw the stem
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1 + 4 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1           , x + 10, y1           )
    ctx.drawLine(x, y1 + 4 * sens, x + 10, y1 + 4 * sens)
    ctx.setPen(self.canvas.default_pen)
    
  def _hhquarter_triplet(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    if (note.time % 24) == 8:
      ctx.setFont(self.canvas.small_font)
      if    y2 > y1: ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_descent // 2, "3")
      else:          ctx.drawText(x - self.canvas.char_h_size // 4, y1 - 1 - self.canvas.default_ascent  // 2, "3")
      ctx.setFont(self.canvas.default_font)
      
    dt   = note.time % 96
    sens = cmp(y2, y1)
    has_next     = next     and (next.time + next.duration <= note.time - dt + 96) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 96:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        if (next.duration <= 24 * 1.5):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration * self.canvas.zoom, y1 + 4 * sens)
        elif not (has_previous and (previous.duration <= 24 * 1.5)):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        if (next.duration <= 12 * 1.5):
          ctx.drawLine(x, y1 + 8 * sens, x + note.duration * self.canvas.zoom, y1 + 8 * sens)
        elif not (has_previous and (previous.duration <= 12 * 1.5)):
          ctx.drawLine(x, y1 + 8 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 8 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      ctx.setPen(self.canvas.bold_pen)
      if (previous.duration > 24 * 1.5): # Else, previous will draw the stem
        ctx.drawLine(x, y1 + 4 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
      if (previous.duration > 12 * 1.5): # Else, previous will draw the stem
        ctx.drawLine(x, y1 + 8 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 8 * sens)
      ctx.setPen(self.canvas.default_pen)
      return

    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1           , x + 10, y1           )
    ctx.drawLine(x, y1 + 4 * sens, x + 10, y1 + 4 * sens)
    ctx.drawLine(x, y1 + 8 * sens, x + 10, y1 + 8 * sens)
    ctx.setPen(self.canvas.default_pen)
  
  def _quarter8(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    dt           = note.time % 144
    has_next     = next     and (next    .duration <= 48 * 1.5) and (next.time + next.duration <= note.time - dt + 144) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.duration <= 48 * 1.5) and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 144:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      return
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1, x + 10, y1)
    ctx.setPen(self.canvas.default_pen)
    
  def _hquarter8(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    sens = cmp(y2, y1)
    dt           = note.time % 144
    has_next     = next     and (next    .duration <= 48 * 1.5) and (next.time + next.duration <= note.time - dt + 144) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.duration <= 48 * 1.5) and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 144:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        if (next.duration <= 24 * 1.5):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration * self.canvas.zoom, y1 + 4 * sens)
        elif not (has_previous and (previous.duration <= 24 * 1.5)):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        ctx.set_line_width(1)
      return
    if has_previous:
      if (previous.duration > 24 * 1.5): # Else, previous will draw the stem
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1 + 4 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1           , x + 10, y1           )
    ctx.drawLine(x, y1 + 4 * sens, x + 10, y1 + 4 * sens)
    ctx.setPen(self.canvas.default_pen)
  
  def _hhquarter8(self, ctx, x, y1, y2, note, previous, next):
    ctx.drawLine(x, y2, x, y1)
    
    sens = cmp(y2, y1)
    dt           = note.time % 144
    has_next     = next     and (next    .duration <= 48 * 1.5) and (next.time + next.duration <= note.time - dt + 144) and (note.time + note.duration == next.time)
    has_previous = previous and (previous.duration <= 48 * 1.5) and (previous.time >= note.time - dt) and (previous.time + previous.duration == note.time)
    if has_next:
      if note.time + note.duration < note.time - dt + 144:
        ctx.setPen(self.canvas.bold_pen)
        ctx.drawLine(x, y1, x + note.duration * self.canvas.zoom, y1)
        if (next.duration <= 24 * 1.5):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration * self.canvas.zoom, y1 + 4 * sens)
        elif not (has_previous and (previous.duration <= 24 * 1.5)):
          ctx.drawLine(x, y1 + 4 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
        if (next.duration <= 12 * 1.5):
          ctx.drawLine(x, y1 + 8 * sens, x + note.duration * self.canvas.zoom, y1 + 8 * sens)
        elif not (has_previous and (previous.duration <= 12 * 1.5)):
          ctx.drawLine(x, y1 + 8 * sens, x + note.duration // 2 * self.canvas.zoom, y1 + 8 * sens)
        ctx.setPen(self.canvas.default_pen)
      return
    if has_previous:
      ctx.setPen(self.canvas.bold_pen)
      if (previous.duration > 24 * 1.5): # Else, previous will draw the stem
        ctx.drawLine(x, y1 + 4 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 4 * sens)
      if (previous.duration > 12 * 1.5): # Else, previous will draw the stem
        ctx.drawLine(x, y1 + 8 * sens, x - note.duration // 2 * self.canvas.zoom, y1 + 8 * sens)
      ctx.setPen(self.canvas.default_pen)
      return
    
    # Single
    ctx.setPen(self.canvas.bold_pen)
    ctx.drawLine(x, y1           , x + 10, y1           )
    ctx.drawLine(x, y1 + 4 * sens, x + 10, y1 + 4 * sens)
    ctx.drawLine(x, y1 + 8 * sens, x + 10, y1 + 8 * sens)
    ctx.setPen(self.canvas.default_pen)

    
  _drawers = { # Maps durations to the corresponding drawer function
    576 : _round,
    288 : _white,
    144 : _black,
    72  : _quarter,
    36  : _hquarter,
    18  : _hhquarter,

    384 : _round,
    192 : _white,
    96  : _black,
    48  : _quarter,
    24  : _hquarter,
    12  : _hhquarter,

    64  : _black_triplet,
    32  : _quarter_triplet,
    16  : _hquarter_triplet,
    8   : _hhquarter_triplet,
    }
  _drawers8 = { # Maps durations to the corresponding drawer function for x/8 rythms
    576 : _round,
    288 : _white,
    144 : _black,
    72  : _quarter8,
    36  : _hquarter8,
    18  : _hhquarter8,
    
    384 : _round,
    192 : _white,
    96  : _black,
    48  : _quarter8,
    24  : _hquarter8,
    12  : _hhquarter8,
    
    # Does triplets make sense in 6/8 rythms ???
    64  : _black_triplet,
    32  : _quarter_triplet,
    16  : _hquarter_triplet,
    8   : _hhquarter_triplet,
    }
  
  def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous = None, next = None):
    if note.appoggiatura:               return self._quarter_appo(ctx, x, y1, y2, note, previous, next)
    if mesure and (mesure.rythm2 == 8): return (PartitionDrawer._drawers8.get(note.duration) or PartitionDrawer._nothing)(self, ctx, x, y1, y2, note, previous, next)
    else:                               return (PartitionDrawer._drawers .get(note.duration) or PartitionDrawer._nothing)(self, ctx, x, y1, y2, note, previous, next)
    
    
    
    
class TablatureDrawer(PartitionDrawer):
  def __init__(self, canvas, partition):
    PartitionDrawer.__init__(self, canvas, partition)
    self.height = 300
    self.strings = partition.view.strings
    self.string_height = canvas.default_line_height + 2
    self.stem_extra_height = self.string_height
    self.stem_extra_height_bottom = self.string_height * 0.6
    
  def note_string_id(self, note):
    if not hasattr(note, "string_id"):
      note.string_id = self.default_string_id(note.value)
    return min(note.string_id, len(self.strings) - 1)
  
  def note_text(self, note):
    if note is self.canvas.cursor: return "_"
    return self.strings[self.note_string_id(note)].value_2_text(note.value)
  
  def default_string_id(self, value):
    value = abs(value)
    for i, string in enumerate(self.strings):
      if string.base_note <= value: return i
    return len(self.strings) - 1
    
  def draw_note_fx_link(self, ctx, note, string_id):
    ax1, ay1, ax2, ay2 = self.note_dimensions(note)
    if note.linked_to: bx1, by1, bx2, by2 = self.note_dimensions(note.linked_to)
    else:              bx1, by1, bx2, by2 = ax1 + note.duration, ay1, ax2 + note.duration, ay2
    anb_char = len(self.note_text(note))
    ctx.drawLine(
      ax1 + 5 + self.canvas.char_h_size * anb_char, (ay1 + ay2) // 2 + self.canvas.default_descent,
      (ax1 + 5 + self.canvas.char_h_size * anb_char + bx1 + 2) // 2, (ay1 + ay2 + by1 + by2) // 4 + 3 + self.canvas.default_descent,
      )
    ctx.drawLine(
      (ax1 + 5 + self.canvas.char_h_size * anb_char + bx1 + 2) // 2, (ay1 + ay2 + by1 + by2) // 4 + 3 + self.canvas.default_descent,
      bx1 + 2                                     , (by1 + by2) // 2 + self.canvas.default_descent,
      )
    if note.linked_to:
      if   note.value < note.linked_to.value:
        ctx.drawText((ax1 + self.canvas.char_h_size * anb_char + bx1) // 2, (ay1 + ay2 + by1 + by2) // 4 + self.canvas.default_descent, "h")
      elif note.value > note.linked_to.value:
        ctx.drawText((ax1 + self.canvas.char_h_size * anb_char + bx1) // 2, (ay1 + ay2 + by1 + by2) // 4 + self.canvas.default_descent, "p")
    
  def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous_time = -32000, next_time = 32000):
    x = x + self.string_height // 2.5
    PartitionDrawer.draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure, previous_time, next_time)
 
  


class DrumsDrawer(PartitionDrawer):
  def __init__(self, canvas, partition):
    PartitionDrawer.__init__(self, canvas, partition)
    self.height = 300
    self.strings = partition.view.strings
    self.string_height = canvas.default_line_height + 2
    self.stem_extra_height = self.string_height
    
  def note_string_id(self, note):
    for i, string in enumerate(self.strings):
      if string.base_note == abs(note.value): return i
    return -1
  
  def note_text(self, note):
    if note is self.canvas.cursor: return "_"
    return "O"
  
  def default_string_id(self, value):
    value = abs(value)
    for i, string in enumerate(self.strings):
      if string.base_note == value: return i
    return -1
  
  def draw(self, ctx, x, y, width, height, drag = 0):
    PartitionDrawer.draw(self, ctx, x, y, width, height, drag)
    if not drag:
      ctx.setFont(self.canvas.small_font)
      ctx.setPen (self.canvas.string_type_2_pen[5])
      for i in range(len(self.strings)):
        ctx.drawText(self.canvas.start_x + 3, self.string_id_2_y(i), model.DRUM_PATCHES[self.strings[i].base_note])
        
      ctx.setFont(self.canvas.default_font)
      ctx.setPen (self.canvas.default_pen)
      
  def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous_time = -32000, next_time = 32000):
    x = x + self.string_height // 2.5
    PartitionDrawer.draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure, previous_time, next_time)






STAFF_STRING_PREVIOUS = { 0 : -1, 2 : 0, 4 : 2, 5 : 4, 7 : 5, 9 :  7, 11 :  9 }
STAFF_STRING_NEXT     = { 0 :  2, 2 : 4, 4 : 5, 5 : 7, 7 : 9, 9 : 11, 11 : 12 }

class StaffDrawer(PartitionDrawer):
  def __init__(self, canvas, partition, compact = 0):
    PartitionDrawer.__init__(self, canvas, partition)
    self.compact = compact
    if compact:
      self.string_height  = canvas.default_line_height // 7 + 2
    else:
      self.string_height  = canvas.default_line_height // 4 + 2
    self.stem_extra_height        = 15 + self.string_height
    self.stem_extra_height_bottom =  3 + self.string_height
    self.up_stem_ratio  = 1.0
    self.height         = 300
    self.strings        = []
    self.value_2_string = {}
    if getattr(partition, "g8", 0):
      string_values_and_line_styles = ((67, 0), (65, 2), (64, 0), (62, 2), (60, 0), (59, 2), (57, 0), (55, 2), (53, 0), (52, 2), (50, 0))
    else:
      string_values_and_line_styles = ((79, 0), (77, 2), (76, 0), (74, 2), (72, 0), (71, 2), (69, 0), (67, 2), (65, 0), (64, 2), (62, 0))
      
    alterations = model.TONALITIES[partition.tonality]
    if alterations:
      if alterations[0] == model.DIESES[0]: self.alteration_type =  1 # diese
      else:                                 self.alteration_type = -1 # bemol
    else:                                   self.alteration_type =  0
    self.alterations  = set(alterations)
    alterations2      = set(alterations)
    
    for note_value, line_style in string_values_and_line_styles:
      alteration      = 0
      draw_alteration = 0
      if (note_value % 12) in self.alterations:
        alteration = self.alteration_type
        if (note_value % 12) in alterations2:
          alterations2.remove(note_value % 12)
          draw_alteration = 1
      StaffString(self, note_value, line_style, alteration, draw_alteration)
    self._sort_strings()
    
    self.key_string = self.strings[7] # G string
    
  def draw_strings(self, ctx, x, y, width, height):
    if self.compact:
      draw_bitmap(self.canvas, ctx,
                  os.path.join(globdef.DATADIR, "clef_de_sol_ps.png"),
                  2,
                  int(self.string_id_2_y(self.strings.index(self.key_string)) - .5 * 112 * 0.62),
                  0.5)
    else:
      draw_bitmap(self.canvas, ctx,
                  os.path.join(globdef.DATADIR, "clef_de_sol.png"),
                  2,
                  int(self.string_id_2_y(self.strings.index(self.key_string)) - 85 * .62),
                  1.0)
    PartitionDrawer.draw_strings(self, ctx, 0, y, width + x, height)
    
    
  def draw(self, ctx, x, y, width, height, drag = 0):
    PartitionDrawer.draw(self, ctx, x, y, width, height, drag, 0)
    if not drag:
      y = self.y + self.start_y + self.stem_extra_height + self.canvas.default_line_height // 4 #+ self.string_height // 2
      
      for string in self.strings:
        y += self.string_height
        if string.draw_alteration:
          if string.alteration == -1:
            ctx.drawText(30 + self.canvas.char_h_size * 0.35 * model.BEMOLS.index(string.base_note % 12), y - self.canvas.default_line_height // 5, u"♭")
          else:
            ctx.drawText(30 + self.canvas.char_h_size * 0.35 * model.DIESES.index(string.base_note % 12), y, u"♯")
      
  def _sort_strings(self):
    self.strings.sort(lambda a, b: cmp(b.base_note, a.base_note))
    for i, string in enumerate(self.strings): string.id = i
    
  def get_string(self, note_value, create = 1):
    if note_value > self.strings[0].base_note:
      if create:
        new_string_value = STAFF_STRING_NEXT[self.strings[0].base_note % 12] + (self.strings[0].base_note // 12) * 12
        if self.strings[0].line_style == 0: new_string_line_style = 1
        else:                               new_string_line_style = 0
        if (new_string_value % 12) in self.alterations: alteration = self.alteration_type
        else:                                           alteration = 0
        string = StaffString(self, new_string_value, new_string_line_style, alteration, 0)
        self._sort_strings()
        self.canvas.render_all()
        return self.get_string(note_value, create)
      else:
        return None
      
    string = self.value_2_string.get(note_value)
    if string: return string
    #for string in self.strings:
    #  if note_value >= string.base_note: return string
      
    if create:
      new_string_value = STAFF_STRING_PREVIOUS[self.strings[-1].base_note % 12] + (self.strings[-1].base_note // 12) * 12
      if self.strings[-1].line_style == 0: new_string_line_style = 1
      else:                                new_string_line_style = 0
      if (new_string_value % 12) in self.alterations: alteration = self.alteration_type
      else:                                           alteration = 0
      string = StaffString(self, new_string_value, new_string_line_style, alteration, 0)
      self._sort_strings()
      self.canvas.render_all()
      return self.get_string(note_value, create)
    
  def note_string_id(self, note):
    unaltered_value, alteration = note.unaltered_value_and_alteration()
    return self.get_string(unaltered_value, 1).id
  
  def note_text(self, note):
    if note is self.canvas.cursor: return "-"
    return self.get_string(note.unaltered_value_and_alteration()[0]).value_2_text(note.value)
  
  def note_dimensions(self, note):
    string_id = self.note_string_id(note)
    x = self.canvas.time_2_x(note.time)
    y = self.string_id_2_y(string_id) - self.string_height
    return (
      x,
      y,
      x + note.duration * self.canvas.zoom,
      y + self.canvas.default_line_height // 2 + 4,
      )
  
  def note_previous(self, note):
    string_id = self.note_string_id(note)
    while 1:
      note = note.previous()
      if not note: return None
      if self.note_string_id(note) == string_id: return note
      
  def render_note(self, note):
    mesure = self.canvas.song.mesure_at(note)
    if mesure.rythm2 == 8: time1 = (note.time // 144) * 144; time2 = max(note.end_time(), time1 + 144)
    else:                  time1 = (note.time //  96) *  96; time2 = max(note.end_time(), time1 +  96)
    
    # Required for updating sharp / natural / flat symbol correctly
    if self.partition.notes:
      time2 = max(time2, self.partition.notes[-1].end_time())
      
    extra_width = 3 + self.canvas.char_h_size * 0.8 # for # and b
    self.canvas.render_pixel(
      self.canvas.time_2_x(time1) - extra_width,
      self.y + self.start_y - 1,
      (time2 - time1) * self.canvas.zoom + 2 + extra_width,
      self.height - self.start_y + 3,
      )
    
  def draw_note(self, ctx, note, string_id, y):
    x = self.canvas.time_2_x(note.time)
    if note.fx != "dead":
      if   note.value < 0:
        color = qt.QColor(150, 150, 150)
        ctx.setPen  (color)
        ctx.setBrush(color)
      elif note.volume == 255:
        ctx.setPen  (self.canvas.accentuated_pen)
        ctx.setBrush(self.canvas.accentuated_brush)
      else:
        ctx.setBrush(self.canvas.default_brush)
        
      if note.base_duration() in (192, 384): ctx.setBrush(self.canvas.none_brush)
      
      ctx.drawEllipse(x + 1, y - 4, 10, 9)
      
    alteration = abs(note.value) - self.strings[string_id].base_note
    
    previous = self.note_previous(note)
    if previous:
      previous_alteration = abs(previous.value) - self.strings[string_id].base_note
      if self.partition.song.mesure_at(previous) is not self.partition.song.mesure_at(note):
        if previous_alteration != self.strings[string_id].alteration:
          previous_alteration = -99 # force the drawing of the alteration mark
    else:
      previous_alteration = self.strings[string_id].alteration
      
    if alteration != previous_alteration:
      if   alteration == -2: ctx.drawText(x - self.canvas.char_h_size, y + self.string_height + 3, u"♭♭")
      elif alteration == -1: ctx.drawText(x - self.canvas.char_h_size, y + self.string_height + 3, u"♭")
      elif alteration ==  0: ctx.drawText(x - self.canvas.char_h_size, y + self.string_height + 3, u"♮")
      elif alteration ==  1: ctx.drawText(x - self.canvas.char_h_size, y + self.string_height + 3, u"♯")
      elif alteration ==  2: ctx.drawText(x - self.canvas.char_h_size, y + self.string_height + 3, u"♯♯")
      
    if note.is_dotted():
      ctx.drawEllipse(x + 12, y + self.string_height / 2 - 1, 3, 3)
      
    if note.fx: getattr(self, "draw_note_fx_%s" % note.fx)(ctx, note, string_id)
    if (note.value < 0) or (note.volume == 255):
      ctx.setPen  (self.canvas.default_pen)
      ctx.setBrush(self.canvas.default_brush)
      
  def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous_time = -32000, next_time = 32000):
    if y1 < y2:
      x  += 11
      y2 += 2
    else:
      x  += 1
      y2 -= 2
    PartitionDrawer.draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure, previous_time, next_time)
    

class StaffString(object):
  def __init__(self, drawer, base_note, line_style, alteration, draw_alteration):
    self.drawer              = drawer
    self.base_note           = base_note
    self.line_style          = line_style
    self.alteration          = alteration
    self.draw_alteration     = draw_alteration
    
    drawer.value_2_string[base_note] = self
    drawer.strings.append(self)
    
  def value_2_text(self, value): return u"o"
  def text_2_value(self, text):  return self.base_note + self.alteration
  
  def __unicode__(self): return _(self.__class__.__name__) % note_label(self.base_note)
  
  def width(self):
    if   self.line_style == 0: return -1
    elif self.line_style == 1: return 8
    return 9
  








import qttable
class LyricsTable(qttable.QTable):
  def activateNextCell(self):
    # XXX QTopia has no insert / remove row methods !
    old_text = self.drawer.lyrics.text
    lines = self.drawer.lyrics.text.split(u"\n")
    lines.insert(self.currentRow() + 1, u"")
    new_text = u"\n".join(lines)
    self.lyrics_changed(old_text, new_text)
    
  def keyPressEvent(self, event):
    qttable.QTable.keyPressEvent(self, event)
    event.accept()
    keyval = event.key()
    if (keyval == qt.QWidget.Key_Backspace) or (keyval == qt.QWidget.Key_Delete):
      if self.currentColumn() == 0:
        old_text = self.drawer.lyrics.text
        lines = self.drawer.lyrics.text.split(u"\n")
        del lines[self.currentRow()]
        new_text = u"\n".join(lines)
        self.lyrics_changed(old_text, new_text)
        
  def lyrics_changed(self, old_text, new_text):
    def do_it():
      self.drawer.lyrics.text = new_text
      self.drawer.update_text()
      self.drawer.canvas.render_all()

    def undo_it():
      self.drawer.lyrics.text = old_text
      self.drawer.update_text()
      self.drawer.canvas.render_all()
      
    UndoableOperation(do_it, undo_it, _(u"edit lyrics"), self.drawer.canvas.main.undo_stack)
    
    
class LyricsDrawer(Drawer):
  def __init__(self, canvas, lyrics):
    Drawer.__init__(self, canvas)
    
    self.last_undoable = None
    self.lyrics  = lyrics
    self.x0      = canvas.start_x + 2
    self.start_y = 0
    self.melody  = None
    self.timeout = None
    self.canvas.partition_2_drawer[lyrics] = self
    self.old_tx = -10000
    
    self.table = LyricsTable(1, 1, self.canvas.viewport())
    self.table.drawer = self
    self.canvas.addChild(self.table)
    self.table.horizontalHeader().hide()
    self.table.verticalHeader  ().hide()
    self.table.setLeftMargin(0)
    self.table.setTopMargin (0)
    self.table.setFrameStyle(qt.QFrame.NoFrame)
    self.table.setShowGrid(0)
    self.table.setVScrollBarMode(qt.QScrollView.AlwaysOff)
    self.table.setHScrollBarMode(qt.QScrollView.AlwaysOff)
    self.table.connect(self.table, qt.SIGNAL("valueChanged(int, int)"), self.on_text_changed)
    self.table.connect(self.table, qt.SIGNAL("currentChanged(int, int)"), self.on_cell_selected)
    
    self.update_melody(0)
    self.update_text()
    #self.table.show()
    
  def update_table_size(self):
    table_height = 0
    for i in range(self.table.numRows()): table_height += self.table.rowHeight(i)
    self.table.setFixedSize(self.canvas.visibleWidth(), table_height)
    
  def update_melody(self, show = 1):
    melody = self.lyrics.get_melody()
    if melody is not self.melody:
      if self.melody: unobserve(self.melody.notes, self.melody_note_listener)
      self.melody = melody
      if self.melody:   observe(self.melody.notes, self.melody_note_listener)
      
    self.table.hide()
    
    self.sizes = []
    if melody and melody.notes:
      previous_time = melody.notes[0].time
      i = 0
      x = 0
      for note in melody.notes[1:]:
        if note.appoggiatura: continue
        dx = ((note.time - previous_time) * self.canvas.zoom)
        x += dx
        self.sizes.append(dx)
        i += 1
        previous_time = note.time
      self.sizes.append(192)
      
      self.table.setNumCols(len(self.sizes))
      i = 0
      for size in self.sizes:
        self.table.setColumnWidth(i, size)
        i += 1
        
      self.update_table_size()
      
      x0 = self.canvas.start_x + 2 + int(melody.notes[0].time * self.canvas.zoom)
      if self.x0 != x0:
        self.x0 = x0
        self.canvas.moveChild(self.table, self.x0, self.y + self.start_y)
       
    if show: self.table.show()
    
  def update_text(self):
    #self.table.hide()
    
    nb_row = self.lyrics.text.count(u"\n") + 1
    if nb_row != self.table.numRows():
      self.table.setNumRows(nb_row)
      self.update_table_size()
      
      # QTopia's row does not have the same height !
      for i in range(1, nb_row):
        self.table.setRowHeight(i, self.table.rowHeight(0))
        
    j = -1
    for line in self.lyrics.text.split(u"\n"):
      j += 1
      i  = -1
      for syllabe in line.split(u"\t"):
        i += 1
        self.table.setText(j, i, syllabe)
      while i  < len(self.sizes) - 1:
        i += 1
        self.table.setText(j, i, u"")
        
    self.table.setUpdatesEnabled(1)
    self.table.show()
    #self.table.
    
  def partition_listener(self, partition, type, new, old): pass
  
  def on_cell_selected(self, row, col):
    self.canvas.ensureVisible(
      self.table.columnPos(col) + self.x0,
      self.table.rowPos   (row) + self.y + self.start_y,
      5, 5,
      )
    self.canvas.ensureVisible(
      self.table.columnPos(col) + self.x0 + self.sizes[col],
      self.table.rowPos   (row) + self.y + self.start_y + self.table.rowHeight(row),
      5, 5,
      )
    
  def on_text_changed(self, row, col):
    lines = []
    for row in range(self.table.numRows()):
      line = []
      for col in range(self.table.numCols()):
        line.append(unicode(self.table.text(row, col)))
      while line and (not line[-1]): del line[-1]
      lines.append(u"\t".join(line))
      
    old_text = self.lyrics.text
    new_text = u"\n".join(lines)
    
    def set_text(text):
      self.lyrics.text = text
      self.update_text()
      
    def do_it  (): set_text(new_text)
    def undo_it(): set_text(old_text)
    
    if self.last_undoable and self.canvas.main.undo_stack.undoables and (self.last_undoable is self.canvas.main.undo_stack.undoables[-1]):
      self.last_undoable.do_func = do_it
      do_it()
    else:
      self.last_undoable = UndoableOperation(do_it, undo_it, _(u"edit lyrics"), self.canvas.main.undo_stack)
      
  def on_text_key_press(self, text, event):
    if    (event.keyval == 32) and (event.state == 0):
      text.get_buffer().insert_at_cursor("\t")
      return 1
    elif ((event.keyval == 45) or (event.keyval == 65455)) and (event.state == 0):
      text.get_buffer().insert_at_cursor("-\t")
      return 1
    
  def melody_note_listener(self, obj, type, new, old):
    self.update_melody()
    
  def draw(self, ctx, x, y, width, height, drag = 0):
    self.start_y = 5 + self.canvas.default_line_height
    #self.canvas.moveChild(self.table, self.x0, self.y + self.start_y)
    
    tx = self.canvas.contentsX() - self.x0
    if tx != self.old_tx:
      self.old_tx = tx
      if tx < 0:
        self.canvas.moveChild(self.table, self.canvas.contentsX() - tx, self.y + self.start_y)
        self.table.setContentsPos(0, self.table.contentsY())
      elif tx + self.table.visibleWidth() > self.table.contentsWidth():
        self.canvas.moveChild(self.table, self.canvas.contentsX() - (tx + self.table.visibleWidth() - self.table.contentsWidth()), self.y + self.start_y)
        self.table.setContentsPos(self.table.contentsWidth() - self.table.visibleWidth(), self.table.contentsY())
      else:
        self.canvas.moveChild(self.table, self.canvas.contentsX(), self.y + self.start_y)
        self.table.setContentsPos(tx, self.table.contentsY())
      
    if Drawer.draw(self, ctx, x, y, width, height, drag):
      ctx.drawText(0, self.y + self.canvas.default_ascent, self.lyrics.header.split("\n", 1)[0])
      
      self.height = self.start_y + max(48, self.table.height() + 2)
      if x <= self.canvas.start_x:
        ctx.setPen(self.canvas.bold_pen)
        lx = self.canvas.start_x - self.canvas.x + 1
        ctx.drawLine(lx, self.y + self.start_y, lx, self.y + self.height)
        ctx.setPen(self.canvas.default_pen)
        
      # Draws the icon
      draw_bitmap(self.canvas, ctx,
                  description(self.lyrics.__class__).icon_filename_for(self.lyrics),
                  4,
                  int(self.y + self.start_y // 2 + self.height // 2 - 48 // 2),
                  0.5)

  def on_button_press(self, event, x, y, in_selection): pass
  
  def destroy(self):
    if self.melody: unobserve(self.melody.notes, self.melody_note_listener)
    
    if hasattr(self.table, "deleteLater"):
      self.table.deleteLater()
    else:
      self.table.hide()
      #self.canvas.viewport().removeChild(self.table)
    Drawer.destroy(self)





def draw_bitmap(canvas, ctx, bitmap_filename, x, y, scale = 1.0):
  if editobj2.GUI == "Qt":
    import editobj2.editor_qt as editor_qt
  else:
    import editobj2.editor_qtopia as editor_qt
    
  if scale < 1.0: pixmap = editor_qt.load_small_icon(bitmap_filename)
  else:           pixmap = editor_qt.load_big_icon  (bitmap_filename)
  ctx.drawPixmap(x, y, pixmap)
