# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Der Halsabschneider

    Notenhälse können mit diesem Skript in Ober- und Unterstimmen abgeschnitten oder verlängert werden. 
    Die Aenderung erfolgt in der aktuellen Notenzeile in der ganzen Partitur.

    Das Skript hat keine Auswirkung auf verbalkte Noten; wird eventuell in einer späteren Version realisiert.    
<<<
History:  05.12.03 - Erste Version
          22.12.03 - Undo aktualisiert
          18.01.04 - Bei Auswahl Oberstimme wurden ober- und Unterstimme beschnitten
          22.01.04 - Schlüssel berücksichtigt
          19.01.07 - Notenhalsende auf einer Linie
          13.11.08 - Absturz bei removeAttribute behoben
         
"""

import xml.dom
from xml.dom.minidom import NodeList

# default Einstellungen für Dialogg
stemLimit = 4
minStemLen = 2


notenString='CDEFGAB'
defaultStemLen = 3.5  # von Capella vorgegeben
actLayout = ''
oberStimme = True
clefOffset = 41       # default

def latin1(u):
    return u.encode('Latin-1')

def getCursor():
    sel = curSelection()
    result = (False, False, False, False)
    if sel == 0:
        messageBox('Fehler', 'keine aktive Partitur')
        return result
    result = sel[0]
    return result

def getMaxPitch(chord):
    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):
    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 calculateClefOffset(c):
    global clefOffset
    # 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 = clefOffset
    clefOffset = offset

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 getDialogValues():

    global stemLimit, minStemLen, oberStimme, adjustedLen
    edit1 = Edit(str(stemLimit), width = 8 )
    lab1 = Label('Schnittlinie:', width = 15 )
    lab11 = Label('  Zwischenräume ab Mittellinie')
    hbox1 = HBox([lab1, edit1, lab11], padding = 8)

    edit2 = Edit(str(minStemLen), width = 8 )
    lab2 = Label('Minimale Halslänge', width = 15 )
    lab21 = Label('  Zwischenräume')
    hbox2 = HBox([lab2, edit2, lab21], padding = 8)

    lab3 = Label('Hälse abschneiden in: ')
    rad3 = Radio([' Oberstimme ', ' Unterstimme'], value = 0, padding = 8)
    lab31 = Label(' ', width = 10)
    hbox3= HBox([lab3, rad3, lab31], text = 'Aktuelle Notenzeile: "' + latin1(actLayout) + '"', padding = 8)

    lab4 = Label(' ', padding = 8)
    lab5 = Label('Eingabe in 0.5er Zw. Schritten möglich', padding = 8)

    adjustedLen = CheckBox('Notenhalsende auf einer Linie', value=0)    
    
    vbox1 = VBox([hbox3, lab4, hbox1, hbox2, adjustedLen, lab4, lab5, lab4])
    
    dlg = Dialog('Der Halsabschneider: Eingabe', vbox1)

    if dlg.run():
        stemLimit = float(edit1.value())
        minStemLen = float(edit2.value())
        oberStimme = (rad3.value() == 0)
        adjustedLen = adjustedLen.value() == 1
        return True
    else:
        return False

    

def cutStem(score, voice):
    if voice.hasAttributes and (voice.getAttribute('stemDir') in ['up', 'down']):
        objList = getElementObjects(voice.getElementsByTagName('noteObjects')[0].childNodes)
        for obj in objList:
            if obj.tagName == 'clefSign':
                clef = obj.getAttribute('clef')
                calculateClefOffset(clef)
                continue
            if obj.tagName == 'chord':
                chord = obj
                maxV=getMaxPitch(chord)
                minV=getMinPitch(chord)

                # Berechnen des Notenkopfabstandes von der Mittellinie
                if voice.getAttribute('stemDir') == 'up':
                    halsBeginn = - maxV
                else: # stemDir = down
                    halsBeginn = minV
                        
                # Berechnen der Halslänge
                if halsBeginn > (stemLimit - minStemLen): 
                    halsLen = minStemLen
                elif adjustedLen:
                    halsLen = stemLimit - halsBeginn
                elif halsBeginn > (stemLimit - defaultStemLen):
                    halsLen = stemLimit - halsBeginn
                else:
                    halsLen = defaultStemLen    # default Len
                        
                stems = chord.getElementsByTagName('stem')
                if halsLen <> defaultStemLen:
                    if stems.length == 0:
                        stem = score.parentNode.createElement('stem')
                        chord.appendChild(stem)
                    else:
                        stem = stems[0]
                    stem.setAttribute('lengthening', str(halsLen-defaultStemLen))
                else:
                    if stems.length <> 0:
                        stems[0].setAttribute('lengthening', '0')
    
def changeDoc(score):
    global actLayout
    (sy, st, vo, ob) = getCursor()
    if (sy, st, vo, ob) == (False, False, False, False):
        return
    system = score.getElementsByTagName('system')[sy]
    staff = system.getElementsByTagName('staff')[st]
    actLayout = staff.getAttribute('layout')

    getDialogValues()
    
    for staff in score.getElementsByTagName('staff'):
        if staff.getAttribute('layout') == actLayout:
            for voice in staff.getElementsByTagName('voice'):
                if voice.hasAttributes():
                    if oberStimme and voice.getAttribute('stemDir') == 'up':
                        cutStem(score, voice)
                    elif not oberStimme and voice.getAttribute('stemDir') == 'down':
                        cutStem(score, voice)
                    else:
                        pass

# Hauptprogramm:

from caplib.capDOM import ScoreChange
import tempfile

class Halsabschneider(ScoreChange):
    def changeScore(self, score):
        changeDoc(score)

if activeScore():
    activeScore().registerUndo("Der Halsabschneider")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)
    
    Halsabschneider(tempInput, tempOutput)

    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)
