# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Schluessel Ueberbalken

    Mit diesem Skript können Schlüssel überbalkt werden. Dazu wird die aktuelle Stimme dupliziert
    und die echten durch grafische Schlüssel ersetzt. Die grafischen Schlüssel können manuell verschoben werden.
    Es wird nur die Stimme an der Cursorposition umgesetzt. Wird das Skript ein zweites mal in der gleichen oder
    der duplizierten Notenzeile aufgerufen, so wird wieder der alte Zustand hergestellt. Ist in der Notenzeile
    nur der Anfangsschlüssel vorhanden, so wird das Skript nicht ausgeführt.
    
    Rückmeldungen bitte an mailto:villpaul@swissonline.ch
<<<

    Bemerkungen:
        - Oktavierte Schlüssel werden berücksichtigt, aber nicht gezeichnet.
        - Die grafischen Objekte sind genau am Raster ausgerichtet und sollten nicht mit der Maus verschoben werden.
          Zum Positionieren mit der Maus oder TAB markieren und dann mit den Cursortasten schrittweise verschieben.
          Damit lassen sich die Objekte genau platzieren.
        - Nachträgliches Umbrechen von bearbeiteten Zeilen kann zu falschen Darstellungen führen. Vor dem Umbrechen
          ist die Zusatzstimme durch einen Skriptaufruf wieder zu entfernen.
        - Beim Rückgängigmachen werden zuvor unsichtbare Noten wieder sichtbar.

    Das Skript führt folgende Aktionen aus:
    - Aktuell Stimme duplizieren
    - In aktueller Stimme Noten und Pausen unsichtbar setzen
    - In aktueller Stimme Schlüssel weiss einfärben
    - In aktueller Stimme Notenlinien über den weissen Schlüssel legen
    - in kopierter Stimme alle Noten stumm schalten
    - in kopierter Stimme echte durch grafische Schlüssel ersetzen
    - in kopierter Stimme die vertikale Notenposition korrigieren

History:    05.09.2006 - Erste Ausgabe
            06.09.2006 - Rückgängig-Funktion implementiert
            16.09.2006 - Absturz wenn drawObjects nicht gelöscht werden

                

"""

from xml.dom.minidom import parseString, NodeList, Node, Element
from caplib.capDOM import ScoreChange
import tempfile, string, new

scriptTagVoice = '21793-10'
scriptTagClone = '21793-11'

doc = parseString('<score/>')

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

def clefToC5Line(clef):
    # Gibt die Linie für C5 aus; unterste Linie = 0
    # clef im Format G2[+/-]
    clef2Line = {'G':-6, 'C': -2, 'F': 2}
    line = clef2Line[ clef[0] ] + 2 * int( clef[1] )
    if len(clef) > 2:
        if clef[2] == '-':
            line += 7
        elif clef[2] == '+':
            line -= 7
    return line

# Wandelt die Schlüsselnamen in ein einheitliches Format um
def clefToLetter(clef):
    if clef == 'bass':
        clef = 'F4'
    elif clef == 'treble':
        clef = 'G2'
    elif clef == 'alto':
        clef = 'C3'
    elif clef == 'tenor':
        clef = 'C4'
    return clef

def addDiatonic(pitch, offset):
    dp = string.find('CDEFGAB', pitch[0]) + 7 * int(pitch[1])
    dp += offset
    dpNew = 'CDEFGAB'[dp % 7] + str(dp / 7)
    return dpNew

def duplicateVoice(voice, clefPos):
    # Notenzeile kopieren
    voice2 = voice.cloneNode(True)
    voice2 = voice.parentNode.appendChild(voice2)

    # Noten der Orginalstimme unsichtbar machen  
    for chord in voice.getElementsByTagName('chord'):
        display = chord.gotoChild('display')        
        display.setAttribute('invisible','true')

    # Pausen der Orginalstimme unsichtbar machen
    for rest in voice.getElementsByTagName('rest'):
        display = rest.gotoChild('display')        
        display.setAttribute('invisible','true')

    # Schlüssel der Orginalstimme weiss einfärben 
    count = 0
    for clefSign in voice.getElementsByTagName('clefSign'):
        if count > 0:
            clefSign.setAttribute('color','FFFFFF')
        count += 1

    # Notenlinien in der Orginalstimme nachziehen
    count = 0
    lastChord = None
    noteObjects = getElementObjects(voice.gotoChild('noteObjects').childNodes)
    for noteObject in noteObjects:
        if noteObject.tagName in ['chord','rest']:
            lastChord = noteObject
        elif noteObject.tagName == 'clefSign' and lastChord:
            drawObjects = lastChord.gotoChild('drawObjects')
            drawObj = drawObjects.gotoChild('drawObj', True)
            notelines = drawObj.gotoChild('notelines')
            notelines.setAttribute('x1',str(clefPos[count] - 2.0))
            notelines.setAttribute('x2',str(clefPos[count] + 2.0))
            notelines.setAttribute('y','0')
            # Set Scripttag for voice
            basic = drawObj.gotoChild('basic')
            basic.setAttribute('tag', scriptTagVoice)
            
            count += 1

    # Schlüssel in der Kopie behandeln
    count = 0
    lastChord = None
    noteObjects = getElementObjects(voice2.gotoChild('noteObjects').childNodes)
    for noteObject in noteObjects:
        # Schlüssel bestimmen und grafischen Schlüssel zeichnen
        if noteObject.tagName in ['chord','rest']:
            lastChord = noteObject
        elif noteObject.tagName == 'clefSign' and lastChord:
            # Schlüsseloffset bestimmen
            clef = noteObject.getAttribute('clef')
            clefText = 'B'
            offsetY = '1'
                
            clef = clefToLetter(clef)

            if clef[0] == 'G':
                clefText = 'B'
                offsetY = str(3 - int(clef[1]) )
            elif clef[0] == 'C':
                clefText = 'D'
                offsetY = str(3 - int(clef[1]) )
            elif clef[0] == 'F':
                clefText = 'F'
                offsetY = str(3 - int(clef[1]))

            # Grafischen Schlüssel zeichnen
            drawObjects = lastChord.gotoChild('drawObjects')
            drawObj = drawObjects.gotoChild('drawObj', True)
            text = drawObj.gotoChild('text')
            text.setAttribute('x', str(clefPos[count - 1] - 1.2))
            text.setAttribute('y', offsetY)
                
            font = text.gotoChild('font')
            font.setAttribute('face','capella3')
            font.setAttribute('height','18')
            font.setAttribute('charSet','2')
            font.setAttribute('pitchAndFamily','2')
    
            content = text.gotoChild('content')
            textNode = doc.createTextNode(clefText)
            content.appendChild(textNode)            

            # Set Scripttag for clone
            basic = drawObj.gotoChild('basic')
            basic.setAttribute('tag', scriptTagClone)

        # Schlüssel der Kopie entfernen und Notenpositionen "korrigieren"
        if noteObject.tagName == 'clefSign':
            clef = noteObject.getAttribute('clef')
            clef = clefToLetter(clef)
            if count == 0:
                baseClefLine = clefToC5Line(clef)
                actualClefLine = baseClefLine
                clefOffset = actualClefLine - baseClefLine
            elif count > 0:
                actualClefLine = clefToC5Line(clef)
                clefOffset = actualClefLine - baseClefLine
                noteObject.parentNode.removeChild(noteObject)
            count += 1
        elif noteObject.tagName == 'chord':
            for head in noteObject.getElementsByTagName('head'):
                pitch = head.getAttribute('pitch')
                pitch = addDiatonic(pitch, clefOffset)
                head.setAttribute('pitch', pitch)
                # Noten der Kopie lautlos stellen
                head.setAttribute('silent','true')

def removeDuplicateVoice(voice):
    voices = voice.parentNode
    for vo in voices.getElementsByTagName('voice'):
        for basic in vo.getElementsByTagName('basic'):
            if basic.hasAttribute('tag') and basic.getAttribute('tag') == scriptTagClone:
                vo.parentNode.removeChild(vo)
                break

    for vo in voices.getElementsByTagName('voice'):
        setVisible = False
        for basic in vo.getElementsByTagName('basic'):
            if basic.hasAttribute('tag') and basic.getAttribute('tag') == scriptTagVoice:
                # Notenzeilenobjekte entfernen
                basic.parentNode.parentNode.removeChild(basic.parentNode)
                setVisible = True

        if setVisible:
            # Noten der Orginalstimme sichtbar machen  
            for chord in vo.getElementsByTagName('chord'):
                display = chord.gotoChild('display')        
                display.setAttribute('invisible','false')

            # Pausen der Orginalstimme unsichtbar machen
            for rest in vo.getElementsByTagName('rest'):
                display = rest.gotoChild('display')        
                display.setAttribute('invisible','false')

            # Schlüssel der Orginalstimme schwarz einfärben 
            for clefSign in vo.getElementsByTagName('clefSign'):
                if clefSign.hasAttribute('color'):
                    clefSign.removeAttribute('color')

    # drawObjects müssen auch entfernt werden, sonst stürzt capella u.U. ab
    for drawObjects in vo.getElementsByTagName('drawObjects'):
        if not drawObjects.getElementsByTagName('drawObj'):
            drawObjects.parentNode.removeChild(drawObjects)

def getCursor():
    sel = curSelection()
    result = None
    if sel == 0:
        messageBox('Fehler', 'keine aktive Partitur')
        return result
    result = sel
    return result

def getClefPos(sy, st, vo):
    clefPos = []
    system = activeScore().system(sy)
    staff = system.staff(st)
    voice = staff.voice(vo)
    prevPos = 0
    for noteObj in voice.noteObjs():
        if noteObj.subType() == NoteObj.CHORD:
            prevPos = noteObj.posX()
        elif noteObj.subType() == NoteObj.REST:
            prevPos = noteObj.posX()
        elif  noteObj.subType() == NoteObj.CLEF:
            if prevPos > 0:
                clefPos.append(noteObj.posX() - prevPos)
    return clefPos
        

def changeDoc(score):
    sel = getCursor()
    if not sel:
        return
    (sy1, st1,vo1,ob1), (sy2,st2,vo2,ob2) = sel
    clefPos = getClefPos(sy1, st1,vo1)
    sy = st = vo = 0
    for system in score.getElementsByTagName('system'):
        if sy == sy1:
            for staff in system.getElementsByTagName('staff'):
                if st == st1:
                    for voice in staff.getElementsByTagName('voice'):
                        if vo == vo1:
                            # Wenn Notenzeile bereits duplicateVoice enthält, wird diese entfernt
                            for basic in voice.getElementsByTagName('basic'):
                                if basic.hasAttribute('tag') and basic.getAttribute('tag') in [scriptTagVoice, scriptTagClone]:
                                    removeDuplicateVoice(voice)
                                    return
                            # Notenzeile nur behandeln wenn mehr als ein Schlüssel vorhanden ist
                            if len(voice.getElementsByTagName('clefSign')) < 2:
                                return

                            clefPos = getClefPos(sy, st,vo)
                            duplicateVoice(voice, clefPos)
                            return
                        vo += 1
                st += 1
        sy +=1

class ScoreChange(ScoreChange):

    def changeScore(self, score):
        changeDoc(score)
        

if activeScore():

    activeScore().registerUndo("Schluessel Ueberbalken")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)

    ScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)


