# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Shaped Notes

Dieses Skript erzeugt Notenköpfe im Format "Shaped Notes". Die bestehenden Notenköpfe werden entfernt und durch Einfachtexte ersetzt.|
Der Font "Shape Notes" muss installiert sein: http://www.paperlesshymnal.com/shapnote/shapnote.ttf|
- Akkorde werden unterstützt|
- Doppelbrevis können nicht ersetzt werden
    
<<<

History:  05.12.2010 - Erstausgabe
          07.12.2010 - remove shaped notes always
          17.01.2011 - correct shapes for half notes

"""
german = ("de", {
    'dialogHeader'      :   ' Shaped Notes ',
    'regUndo'           :   'Shaped Notes',
    'radio_11'          :   'Shaped Notes erzeugen',
    'radio_12'          :   'Shaped Notes entfernen',
    'radio_21'          :   'Four-shape System',
    'radio_22'          :   'Seven-shape System',
    'font-hint'         :   'Hinweis: Font »Shape Notes« muss installiert sein!'
    
    } )

try:
    exec('from %s import translations' % ( translationModule() ))
    translations.append(german)
    setLanguages(translations)
except:
    def tr(s):
        return german[1].get(s, "???")
    
#-------------------------------------------------------------------
    
from xml.dom.minidom import parseString, NodeList, Node, Element
from caplib.rational import Rational
from caplib.capDOM import ScoreChange
import tempfile, string, new

shapeFormat = 7

scriptTag = '21793-30'

#            do   re   mi   fa   so   la   ti       stem up
shape7u  = [['1', '2', '3', '4', '5', '6', '7'],    # Viertel
            ['!', '@', '#', '$', '%', '^', '&'],    # Halbe
            ['Q', 'W', 'E', 'R', 'T', 'Y', 'U'],    # Ganze
            ['Z', 'X', 'C', 'V', 'B', 'N', 'M'],    # Doppelganze
            ['A', 'S', 'D', 'F', 'G', 'H', 'J']     # Doppeldoppelganze ??
            ]

#            do   re   mi   fa   so   la   ti       stem down
shape7d  = [['1', '2', '3', '8', '5', '6', '7'],    # Viertel
            ['!', '@', '#', '*', '%', '^', '&'],    # Halbe
            ['Q', 'W', 'E', 'I', 'T', 'Y', 'U'],    # Ganze
            ['Z', 'X', 'C', '<', 'B', 'N', 'M'],    # Doppelganze
            ['A', 'S', 'D', 'K', 'G', 'H', 'J']     # Doppeldoppelganze ??
            ]

#           fa   so   la   fa   so   la   mi        stem up
shape4u = [['4', '5', '6', '4', '5', '6', '3'],    # Viertel
           ['$', '%', '^', '$', '%', '^', '#'],    # Halbe
           ['R', 'T', 'Y', 'R', 'T', 'Y', 'E'],    # Ganze
           ['V', 'B', 'N', 'V', 'B', 'N', 'C'],    # Doppelganze
           ['F', 'G', 'H', 'F', 'G', 'H', 'D']     # Doppeldoppelganze ??
           ]

#           fa   so   la   fa   so   la   mi        stem down    
shape4d = [['8', '5', '6', '8', '5', '6', '3'],    # Viertel
           ['*', '%', '^', '*', '%', '^', '#'],    # Halbe
           ['I', 'T', 'Y', 'I', 'T', 'Y', 'E'],    # Ganze
           ['<', 'B', 'N', '<', 'B', 'N', 'C'],    # Doppelganze
           ['K', 'G', 'H', 'K', 'G', 'H', 'D']     # Doppeldoppelganze ??
           ]


def latin1(u):
    return u.encode('Latin-1')

doc = parseString('<score/>')


def drawGraphicalHead(chord, pitch, shape, duration, verticalOffset = 0):
     drawObjects = chord.gotoChild('drawObjects')
     drawObj = drawObjects.gotoChild('drawObj', True)
     text = drawObj.gotoChild('text')
     text.setAttribute('x','0')
     text.setAttribute('y',str(verticalOffset))
     font = text.gotoChild('font')
     font.setAttribute('face','Shape Notes')
     font.setAttribute('height','18')
     font.setAttribute('pitchAndFamily','34')
     basic = drawObj.gotoChild('basic')
     basic.setAttribute('vertAlign','2')
     basic.setAttribute('tag', scriptTag)
     content = text.gotoChild('content')
     textNode = doc.createTextNode(shape[duration][pitch])
     content.appendChild(textNode)

def hideHeads(chord):
    for head in chord.getElementsByTagName('head'):
        head.setAttribute('shape','none')

def showHeads(chord):
    for head in chord.getElementsByTagName('head'):
        if head.hasAttribute('shape'):
            head.removeAttribute('shape')

def getDuration(chord):
    duration = chord.gotoChild('duration')
    base = duration.getAttribute('base')
    dur =  eval(base + '.0')
    if dur < 0.5:
        return 0
    if dur < 1:
        return 1
    if dur < 2:
        return 2
    else:
        return 3
        
def getStemDir(chord, voiceStemDir, clefWeight, shapeFormat):
    chordStemDir = 'auto'
    for stem in chord.getElementsByTagName('stem'):
        chordStemDir = stem.getAttribute('dir')
    if chordStemDir <> 'auto':
        stemDir = chordStemDir
    else:
        if voiceStemDir <> 'auto':
            stemDir = voiceStemDir
        else:
            hwMin = 999
            hwMax = 0
            for head in chord.getElementsByTagName('head'):
                pitch = head.getAttribute('pitch')
                headWeight = ( ord(pitch[0]) - ord('C') ) % 7 + 7 * eval(pitch[1])
                hwMin = min(headWeight, hwMin)
                hwMax = max(headWeight, hwMax)

            stemDir = 'up'
            if (hwMin + hwMax)/2.0 >= clefWeight:
                stemDir = 'down'

    if shapeFormat == 0:        # Four-shape format
        if stemDir == 'up':
            return 'up', shape4u
        else:
            return 'down', shape4d
    else:                       # Seven-shape format
        if stemDir == 'up':
            return 'up', shape7u
        else:
            return 'down', shape7d

def getClefWeight(clefSign):
    clef = clefSign.getAttribute('clef')
    if clef == 'treble':
        clef = 'G2'
    elif clef == 'bass':
        clef = 'F4'
    elif clef == 'alto':
        clef = 'C3'
    elif clef == 'tenor':
        clef = 'C4'

    if clef[0] == 'G':
        clefWeight = (ord('D') - ord('C')) % 7 + 7 * 6
    elif clef[0] == 'F':
        clefWeight = (ord('C') - ord('C')) % 7 + 7 * 5
    elif clef[0] == 'C':
        clefWeight = (ord('G') - ord('C')) % 7 + 7 * 5

    clefWeight += (-eval(clef[1]) + 1) * 2

    if '-' in clef:         # oktavierender Schlüssel
        clefWeight -= 7
    elif '+' in clef:
        clefWeight += 7

    return clefWeight       # entspricht Notenwert auf Mittellinie

def removeShapedNotes(score):
    for chord in score.getElementsByTagName('chord'):
        showHeads(chord)

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



# --------------- User Input ------------------------------------
uiActionSelect = Radio([tr('radio_11'),
                        tr('radio_12')], value = 0)


shapeSelect = Radio([tr('radio_21'),
                    tr('radio_22')], value = 1)

dlg = Dialog(tr('dialogHeader'),
             VBox([uiActionSelect,
                   Label(''),
                   shapeSelect,
                   Label(''),
                   Label(tr('font-hint')),
                   Label('')], padding = 6)
             )
if dlg.run():
    actionSelect = uiActionSelect.value()
    shapeFormat = shapeSelect.value()
    handlingOk = True
else:
    handlingOk = False


def changeDoc(score):
    for voice in score.getElementsByTagName('voice'):
        fifths = 0
        clefWeight = 41

        noteObjects = voice.gotoChild('noteObjects')
        childs = getElementObjects(noteObjects.childNodes)

        voiceStemDir = 'auto'
        if voice.hasAttribute('stemDir'):
            voiceStemDir = 'down'
            if voice.getAttribute('stemDir') == 'up':
                voiceStemDir = 'up'
                

        for child in childs:

            if child.tagName == 'clefSign':
                clefWeight = getClefWeight(child)
                continue
            
            if child.tagName == 'keySign':
                fifths = eval(child.getAttribute('fifths'))
                continue

            if child.tagName == 'chord':
                hideHeads(child)
                stemDir, shape = getStemDir(child, voiceStemDir, clefWeight, shapeFormat)
                
                duration = getDuration(child)

                hwMin = 999
                hwMax = 0
                for head in child.getElementsByTagName('head'):
                    pitch = head.getAttribute('pitch')
                    headWeight = ( ord(pitch[0]) - ord('C') ) % 7 + 7 * eval(pitch[1])
                    hwMin = min(headWeight, hwMin)
                    hwMax = max(headWeight, hwMax)

                if stemDir == 'up':
                    diatonic = hwMax
                else:
                    diatonic = hwMin

                for head in child.getElementsByTagName('head'):
                    pitch = head.getAttribute('pitch')
                    headWeight = ( ord(pitch[0]) - ord('C') ) % 7 + 7 * eval(pitch[1])
                    note = ( ord(pitch[0]) - ord('C') ) % 7
                    note = (note - 4 * fifths ) % 7
                    verticalOffset = (diatonic - headWeight) / 2.0
                    drawGraphicalHead(child, note, shape, duration, verticalOffset)

        

class ScoreChange(ScoreChange):
    def changeScore(self, score):
        if actionSelect == 0:
            changeDoc(score)
        elif actionSelect == 1:
            removeShapedNotes(score)
            
        
if activeScore() and handlingOk:

    activeScore().registerUndo( tr('regUndo') )
    activeScore().deleteTaggedGraphics(scriptTag)   # remove always

    tempInput = tempfile.mktemp('.capx')
    tempOutput = tempfile.mktemp('.capx')
    activeScore().write(tempInput)

    ScoreChange(tempInput, tempOutput)

    activeScore().read(tempOutput)
        
    os.remove(tempInput)
    os.remove(tempOutput)

