# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Objekte angleichen

    - Alle Dynamikzeichen werden gleich positioniert|
    - Alle cres. und decresc werden waagrecht ausgerichtet
      und horizontal gleich positioniert|
    - Akkorde/Gitarrengriffe werden vertikal
      ausgerichtet und horizontal linksbündig zu den Noten gesetzt|
    - Akkorde/Gittarrengriffe über 1er Notenzeile möglich|
    - Die vertikale Position ist jeweils wählbar|
    - Wählbar ob Anpassung in der aktuellen Notenzeile oder der ganzenn
      Partitur erfolgen soll.
    - Ausgewählte Objekte können gelöscht werden
<<<

History: 24.10.03 - Auswahl nur einer Notenzeile moeglich 
         25.10.03 - Mehrere Dynamikzeichen an einer Note werden nebeneinander gesetzt
         05.11.03 - Umlaute im Mustersystem erlaubt
         10.11.03 - Akkorde auch unterhalb Notenlinien erlaubt
         13.11.03 - Verwendung der Klasse ScoreChange
         09.01.04 - Fehler: halbe Schritte waren nicht möglich
         21.01.04 - Absturz bei Staccatozeichen
         02.02.04 - Bei Dynamikzeichen werden auch Pausen berücksichtigt
         31.03.04 - Absturz bei Text ohne xy
         29.04.04 - Akkorde/Gitarrengriffe über 1er Notenzeile
         01.05.04 - "Über erster NotenZeile" Gruppenabstand berücksichtigt
         12.01.06 - Ausgewählte Objekte können gelöscht werden
         18.03.06 - Crescendo nur optional ausrichten
                  - Bereichswahl ganze Partitur oder aktuelles System
         20.09.06 - Crescendo Öffnung wählbar
         31.03.10 - Titel geändert
                    
"""

import xml.dom.minidom, codecs
from xml.dom.minidom import NodeList

def latin1(u):
    return u.encode('Latin-1')
def latin1_d(u):
    return u.decode('Latin-1')

def getCursor():
    sel = curSelection()
    result = (-1, -1, -1, -1)
    if sel == 0:
        messageBox('Fehler', 'keine aktive Partitur')
        return result
    #if sel[0] != sel[1]:
    #    messageBox('Fehler', 'Markierung ist nicht leer')
    #    return result
    result = sel[0]
    return result

def removeElement(element):
    # removes element and all parentNodes if they contain no more elementsNodes
    cn = element
    pn = cn.parentNode
    pn.removeChild(cn)
    delete = True
    for c in pn.childNodes:
        if c.nodeType == c.ELEMENT_NODE and c.tagName <> 'basic':
            delete = False
    while delete:
        cn = pn    
        pn = cn.parentNode
        pn.removeChild(cn)
        for c in pn.childNodes:
            if c.nodeType == c.ELEMENT_NODE and c.tagName <> 'basic':
                delete = False


def getNoteObjects(voice):  # returns a List
    noteObject = voice.getElementsByTagName('noteObjects')[0]
    objList = noteObject.childNodes
    newList = NodeList()
    for n in range(objList.length):
        if objList[n].nodeType == objList[n].ELEMENT_NODE:
            newList.append(objList[n])
    return newList

def changeDynamicPos(staff, yPos, deleteSelected):
    #
    # Alle Dynamikzeichen werden unter den Noten gleich ausgerichtet
    #
    for voice in staff.getElementsByTagName('voice'):
        noteObjects = getNoteObjects(voice)
        for noteObject in noteObjects:
            if noteObject.tagName in ['chord', 'rest']:
                count = 0
                for text in noteObject.getElementsByTagName('text'):
                    isDynamic = False
                    for font in text.getElementsByTagName('font'):
                        if font.getAttribute('face') == 'capella3':
                            for content in text.getElementsByTagName('content'):
                                # messageBox('content', str(content.firstChild.nodeValue) )
                                if latin1(content.firstChild.nodeValue) in ('f','g','h','p','q','r','j','i','s','|','z','{'):
                                    isDynamic = True

                    if isDynamic:                                            
                        if deleteSelected:
                            removeElement(text)
                        else:
                            text.setAttribute('x', str(-2 + 3 * count))
                            text.setAttribute('y', str(yPos))
                            count = count + 1
                        

def changeWedgePos(staff, yPos, wSpan, deleteSelected):
    #
    # Alle Crescendo und Decrescendo werden unter den Noten gerade ausgerichtet
    #
    for wedge in staff.getElementsByTagName('wedge'):
        if deleteSelected:
            removeElement(wedge)
        else:
            basics = wedge.parentNode.getElementsByTagName('basic')
            if basics.length > 0:
                # Crescendo haengt an zwei Noten
                wedge.setAttribute('y1', str(yPos))
                wedge.setAttribute('y2', str(yPos))
                if wedgeAdjust:
                    wedge.setAttribute('x1', '0')
                    wedge.setAttribute('x2', '1')
            else:
                # Crescendo haengt nur an einer Noten
                wedge.setAttribute('y1', str(yPos))
                wedge.setAttribute('y2', str(yPos))
                if wedgeAdjust:
                    wedge.setAttribute('x1', '-0.5')
                    wedge.setAttribute('x2', '3')
            if wSpan > 0:
                wedge.setAttribute('span', str(wSpan))
                

def changeTransposablePos(staff, yPos, extra, deleteSelected):
    #
    # Transponierbare Objekte vertikal und horizontal ausrichten
    #
    for transp in staff.getElementsByTagName('transposable'):
        if deleteSelected:
            removeElement(transp)
        else:
            for drawObj in transp.getElementsByTagName('drawObj'):
                if drawObj.getAttribute('base') <> '':
                    texts = drawObj.getElementsByTagName('text')
                    if len(texts) == 0:
                        maxY, minX = 0,0
                    else:                    
                        maxY = float(texts[0].getAttribute('y'))
                        minX = float(texts[0].getAttribute('x'))
                    for text in texts:
                        y = float(text.getAttribute('y'))
                        x = float(text.getAttribute('x'))
                        if y > maxY:
                            maxY = y
                        if x < minX:
                            minX = x
                    for text in texts:
                        y = float(text.getAttribute('y')) - maxY + yPos - extra
                        text.setAttribute('y', str(y))
                        x = float(text.getAttribute('x')) - minX
                        text.setAttribute('x', str(x))
    
def changeGuitarPos(staff, yPos, extra, deleteSelected):
    #
    # Guitar Objekte vertikal und horizontal ausrichten
    #
    for guitar in staff.getElementsByTagName('guitar'):
        if deleteSelected:
            removeElement(guitar)
        else:
            guitar.setAttribute('x', '0')
            guitar.setAttribute('y', str(yPos - extra))


def changeThisStaff(staff):
    if changeActStaff:
        if staff.tagName == 'staff':
            if staff.getAttribute('layout') == selectedStaffLayout:
                return True
        return False
    else:
        return True
        
def getSelectedStaffLayout(score):
    (sy, st, vo, ob) = getCursor()
    system = score.getElementsByTagName('system')[sy]
    staff = system.getElementsByTagName('staff')[st]
    staffLayout = staff.getAttribute('layout')
    return staffLayout


topDist = dict()
topDistSystem = dict()

def getDist(score):
    global topDist, topSystemDist
    for staffLayout in score.getElementsByTagName('staffLayout'):
        description = latin1(staffLayout.getAttribute('description'))
        distances = staffLayout.getElementsByTagName('distances')[0]
        top = eval(distances.getAttribute('top'))
        bottom = eval(distances.getAttribute('bottom'))
        if distances.hasAttribute('group'):
            bottom = bottom + eval(distances.getAttribute('group'))
        topDist[description] = (top, bottom)

def getDistSystem(system):
    global topDist, topSystemDist
    first = True
    for staff in system.getElementsByTagName('staff'):
        layout = latin1(staff.getAttribute('layout'))
        extraDistance = staff.getElementsByTagName('extraDistance')
        top,b =  topDist[layout]
        if extraDistance.length > 0 and extraDistance[0].hasAttribute('top'):
            top = top + eval(extraDistance[0].getAttribute('top'))

        if first:
            previous = 0
            first = False
        else:
            previous = previous + bottom + top

        topDistSystem[layout] = previous
        
        t,bottom =  topDist[layout]
        if extraDistance.length > 0 and extraDistance[0].hasAttribute('bottom'):
            bottom = bottom + eval(extraDistance[0].getAttribute('bottom'))
    
        
def changeDoc(score):
    global changeActStaff, selectedStaffLayout, wedgeAdjust
    (sy, st, vo, ob) = getCursor()
    if sy == -1:
        return

    selectedStaffLayout = getSelectedStaffLayout(score)

    #----------------------------------------------------
    #----- Dialog Optionen laden ------------------------
    options = ScriptOptions() 
    opt = options.get()

    #----------------------------------------------------
    #----- Dialogaufbau ---------------------------------
    checkAkk = CheckBox('Akkorde anpassen', value='0')
    akkY = ComboBox([str(i) for i in range(0,13)],
                      value=int(opt.get('yAkk', '4')))
    upAkk = CheckBox('oberhalb', value=int(opt.get('upAkk', '0')))
    hboxAkk  = HBox([Label('        &Vertikale Lage'), akkY,
                  Label('/2 Zw. unterhalb /'), upAkk, Label('Notenlinien        ')], padding=8)
    boxAkk   = VBox([checkAkk, hboxAkk], padding=8)
    
    
    checkWed = CheckBox('Crescendo/Decrescendo anpassen', value='0')
    wedY = ComboBox([str(i) for i in range(0,16)],
                      value=int(opt.get('yWed', '8')))
    upWed = CheckBox('oberhalb', value=int(opt.get('upWed', '0')))
    hboxWed  = HBox([Label('        &Vertikale Lage'), wedY,
                  Label('/2 Zw. unterhalb /'), upWed, Label('Notenlinien')], padding=8)
    wedgeAdjust = CheckBox('Horizontal ausrichten', value=int(opt.get('wedgeAdjust',0)))
    wedgeSpan   = Edit('', width = 10)
    boxWed   = VBox([checkWed,
                     hboxWed,
                     HBox([Label('            '),wedgeAdjust]),
                     HBox([Label('            Öffnung   '), wedgeSpan, Label(' / 32   (leer=keine Änderung)' )])
                     ], padding = 8 )
    
    
    checkDyn = CheckBox('Dynamikzeichen anpassen', value='0')
    dynY = ComboBox([str(i) for i in range(0,16)],
                      value=int(opt.get('yDyn', '8')))
    upDyn = CheckBox('oberhalb', value=int(opt.get('upDyn', '0')))
    hboxDyn  = HBox([Label('        &Vertikale Lage'), dynY,
                  Label('/2 Zw. unterhalb /'), upDyn, Label('Notenlinien')], padding=8)
    boxDyn   = VBox([checkDyn, hboxDyn], padding=8 )
    
    
    checkGit = CheckBox('Gitarrengriffe ausrichten', value='0')
    gitY = ComboBox([str(i) for i in range(6,23)],
                      value=int(opt.get('yGit', '12')))
    hboxGit  = HBox([Label('        &Vertikale Lage'), gitY,
                  Label('/2 Zw. über Notenlinien')], padding=8)
    boxGit   = VBox([checkGit, hboxGit], padding=8)
    

    #-----------------DialogBox check Score ------------------------
    checkSystem = CheckBox('Nur aktuelles System (sonst ganze Partitur)' + ' ' * 65 , value = 0)
    checkStaff  = CheckBox('Nur aktuelle Notenzeile "' + latin1(selectedStaffLayout)+'"', value = 0)
    

    #-----------------Akkorde/Gitarrengriffe über erste Notenlinie -----------------
    cboxOnTop = CheckBox('Akkorde/Gitarrengriffe über 1ste Notenzeile', value = 0)

    #-----------------Ausgewählte Objekte löschen                  -----------------
    deleteSelected = CheckBox('Ausgewählte Objekte löschen', value = 0)

    leer = Label('')    

    vBox   = VBox([VBox([boxDyn,
                         boxWed,
                         boxAkk,
                         boxGit,
                         HBox([Label('        '),
                               cboxOnTop]),
                         deleteSelected], text = 'Aktion', padding = 4),
                   VBox([checkSystem,
                         checkStaff],
                        text= 'Bereichseinschränkung', padding = 6)
                   ], padding = 8)
    
    dlg = Dialog('Skript Objekte angleichen', vBox)

    if dlg.run():
        changeActSystem = checkSystem.value() == 1
        changeActStaff  = checkStaff.value() == 1
        
        modGit = checkGit.value()
        yGit   = - gitY.value()/2.0 - 5
    
        modAkk = checkAkk.value()
        if upAkk.value():
            yAkk   = - akkY.value()/2.0 - 2
        else:
            yAkk   = + akkY.value()/2.0 + 2
    
        modWed = checkWed.value()
        if upWed.value():
            yWed   = - wedY.value()/2.0 - 2
        else:
            yWed   = + wedY.value()/2.0 + 2
        wedgeAdjust = wedgeAdjust.value()
        try: wSpan = float(wedgeSpan.value()) / 32.0
        except: wSpan = 0.0
        
        modDyn = checkDyn.value()
        if upDyn.value():
            yDyn   = - dynY.value()/2.0 - 2
        else:
            yDyn   = + dynY.value()/2.0 + 2

        onTop = cboxOnTop.value()            

        deleteSelected = deleteSelected.value() == 1

        getDist(score)

        sys = 0                    
        for system in score.getElementsByTagName('system'):
            if changeActSystem and sys <> sy:
                sys += 1
                continue
            getDistSystem(system)
            for staff in system.getElementsByTagName('staff'):
                if onTop:
                    layout = latin1(staff.getAttribute('layout'))
                    topDistance = topDistSystem[layout]
                else:
                    topDistance = 0
                if changeThisStaff(staff):
                    if modGit:
                        changeGuitarPos(staff, yGit,topDistance, deleteSelected)
                    if modAkk:
                        changeTransposablePos(staff, yAkk, topDistance, deleteSelected)
                    if modWed:
                        changeWedgePos(staff, yWed, wSpan, deleteSelected)
                    if modDyn:
                        changeDynamicPos(staff, yDyn, deleteSelected)

            sys += 1

        #----------------------------------------------------
        #----- Dialog Optionen speichern --------------------
        opt.update(dict(yAkk=akkY.value(), upAkk=upAkk.value(), yDyn=dynY.value(), yWed=wedY.value(), yGit=gitY.value(),
                        upWed=upWed.value(), upDyn=upDyn.value(), wedgeAdjust=wedgeAdjust ))
        options.set(opt)
    
        
# Hauptprogramm:

from caplib.capDOM import ScoreChange
import tempfile

class newScoreChange(ScoreChange):
    def changeScore(self, score):
        changeDoc(score)

if activeScore():
    activeScore().registerUndo("Objekte_angleichen")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)
    
    newScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)

