# -*- coding: ISO-8859-1 -*-
""" capellaScript -- (c) Paul Villiger
>>> Akkord_Formatierer

    Mit diesem Skript lassen sich Formate von Akkordbezeichnungen verändern und angleichen, sowie Akkorde über den Liedtext eingeben.
    Folgende Einstellungen sind möglich||
    - Wechsel zwischen verschiedenen Dastellungen|
    - Schriftart, Schriftgrösse|
    - Abstand von der Mittellinie|
    - Horizontale Ausrichtung korrigieren|
    - Capellasymbole für # und b|
    - Akkordinhalt ersetzen|
    - Teilbereich möglich|
    |
    >>> Bitte Hinweise im Skript lesen <<<
    
<<<

Mögliche Erweiterungen:
    - Akkorde im RTF-Format ersetzen
    - Do, Re Mi ... Akkorde berücksichtigen, sofern dazu ein Bedürfnis besteht

Zusammensetzen der Akkordsymbole:
          Text1  /
     Akk1       /
               / Akk2 Text2

    - Text1, /, Akk2 und Text2 sind optional
    - Das Programm versucht Akk1 und Akk2 zu erkennen. Wenn nicht erkannt, werden sie als Text behandelt

Hinweise:
- Erstreckt sich die Markierung über mehr als eine System, so werden alle Noten diesem System berücksichtigt.
- Sind in einem System mehr als eine Stimme markiert, so wird nur die oberste Stimme berücksichtigt
- wenn ein Bereich markiert ist, so werden nur Noten innerhalb der Markierung berücksichtigt,
  wenn keine Noten markiert sind, so gilt die Note rechts vom Cursor 
- Achtung: Akkordsymbole im RTF-Format (Textfeld), Gitarrensymbolen oder mit Grafiken werden nicht unterstützt resp. falsch konvertiert.

Akkordeingabe über Liedtext:
- Für die Noten gilt die deutsche Schreibweise C, D, E, F, G, A, H, B, Es, Fis, Eb, F#, ...
- Moll wird mit Kleinbuchstaben c oder mit Cm eingegeben
- Text nach einem Leerzeichen wird hochgestellt "C 7". Tipp: Zuerst C7 schreiben und erst danach Leerzeichen einfügen. 
- Text nach "/" wird Tiefgestellt.
- Die Formatangaben werden immer aus dem Dialog übernommen, unabhängig davon ob die CheckBox gesetzt ist.
- Es wird nur der Liedtext der ersten Strophe berücksichtigt.
- Akkorde werden in der ganzen Partitur umgesetzt
- Die Akkordangaben im Liedtext werden nicht gelöscht
- Unterstrichene Akkorde, z.B. für Terzbässe, werden mit vorgestelltem '_' erzeugt: '_D'. Tipp: zuerst D schreiben dann _
- Wenn bereits Liedtext vorhanden ist, so kann ein leere Strophe 1 eingefügt und wieder gelöscht werden.

Uebernahme von Akkorden in Liedtext:
- Diese Funktion dient dazu bestehende Akkorde mit Textfeldern umzuwandeln.
- Die Akkordstufe wird in die erste Zeile des Liedtextes übernommen
- Danach sind die Akkorde zu editieren resp. zu ergänzen und können aus dem Liedtext übernommen werden
- Vorgehen:
    1) Punkt 6 - Akkorde in Liedtext übernehmen
    2) Akkorde im Liedtext editieren
    3) Punkt 7 - Akkorde Löschen
    4) Punkt 2 - Akkorde aus Liedtext übernehmen
    5) Punkt 4 - Liedtext erste Strophe löschen



History: 09.03.05 Erstausgabe
         10.03.05 Nur Akkorde mit Einfachtext werden behandelt
         28.04.05 neu horizontale Ausrichtung korrigieren
         21.05.05 Einstellungen werden gespeichert
                  neu H...Bb Darstellung
         26.01.06 Bei Cm maj7 geht moll verloren
         18.02.06 Akkordeingabe über Liedtext
         19.02.06 Funktion Liedtext löschen und einfügen von Strophe 1
         24.02.06 Fonts werden auch aus vorhandenen Textelementen übernommen
         15.09.07 Script Internationalisierung
         27.10.07 Fehlerkorrekturen/Erweiterungen (J.Jørgen von Bargen, mit JJvB markiert)
                  (1) Bei Eingabe von C# 7b9#13 wurden die Vorzeichen im 7b9#13 nicht
                  mit Vorzeichen in Capella-Font ersetzt
         15.05.08 Funktion Akkorde in Liedtext übernehmen
                  Funktion Akkorde entfernen
         29.09.08 Erweiterung um Solfeggio und International mit B Auflösezeichen
         08.10.08 B Auflösezeichen in Bassnote (s3)
         18.08.10 Fehler bei Bassakkorden, falsche Reihenfolge
                  Akkordtyp mit B oder H wird erkannt
         28.01.11 Suffixgroesse einstellbar
         12.02.11 Fehlerhafte Akkorde von capscan werden richtig verarbeitet
         31.05.11 Akkorde der Länge 12 werden gelesen -> Länge 21
         14.11.11 Akkorde -> Liedtext erweitert
         19.03.12 Nur Dur- oder Moll-Akkorde bearbeiten
                  


"""

german = ("de", {
    'radio_1'           :   '1. Akkorde neu formatieren',
    'radio_2'           :   '2. Akkorde aus Liedtext übernehmen',
    'radio_3'           :   '3. (Akkorde aus Einfachtext übernehmen)',
    'radio_4'           :   '4. Liedtext Strophe 1 löschen ',
    'radio_5'           :   '5. Liedtext Strophe 1 einfügen ',
    'radio_6'           :   '6. Akkorde in Liedtext übernehmen',
    'radio_7'           :   '7. Akkorde entfernen',
    'textOnLine'        :   'Text auf einer Linie',
    'textHighLow'       :   'Text hoch/tief gestellt',
    'actionSelect'      :   'Aktion auswählen',
    'chordType'         :   'Akkord Typ',
    'chordStyle'        :   'Akkord Style',
    'varyFont'          :   'Font anpassen',
    'fontType'          :   'Schrift',
    'fontSize'          :   'Höhe',
    'useCapellaFont'    :   'Capella Font für b und # verwenden',
    'varyVerticalPos'   :   'Vertikale Position anpassen',
    'varyHorizontalPos' :   'Horizontale Position anpassen',
    'replaceChorCont'   :   'Akkordinhalt ersetzen *)',
    'onlyMarkedRange'   :   'Nur auf markierten Bereich anwenden *)',
    'note'              :   '^ Hinweis: Die Auswahlfelder gelten nur für 1.\n   *) nur für 1. relevant',
    'dialogHeader'      :   '--- Akkord Formatierer ---',
    'hint'              :   'Hinweis',
    'notImplemented'    :   'Diese Funktion ist noch nicht implementiert',
    'regUndo'           :   'Akkord Formatierer',
    'style1'            :   'International (Eb, Bb, B, B#)',
    'style2'            :   'Skandinavisch (Eb, B, H, H#)',
    'style3'            :   'Deutsch (Es, B, H, His, es, b, h, his)',
    'style4'            :   'Skandinavisch (Eb, Bb, H, H#)',
    'style5'            :   'International (B Auflösung)',
    'style6'            :   'Solfeggio 1 (Me, Te, Ti, Si#)',
    'style7'            :   'Solfeggio 2 (Mib, Sib, Si, Si#)',
    'suffixFactor'      :   'Suffixgröße',
    'handleMajChords'   :   'Dur Akkorde bearbeiten',
    'handleMinChords'   :   'Moll Akkorde bearbeiten'
    })

try:
    exec('from %s import translations' % ( translationModule() ))
    translations.append(german)
    setLanguages(translations)
except:
    def tr(s):
        return german[1].get(s, "???")
#-------------------------------------------------------------------

import new
from xml.dom.minidom import NodeList, Node, Element
from xml.dom.minidom import parseString, NodeList, Node, Element
# import win32traceutil

fontSelection = ['Arial','Times New Roman', 'Tahoma', 'Verdana']

def latin1_e(u):
    return u.encode('Latin-1')

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 getUsedFonts():
    # Skript dursucht rekursiv alle drawObject nach Fonts
    def searchFont(d):
        if type(d) == type(dict()):
            for key, value in d.items():
                if key == 'face' and 'capella' not in value:
                    fontList[value] = True
                elif type(value) == type(dict()):
                    searchFont(value)
                elif type(value) == type([]):
                    for l in value:
                        searchFont(l)
                    
    fontList = {}
    for noteObject in activeScore().noteObjs():
        for drawObj in noteObject.drawObjs():
            searchFont(drawObj)

    fonts = fontList.keys()
    fonts.sort()
    return fonts

for font in getUsedFonts():
    if font not in fontSelection:
        fontSelection.append(font)   # Wenn Font noch nicht vorhanden so wird er angehängt

def getLyrics():
    # kopiert alle Liedtexte mit Position aus der aktuellen Partitur
    lyrics = {}

    def changeDoc(score):
        
        def getText(node):
            for child in node.childNodes:
                if child.nodeType == child.TEXT_NODE:
                    return latin1_e(child.data)
                
            return ''

        sy = 0
        for system in score.getElementsByTagName('system'):
            st = 0
            for staff in system.getElementsByTagName('staff'):
                vo = 0
                for voice in staff.getElementsByTagName('voice'):
                    ob = 0
                    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':
                                    lyrics[(sy, st, vo, ob)] = getText(verse)
                            ob += 1
                        obj = obj.nextSibling
                        
                    vo += 1
                st += 1
            sy += 1


    from caplib.capDOM import ScoreChange
    import tempfile

    class ScoreChange(ScoreChange):
        def changeScore(self, score):
            changeDoc(score)

    if activeScore():
        tempInput = tempfile.mktemp('.capx')
        tempOutput = tempfile.mktemp('.capx')
        activeScore().write(tempInput)
        
        ScoreChange(tempInput, tempOutput)

        os.remove(tempInput)
        os.remove(tempOutput)

    return lyrics
    # ENDE getLyrics()
    # ----------------------------------------------------------------

def shiftLyrics(down=True):

    def changeDoc(score):
        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

    from caplib.capDOM import ScoreChange
    import tempfile

    class ScoreChange(ScoreChange):
        def changeScore(self, score):
            changeDoc(score)

    if activeScore():
        tempInput = tempfile.mktemp('.capx')
        tempOutput = tempfile.mktemp('.capx')
        activeScore().write(tempInput)
        
        ScoreChange(tempInput, tempOutput)

        activeScore().read(tempOutput)
        os.remove(tempInput)
        os.remove(tempOutput)

    # ENDE removeLyrics()
    # ----------------------------------------------------------------







# import win32traceutil
import string
from string import lower


chordConversion = {'c':'C','d':'D','e':'E','f':'F','g':'G','a':'A','h':'B','b':'Bb',
                   'cis':'C#','ces':'Cb','dis':'D#','des':'Db','eis':'E#','fis':'F#','fes':'Fb',
                   'gis':'G#','ges':'Gb','ais':'A#','his':'B#','hes':'Bb','es':'Eb','as':'Ab','fab':'Fb'}

notes = ['Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb',
         'F',  'C',  'G',  'D',  'A',  'E',  'B',
         'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']

options = ScriptOptions() 
opt = options.get()

class ChordSymbol(NoteObj):
    def __init__(self):
        # amerikanisch
        # ... Eb,B,F,C,...,Bb,F#    International
        self.notes1 = ['Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb',            # chordType = 1
                      'F',  'C',  'G',  'D',  'A',  'E',  'B',
                      'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
        # ... Eb,B,F,C,...,H,F#     Skandinavisch
        self.notes2 = ['Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'B',             # chordType = 2
                      'F',  'C',  'G',  'D',  'A',  'E',  'H',
                      'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'H#']
        # ... Es,B,F,C,...,H,Fis    Deutsch Dur
        self.notes3 = ['Fes', 'Ces', 'Ges', 'Des', 'As', 'Es', 'B',         # chordType = 3 (Dur)
                      'F',  'C',  'G',  'D',  'A',  'E',  'H',
                      'Fis', 'Cis', 'Gis', 'Dis', 'Ais', 'Eis', 'His']
        # ... es,b,f,c,...,h,fis    Deutsch moll
        self.notes4 = ['fes', 'ces', 'ges', 'des', 'as', 'es', 'b',         # chordType = 3 (moll)
                      'f',  'c',  'g',  'd',  'a',  'e',  'h',
                      'fis', 'cis', 'gis', 'dis', 'ais', 'eis', 'his']
        # ... Eb,Bb,F,C,...,H,F#    Skandinavisch Bb
        self.notes5 = ['Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb',            # chordType = 4
                      'F',  'C',  'G',  'D',  'A',  'E',  'H',
                      'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'H#']
        # ... Eb,B,F,C,...,Bb,F#    International B Auflösung
        self.notes6 = ['Fb', 'Cb', 'Gb', 'Db', 'Ab', 'Eb', 'Bb',            # chordType = 5
                      'F',  'C',  'G',  'D',  'A',  'E',  'B%',
                      'F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
        # Solfeggio1 (..,Me,Te,Do,So,..,Ti,Fi)
        self.notes7 = ["Fab","Dob","Se","Ra", "Le", "Me", "Te",             # chordType = 6
                        "Fa","Do","So", "Re","La","Mi","Ti",
                        "Fi", "Di", "Si",  "Ri", "Li", "Mi#","Si#"]
        # Solfeggio2 (..,Mib,Sib,Fa,Do,Sol,..,Si,Fa#)
        self.notes8 = ["Fab","Dob","Se","Reb","Lab","Mib","Sib",            # chordType = 7
                        "Fa","Do","Sol","Re","La","Mi","Si",
                        "Fa#","Do#","Sol#","Re#","La#","Mi#","Si#"]
        
        self.notes  = self.notes2

        self.chordType =        int(opt.setdefault('chordType','2'))
        self.chordFace =        int(opt.setdefault('chordFace','2'))         # 1 = auf einer Linie, 2 = hoch/tief
        self.chordFont =        int(opt.setdefault('chordFont','0'))         # gemäss fontSelection
        self.chordHeight =      int(opt.setdefault('chordHeight','12'))
        self.chordWeight =      int(opt.setdefault('chordWeight','400'))
        self.useCapFont =       opt.setdefault('useCapFont','True') == 'True'
        self.changeFont =       opt.setdefault('changeFont','False') == 'True'
        self.changeVPos =       opt.setdefault('changeVPos','False') == 'True'
        self.vPos =             int(opt.setdefault('vPos','-4'))
        self.changeHPos =       opt.setdefault('changeHPos','False') == 'True'
        self.hPos =             int(opt.setdefault('hPos','0'))
        self.replaceChord =     opt.setdefault('replaceChord','False') == 'True'
        self.replaceString =    opt.setdefault('replaceString','C')
        self.onlyMarkedChords = opt.setdefault('onlyMarkedChords','False') == 'True'
        self.actionSelect =     int(opt.setdefault('actionSelect','0'))
        self.suffixFactor =     int(opt.setdefault('suffixFactor','2'))  # 70%

        if self.chordFont >= len(fontSelection):
            self.chordFont = 0

        options.set(opt)        

        self.minor = False #  Moll
        self.chordTypeExtracted = 0 # 0 = undefined, 1 = international, >1 = H == B

    # extrahiert am Stringanfang die Note, Dur/Moll und die Position innerhalb notes
    def getChord(self, chordString):
        intervalOffset = 0
        chord = ''
        minor = False
        tail = chordString
        if len(tail) > 1 and lower(tail[0:2]) in ['b#','bb']:
            chord = chord + tail[0:2]
            tail = tail[2:]
        elif len(tail) > 2 and lower(tail[0:3]) in ['cis','ces','dis','des','eis','fis','fes','gis','ges','ais','his','hes','fab']:
            chord = chordConversion[lower(tail[0:3])]
            tail = tail[3:]
        elif len(tail) > 1 and lower(tail[0:2]) in ['es','as']:
            chord = chordConversion[lower(tail[0:2])]
            tail = tail[2:]
#       elif len(tail) > 0 and lower(tail[0:1]) == 'b' and self.chordType > 0:  # Wenn international wird B wie H behandelt
        elif len(tail) > 0 and lower(tail[0:1]) == 'b' and self.chordTypeExtracted == 1:  # Wenn international wird B wie H behandelt
            chord = chordConversion['h']
            tail = tail[1:]
        elif len(tail) > 0 and lower(tail[0:1]) in 'cdefgahb':
            chord = chordConversion[lower(tail[0])]
            tail = tail[1:]
            if len(tail) > 0:
                if tail[0] in 'b#':
                    chord = chord + tail[0]
                    tail = tail[1:]
                elif tail[0] == 'x':
                    chord = chord + '#'
                    tail = tail[1:]
                    intervalOffset = +15     # note -12  E## -12 --> F#
                elif tail[0] == 'p':
                    chord = chord + 'b'
                    tail = tail[1:]
                    intervalOffset = +5     # note +12  Fbb +12 --> Eb
                    
            
        if tail[0:1] == 'm' and tail[0:3] <> 'maj':
            minor = True
            tail = tail[1:]
        if len(chordString) > 0 and chordString[0] in 'cdefgahb':
            minor = True
        interval = -1
        for i in range(len(notes)):
            if chord == notes[i]:
                 interval = i
                 break

        interval = (interval+intervalOffset) % (len(notes)-1)
        chord = notes[interval]                
                
        return tail, chord, minor, interval

    def genStringDict(self, s, x, y, height, weight, face, underline=False, vpos=0):
        # vpos 0 = normal, 1 = oben, 2 = unten 3 = unten gross

        font = {'face': face,
                'height': height,
                'weight': weight,
                'underline': underline}

        newS = {'x': x,
                'y': y,
                'type': 'text',
                'content': s }
        fontFactor = self.fontFactor / 100.0

        if vpos == 0:
            pass
        elif vpos == 1:
            font['height'] = font['height'] * fontFactor
            newS['y'] = y - (height / 2.0) / 6.0
        elif vpos == 2:
            newS['y'] = y + (height / 2.0 ) / 6.0
        elif vpos == 3:
            newS['y'] = y + (height / 2.0 ) / 6.0
            font['height'] = font['height'] * 1.6
            if s == '/':
                newS['x'] = newS['x'] - ( height * fontFactor / 6.0 ) * 0.3  

        
        if 'capella' in face:
            font['charSet'] = 2
            font['pitchAndFamily'] = 2
            font['height'] = font['height'] * 16.0 / 14.0
            # JJvB : für vpos muss neue höhe benutzt werden
            height = font['height']
            newS['font'] = font
            dx = (height / 7.0 ) * 0.4   # Annäherung für capellafont b und # und %
            newS['content'] = {'b':'Q','#':'S','%':'R'}[s]
            
            # vertikale Position von # und b korrigieren        
            if s in '#%':
                newS['y'] -= height * 0.5 / 6.0 * 0.8
            elif s == 'b':
                newS['y'] -= height * 0.5 / 6.0 * 0.5

            # horizontale Position von # und b korrigieren        
            newS['x'] += height / 3.0 / 6.0 * 0.3
                
        else:
            font['pitchAndFamily'] = 0
            newS['font'] = font
            dx, dy = self.noteObj.textSize(newS)

        if vpos == 3 and s == '/':
            dx = dx - ( height * fontFactor / 6.0 ) * 0.2    # / unterschneiden
        newX = newS['x'] + dx
        return newS, newX

    def userInput(self):
        suffixFactorValues = ['50','60','70','80','90','100']
        uiActionSelect = Radio([tr('radio_1'),
                                tr('radio_2'),
                                tr('radio_3'),
                                tr('radio_4'),
                                tr('radio_5'),
                                tr('radio_6'),
                                tr('radio_7')], value = self.actionSelect)
        
        uiChangeFont = CheckBox('', value = self.changeFont, width = 3)
        uiNewFont = ComboBox(fontSelection, value = self.chordFont, width = 20)

        uiChangeVPos =  CheckBox('', value = self.changeVPos, width = 3)
        uiVPos = Edit(value = str( - self.vPos), width = 10)

        uiChangeHPos =  CheckBox('', value = self.changeHPos, width = 3)
        uiHPos = Edit(value = str( self.hPos), width = 10)

        uiUseCapFont =  CheckBox('', value = self.useCapFont, width = 3)

        uiChordType = ComboBox([tr('style1'), tr('style2'), tr('style3'), tr('style4'), tr('style5'), tr('style6'), tr('style7')], value = self.chordType - 1, width = 25)
        uiChordFace = ComboBox([tr('textOnLine'), tr('textHighLow')], value = self.chordFace - 1, width = 25)

        uiChordHeight = Edit(value = str(self.chordHeight), width = 5)

        uiSuffixFactor = ComboBox(suffixFactorValues, value = self.suffixFactor, width = 10)
        
        uiReplaceChord = CheckBox('', value = self.replaceChord, width = 3)
        uiReplaceString = Edit(value = self.replaceString, width = 10)

        uiOnlyMarkedChords = CheckBox('', value = self.onlyMarkedChords, width = 3)
        uiHandleMajChords = CheckBox('', value = True, width = 3)
        uiHandleMinChords = CheckBox('', value = True, width = 3)

        dlg = Dialog(tr('dialogHeader'),
                     VBox([
                         HBox([uiActionSelect], text = tr('actionSelect')),
                         HBox([Label(tr('chordType'), width = 14), uiChordType]),
                         HBox([Label(tr('chordStyle'), width = 14), uiChordFace ]),
                         HBox([Label(tr('suffixFactor'), width = 14), uiSuffixFactor ]),
                         VBox([
                             HBox([uiChangeFont, Label(tr('varyFont'))]),
                             HBox([Label('', width = 3), Label(tr('fontType'), width = 15), uiNewFont]),
                             HBox([Label('', width = 3), Label(tr('fontSize'), width = 15), uiChordHeight])
                             ], padding = 0),
                         HBox([uiUseCapFont, Label(tr('useCapellaFont'))  ]),
                         HBox([uiChangeVPos, Label(tr('varyVerticalPos'), width = 25), uiVPos]),
                         HBox([uiChangeHPos, Label(tr('varyHorizontalPos'), width = 25), uiHPos]),
                         HBox([uiReplaceChord, Label(tr('replaceChorCont'), width = 25), uiReplaceString]),
                         HBox([uiOnlyMarkedChords, Label(tr('onlyMarkedRange'))  ]),
                         HBox([uiHandleMajChords, Label(tr('handleMajChords')) ]),
                         HBox([uiHandleMinChords, Label(tr('handleMinChords')) ]),
                         Label(tr('note')),  
                         Label('')
                         ], padding = 6)
                     )
        if dlg.run():
            self.actionSelect = uiActionSelect.value()
            
            self.chordType = uiChordType.value() + 1

            self.changeFont = uiChangeFont.value() == 1
            self.chordFont = uiNewFont.value()
            self.chordHeight = long(uiChordHeight.value())

            self.changeVPos = uiChangeVPos.value() == 1
            self.vPos = - long(uiVPos.value())

            self.changeHPos = uiChangeHPos.value() == 1
            self.hPos = long(uiHPos.value())

            self.useCapFont = uiUseCapFont.value() == 1
            self.chordFace = uiChordFace.value()+ 1

            self.replaceChord = uiReplaceChord.value() == 1
            self.replaceString = uiReplaceString.value()
            self.onlyMarkedChords = uiOnlyMarkedChords.value() == 1
            self.suffixFactor = uiSuffixFactor.value()
            self.fontFactor = long(suffixFactorValues[self.suffixFactor])
            self.handleMajChords = uiHandleMajChords.value() == 1
            self.handleMinChords = uiHandleMinChords.value() == 1

            # save new options            
            opt['actionSelect']     = str(self.actionSelect)
            opt['chordType']        = str(self.chordType)
            opt['changeFont']       = str(self.changeFont)
            opt['chordFace']        = str(self.chordFace)
            opt['chordFont']        = str(self.chordFont)
            opt['chordHeight']      = str(self.chordHeight)
            opt['chordWeight']      = str(self.chordWeight)
            opt['useCapFont']       = str(self.useCapFont)
            opt['changeVPos']       = str(self.changeVPos)
            opt['vPos']             = str(self.vPos)
            opt['changeHPos']       = str(self.changeHPos)
            opt['hPos']             = str(self.hPos)
            opt['replaceChord']     = str(self.replaceChord)
            opt['replaceString']    = self.replaceString
            opt['onlyMarkedChords'] = str(self.onlyMarkedChords)
            opt['suffixFactor']     = str(self.suffixFactor)

            options.set(opt)            
            
            
            ok = True
        else:
            ok = False
        return ok

    def analyzeChordSymbol(self, drawObj):
        self.origContents = ''
        self.foundNoneText = False
        self.nRefNote = drawObj['nRefNote']
        self.type = drawObj['type']
        if len(drawObj['items']) == 12:
            items = drawObj['items'][self.nRefNote+4]    # erster Eintrag Fb
        else:        
            items = drawObj['items'][self.nRefNote+8]    # erster Eintrag Fb
        if items['type'] == 'group':
            self.contents = ''
            for item in items['items']:
                if item['type'] <> 'text':
                    self.foundNoneText = True
                    return
                font = item['font']
                if 'capella' in font['face']:
                    if item['content'] == 'Q':
                        self.contents = self.contents + 'b'
                    elif item['content'] == 'S':
                        self.contents = self.contents + '#'
                    elif item['content'] == 'P':
                        self.contents = self.contents + 'p'  # Doppel b
                    elif item['content'] == 'T':
                        self.contents = self.contents + 'x'  # Doppelkreuz
                else:            
                    self.contents = self.contents + item['content']
            items = items['items'][0]
            
        elif items['type'] == 'text':
            self.contents = items['content']
        else:
            self.foundNoneText = True
            return
        # Extract chordType
        if len(drawObj['items']) == 12:
            items13 = drawObj['items'][9]    # Eintrag H oder B
        else:
            items13 = drawObj['items'][13]    # Eintrag H oder B
        if items13['type'] == 'group':
            contents = ''
            for item in items13['items']:
                if item['type'] <> 'text':
                    continue
                contents = contents + item['content']
            
        elif items13['type'] == 'text':
            contents = items13['content']
        if lower(contents[0]) == 'h':
            self.chordTypeExtracted == 2    # H and B
        elif lower(contents[0]) == 'b':
            self.chordTypeExtracted == 1    # international B and Bb
        else:
            self.chordTypeExtracted == 0    # rest

        self.origContents = self.contents
        
        # Get Font    
        font = items['font']
        self.fontPitchAndFamily = font.get('pitchAndFamily',0)
        self.fontHeight = font.get('height',12)
        self.fontWeight = font.get('weight', 400)
        self.fontFace = font.get('face','Arial')
        self.underline = font.get('underline', False)

        # Chord-String ersetzen
        if self.replaceChord:
            if self.replaceString[0:1] == '_':
                self.contents = self.replaceString[1:]
                self.underline = True
            else:
                self.contents = self.replaceString
            
        # Notenbezeichnung abschneiden
        self.contents, self.chord1, self.minor1, self.interval1 = self.getChord(self.contents)

        self.x = items['x']
        self.y = items['y']

        # 2ten Chord finden nach '/'
        contList = string.split(self.contents, '/', 1)
        if len(contList) > 1:
            tail, self.chord2, self.minor2, self.interval2 = self.getChord(contList[1])
            self.contents = contList[0] + '/' + tail
        else:
            self.chord2, self.minor2 = '', ''
        
        

    def generateChordSymbol(self):
        newObj = dict()
        newObj['nRefNote'] = self.nRefNote
        newObj['type'] = self.type
        newObj['items'] = []

        if self.changeFont:
            self.fontFace = fontSelection[self.chordFont]
            self.fontHeight = self.chordHeight
            self.fontWeight = self.chordWeight

        if self.changeVPos:
            self.y = self.vPos
        if self.changeHPos:
            self.x = self.hPos
        for n in range(len(self.notes)):
            item = self.generateChordGroup(n)   # self.notes[n] + self.contents}
            newObj['items'].append(item)
        return newObj

    def generateChordGroup(self, n):  # n = Index in self.notes
        
        s1, s2, s3, s4 = '', '', '', ''
        if self.chordType in [1,2,4,5,6,7]:
            if self.chordType == 1:
                s1 = self.notes1[n]
            elif self.chordType == 2:
                s1 = self.notes2[n]
            elif self.chordType == 4:
                s1 = self.notes5[n]
            elif self.chordType == 5:
                s1 = self.notes6[n]
            elif self.chordType == 6:
                s1 = self.notes7[n]
            else:
                s1 = self.notes8[n]
            if self.minor1:
                s1 = s1 + 'm'
            contList = string.split(self.contents, '/', 1)
            s2 = contList[0]
            s4 = ''
            if len(contList) > 1:
                s4 = contList[1]
                s3 = ''
                if self.interval2 >= 0:
                    m = n + self.interval2 - self.interval1
                    while m < 0:
                        m += 12
                    while m >= len(self.notes):
                        m -= 12
                    
                    if self.chordType == 1:
                        s3 = self.notes1[m]
                    elif self.chordType == 2:
                        s3 = self.notes2[m]
                    elif self.chordType == 4:
                        s3 = self.notes5[m]
                    elif self.chordType == 5:
                        s3 = self.notes6[m]
                    elif self.chordType == 6:
                        s3 = self.notes7[m]
                    else:
                        s3 = self.notes8[m]
                        
                    if self.minor2:
                        s3 = s3 + 'm'
        else:
            # Deutscher Standard mit H und moll klein
            if self.minor1:
                s1 = self.notes4[n]
            else:
                s1 = self.notes3[n]
            contList = string.split(self.contents, '/', 1)
            s2 = contList[0]
            s4 = ''
            if len(contList) > 1:
                s4 = contList[1]
                s3 = ''
                if self.interval2 >= 0:
                    m = n + self.interval2 - self.interval1
                    while m < 0:
                        m += 12
                    while m >= len(self.notes):
                        m -= 12
                    if self.minor2:
                        s3 = self.notes4[m]
                    else:
                        s3 = self.notes3[m]

        # Zusammensetzen von s1 .. s4
        #     s2  /
        # s1     /
        #       / s3 s4
        s = {'type':'group', 'items': [] }
        if self.chordFace == 1: # auf einer Linie
            pos = [0,0,0,0]
        else:
            pos = [0,1,2,3]
        if len(s1) > 1 and s1[1] in 'b#%' and self.useCapFont:
            newS, newX = self.genStringDict(s1[0], self.x, self.y, self.fontHeight, self.fontWeight, self.fontFace, self.underline, vpos=pos[0])
            s['items'].append(newS)
            newS, newX = self.genStringDict(s1[1], newX, self.y, self.fontHeight, self.fontWeight, 'capella3', vpos=pos[0])
            s['items'].append(newS)
            if 'm' in s1:
                newS, newX = self.genStringDict('m', newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[0])
                s['items'].append(newS)
        else:
            s1 = string.replace(s1,'%','')
            newS, newX = self.genStringDict(s1, self.x, self.y, self.fontHeight, self.fontWeight, self.fontFace, self.underline, vpos=pos[0])
            s['items'].append(newS)
            
        if len(s2) > 0:
            # JJvB : ersetzen von # und b durch capella font
            while (s2.find('b')>=0 or s2.find('#')>=0) and self.useCapFont:
                posb = s2.find('b')
                posh = s2.find('#')
                if posb>=0 and posh>=0 :
                    i = min(posb,posh)
                else :
                    i = max(posb,posh)
                left = s2[0:i]
                mid = s2[i]
                right = s2[i+1:]
                if left != '':
                    newS, newX = self.genStringDict(left, newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[1])
                    s['items'].append(newS)
                newS, newX = self.genStringDict(mid, newX, self.y, self.fontHeight, self.fontWeight, 'capella3', vpos=pos[1])
                s['items'].append(newS)
                s2 = right
            if s2 != '':
                newS, newX = self.genStringDict(s2, newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[1])
                s['items'].append(newS)
            
        if len(s3+s4) > 0:
            newS, newX = self.genStringDict('/', newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[3])
            s['items'].append(newS)
            if len(s3) > 0:
                if len(s3) > 1 and s3[1] in 'b#%' and self.useCapFont:
                    newS, newX = self.genStringDict(s3[0], newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[2])
                    s['items'].append(newS)
                    newS, newX = self.genStringDict(s3[1], newX, self.y, self.fontHeight, self.fontWeight, 'capella3', vpos=pos[2])
                    s['items'].append(newS)
                    if 'm' in s3:
                        newS, newX = self.genStringDict('m', newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[2])
                        s['items'].append(newS)
                else:
                    s3 = string.replace(s3,'%','')
                    newS, newX = self.genStringDict(s3, newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[2])
                    s['items'].append(newS)
            if len(s4) > 0:
                newS, newX = self.genStringDict(s4, newX, self.y, self.fontHeight, self.fontWeight, self.fontFace, vpos=pos[2])
                s['items'].append(newS)
        return s            
        
    def between(self, a, x,y):
        if self.onlyMarkedChords:
            res = ( a >= min(x,y) and a <= max(x,y) )
        else:
            res = True
        return res

    def convertChordSymbol(self, noteObj):
        self.noteObj = noteObj
        for i in range(noteObj.nDrawObjs()):
            drawObj = noteObj.drawObj(i)
            if drawObj['type'] == 'transposable':
                self.analyzeChordSymbol(drawObj)
                
                if self.handleMajChords and not self.minor1:
                    pass
                elif self.handleMinChords and self.minor1:
                    pass
                else:
                    continue

                if self.foundNoneText:
                    pass
                else:                
                    newDrawObj = self.generateChordSymbol()
                    noteObj.replaceDrawObj(i, newDrawObj)


    def getChordSymbol(self, noteObj):
        self.origContents = ''
        self.noteObj = noteObj
        for i in range(noteObj.nDrawObjs()):
            drawObj = noteObj.drawObj(i)
            if drawObj['type'] == 'transposable':
                self.analyzeChordSymbol(drawObj)
                break               # only 1st chordSymbol
        return self.origContents
    

cs = ChordSymbol()

def chordToLyrics(lyricsDict):

    def changeDoc(score):
            
        sy = 0
        for system in score.getElementsByTagName('system'):
            st = 0
            for staff in system.getElementsByTagName('staff'):
                vo = 0
                for voice in staff.getElementsByTagName('voice'):
                    ob = 0
                    noteObjects = voice.gotoChild('noteObjects')
                    obj = noteObjects.firstChild
                    while obj:
                        if obj.nodeType == obj.ELEMENT_NODE:
                            if obj.tagName == 'chord':
                                if (sy, st, vo, ob) in lyricsDict:
                                    lyric = obj.gotoChild('lyric')
                                    for vers in lyric.getElementsByTagName('vers'):
                                        if verse.getAttribute('i') == '0':
                                            verse.parentNode.removeChild(verse)
                                    verse = lyric.gotoChild('verse', True)
                                    verse.setAttribute('i','0')
                                    textNode = doc.createTextNode(lyricsDict[(sy, st, vo, ob)])
                                    verse.appendChild(textNode)
                            ob += 1
                        obj = obj.nextSibling
                        
                    vo += 1
                st += 1
            sy += 1

    from caplib.capDOM import ScoreChange
    import tempfile

    class ScoreChange(ScoreChange):
        def changeScore(self, score):
            changeDoc(score)

    if activeScore():
        tempInput = tempfile.mktemp('.capx')
        tempOutput = tempfile.mktemp('.capx')
        activeScore().write(tempInput)
        
        ScoreChange(tempInput, tempOutput)

        activeScore().read(tempOutput)
        os.remove(tempInput)
        os.remove(tempOutput)

def removeChords():
    
    def changeDoc(score):
            
        for chord in score.getElementsByTagName('chord'):
            for drawObj in chord.getElementsByTagName('drawObj'):
                if drawObj.getElementsByTagName('transposable'):
                    drawObj.parentNode.removeChild(drawObj)
            # drawObjects müssen auch entfernt werden, sonst stürzt capella u.U. ab
            for drawObjects in chord.getElementsByTagName('drawObjects'):
                if not drawObjects.getElementsByTagName('drawObj'):
                    drawObjects.parentNode.removeChild(drawObjects)

    from caplib.capDOM import ScoreChange
    import tempfile

    class ScoreChange(ScoreChange):
        def changeScore(self, score):
            changeDoc(score)

    if activeScore():
        tempInput = tempfile.mktemp('.capx')
        tempOutput = tempfile.mktemp('.capx')
        activeScore().write(tempInput)
        
        ScoreChange(tempInput, tempOutput)

        activeScore().read(tempOutput)
        os.remove(tempInput)
        os.remove(tempOutput)


if activeScore():
    if not cs.userInput():
        pass
    elif cs.actionSelect == 0:
        activeScore().registerUndo(tr('regUndo'))
        sel = curSelection()
        if sel == 0:
            sel = (0,0,0,0),(0,0,0,0)
        (sy0, st0, vo0, no0),(sy1, st1, vo1, no1) = sel
        
        # Erstreckt sich die Markierung über mehr als eine System,
        #   so werden alle Noten dieser Systeme berücksichtigt.
        if sy0 <> sy1:
            st0 = vo0 = no0 = 0
            st1 = vo1 = no1 = 10000
        else:
        # sind in einem System mehr als eine Stimme markiert,
        #   so wird nur die oberste Stimme berücksichtigt
            st0 = st1 = min(st0, st1)
            vo0 = vo1 = min(vo0, vo1)
            

        # wenn Bereich markiert ist, nur Noten innerhalb Markierung berücksichtigen
        # wenn keine Noten markiert sind, gilt die Note rechts vom Cursor 
        if no1 <> no0:
            if no1 > no0:   no1 -= 1
            else:           no0 -= 1   

        for sy in range(activeScore().nSystems()):
            if not cs.between(sy, sy0,sy1):
                continue
            system = activeScore().system(sy)
            for st in range(system.nStaves()):
                if not cs.between(st, st0,st1):
                    continue
                staff = system.staff(st)
                for vo in range(staff.nVoices()):
                    if not cs.between(vo, vo0,vo1):
                        continue
                    voice = staff.voice(vo)
                    for no in range(voice.nNoteObjs()):
                        if not cs.between(no, no0,no1):
                            continue
                        noteObj = voice.noteObj(no)
                        cs.convertChordSymbol(noteObj)
    elif cs.actionSelect == 1:

        cs.chordTypeExtracted = cs.chordType

        activeScore().registerUndo(tr('regUndo'))
        lyricDict = getLyrics()
        for (sy, st, vo, ob), text in lyricDict.items():
            cs.noteObj = activeScore().system(sy).staff(st).voice(vo).noteObj(ob)
            if text[0:1] == '_':
                cs.underline = True
                text = text[1:]
            else:
                cs.underline = False
            
            # 1ten Chord finden
            cs.contents, cs.chord1, cs.minor1, cs.interval1 = cs.getChord(text)
            
            if cs.interval1 == -1:    # Keinen Akkord gefunden
                continue

            if cs.handleMajChords and not cs.minor1:
                pass
            elif cs.handleMinChords and cs.minor1:
                pass
            else:
                continue
            
            cs.nRefNote = cs.interval1 - 8
            cs.type = 'transposable'
            cs.y = cs.vPos
            cs.x = cs.hPos
            cs.changeFont = True

            # 2ten Chord finden nach '/'
            contList = string.split(cs.contents, '/', 1)
            if len(contList) > 1:
                tail, cs.chord2, cs.minor2, cs.interval2 = cs.getChord(contList[1])
                cs.contents = contList[0] + '/' + tail
                # cs.interval2 =  cs.interval2 - cs.interval1 + 24  # Fehler korr 30.10.2011
            else:
                cs.chord2, cs.minor2 = '', ''

            newDrawObj = cs.generateChordSymbol()
            cs.noteObj.addDrawObj(newDrawObj)
    elif cs.actionSelect == 2:
        messageBox(tr('hint'), tr('notImplemented') )
    elif cs.actionSelect == 3:
        activeScore().registerUndo(tr('regUndo'))
        shiftLyrics(down=True)
    elif cs.actionSelect == 4:
        activeScore().registerUndo(tr('regUndo'))
        shiftLyrics(down=False)
    elif cs.actionSelect == 5:
        lyricDict = {}
        activeScore().registerUndo(tr('regUndo'))
        # shiftLyrics(down=False)
        activeScore().registerUndo(tr('regUndo'))
        sel = curSelection()
        if sel == 0:
            sel = (0,0,0,0),(0,0,0,0)
        (sy0, st0, vo0, no0),(sy1, st1, vo1, no1) = sel
        
        # Erstreckt sich die Markierung über mehr als eine System,
        #   so werden alle Noten dieser Systeme berücksichtigt.
        if sy0 <> sy1:
            st0 = vo0 = no0 = 0
            st1 = vo1 = no1 = 10000
        else:
        # sind in einem System mehr als eine Stimme markiert,
        #   so wird nur die oberste Stimme berücksichtigt
            st0 = st1 = min(st0, st1)
            vo0 = vo1 = min(vo0, vo1)
            

        # wenn Bereich markiert ist, nur Noten innerhalb Markierung berücksichtigen
        # wenn keine Noten markiert sind, gilt die Note rechts vom Cursor 
        if no1 <> no0:
            if no1 > no0:   no1 -= 1
            else:           no0 -= 1   

        for sy in range(activeScore().nSystems()):
            if not cs.between(sy, sy0,sy1):
                continue
            system = activeScore().system(sy)
            for st in range(system.nStaves()):
                if not cs.between(st, st0,st1):
                    continue
                staff = system.staff(st)
                for vo in range(staff.nVoices()):
                    if not cs.between(vo, vo0,vo1):
                        continue
                    voice = staff.voice(vo)
                    for no in range(voice.nNoteObjs()):
                        if not cs.between(no, no0,no1):
                            continue
                        noteObj = voice.noteObj(no)
                        chordSymbol = cs.getChordSymbol(noteObj)
                        if chordSymbol:
                            if cs.handleMajChords and not cs.minor1:
                                pass
                            elif cs.handleMinChords and cs.minor1:
                                pass
                            else:
                                continue

                            lyricDict[(sy, st, vo, no)] = chordSymbol

        chordToLyrics(lyricDict)
    elif cs.actionSelect == 6:
        activeScore().registerUndo(tr('regUndo'))
        removeChords()
