# -*- coding: ISO-8859-1 -*-
""" capellaScript -- 04.12.2003 Paul Villiger
>>> Akkorde aufspalten

    In Akkorden geschriebene Noten werden auf einzelne Notenzeilen aufgeteilt.
    Die Anzahl der hinzugefügten Notenzeilen wird aus der maximalen Anzahl
    der Notenköpfe abgeleitet. Die oberste Note wird in die erste Notenzeile geschrieben.
    Fehlende Noten werden mit der untersten Note oder mit einer Pause aufgefüllt.|
|
    Bemerkunge: Die Partitur oder Einzelzeile darf nicht mehrstimmig sein.|
|
    Rückmeldungen bitte an mailto:villpaul@swissonline.ch
<<<

History: 13.11.03 - Verwendung der Klasse ScoreChange
         04.12.03 - Aufspalten nur in einer Notenzeile möglich

"""

def latin1(u):
    return u.encode('Latin-1')

def getAllLayouts(score):
    # Gibt eine Liste zurueck mit allen Eintraegen aus dem Mustersystem,
    # sowie deren Verwendungshaeufigkeit
    staffLayouts = score.getElementsByTagName('staffLayout')
    for i in range(staffLayouts.length):
        staffLayouts[i].count = 0
        for staff in score.getElementsByTagName('staff'):
            if staffLayouts[i].getAttribute('description') == staff.getAttribute('layout'):
                staffLayouts[i].count += 1
    return staffLayouts


def removeAttr(el, att):
    """Entfernt das Attribut, falls vorhanden
       Die minidom-Methode removeAttribute wirft entgegen der Dok. eine Ausname aus
    """
    if el.getAttribute(att):
        el.removeAttribute(att)

def getFirstChildElement(el, childTag):
    for n in el.childNodes:
        if n.nodeType == n.ELEMENT_NODE and n.tagName == childTag:
            return n
    return None

def findStaffLayout(staffLayouts, description):
    layout = None
    for l in staffLayouts:
        if l.getAttribute('description') == description:
            layout = l
    assert layout
    return layout

def removeHeads(score, el, nr):
    # entfernt alle Notenköpfe ausser nr aus einer Notenzeile
    # TODO Option: Fehlende Noten durch Pause ersetzen
    # nr = 0 entspricht dem höchsten Ton des Akkordes
    for chord in el.getElementsByTagName('chord'):
        heads = chord.getElementsByTagName('head')
        h = heads.length
        j = h
        for i in range(h):
            if i <> nr:
                if j > 1:
                    # ein Notenkopf muss übrig bleiben
                    heads[h-i-1].parentNode.removeChild(heads[h-i-1])
                    j = j - 1
                else: 
                    if setRest:
                        duration = chord.getElementsByTagName('duration')[0].cloneNode(1)
                        rest = score.parentNode.createElement('rest')
                        rest.appendChild(duration)
                        chord.parentNode.replaceChild(rest,chord)


def changeDoc(score):

    #----------------------------------------------------
    #----- Dialogaufbau ---------------------------------
    checkRest = CheckBox('Fehlende Noten durch Pausen ersetzen', value = 0 )
    checkSingleStaff = CheckBox('Nur diese Notenzeile aufspalten',value = 0 )
    
    allLayouts = getAllLayouts(score)
    layoutList = [ latin1(allLayouts[i].getAttribute('description')) +' (' + str(allLayouts[i].count) + ')' 
                   for i in range(allLayouts.length ) ]
    selLayout = ComboBox( layoutList, value = 0, width=32)

    hBox1 = HBox([checkSingleStaff, selLayout], padding = 8)
    
    boxRest   = VBox([hBox1, checkRest], padding=8)
    
    vBox   = VBox([boxRest], padding=8)
    
    dlg = Dialog('Akkorde aufspalten', vBox)
    
    if dlg.run():
        global setRest, singleStaff, selectedLayout
        setRest = checkRest.value() == 1
        singleStaff = checkSingleStaff.value() == 1
        selectedLayout = allLayouts[selLayout.value()].getAttribute('description')
    else:
        return
    #----- Dialogende-- ---------------------------------
    #----------------------------------------------------


    staffLayouts = score.getElementsByTagName('staffLayout')
    assert len(staffLayouts) > 0
    systems = getFirstChildElement(score, 'systems')
    assert systems

    # prüfen, dass nur einstimmige Notenzeilen vorhanden sind
    #--------------------------------------------------------
    for staff in systems.getElementsByTagName('staff'):
        if singleStaff and staff.getAttribute('layout') <> selectedLayout:
            pass
        else:
            if staff.getElementsByTagName('voice').length > 1:
                messageBox('Fehler: Akkorde aufspalten', 'Die Partitur enthält mehrstimmige Notenzeilen! \n\n' +
                                                         'Bitte zuerst Stimmen in Notenzeilen aufspalten.')
                return


    # 1. Schritt: Maximale Stimmenzahl der Notenzeilen zählen
    #--------------------------------------------------------
    for layout in staffLayouts:
        layout.setAttribute('maxHeads', '1');
    assert len(staffLayouts) > 0
    for system in systems.getElementsByTagName('system'):
        for staff in system.getElementsByTagName('staff'):
            if singleStaff and staff.getAttribute('layout') <> selectedLayout:
                pass
            else:
                layoutDescription = staff.getAttribute('layout')
                layout = findStaffLayout(staffLayouts, layoutDescription)
                for chord in staff.getElementsByTagName('chord'):
                    nHeads = chord.getElementsByTagName('head').length
                    if nHeads > int(layout.getAttribute('maxHeads')):
                        layout.setAttribute('maxHeads', str(nHeads))

    # 2. Schritt: Mustersystem entsprechend erweitern
    #             und temporäre Attribute 'maxVoices' entfernen
    #----------------------------------------------------------
    for layout in staffLayouts:
        descr = layout.getAttribute('description')
        if singleStaff and descr <> selectedLayout:
            pass
        else:
            maxHeads = int(layout.getAttribute('maxHeads'))
            removeAttr(layout, 'maxHeads')
            for i in range(maxHeads-1):
                clone = layout.cloneNode(1)
                clone.setAttribute('description', '%s_%d' % (descr, i+1))
                layout.parentNode.insertBefore(clone, layout)
            layout.setAttribute('description', '%s_%d' % (descr, maxHeads))

    # 3. Schritt: Stimmen in Notenzeilen umwandeln
    #--------------------------------------------------------
    for system in systems.getElementsByTagName('system'):
        for staff in system.getElementsByTagName('staff'):
            if singleStaff and staff.getAttribute('layout') <> selectedLayout:
                pass
            else:
                # maximale Anzahl Notenköpfe in Zeile bestimmen
                maxHeads = 1
                for chord in staff.getElementsByTagName('chord'):
                    nHeads = chord.getElementsByTagName('head').length
                    if nHeads > maxHeads:
                        maxHeads = nHeads
    
                layoutDescription = staff.getAttribute('layout')
                for i in range(maxHeads-1):
                    clone = staff.cloneNode(1)
                    removeHeads(score, clone, i)
                    clone.setAttribute('layout', '%s_%d' % (layoutDescription, i+1))
                    staff.parentNode.insertBefore(clone, staff)
                staff.setAttribute('layout', '%s_%d' % (layoutDescription, maxHeads))
                removeHeads(score, staff, maxHeads-1)



# Hauptprogramm:

from caplib.capDOM import ScoreChange
import tempfile

class newScoreChange(ScoreChange):
    def changeScore(self, score):
        changeDoc(score)

if activeScore():
    activeScore().registerUndo("Staff Editor")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)
    
    newScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)
