# -*- coding: ISO-8859-1 -*-
""" capellaScript -- 13.12.2005 Andreas Herzog
>>> Pausenmanager

    Das Skript fasst mehrere Pausen zusammen bzw. strukturiert Pausenaufteilungen um.|
    |
    Bedienung: Cursor in die jeweilige Zeile und Skript ausführen. Das Skript versucht dann die Pausen sinnvoll zusammen zu fassen.
    |
    Version 1.01   |
    
    |
    
   |

<<<

# Version 1.0: Ursprungsversion
          1.01 15.03.2008 Berücksichtigung von 3-fach Punktierung (Vlg)


"""


import xml.dom
import string, new


from xml.dom.minidom import NodeList, Node, Element



# doc = [] # parentNode von score

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 addNewElementNode(el,tagName):
    # add new Node with tagName "tagName" to el 
    global doc
    newChild = doc.createElement(tagName)
    el.appendChild(newChild)
    return newChild

options = ScriptOptions() 
opt = options.get()

class settings:
    def __init__(self):
        self.kleinNachGross = 0
        self.PunktierteNoten = 1
        self.NotenLaenge = 0


def getOptions():
    global kleinNachGross, PunktierteNoten,NotenLaenge
        
    kleinNachGross = eval(opt.get('kleinNachGross',str(kleinNachGross)))
    PunktierteNoten = eval(opt.get('PunktierteNoten',str(PunktierteNoten)))
    NotenLaenge = eval(opt.get('NotenLaenge',str(NotenLaenge)))
    

    
def setOptions():
    global dlgSet

    opt.update(dict(kleinNachGross= str(kleinNachGross),
                    PunktierteNoten = str(PunktierteNoten),		
                    NotenLaenge = str(NotenLaenge)
                   ))
                

    options.set(opt)
    
def Test(text):
    messageBox('Test',str(text))
            
         
def getCursor():
    sel = curSelection()
    result = None
    if sel == 0:
        messageBox('Fehler', 'keine aktive Partitur')
        return result
    result = sel[0]
    return result

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 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 getTime(chord):			
    objList = getElementObjects(chord.parentNode.parentNode.getElementsByTagName('noteObjects')[0].childNodes)
    time = 0
    i = 0
    while chord <> objList[i]:
        objects = objList[i]
        addTime = getLength(objects)
        time +=  addTime
        i += 1
    return time

def getIrregular(object):
    irregular = float(1)
    count = 0
    tripartite = 'false'
    for duration in object.getElementsByTagName('duration'):
        for tuplet in duration.getElementsByTagName('tuplet'):
            count = string.atoi(tuplet.getAttribute('count'))
            tripartite = tuplet.getAttribute('tripartite')
                
    if count == 2 and tripartite == 'true':
        irregular = float(3) /float (4)
    elif count == 3:
        irregular =  float(2) /float (3)
    elif count == 5:
        irregular = float(4) /float (5)
    elif count == 7:
        irregular = float(4) /float (7)
    else:
        irregular = 1

    return irregular
    

def getLength(object):
    length = 0
    if object.tagName in ['chord','rest']:
        for duration in object.getElementsByTagName('duration'):
            if duration.getAttribute('noDuration') <> 'true':
                if not duration.getAttribute('base') in ['1']:
                    length = 1 / string.atof(duration.getAttribute('base')[2:])

                else:
                    length = 1
                dotsMult = 1
                if duration.getAttribute('dots') == '1':
                    dotsMult = 1.5
                elif duration.getAttribute('dots') == '2':
                    dotsMult = 1.75
                elif duration.getAttribute('dots') == '3':
                    dotsMult = 1.875
                length = length * dotsMult
                length = length * getIrregular(object)

    return length

def getRegularLength(object):
    length = 0
    if object.tagName in ['chord','rest']:
        for duration in object.getElementsByTagName('duration'):
            if duration.getAttribute('noDuration') <> 'true':
                if not duration.getAttribute('base') in ['1']:
                    length = 1 / string.atof(duration.getAttribute('base')[2:])

                else:
                    length = 1
                dotsMult = 1
                if duration.getAttribute('dots') == '1':
                    dotsMult = 1.5
                if duration.getAttribute('dots') == '2':
                    dotsMult = 1.75					
                length = length * dotsMult

    return length
        
def fillUpWithRest(newObject, differenzNeu, tuplet):
    global PunktierteNoten, NotenLaenge, kleinNachGross
    
    objectBefore = newObject.nextSibling

    i = 1
    j=0
    while j < NotenLaenge:
        i=i*2
        j+=1
    
    while differenzNeu > 0 and i < 256:
        
        a = 0
        lengthFound = False
        #Test(str(i) + ',' + str((1/float(i))*1.50)+','+str(differenzNeu))
        if ((1/float(i))*1.75) <= differenzNeu and PunktierteNoten == 1:
             #Test(str(i) + ',' + str((1/i)*1.75)+','+str(differenzNeu))
             newRest = addNewElementNode(newObject.parentNode, 'rest')
             newRestDuration = addNewElementNode(newRest, 'duration')
             if tuplet <> None:
                newRestDuration.appendChild(tuplet)
             newObject.parentNode.insertBefore(newRest, objectBefore)
             newRestDuration.setAttribute('base', '1/'+str(i))
             newRestDuration.setAttribute('dots','2')
             a = (1/float(i))*1.75
             if kleinNachGross == 0:
                 objectBefore = newObject.nextSibling
             if kleinNachGross == 1:
                 objectBefore = newRest.nextSibling
             lengthFound = True
             
        else:
            if (1/float(i))*1.50 <= differenzNeu and PunktierteNoten == 1:
                newRest = addNewElementNode(newObject.parentNode, 'rest')
                newRestDuration = addNewElementNode(newRest, 'duration')
                if tuplet <> None:
                    newRestDuration.appendChild(tuplet)
                newObject.parentNode.insertBefore(newRest, objectBefore)
                newRestDuration.setAttribute('base', '1/'+str(i))
                newRestDuration.setAttribute('dots','1')
                a = (1/float(i))*1.50
                if kleinNachGross == 0:
                    objectBefore = newObject.nextSibling
                if kleinNachGross == 1:
                    objectBefore = newRest.nextSibling
                lengthFound = True
                
            else:
                if (1/float(i)) <= differenzNeu:
                    newRest = addNewElementNode(newObject.parentNode, 'rest')
                    newRestDuration = addNewElementNode(newRest, 'duration')
                    if tuplet <> None:
                        newRestDuration.appendChild(tuplet)
                    newObject.parentNode.insertBefore(newRest, objectBefore)
                    newRestDuration.setAttribute('base', '1/'+str(i))
                    newRestDuration.setAttribute('dots','')	
                    a = (1/float(i))
                    if kleinNachGross == 0:
                        objectBefore = newObject.nextSibling
                    if kleinNachGross == 1:
                        objectBefore = newRest.nextSibling
                    lengthFound = True
                    
        differenzNeu = differenzNeu - a
        if lengthFound == False:
            i*=2	


def changeVoice(voice):

    defaultTimeStr = voice.parentNode.parentNode.getAttribute('defaultTime')
    if defaultTimeStr == 'infinite':
        defaultTimeStr = '1000/1'	
    if defaultTimeStr == 'C':
        defaultTimeStr = '4/4'
    if defaultTimeStr == 'allaBreve':
        defaultTimeStr = '2/2'
            
    defaultTime = string.atof(defaultTimeStr[:defaultTimeStr.index('/')]) / string.atof(defaultTimeStr[defaultTimeStr.index('/')+1:])

    timeSigns = [[0,defaultTime]]
    
    
    for timeSign in voice.getElementsByTagName('timeSign'):
        explicitTimeStr = timeSign.getAttribute('time')
        if explicitTimeStr == 'infinite':
            explicitTimeStr = '1000/1'		
        if explicitTimeStr == 'C':
            explicitTimeStr = '4/4'
        if explicitTimeStr == 'allaBreve':
            explicitTimeStr = '2/2'
        
        explicitTime = string.atof(explicitTimeStr[:explicitTimeStr.index('/')]) / string.atof(explicitTimeStr[explicitTimeStr.index('/')+1:])
        timeSigns += [[getTime(timeSign),explicitTime]]

    rests = voice.getElementsByTagName('rest')
    
    for rest in rests:
        try:
            if rest.parentNode.tagName == 'noteObjects':
                completeLength = getRegularLength(rest)
                restIrregular = getIrregular(rest)
                irregularTuplet = None
                if restIrregular <> 1:
                    irregularTuplet = rest.getElementsByTagName('duration')[0].getElementsByTagName('tuplet')[0].cloneNode(True) # Hier später verändern ([0]) um Triolen etc wieder zu bearbeiten
                
                restsToDelete=[rest]
                restTime = getTime(rest)
                i=0
                while i < len (timeSigns):
                    if timeSigns[i][0] <= restTime:
                        timeSignVal = timeSigns[i][1]
                    i+=1

        
                obj1 = rest.nextSibling
                while obj1 in voice.getElementsByTagName('noteObjects')[0].childNodes:
                    try:
                        if obj1.tagName == 'rest':
                            
                            if getIrregular(obj1) == restIrregular:
                                
                                obj1Time = getTime(obj1)
            
                                if int(restTime / timeSignVal) == int(getTime(obj1) / timeSignVal):
                                    
                                    completeLength += getRegularLength(obj1)
                                    restsToDelete +=[obj1]
                            else:
                                break
        
                        elif obj1.tagName in ['chord','barline','timeSign','clefSign']:
                            break
                    except:
                        pass
                    obj1 = obj1.nextSibling

    
                fillUpWithRest(rest, completeLength, irregularTuplet)

        
        
                i=0
                while i < len(restsToDelete):
                    restsToDelete[i].parentNode.removeChild(restsToDelete[i])
                    i+=1	

        except:
            pass	

def getDialogValues():

    global Auswahl, PunktierteNoten, NotenLaenge, kleinNachGross,PunktierteNoten,NotenLaenge
    AuswahlRadio = Radio(['nur Zeile mit Cursor','Zeile mit Cursor in allen Systemen','alle Zeilen'],text='Bearbeiten:', value=0)
    PunktierteNotenCheck = CheckBox('Punktierte Noten verwenden', value=PunktierteNoten)
    NotenLaengeBox = ComboBox(['Ganze','Halbe','Viertel','Achtel','Sechszehntel'], value = NotenLaenge, width=10)
    NotenLaengeLabel = Label('maximale Pausenlänge:',width=5)
    SortierungRadio = Radio(['kleine Notenwerte zuerst','große Notenwerte zuerst'], text = 'Sortierung', value = kleinNachGross)
    hbox1 = HBox([AuswahlRadio], padding= 4)
    hbox2 = HBox([PunktierteNotenCheck], padding= 4)
    hbox3 = HBox([NotenLaengeLabel, NotenLaengeBox], padding= 4)
    
    vbox  = VBox([hbox1,hbox3,hbox2,SortierungRadio], text='', padding=8)
    dlg = Dialog('Pausen ersetzen: ', vbox)

    if dlg.run():
        Auswahl = AuswahlRadio.value()
        PunktierteNoten = PunktierteNotenCheck.value()
        NotenLaenge = NotenLaengeBox.value()
        kleinNachGross = SortierungRadio.value()

        return True
    else:
        return False
        

def changeDoc(score):
    global Auswahl, Richtung
    global kleinNachGross,PunktierteNoten,NotenLaenge
    
    kleinNachGross = 0
    PunktierteNoten = 1
    NotenLaenge = 0

    
    sel = getCursor()
    if sel == None:
        #
        return
    else:
        getOptions()
        if getDialogValues():
            setOptions()
            systemSel = score.getElementsByTagName('system')[sel[0]]
            staffSel = systemSel.getElementsByTagName('staff')[sel[1]]
            staffSelLayout = staffSel.getAttribute('layout')
            voiceSel = staffSel.getElementsByTagName('voice')[sel[2]]
            if Auswahl == 0:
                changeVoice(voiceSel)
            else:
                for staff in score.getElementsByTagName('staff'):
                    if staff.getAttribute('layout') == staffSelLayout or Auswahl == 2:
                        for voice in staff.getElementsByTagName('voice'):
                            changeVoice(voice)
        

        
# Hauptprogramm:

from caplib.capDOM import ScoreChange
import tempfile

class ScoreChange(ScoreChange):
    
    def changeScore(self, score):
       global doc
       doc = score.parentNode  
       changeDoc(score)
        
        

if activeScore():

    activeScore().registerUndo("Pausenmanager")
    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput, xml=1)
    ScoreChange(tempInput, tempOutput)
    activeScore().read(tempOutput)
    os.remove(tempInput)
    os.remove(tempOutput)

