# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c)Paul Villiger, Hartmut Lemmel
>>> Haltebogen Manager

    Haltebogen werden in Bindebogen umgewandelt oder umgekehrt, wahlweise
    in der gesamten Partitur oder im markierten Bereich bzw. an der Cursorposition. Auch
    Halte/Bindebogen am Zeilenende werden berücksichtigt.
    Bedingung zum Setzen von Haltebogen: Beide Akkorde müssen gleich sein und am
    ersten Akkord hängt ein Bindebogen mit der Länge 0 oder 1.

<<<
History:  01.05.2004 Erstausgabe
          07.05.2004 Absturz wenn eine Zeile keine Noten enthält
          27.6.2005, Hartmut Lemmel, 
              Umwandlung wahlweise nur im markierten Bereich bzw. an der
              Cursorposition ausführen.
              Einschränkung: Die Markierung z.B. eines Taktes in allen
                 Stimmen eines Systems wird nicht korrekt erkannt. Man
                 sollte also entweder nur Noten innerhalb einer Stimme
                 markieren oder ganze Zeilen bzw. Systeme (mit Alt+Mausklick).
          08.07.2006 wenn ganze Partitur und Umwandlung in Bindebogen,
                     dann werden alle Haltebogenfragmente gelöscht
 

"""

from caplib.capDOM import ScoreChange
import tempfile
from xml.dom.minidom import NodeList, Node, Element


def latin1_e(u):
    return u.encode('Latin-1')
def latin1_d(u):
    return u.decode('Latin-1')

def addElementNode(el,tagName):
    # add new Node to el if Node "tagName" does not exist
    # otherwise return the existing Node
    global doc
    childs = el.childNodes
    for n in range(childs.length):
        if childs[n].nodeType ==childs[n].ELEMENT_NODE and childs[n].tagName == tagName:
            return childs[n]
    newChild = doc.createElement(tagName)
    el.appendChild(newChild)
    return newChild

def calculateClefOffset(c, lastoffset):
    # Berechnet den Offset der Note, welche bei diesem Schluessel auf der Mittellinie steht
    # Schlüssel umwandeln
    if c == 'treble':
        newC = 'G2'
    elif c == 'bass':
        newC = 'F4'
    elif c == 'alto':
        newC = 'C3'
    elif c == 'tenor':
        newC = 'C4'
    else:
        newC = c
    
    # Schlüssel analysieren
    clef = newC[0]
    line = int(newC[1])
    if newC.find('-') > 0:
        okt = -7
    elif newC.find('+') > 0:
        okt = 7
    else:
        okt = 0
    # Offset berechnen
    offset = - 2 * line + okt
    if clef == 'G':  # G-Schlüssel
        offset += 45
    elif clef == 'F':
        offset += 37 # F-Schlüssel
    elif clef == 'C':
        offset += 41 # C-Schlüssel
    elif clef in  ['N','P']:
        offset += 47 # Schlagzeug & Kein Schlüssel
    else:
        offset = lastoffset
    return offset
notenString='CDEFGAB'
def getMaxPitch(chord,clefOffset):
    maxV = 0
    for head in chord.getElementsByTagName('head'):
        pitch = head.getAttribute('pitch')
        pitchN = notenString.find(pitch[0]) + 7 * long(pitch[1])
        if pitchN > maxV:
            maxV=pitchN
    maxV = - (maxV - clefOffset) / 2.0  # B5 auf Mittellinie
    return maxV       
def getMinPitch(chord,clefOffset):
    minV = 999
    for head in chord.getElementsByTagName('head'):
        pitch = head.getAttribute('pitch')
        pitchN = notenString.find(pitch[0]) + 7 * long(pitch[1])
        if pitchN < minV:
            minV=pitchN
    minV = - ((minV - clefOffset) / 2.0)  # B5 auf Mittellinie
    return minV
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
def getStemOrientation(voice,chord):
    clefOffset = 41       # default
    voiceUp = False
    voiceDown = False
    if voice.hasAttributes:
        stemDir = voice.getAttribute('stemDir')
        voiceUp =   stemDir == 'up'
        voiceDown =   stemDir == 'down'
    objList = getElementObjects(voice.getElementsByTagName('noteObjects')[0].childNodes)
    objNumber = 0
    for obj in objList:
        if obj==chord:
            break
        if obj.tagName == 'clefSign':
            clef = obj.getAttribute('clef')
            clefOffset=calculateClefOffset(clef,clefOffset)
    stemUp = False        
    stemDown = False        
    for stem in chord.getElementsByTagName('stem'):
        if stem.hasAttribute('dir'):
            stemUp = stem.getAttribute('dir') == 'up'
            stemDown = stem.getAttribute('dir') == 'down'
    maxV=getMaxPitch(chord,clefOffset)
    minV=getMinPitch(chord,clefOffset)
    # Halsrichtung bestimmen
    if stemUp:
        stemDir=1
    elif stemDown:
        stemDir=-1
    elif voiceUp:
        stemDir=1
    elif voiceDown:
        stemDir=-1
    elif (maxV + minV) > 0:
        stemDir=1
    else:
        stemDir=-1
#    text='stemUp='+str(stemUp)+' stemDown='+str(stemDown)
#    text+='\nvoiceUp='+str(voiceUp)+' voiceDown='+str(voiceDown)
#    text+='\nmaxV='+str(maxV)+' minV='+str(minV)
#    text+='\nstemDir='+str(stemDir)
#    messageBox('getStemOrientation',text)
    return stemDir


def addDrawObj(score,chord,obj,range,valign):   
                    basic = score.parentNode.createElement('basic')
                    if range!=0:
                        basic.setAttribute('noteRange', str(range))
                    if valign!=0:
                        basic.setAttribute('vertAlign', str(valign))
                    drawObj = score.parentNode.createElement('drawObj')
                    drawObj.appendChild(obj)
                    drawObj.appendChild(basic)
                    drawObjects = chord.getElementsByTagName('drawObjects')
                    if drawObjects.length == 0:
                        # noch kein drawObjects vorhanden
                        drawObjects = score.parentNode.createElement('drawObjects')
                        drawObjects.appendChild(drawObj)
                        chord.appendChild(drawObjects)
                    else:
                        # drawObjects ist bereits vorhanden
                        drawObjects[0].appendChild(drawObj)

def convertTie2Slur(score,voice,chord1,chord2,linebreak):
            # Der Beginn jedes Haltebogens wird durch ein Bindebogen ersetzt
            ties = chord1.getElementsByTagName('tie')
            if ties.length > 0:
                if ties[0].getAttribute('begin'):
                  sign = getStemOrientation(voice,chord1)  
#                    sign = 1 # Bogen unterhalb
#                    sign = -1 # Bogen oberhalb
                  if linebreak:
                    y1=0.9 # vertikalabstand vom Kopf
                    slur = score.parentNode.createElement('slur')
                    slur.setAttribute('x1', str((24+0 )/32.0))
                    slur.setAttribute('x2', str((24+10)/32.0))
                    slur.setAttribute('x3', str((24+52+32)/32.0))
                    slur.setAttribute('x4', str((24+68+32)/32.0))
                    slur.setAttribute('y1', str(sign * y1))
                    slur.setAttribute('y2', str(sign * (y1 + 24 /32.0)))
                    slur.setAttribute('y3', str(sign * (y1 + 32 /32.0)))
                    slur.setAttribute('y4', str(sign * (y1 + 22 /32.0)))
                    addDrawObj(score,chord1,slur,0,1)
                    slur = score.parentNode.createElement('slur')
                    slur.setAttribute('x1', str((12-0 )/32.0))
                    slur.setAttribute('x2', str((12-10)/32.0))
                    slur.setAttribute('x3', str((12-52)/32.0))
                    slur.setAttribute('x4', str((12-68)/32.0))
                    slur.setAttribute('y1', str(sign * y1))
                    slur.setAttribute('y2', str(sign * (y1 + 24 /32.0)))
                    slur.setAttribute('y3', str(sign * (y1 + 32 /32.0)))
                    slur.setAttribute('y4', str(sign * (y1 + 22 /32.0)))
                    addDrawObj(score,chord2,slur,0,1)                      
                  else:
                    y1=1 # vertikalabstand vom Kopf
                    slur = score.parentNode.createElement('slur')
                    slur.setAttribute('x1', '0.75')
                    slur.setAttribute('x2', '1.75')
                    slur.setAttribute('x3', '-0.75')
                    slur.setAttribute('x4', '0.25')
                    slur.setAttribute('y1', str(sign * y1))
                    slur.setAttribute('y2', str(sign * (y1 + 24 /32.0)))
                    slur.setAttribute('y3', str(sign * (y1 + 24 /32.0)))
                    slur.setAttribute('y4', str(sign * y1))
                    addDrawObj(score,chord1,slur,1,1)                
                # Haltebogenattribute entfernen                       
                for head in chord1.getElementsByTagName('head'):
                    for tie in head.getElementsByTagName('tie'):
                        tie.setAttribute('begin','false')
                        if scriptRange <> 0:
                            tie.setAttribute('end','false')
                            
                for head in chord2.getElementsByTagName('head'):
                    for tie in head.getElementsByTagName('tie'):
                        tie.setAttribute('end','false')

def convertSlur2Tie(score, chord1, chord2, linebreak):
  if chord1.tagName == 'chord' and chord2.tagName == 'chord':
    for drawObj in chord1.getElementsByTagName('drawObj'):
        slurs = drawObj.getElementsByTagName('slur')
        # ist Bindebogen vorhanden ?
        if (slurs.length > 0):
            basics = drawObj.getElementsByTagName('basic')
            # noteRange = 0 oder 1
            range=0
            if basics.length:
                rr=basics[0].getAttribute('noteRange')
                if rr!='':
                    range=int(rr)
            if range <= 1:
                headsA = chord1.getElementsByTagName('head')
                headsB = chord2.getElementsByTagName('head')
                hA = []
                hB = []
                for head in headsA:
                    pitch = head.getAttribute('pitch')
                    for alter in head.getElementsByTagName('alter'):
                        pitch = pitch + alter.getAttribute('step')
                    hA.append(pitch)
                for head in headsB:
                    pitch = head.getAttribute('pitch')
                    for alter in head.getElementsByTagName('alter'):
                        pitch = pitch + alter.getAttribute('step')
                    hB.append(pitch)
                if hA == hB:       # Akkorde vergleichen
                    # an jeden Notenkopf muss ein Haltebogen gehängt werden
                    for head in headsA:
                        tie = addElementNode(head, 'tie')
                        tie.setAttribute('begin', 'true')
                    for head in headsB:
                        tie = addElementNode(head, 'tie')
                        tie.setAttribute('end', 'true')

                    # Bindebogen entfernen                                            
                    old = drawObj.parentNode.removeChild(drawObj)
                    old.unlink()

                    if linebreak: # Zeilenumbruch -> evt. Bindebogen an zweiter Note entf.
                      for drawObj2 in chord2.getElementsByTagName('drawObj'):
                        slurs2 = drawObj2.getElementsByTagName('slur')
                        if (slurs2.length > 0):
                            basics2 = drawObj2.getElementsByTagName('basic')
                            range2=0
                            if basics.length:
                                rr=basics2[0].getAttribute('noteRange')
                                if rr!='':
                                    range2=int(rr)
                            if range2==0: # Bindebogen hat range 0
                                old = drawObj2.parentNode.removeChild(drawObj2)
                                old.unlink()
                                

class ScoreChange(ScoreChange):
    def changeScore(self, score):
        global scriptAction, scriptRange, doc
        doc = score.parentNode


        (sy1, st1, vo1, ob1),(sy2, st2, vo2, ob2) = (0,0,0,0),(999,999,999,999)  # ganze Partitur

        if scriptRange==0:
            sel = curSelection()
            if sel <> 0:
                (sy1,st1,vo1,ob1) = sel[0]
                (sy2,st2,vo2,ob2) = sel[1]
                if sel[0]==sel[1]:
                    if ob1>0:
                        ob1-=1
                else:
                    if sy2>sy1:  # mehrere Systeme markiert -> immer alle Zeilen
                        st1=0
                        st2=999
                    if st2>st1:  # mehrere Zeilen markiert -> immer alle Stimmen
                        vo1=0
                        vo2=999
                    if vo2>vo1:  # mehrere Stimmen markiert -> immer alle Objekte
                        ob1=0
                        ob2=999
                    

#                text='sy = '+str(sy1)+' bis '+str(sy2)+'\nst = '+str(st1)+' bis '+str(st2)+'\nvo = '+str(vo1)+' bis '+str(vo2)+'\nob = '+str(ob1)+' bis '+str(ob2)
#                messageBox('Markierung',text)
 
        sy = 0
        systems=score.getElementsByTagName('system')
        for system in systems:
            if sy1 <= sy <= sy2:
                st = 0
                for staff in system.getElementsByTagName('staff'):
                    if st1 <= st <= st2:
                        vo = 0
                        for voice in staff.getElementsByTagName('voice'):
                            if vo1 <= vo <= vo2:
                                # Liste markierter Noten und Pausen dieser Stimme erstellen
                                noteObjects=voice.getElementsByTagName('noteObjects')[0]
                                chords = NodeList()
                                ob = 0
                                for noteObj in noteObjects.childNodes:
                                  if noteObj.nodeType == noteObjects.ELEMENT_NODE:
                                    if ob>=ob1 and ob<=ob2:
                                        if noteObj.tagName in ['chord','rest']:
                                            chords.append(noteObj)
                                    ob+=1
                                # ersten Akkord der Folgezeile an die Notenliste dranhängen
#                                text='Markierung = '+str(ob1)+' bis '+str(ob2)+', max = '+str(ob)
#                                messageBox('markierung',text)

                                chordInNextStaff=0
                                if sy+1 <= sy2 or ob2>=ob:
                                  if sy+1 < systems.length:
                                    for staff2 in systems[sy+1].getElementsByTagName('staff'):
                                        if staff2.getAttribute('layout')==staff.getAttribute('layout'):
                                            voices2=staff2.getElementsByTagName('voice')
                                            if voices2.length > vo:
                                              for noteObj2 in voices2[vo].getElementsByTagName('noteObjects')[0].childNodes:
                                                if noteObj2.nodeType == noteObjects.ELEMENT_NODE:
                                                  if noteObj2.tagName in ['chord','rest']:
                                                    chords.append(noteObj2)
                                                    chordInNextStaff=noteObj2
                                                    break
                                            break
                                # alle aufeinanderfolgenden Paare der Notenliste durchgehen
                                first=1
                                for chord in chords:
                                    if first:
                                        first=0
                                    elif scriptAction == 0:   # Haltebogen in Bindebogen umwandeln
                                        convertTie2Slur(score,voice,lastchord,chord,chord==chordInNextStaff)
                                    elif scriptAction == 1:   # Bindebogen in Haltebogen umwandeln
                                        convertSlur2Tie(score,lastchord,chord,chord==chordInNextStaff)
                                    lastchord=chord
                            vo += 1
                    st += 1
            sy += 1

        if scriptAction == 0 and scriptRange <> 0:
            # wenn ganze Partitur und Umwandlung in Bindebogen, dann werden alle Haltebogenfragmente gelöscht
            for tie in score.getElementsByTagName('tie'):
                tie.setAttribute('end','false')
                tie.setAttribute('begin','false')


        
            
def scriptDialog():
    global scriptAction, scriptRange

    options = ScriptOptions() 
    opt = options.get()  

    lab = Label(' ')
    action = Radio(['Haltebogen -> Bindebogen'
                    ,'Bindebogen -> Haltebogen'
                    ], value=int(opt.get('tieManagerAction','0')), width=32 )
    selectRange = CheckBox('Ganze Partitur?' , value = int(opt.get('tieManagerRange','1')))
    
    dlg = Dialog('  -- Haltebogen Manager --  ', VBox([action,selectRange]))

    if dlg.run():
        scriptAction = action.value()
        scriptRange = selectRange.value()
        opt.update(dict(tieManagerAction=str(scriptAction), tieManagerRange=str(scriptRange)))
        options.set(opt)
        return True
    else:
        return False
        
# Hauptprogramm:

if activeScore():

    if scriptDialog():
    
        activeScore().registerUndo("Haltebogen Manager")
        tempInput = tempfile.mktemp('.capx')
        tempOutput = tempfile.mktemp('.capx')
        activeScore().write(tempInput)
    
        ScoreChange(tempInput, tempOutput)
    
        activeScore().read(tempOutput)
        os.remove(tempInput)
        os.remove(tempOutput)


