# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Bandoneon Ziffern

    Mit diesem Skript werden ein oder mehrere Ziffern untereinander über oder unter eine Note gestellt.
    Die Ziffern werden bei der Eingabe durch das Leerzeichen getrennt.
    Nach jedem Durchlauf wechselt das Skript zur nächsten Note/Pause bis zum Zeilenende oder Abbruch.||
    Einstellmöglichkeiten:|
    Schrift - Abstand von Kopf oder Mittellinie - Abstand der Textzeilen - Verankern an Kopf oder Mittellinie - Weiterschalten zur nächsten Note|
    Eingabe Sonderzeichen: Kreuz /+ ; Stern /*, Kreuznull /o, Dreieck /t, Druck /d, Zug /z

<<<

History:  21.02.2004 - Erste Ausgabe
          22.02.2004 - Ab 2ter Abfrage Fenster verkleinert
          03.11.2004 - Anker an Kopf erst beim zweiten Durchlauf wirksam
                     - Sonderzeichen (Vorabzug)
          10.11.2004 - Feste Taktstriche werden übersprungen
                     - Horizontale Position wählbar
          11.11.2004 - Sonderzeichen; Font capella3-Bandoneon muss installiert sein
          29.05.2010 - Anpassung capella7 - Sonderzeichen in utf-8
          20.08.2010 - Korrektur utf-8 capella7 Stepup02
                       Textübernahme aus Liedtext
          19.05.2012 - Fehler mit unicode in aktueller capella Version 7.1-8

"""
import new
from xml.dom.minidom import parseString, NodeList, Node, Element
from string import strip, replace


# Die Zeichen für die Eingabe der Sonderzeichen können angepasst werden

                  # Eingabe                                    # Height factor /16
                        # Zeichen                                   # pitchAndFamily
                                    # Font                               # Charset                            
                                                                             # dy / 32
specialChars1 ={'/+' : [chr(126),  'capella3-Bandoneon',       14, '2', '2', 0 ],       # Kreuz
                '/*' : [chr(125),  'capella3-Bandoneon',       14, '2', '2', 0 ],       # Stern
                '/o' : [chr(127),  'capella3-Bandoneon',       14, '2', '2', 0 ],       # KreuzNull
                '/t' : [chr(130),  'capella3-Bandoneon',       14, '2', '2', 0 ],       # Dreieck (Triangel)
                '/d' : [chr(129),  'capella3-Bandoneon',       14, '2', '2', 8 ],       # Druck
                '/z' : [chr(128),  'capella3-Bandoneon',       14, '2', '2', 8 ]        # Zug
                }
specialChars2 ={'/+' : [u'\u007E',  'capella3-Bandoneon',       14, '2', '2', 0 ],       # Kreuz
                '/*' : [u'\u007D',  'capella3-Bandoneon',       14, '2', '2', 0 ],       # Stern
                '/o' : [u'\u007F',  'capella3-Bandoneon',       14, '2', '2', 0 ],       # KreuzNull
                '/t' : [u'\x82',  'capella3-Bandoneon',       14, '2', '2', 0 ],       # Dreieck (Triangel)
                '/d' : [u'\x81',  'capella3-Bandoneon',       14, '2', '2', 8 ],       # Druck
                '/z' : [u'\x80',  'capella3-Bandoneon',       14, '2', '2', 8 ]        # Zug
                }



if capVersion() < (7, 0, 0):
    specialChars = specialChars1
else:
    specialChars = specialChars2
    
objectCounter = 0


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)


fonts = (('Arial',34)   # face, pitchAndFamily
         ,('Tahoma',0)
         ,('Times New Roman',None))

def latin1_e(u):
    return u.encode('Latin-1')
def latin1_d(u):
    return u.decode('Latin-1')


class settings:
    def __init__(self):
        self.face      = 2    # fonts[2]
        self.fontsize  = 8    #
        self.schnitt   = 0    # bold = 2 normal = 0
        self.y         = 4    # Abstand von der Mittellinie
        self.x         = 0    # horizontale Ausrichtung
        self.linedist  = 1.5  # Text Linienabstand
        self.vposdown  = False # unterhalb der Notenlinien
        self.text      = ''
        self.nextChord = True
        self.anchorHead = False # An Kopf verankern

defaults = settings()
dlgSet = settings()

options = ScriptOptions()
opt = options.get()


def getOptions():
    global dlgSet
    dlgSet.face     = eval(opt.get('face',str(dlgSet.face)))
    dlgSet.fontsize = eval(opt.get('fontsize',str(dlgSet.fontsize)))
    dlgSet.schnitt  = eval(opt.get('schnitt',str(dlgSet.schnitt)))
    dlgSet.linedist = eval(opt.get('linedist',str(dlgSet.linedist)))
    dlgSet.vposdown = eval(opt.get('vposdown', str(dlgSet.vposdown)))
    dlgSet.anchorHead = eval(opt.get('anchorHead', str(dlgSet.anchorHead)))
    dlgSet.y        = eval(opt.get('y',str(dlgSet.y)))
    dlgSet.x        = eval(opt.get('x',str(dlgSet.x)))
    

def setOptions():
    global dlgSet
    opt.update(dict(face     = str(dlgSet.face),
                    fontsize = str(dlgSet.fontsize),
                    schnitt  = str(dlgSet.schnitt),
                    linedist = str(dlgSet.linedist),
                    vposdown = str(dlgSet.vposdown),
                    anchorHead = str(dlgSet.anchorHead),
                    x        = str(dlgSet.x),
                    y        = str(dlgSet.y),
                    ))
    options.set(opt)

def scriptDialog():
    global dlgSet
    # **** Schrift ***
    labelLeerzeile=Label('', width = 20)

    cboxSchriften = ComboBox([fonts[i][0] for i in range(len(fonts))], value=dlgSet.face, width = 18)
    labelSchriften = Label('Schriften', width = 18)
    vboxSchriften = VBox([labelSchriften, labelLeerzeile, cboxSchriften])

    cboxSchriftschnitt = ComboBox(['normal',
                                 'kursiv',
                                 'fett',
                                 'fett und kursiv'], value = dlgSet.schnitt, width = 18)
    labelSchriftschnitt = Label('Schriftschnitt', width = 18)
    vboxSchriftschnitt  = VBox([labelSchriftschnitt, labelLeerzeile, cboxSchriftschnitt])

    editSchriftgrad   = Edit(str(dlgSet.fontsize))
    labelSchriftgrad  = Label('Schriftgrad', width = 18)
    vboxSchriftgrad   = VBox([labelSchriftgrad, labelLeerzeile, editSchriftgrad])

    hboxSchrift       = HBox([vboxSchriften, vboxSchriftschnitt, vboxSchriftgrad], text='Schrift')

    # **** Position ****

    vboxAbstand    = VBox([Label('Abst. von Mittellinie/Kopf'), Label('Linienabstand',width = 18), Label('Abstand horizontal')], padding = 8)

    editAbstand    = Edit(str(dlgSet.y))
    editLineDist   = Edit(str(dlgSet.linedist))
    posX           = Edit(str(dlgSet.x))
    vboxLineDist   = VBox([editAbstand, editLineDist, posX])

    vboxZw         = VBox([Label(' Zw', width = 9), Label(' Zw'), Label(' /4 Zw')], padding = 8)    

    cboxVPos       = CheckBox('    Unterhalb Notenlinien', value= dlgSet.vposdown)
    cboxAnchorHead = CheckBox('    An Kopf verankern', value = dlgSet.anchorHead)
    vboxVPos       = VBox([cboxVPos, cboxAnchorHead])

    hboxPosition   = HBox([vboxAbstand, vboxLineDist, vboxZw, vboxVPos], text = 'Position')

    # **** Inhalt, Action ****

    labelText      = Label('Inhalt', width = 10)
    editText       = Edit('' , width = 20)
    hboxText       = HBox([labelText, editText])

    labelTextHelp1 = Label('', width = 10)
    labelTextHelp2  = Label('Linien trennen durch Leerzeichen.')
    hboxTextHelp    = HBox([labelTextHelp1, labelTextHelp2])


    cboxAction     = ComboBox(['Text setzen', 'Felder zurücksetzen', 'Abbrechen','Aus Liedtext übernehmen', 'Liedtextzeile einfügen', 'Liedtextzeile löschen'], value = 0, width = 18)
    labelAction    = Label('Aktion    ', padding = 15 )

    cboxNextChord  = CheckBox('', value=True)
    labelNextChord = Label('   Nächste Note bearbeiten  ')
    hboxNextChord  = HBox([labelAction, cboxAction, labelNextChord, cboxNextChord])



    vbox = VBox([hboxText,
                 hboxTextHelp,
                 labelLeerzeile,
                 hboxSchrift,
                 hboxPosition,
                 labelLeerzeile,
                 hboxNextChord,
                 labelLeerzeile])
    dlg = Dialog('-- Bandoneon Ziffern --', vbox)
    if dlg.run():
        dlgSet.face      = cboxSchriften.value()
        dlgSet.fontsize  = eval(editSchriftgrad.value())
        dlgSet.schnitt   = cboxSchriftschnitt.value()
        dlgSet.x         = eval(posX.value())
        dlgSet.y         = eval(editAbstand.value())
        dlgSet.text      = editText.value()
        dlgSet.linedist  = eval(editLineDist.value())
        dlgSet.nextChord = cboxNextChord.value()
        dlgSet.vposdown  = cboxVPos.value()
        dlgSet.anchorHead = cboxAnchorHead.value()

        return cboxAction.value()
    else:
        return 2  # Abbrechen

def scriptDialog2():
    global dlgSet
    # **** Schrift ***

    labelLeerzeile=Label('', width = 22)
    editText       = Edit('' , width = 20)

    vbox = VBox([labelLeerzeile,editText],text='Inhalt -- Cursor +'+ str(objectCounter))
    
    dlg = Dialog('-- Bandoneon Ziffern --', vbox)
    
    if dlg.run():
        dlgSet.text      = editText.value()
        return 0  # weiter
    else:
        return 2  # Abbrechen



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

def getCursor():
    sel = curSelection()
    result = None
    if sel == 0:
        return result
    result = sel[0]
    return result

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 addText(chord, dx, dy, face, size, pitch, text, schnitt, linedist, down, anchorHead ):
    if strip(text) == '':
        return False
    texts = strip(text).split(' ')
    drawObjects = addElementNode(chord,'drawObjects')
    drawObj = addNewElementNode(drawObjects,'drawObj')
    group = addNewElementNode(drawObj,'group')

    count = 0
    for t in texts:
        specChar = specialChars.get(t, [])
        spec = specChar <> []
        if spec:
            specOff = float(specChar[5]) / 32
        else:
            specOff = 0
            
        drawObj = addNewElementNode(group,'drawObj')
        textOb = addNewElementNode(drawObj,'text')
        textOb.setAttribute('align','center')
        textOb.setAttribute('x',str(dx))
        if down:
            textOb.setAttribute('y',str(- dy - specOff + count*linedist))
        else:
            textOb.setAttribute('y',str(+ dy - specOff - (len(texts) - 1 - count)*linedist))

        fontOb = addNewElementNode(textOb,'font')
        if spec:
            fontOb.setAttribute('face',specChar[1])
            fontOb.setAttribute('height',str(size * specChar[2] / 10.0))
            fontOb.setAttribute('pitchAndFamily',specChar[3])
            fontOb.setAttribute('charSet',specChar[4])
        else:        
            fontOb.setAttribute('face',face)
            fontOb.setAttribute('height',str(size))
            if pitch<>None:
                fontOb.setAttribute('pitchAndFamily',str(pitch))
            if schnitt in [1,3]:  # italic
                fontOb.setAttribute('italic','true')
            if schnitt in [2,3]:  # bold
                fontOb.setAttribute('weight','700')

        content = addNewElementNode(textOb,'content')
        if spec:
            if capVersion() < (7, 0, 0):            
                textNode = doc.createTextNode(latin1_d(specChar[0]))
            else:
                textNode = doc.createTextNode(specChar[0])
        else:
            if capVersion() > (7, 1, 7):            
                textNode = doc.createTextNode(t)
            else:
                textNode = doc.createTextNode(unicode(t,'Latin-1'))
        content.appendChild(textNode)
        if anchorHead:
            basic = addNewElementNode(drawObj,'basic')
            basic.setAttribute('vertAlign','1')

        count += 1

    return True

def handleLyrics(score):
    
    def getText(node):
        for child in node.childNodes:
            if child.nodeType == child.TEXT_NODE:
                return latin1_e(child.data)
            
        return ''

    for system in score.getElementsByTagName('system'):
        for staff in system.getElementsByTagName('staff'):
            for voice in staff.getElementsByTagName('voice'):
                noteObjects = voice.gotoChild('noteObjects')
                obj = noteObjects.firstChild
                while obj:
                    if obj.nodeType == obj.ELEMENT_NODE:
                        for verse in obj.getElementsByTagName('verse'):
                            if verse.getAttribute('i') == '0':
                                text = getText(verse)
                                if text:
                                    text = text.replace('$',' ')
                                    text = text.replace('!',' ')
                                    dx = 0.75 + dlgSet.x /4.0
                                    dy = - dlgSet.y
                                    # drawObj Text einfuegen
                                    addText(obj, dx, dy,
                                            fonts[dlgSet.face][0],
                                            dlgSet.fontsize,
                                            fonts[dlgSet.face][1],
                                            text,
                                            dlgSet.schnitt,
                                            dlgSet.linedist,
                                            dlgSet.vposdown,
                                            dlgSet.anchorHead)
                    obj = obj.nextSibling



def shiftLyrics(score, down=True):

    if down:
        increment = -1
        toRemove = '-1'
    else:
        increment = 1
        toRemove = '9'
        
    for system in score.getElementsByTagName('system'):
        for staff in system.getElementsByTagName('staff'):
            for voice in staff.getElementsByTagName('voice'):
                noteObjects = voice.gotoChild('noteObjects')
                obj = noteObjects.firstChild
                while obj:
                    if obj.nodeType == obj.ELEMENT_NODE:
                        for lyric in obj.getElementsByTagName('lyric'):
                            for verse in obj.getElementsByTagName('verse'):
                                i = int(verse.getAttribute('i'))
                                verse.setAttribute('i',str(i + increment))
                            for verse in obj.getElementsByTagName('verse'):
                                if verse.getAttribute('i') == toRemove:
                                    verse.parentNode.removeChild(verse)
                            if not lyric.getElementsByTagName('verse'):
                                lyric.parentNode.removeChild(lyric)
                    obj = obj.nextSibling



def changeDoc(score):
    global fonts, dlgSet, objectCounter

    sel = getCursor()
    if sel == None:
        return False
    else:
        sy,st,vo,ob = sel
        system = score.getElementsByTagName('system')[sel[0]]
        staff = system.getElementsByTagName('staff')[sel[1]]
        voice = staff.getElementsByTagName('voice')[sel[2]]
        objList = getNoteObjects(voice)
        if objList.length <= sel[3] + cursorOffset:
            return False
        obj = objList[sel[3] + cursorOffset]
        if obj.tagName in  ['chord', 'rest']:

            action = 1  # mindestens ein Durchlauf
            while action == 1:
                getOptions()
                if objectCounter == 0:
                    action = scriptDialog()
                else:
                    action = scriptDialog2()
                if action == 1:
                    dlgSet = defaults
                setOptions()
            if action == 2:   # Abbruch
                return False
            if action == 3:   # Text aus Liedtext übernehmen
                handleLyrics(score)
                return False
            if action == 4:   # Liedtextzeile einfügen
                shiftLyrics(score, False)
                return False
            if action == 5:   # Liedtextzeile löschen
                shiftLyrics(score)
                return False
                

            dx = 0.75 + dlgSet.x /4.0
            dy = - dlgSet.y
            # drawObj Text einfuegen
            addText(obj, dx, dy,
                    fonts[dlgSet.face][0],
                    dlgSet.fontsize,
                    fonts[dlgSet.face][1],
                    dlgSet.text,
                    dlgSet.schnitt,
                    dlgSet.linedist,
                    dlgSet.vposdown,
                    dlgSet.anchorHead)
            objectCounter += 1

    return dlgSet.nextChord


# Hauptprogramm:

from caplib.capDOM import ScoreChange
import tempfile

class ScoreChange(ScoreChange):
    def changeScore(self, score):
        global doc, nextChord
        doc = score.parentNode
        nextChord = changeDoc(score)

if activeScore():
    activeScore().registerUndo("Buchstaben und Ziffern")

    cursorOffset = 0
    nextChord = True
    while nextChord:
        tempInput = tempfile.mktemp('.capx')
        tempOutput = tempfile.mktemp('.capx')
        activeScore().write(tempInput)

        ScoreChange(tempInput, tempOutput)

        activeScore().read(tempOutput)
        os.remove(tempInput)
        os.remove(tempOutput)

        cursorOffset += 1
