# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Fingersatz Editor

Mit diesem Skript wird der Fingersatz über den Liedtext eingegeben.||
Vorgehen:|
- Erste Strophe Liedtext "Freimachen" (Punkt 1)|
- Bestehender Fingersatz in Liedtext übernehmen (Punkt 2, sofern er mit diesem Skript erstellt wurde)|
- Liedtext editieren|
- Fingersatz aus dem Liedtext erstellen (Punkt 3)|
- Erste Strophe Liedtext entfernen (Punkt 4)||
Nachträglich können die so erstellten Fingersätze mit Punkt 5 neu formatiert werden.||
Im Liedtext kann der Fingersatz in der 1sten Liedstrophe einegegeben werden. Die Zahlen 1..5, R, L und S sind als Eingabe möglich.
R erzeugt den Fingersatz für die rechte Hand und wird oben notiert. L ist für die linke Hand und wird unten notiert. Ohne R und L gilt bei einem Bassschlüssel die linke Hand, sonst rechts.|
S erzeugt einen stummen Fingerwechsel im Format '1-2'.
<<<

Version: 1.01
Date:    23.12.2010

History:  18.12.2010 - Erste Testversion
          19.12.2010 - Fingersatz nur löschen wenn Liedtext nicht leer, löschen als Option
          20.12.2010 - Position beibehalten
                     - Format alle Fingersätze ändern (ohne Liedtext)
          21.12.2010 - Kosmetik
          23.12.2010 - Stummer Fingerwechsel mit 'S'

"""
german = ("de", {
    'dialogHeader'      :   ' Fingersatz Editor ',
    'regUndo'           :   'Fingersatz Editor',
    'actionSelect'      :   'Aktion auswählen',
    'radio_1'           :   '1. Neue Strophe ohne Liedtext einfügen',
    'radio_2'           :   '2. Fingersatz in Liedtext übernehmen',
    'radio_3'           :   '3. Fingersatz aus Liedtext erstellen',
    'radio_4'           :   '4. Liedtext aus Strophe 1 löschen',
    'radio_5'           :   '5. Format für Fingersätze ändern',
    'overwriteLyric'    :   'Liedtext überschreiben?',
    'deleteAtEmptyLyric':   'Fingersatz bei leerem Liedtext löschen?',
    'recalculateFingering': 'Fingersatz Position neu berechnen?',
    'options'           :   'Optionen                                                             zu Aktion',
    'parameter'         :   'Parameter ...',
    'warning'           :   ' Warnung ',
    'lyricNotEmpty'     :   'Liedtext 1. Strophe ist nicht leer!'
    
    
    } )

try:
    exec('from %s import translations' % ( translationModule() ))
    translations.append(german)
    setLanguages(translations)
except:
    def tr(s):
        return german[1].get(s, "???")
#-------------------------------------------------------------------
    
from xml.dom.minidom import parseString, NodeList, Node, Element
from caplib.rational import Rational
from caplib.capDOM import ScoreChange
import tempfile, string, new

scriptTagRight = '21793-40'
scriptTagLeft = '21793-41'
scriptTagSilentR = '21793-42'
scriptTagSilentL = '21793-43'

def latin1(u):
    return u.encode('Latin-1')

doc = parseString('<score/>')

# START SETTINGS ################

class settings:

    def __init__(self):
        self.overwriteLyric = False
        self.fontFace = 'Arial'
        self.fontItalic = False
        self.fontBold = False
        self.fontHeight = '9.0'
        self.fontFactorLineHeight = 1.0
        self.actionSelect = 2
        self.deleteAtEmptyLyric = False
        self.recalculateFingering = False

        self.getOptions()
        
     
    def getOptions(self):
        options = ScriptOptions() 
        opt = options.get()
        self.overwriteLyric         = eval(opt.get('Fingering_overwiriteLyric',    str(self.overwriteLyric)))
        self.fontFace               = opt.get('Fingering_fontFace',                self.fontFace)
        self.fontItalic             = eval(opt.get('Fingering_fontItalic',         str(self.fontItalic)))
        self.fontBold               = eval(opt.get('Fingering_fontBold',           str(self.fontBold)))
        self.fontHeight             = opt.get('Fingering_fontHeight',              self.fontHeight)
        self.fontFactorLineHeight   = eval(opt.get('Fingering_fontFactorLineHeight',   str(self.fontFactorLineHeight)))
        
        
    def setOptions(self):
        options = ScriptOptions() 
        opt = options.get()
        opt.update(dict(Fingering_overwiriteLyric       = str(self.overwriteLyric),
                        Fingering_fontFace              = str(self.fontFace),
                        Fingering_fontItalic            = str(self.fontItalic),
                        Fingering_fontBold              = str(self.fontBold),
                        Fingering_fontHeight            = str(self.fontHeight),
                        Fingering_fontFactorLineHeight  = str(self.fontFactorLineHeight),
                        ))
        options.set(opt)

dlgSet = settings()


# END SETTINGS ################


def gotoChild(self, name, new=False):
    newEl = None
    if new:
        pass
    else:
        for child in self.childNodes:
            if child.nodeType == child.ELEMENT_NODE and child.tagName == name:
                newEl = child
                break
    if newEl == None:
        newEl = doc.createElement(name)
        self.appendChild(newEl)
    return newEl
Node.gotoChild = new.instancemethod(gotoChild,None,Node)

def getElementObjects(objList):  # returns a List
    newList = NodeList()
    for n in range(objList.length):
        if objList[n].nodeType == objList[n].ELEMENT_NODE:
            newList.append(objList[n])
    return newList

fontDict= {}
def collectFonts(score):
# Diese Prozedur sucht nach allen in der Partitur vorhandenen Schriften
# und speichert diese in fontDict ab

    for font in score.getElementsByTagName('font'):
        face = latin1(font.getAttribute('face'))
        if face and ( 'capella' not in face) :
            fontDict[face] = face

    # Einige Standardschriften laden (kann bei Bedarf erweiter werden)
    fonts = ['Arial',
             'Tahoma',
             'Times New Roman']
    for face in fonts:
        fontDict[face] = face

# ----------------------------------------------------------------
def shiftLyrics(score,down=True):
    if down:
        increment = -1
        toRemove = '-1'
    else:
        increment = 1
        toRemove = '9'
        
    for system in score.getElementsByTagName('system'):
        for staff in system.getElementsByTagName('staff'):
            for voice in staff.getElementsByTagName('voice'):
                noteObjects = voice.gotoChild('noteObjects')
                obj = noteObjects.firstChild
                while obj:
                    if obj.nodeType == obj.ELEMENT_NODE:
                        for lyric in obj.getElementsByTagName('lyric'):
                            for verse in obj.getElementsByTagName('verse'):
                                i = int(verse.getAttribute('i'))
                                verse.setAttribute('i',str(i + increment))
                            for verse in obj.getElementsByTagName('verse'):
                                if verse.getAttribute('i') == toRemove:
                                    verse.parentNode.removeChild(verse)
                            if not lyric.getElementsByTagName('verse'):
                                lyric.parentNode.removeChild(lyric)
                    obj = obj.nextSibling


# END shiftLyrics()
# ----------------------------------------------------------------

def getText(node):
    for child in node.childNodes:
        if child.nodeType == child.TEXT_NODE:
            return child.data
    return ''

def setFingering(chord, fingering, lr, xn = '', yn = ''):
    hand = lr
    silent = False
    if 'r' in fingering or 'R' in fingering:
        hand = 'right'
    elif 'l' in fingering or 'L' in fingering:
        hand = 'left'
    if 's' in fingering or 'S' in fingering or '-' in fingering:
        silent = True
    fng = []
    for c in fingering:
        if c in '12345':
            fng.append(c)

    if not fng:
        return      # No digits found
    if silent and len(fng) < 2:
        fng.append('?')

    if silent and hand == 'right':
        x = 0.7
        dy = -1.80 / 9.0 * float(dlgSet.fontHeight) * dlgSet.fontFactorLineHeight 
        y = -0.50
        vertAlign = '4'
        align = 'center'
        tag = scriptTagSilentR
        fng = ['%s-%s' % (fng[0], fng[1])]
        if xn <> '' and not dlgSet.recalculateFingering:
            x = eval(xn)
            y = eval(yn)
        
    elif silent and hand == 'left':
        x = 0.7
        dy = -1.80 / 9.0 * float(dlgSet.fontHeight) * dlgSet.fontFactorLineHeight 
        y = 1.8 * float(dlgSet.fontHeight) / 9.0
        vertAlign = '5'
        align = 'center'
        tag = scriptTagSilentL
        fng = ['%s-%s' % (fng[0], fng[1])]
        if xn <> '' and not dlgSet.recalculateFingering:
            x = eval(xn)
            y = eval(yn) + y

    elif hand == 'right':
        x = 0.2
        dy = -1.80 / 9.0 * float(dlgSet.fontHeight) * dlgSet.fontFactorLineHeight 
        y = -0.50
        vertAlign = '4'
        fng.sort()
        fng.reverse()
        tag = scriptTagRight
        if xn <> '' and not dlgSet.recalculateFingering:
            x = eval(xn)
            y = eval(yn)
    else:
        x = 0.2
        dy = 1.80 / 9.0 * float(dlgSet.fontHeight) * dlgSet.fontFactorLineHeight
        y = 1.8 * float(dlgSet.fontHeight) / 9.0
        vertAlign = '5'
        fng.sort()
        tag = scriptTagLeft

        if xn <> '' and not dlgSet.recalculateFingering:
            x = eval(xn)
            y = eval(yn) + y
    
    drawObjects = chord.gotoChild('drawObjects')
    drawObj = drawObjects.gotoChild('drawObj', True)
    basic = drawObj.gotoChild('basic')
    basic.setAttribute('vertAlign',vertAlign)
    basic.setAttribute('tag', tag)
    group = drawObj.gotoChild('group')

    for c in fng:
        gDrawObj = group.gotoChild('drawObj', True)
        basic = gDrawObj.gotoChild('basic')
        basic.setAttribute('vertAlign',vertAlign)

        text = gDrawObj.gotoChild('text')
        text.setAttribute('x',str(x))
        text.setAttribute('y',str(y))
        font = text.gotoChild('font')
        font.setAttribute('face',dlgSet.fontFace)
        font.setAttribute('height',dlgSet.fontHeight)
        if dlgSet.fontItalic:
            font.setAttribute('italic','true')
        if dlgSet.fontBold:
            font.setAttribute('weight','700')
        content = text.gotoChild('content')
        textNode = doc.createTextNode(c)
        content.appendChild(textNode)
        
        if silent:
            text.setAttribute('align', align)
            break
        
        y += dy
        

def getFingering(chord):
    fingering = ''
    x = y = ''
    for drawObj in chord.getElementsByTagName('drawObj'):
        for basic in drawObj.getElementsByTagName('basic'):
            if basic.hasAttribute('tag'):
                tag = basic.getAttribute('tag')
                if tag in [scriptTagLeft, scriptTagRight, scriptTagSilentR, scriptTagSilentL]:
                    if tag == scriptTagLeft:
                        fingering += 'L'
                    elif tag == scriptTagRight:
                        fingering += 'R'
                    elif tag == scriptTagSilentL:
                        fingering += 'SL'
                    elif tag == scriptTagSilentR:
                        fingering += 'SR'
 

                    for text in drawObj.getElementsByTagName('text'):
                        content = text.gotoChild('content')
                        if content.firstChild:
                            fingering += content.firstChild.nodeValue
                        if x == '':
                            x = text.getAttribute('x')
                            y = text.getAttribute('y')
                            if 'L' in fingering:
                                font = text.gotoChild('font')
                                fontHeight = font.getAttribute('height')
                                dy = str(eval('1.8 / 9.0 * %s' % (fontHeight)))
                                y = str(eval('%s - %s' % (y, dy)))
                            
                    break
                            
    return fingering, x, y

def getLyricFingering(chord):
    fingering = ''
    for lyric in chord.getElementsByTagName('lyric'):
        for verse in lyric.getElementsByTagName('verse'):
            i = int(verse.getAttribute('i'))
            if i == 0:
                fingering = getText(verse)
                break
    return fingering

def setLyricFingering(chord, fingering):
    lyric = chord.gotoChild('lyric')
    for verse in lyric.getElementsByTagName('verse'):
        i = int(verse.getAttribute('i'))
        if i == 0:
            verse.parentNode.removeChild(verse)
            
    verse = lyric.gotoChild('verse', True)
    verse.setAttribute('i','0')
    textNode = doc.createTextNode(fingering)
    verse.appendChild(textNode)
    
def getHand(voice):
    hand = 'right'
    for clefSign in voice.getElementsByTagName('clefSign'):
        clef = clefSign.getAttribute('clef')
        if clef == 'bass' or clef[0] == 'F':
            hand = 'left'
        break
    return hand
                
    
def removeFingering(chord):
    for drawObj in chord.getElementsByTagName('drawObj'):
        for basic in drawObj.getElementsByTagName('basic'):
            if basic.hasAttribute('tag'):
                tag = basic.getAttribute('tag')
                if tag  in [scriptTagLeft, scriptTagRight, scriptTagSilentR, scriptTagSilentL]:
                    drawObj.parentNode.removeChild(drawObj)
                    return

def isLyricEmpty(score):
    for chord in score.getElementsByTagName('chord'):
        for lyric in chord.getElementsByTagName('lyric'):
            for verse in lyric.getElementsByTagName('verse'):
                i = int(verse.getAttribute('i'))
                if i == 0:
                    return False

    return True
        
# --------------- User Input ------------------------------------

handlingOk = True


def userInput():
    
    style = 0
    if dlgSet.fontBold:
        style += 1
    if dlgSet.fontItalic:
        style += 2

    fontList = fontDict.keys()
    fontList.sort()
    fontValue = 0
    if dlgSet.fontFace in fontList:
        fontValue = fontList.index(dlgSet.fontFace)

    uiActionSelect = Radio([tr('radio_1'),
                            tr('radio_2'),
                            tr('radio_3'),
                            tr('radio_4'),
                            tr('radio_5')], value = dlgSet.actionSelect)

    overwriteLyric = CheckBox('', value = dlgSet.overwriteLyric, width = 3)
    deleteAtEmptyLyric = CheckBox('', value = dlgSet.deleteAtEmptyLyric, width = 3 )
    recalculate = CheckBox('', value = dlgSet.recalculateFingering, width = 3 )
    fontSelect = ComboBox(fontList, value = fontValue, width = 30)
    fontSize = Edit(dlgSet.fontHeight, width = 6)
    fontStyle = ComboBox(['normal','fett','kursiv','fett & kursiv'], value = style, width = 10)
    fontPadding = Edit(str(dlgSet.fontFactorLineHeight * 100), width = 6)

    dlg = Dialog(tr('dialogHeader'),
                 VBox([
                     HBox([uiActionSelect, Label(' ', width = 8)], text = tr('actionSelect')),
                     VBox([ 
                     HBox([overwriteLyric, Label(tr('overwriteLyric'), width = 30 ), Label('2.', width = 8)]),
                     HBox([deleteAtEmptyLyric, Label(tr('deleteAtEmptyLyric'), width = 30 ), Label('3.')]),
                     HBox([recalculate, Label(tr('recalculateFingering'), width = 30), Label('3.+5.')]),
                     ], text = tr('options'), padding = 0),
                     VBox([
                     HBox([Label('Font:', width = 10), fontSelect]),
                     HBox([Label('Grösse:', width = 10), fontSize, Label('(Bsp. 8.5)     Style:', width = 12), fontStyle, Label('  ')]),
                     HBox([Label('Zeilenabstand:', width = 10), fontPadding, Label('%')])
                     ], text = tr('parameter'), padding = 6)
                     ])
                 )
    if dlg.run():
        dlgSet.actionSelect = uiActionSelect.value()
        dlgSet.overwriteLyric = overwriteLyric.value()
        dlgSet.deleteAtEmptyLyric = deleteAtEmptyLyric.value()
        dlgSet.recalculateFingering = recalculate.value()
        handlingOk = True
        dlgSet.fontFace = fontList[fontSelect.value()]
        fontStyle = fontStyle.value()
        if fontStyle == 0:
            dlgSet.fontItalic = fontItalic = False
            dlgSet.fontBold = fontBold = False
        elif fontStyle == 1:
            dlgSet.fontItalic = fontItalic = False
            dlgSet.fontBold = fontBold = True
        elif fontStyle == 2:
            dlgSet.fontItalic = fontItalic = True
            dlgSet.fontBold = fontBold = False
        elif fontStyle == 3:
            dlgSet.fontItalic = fontItalic = True
            dlgSet.fontBold = fontBold = True
        try:    
            dlgSet.fontHeight = fontSize.value()
            test = float(dlgSet.fontHeight)
        except:
            handlingOk = False
            messageBox(' Hinweis ', 'Bitte Fontgrösse richtig eingeben!')

        fontPadding = fontPadding.value()
        if fontPadding.isdigit():
            dlgSet.fontFactorLineHeight = eval(fontPadding) / 100.0
                        
        
    
    else:
        handlingOk = False

    if handlingOk:
        dlgSet.setOptions()


def changeDoc(score):
    collectFonts(score)
    userInput()
    if not handlingOk:
        return
    
    if dlgSet.actionSelect == 0:
        # Liedtext leere Strophe einfügen
        shiftLyrics(score,down = False)
    elif dlgSet.actionSelect == 1:
        # Text in Liedtext übernehmen
        if not dlgSet.overwriteLyric:
            if not isLyricEmpty(score):
                messageBox(tr('warning'),tr('lyricNotEmpty'))
                return
        for chord in score.getElementsByTagName('chord'):
            fingering, x, y = getFingering(chord)
            if fingering:
                setLyricFingering(chord, fingering)
        
    elif dlgSet.actionSelect == 2:
        # Text aus Liedtext übernehmen
        for voice in score.getElementsByTagName('voice'):
            hand = getHand(voice)
            for chord in voice.getElementsByTagName('chord'):
                if dlgSet.deleteAtEmptyLyric:
                    removeFingering(chord)
                fingering = getLyricFingering(chord)
                origFingering, x, y = getFingering(chord)                
                if fingering:
                    removeFingering(chord)
                    setFingering(chord, fingering, hand, x, y)
                
    elif dlgSet.actionSelect == 3:
        # Liedtext 1. Strophe löschen
        shiftLyrics(score, down = True)

    elif dlgSet.actionSelect == 4:
        # Fingersatz updaten
        for voice in score.getElementsByTagName('voice'):
            hand = getHand(voice)
            for chord in voice.getElementsByTagName('chord'):
                origFingering, x, y = getFingering(chord)
                if origFingering:
                    removeFingering(chord)
                    setFingering(chord, origFingering, hand, x, y)
                    
                
            
        
        

class ScoreChange(ScoreChange):
    def changeScore(self, score):
        changeDoc(score)
        
if activeScore() and handlingOk:

    activeScore().registerUndo( tr('regUndo') )
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)

    ScoreChange(tempInput, tempOutput)

    if handlingOk:
        activeScore().read(tempOutput)
        
    os.remove(tempInput)
    os.remove(tempOutput)

