# -*- coding: UTF-8 -*-
""" capellaScript -- (C) 2025 Ken Haiker, acaMusic (www.acamusic.de)
                         Implementation: Brian Schueler
>>> aL.Editor
    Rev 0.2.20 (23.07.2026) --- Plugin für Gitarristen (und andere Saiteninstrumente wie Bass, Ukulele usw.)
	||
	Das aL.Editor-Plugin (aL = acaLead) erstellt ein klassisches Notensystem aus in einer Notenzeile 
	vorgegebenen Tonlängen (die "korrekten" Tonhöhen fehlen) sowie zusätzlich direkt aus dem Griffbrett 
	gewonnenen Saiten- und Bund-Angaben (z.B. A-Saite, 3.Bund = Note c4). Letztere dienen z.B. dem 
	Notenunkundigen als Ersatz für die fehlenden Tonhöhen.  Mithilfe des aL.Tab-Plugins kann danach das 
	von diesem Plugin erstellte klassische Notensystem in eine vollständige Tabulatur umgewandelt werden.
	||
	Ein ausführliches Plugin-Handbuch gibt es auf der Homepage
    ||
    -------------   www.acaMusic.de   -------------
    ||
<<<

History:  Januar 2026 - Erste Version

"""

import pprint
import re
import tempfile
import zipfile
from caplib.capDOM import ScoreChange, childElements
from xml.dom.minidom import NodeList, Node, Element

sys.stdout = open(tempfile.gettempdir()+'capella-al-editor.log', 'w')

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
        for child in self.childNodes:
                if child.nodeType == child.ELEMENT_NODE and child.tagName == name:
                        newEl = child
                        break

        if new == False:
                return newEl

        if newEl == None:
                newEl = doc.createElement(name)
                self.appendChild(newEl)
        return newEl
import new
Node.gotoChild = new.instancemethod(gotoChild,None,Node)

# Get the base child object and the child list of an object
def getChildren(el, name, forcedPlural = None):
	if name == 'staff':
		namePlural = 'staves'
	elif name == 'drawObj':
		namePlural = 'drawObjects'
	elif name == 'noteObj':
		namePlural = 'noteObjects'
	else:
		namePlural = name+'s'
	if forcedPlural is not None:
		namePlural = forcedPlural
	plElems = childElements(el, namePlural)
	if len(plElems) > 0:
		plFirstElem = plElems[0]
		retval = childElements(plFirstElem, name)
		return (plFirstElem, retval)
	else:           
		xElem = el.getElementsByTagName(name)
		if xElem.length > 0:
			x = xElem[0]
			childNodes = getElementObjects(x.childNodes)
			return (x, childNodes)
		else:
			return (None, [])

# Replace a named child tree
def setChildren(el, children, name):
        oldchildren = childElements(el, name)
        for oldchild in oldchildren:
                el.removeChild(oldchild)
        for child in children:
                el.appendChild(child)

def getDurationByBase(base, dots = 0, tuplet = 0):
	base = base.split('/')
	value = 0
	if len(base) == 2:
		value = float(base[0])/float(base[1])
	else:
		value = float(base[0])

	# Tuplet Note
	if tuplet > 0:
		if tuplet == 3:
			value = value * 2.0 / 3.0

	# Dotted Note
	dot_value = value
	if dots > 0:
		for i in range(0,dots):
			dot_value = dot_value / 2
			value = value + dot_value
	return value

# Get the decimal duration of a note/chord/rest
def getObjDuration(noteObj):
	duration = noteObj.getElementsByTagName('duration')
	# Only NoteObjects that have a duration
	if len(duration) > 0:
		noDuration = duration[0].getAttribute('noDuration')
		if noDuration == 'true':
			return 0.0

		baseT = duration[0].getAttribute('base')
		if baseT is None or baseT == "":
			return 0.0

		dots = duration[0].getAttribute('dots')
		if dots is None or dots == '':
			dots = '0'

		tuplet = '0'
		tupletNode = duration[0].gotoChild('tuplet', False)
		if tupletNode is not None:
			tuplet = tupletNode.getAttribute('count')

		return getDurationByBase(baseT, int(dots), int(tuplet))
	return None

# Get the total duration of a voice
def getVoiceTotalTime(voice):
	totalTime = 0.0
	onoteObjects, noteObjects = getChildren(voice, 'noteObjects')
	for obj in noteObjects:
		duration = getObjDuration(obj)
		if duration is not None:
			totalTime = totalTime + duration
	return totalTime

# Get the total duration of a staff
def getStaffTotalTime(staff):
	totalTime = 0.0
	ovoices, voices = getChildren(staff, 'voice')
	for voice in voices:
		tt = getVoiceTotalTime(voice)
		totalTime = max(totalTime, tt)
	return totalTime

def getPosAtCursor(score):
	pos = 0.0
	sys1 = curSelection()[0][0]
	sta1 = curSelection()[0][1]
	voi1 = curSelection()[0][2]
	nObj1 = curSelection()[0][3]

	osystens, systems = getChildren(score, 'system')
	systemID = 0
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		staffID = 0
		savedPosSystem = pos
		for staff in staves:
			pos = savedPosSystem
			ovoices, voices = getChildren(staff, 'voice')
			voiceID = 0
			savedPosStaff = pos
			for voice in voices:
				pos = savedPosStaff
				oobjs, objs = getChildren(voice, 'noteObjects')
				numObjs = len(objs)
				objID = 0
				for i in range(0, numObjs):
					obj = objs[i]
					if (systemID, staffID, voiceID, objID) == (sys1, sta1, voi1, nObj1):
						return pos
					dura = getObjDuration(obj)
					if dura is not None:
						pos = pos + dura
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	return None
		
def getStavesByLayout(score, name):
	retval = []
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		newStaves = []
		for staff in staves:
			if staff.getAttribute('layout') == name:
				retval.append(staff)
	return retval
		
def addNote(voice, newNoteObj):
	oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
	newObjs = []
	for noteObj in noteObjs:
		newObjs.append(noteObj)
	newObjs.append(newNoteObj)
	setChildren(oNoteObjs, newObjs, 'noteObjects')

def clearStaff(staff):
	ovoices, voices = getChildren(staff, 'voice')
	for voice in voices:
		clearVoice(voice)

def copyStaff(src_staff, dest_staff):
	osrc_voices, src_voices = getChildren(dest_staff, 'voice')
	odest_voices, dest_voices = getChildren(dest_staff, 'voice')
	cloned_staff= src_staff.cloneNode(deep=True)
	voices = dest_staff.gotoChild('voices', False)
	if voices is not None:
		for dest_voice in dest_voices:
			voices.removeChild(dest_voice)
	ocloned_voices, cloned_voices = getChildren(cloned_staff, 'voice')
	setChildren(odest_voices, cloned_voices, 'voices')

def getReAlignFileCopyList(score):
	copyList = []
	RTFobjs = score.getElementsByTagName('richText')
	objCounter = 1
	for RTFobj in RTFobjs:
		oldfilename = RTFobj.getAttribute('file')
		newfilename = re.sub('(.*)[0-9]+\.rtf','\\1', oldfilename)+str(objCounter)+'.rtf'
		oldfilenameHtml = RTFobj.getAttribute('fileHtml')
		newfilenameHtml = re.sub('(.*)[0-9]+\.html','\\1', oldfilenameHtml)+str(objCounter)+'.html'
		RTFobj.setAttribute('file',newfilename)
		RTFobj.setAttribute('fileHtml',newfilenameHtml)
		copyList.append((oldfilename, newfilename))
		copyList.append((oldfilenameHtml, newfilenameHtml))
		objCounter = objCounter + 1
	#WMFobjs = src_staff.getElementsByTagName('metafile')
	#for WMFobj in WMFobjs:
		#oldfilnam = WMFobj.getAttribute('file')
		#WMFobj.setAttribute('file',mapOtherFiles[oldfilnam])
	return copyList

def copyAndRepackFiles(zipFile, copyList):
	writeFileNameWithDataList = []

	zip = zipfile.ZipFile(zipFile,'r', zipfile.ZIP_DEFLATED)
	for fileInfo in zip.filelist:
		fileName = fileInfo.filename
		srcFile = zip.open(fileName)
		data = srcFile.read()
		srcFile.close()
		skip = False
		for copyListItem in copyList:
			srcFileName, destFileName = copyListItem
			if fileName == destFileName and srcFileName != destFileName:
				skip = True
		if not skip:
			writeFileNameWithDataList.append((fileName, data))

	for fileSrcDst in copyList:
		srcFileName, destFileName = fileSrcDst
		if srcFileName != destFileName:
			srcFile = zip.open(srcFileName)
			data = srcFile.read()
			srcFile.close()
			writeFileNameWithDataList.append((destFileName, data))
	zip.close()

	zip2 = zipfile.ZipFile(zipFile,'w', zipfile.ZIP_DEFLATED)
	for writeFileNWD in writeFileNameWithDataList:
		destFileName, data = writeFileNWD
		zi = zipfile.ZipInfo(destFileName, (1980, 1, 1, 0, 0, 0))
		zip2.writestr(zi, data)
	zip2.close()

def clearVoice(voice):
	oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
	newObjs = []
	for noteObj in noteObjs:
		name = noteObj.localName
		print(name)
		oNoteObjs.removeChild(noteObj)
	setChildren(oNoteObjs, newObjs, 'noteObjects')

def concatStaves(staves):
	print('concatStaves:', staves)
	newStaff = staves[0].cloneNode(deep = True)
	ovoices, voices = getChildren(newStaff, 'voice')
	print('concatStaves - numStaves:', len(staves))
	print('concatStaves - numVoices:', len(voices))
	voiceNo = 0
	for voice in voices:
		voiceNoteObjs = voice.gotoChild('noteObjs')
		for staff in staves[1:]:
			osvoices, svoices = getChildren(staff, 'voice')
			osVoiceNo = 0
			for svoice in svoices:
				if osVoiceNo == voiceNo:
					onoteObjects, noteObjects = getChildren(svoice, 'noteObjects')
					noteObjID = 0
					for noteObj in noteObjects:
						newNoteObj = noteObj.cloneNode(deep=True)
						# Add key sign 0 (C/Am) if nothing is given
						if noteObjID == 1:
							if noteObjects[0].localName == 'clefSign' and noteObj.localName != 'keySign':
								newKeySign = doc.createElement('keySign') 
								newKeySign.setAttribute('fifths', '0')
								addNote(voice, newKeySign)
								print('concatStaves - add (brand new) keySign:', newKeySign)
						addNote(voice, newNoteObj)
						print('concatStaves - add note:', newNoteObj)
						noteObjID = noteObjID + 1
				osVoiceNo = osVoiceNo + 1
		voiceNo = voiceNo + 1
	return newStaff	

def trimRanges(itemPositionRanges):
	# Filter out None or '-' items after first element
	posRanges = []
	first = True
	lastValidItem = '-'
	for itemPosRange in itemPositionRanges:
		(begin, end), item = itemPosRange
		empty = False
		if item is None or item == '-' or item == '' or ( type(item) is list and len(item)>0 and '-' in item ):
			empty = True
		else:
			first = False
		if not empty or first:
			lastValidItem = item
		item = lastValidItem
		itemPosRange = (begin, end), item
		posRanges.append(itemPosRange)
	return posRanges

def tidyRanges(itemPositionRanges):
	# Filter out zero time ranges
	posRanges = []
	for itemPosRange in itemPositionRanges:
		(begin, end), item = itemPosRange
		if begin is None:
			begin = 0.0
			itemPosRange = ((begin, end), item)
		if abs(end-begin) > 1/256:
			posRanges.append(itemPosRange)

	# Fusion of splitted time ranges on same item
	retVal = []
	orgPosRange = None
	posRangeID = 0
	(l_begin, l_end), l_item = posRanges[-1]
	lastPosRange = (l_end, l_end), l_item
	posRanges.append(lastPosRange)
	for posRange in posRanges:
		(begin, end), item = posRange
		if orgPosRange is not None:
			(o_begin, o_end), o_item = orgPosRange
			if o_item != item or posRangeID >= len(posRanges)-1:
				thisEnd = begin
				if posRangeID >= len(posRanges)-1:
					thisEnd = end
				retVal.append(((o_begin, thisEnd), o_item))
				orgPosRange = posRange
		else:
			orgPosRange = posRange
		posRangeID = posRangeID + 1
	if retVal == []:
		retVal = posRanges
	return retVal
		

def getItemPositions(objFunc, staff, countOnly = False, posRanges = None, posRanges2 = None):
	ovoices, voices = getChildren(staff, 'voice')
	itemPosList = []
	for voice in voices:
		pos = -0.001
		onoteObjects, noteObjects = getChildren(voice, 'noteObjects')
		for obj in noteObjects:
			duration = getObjDuration(obj)
			if posRanges2 is not None:
				text = objFunc(obj, posRanges, posRanges2, pos)
			elif posRanges is not None:
				text = objFunc(obj, posRanges, pos)
			else:
				text = objFunc(obj)
			if len(text) > 0:
				itemPosList.append((pos, text))
			voltas = obj.getElementsByTagName('volta')
			if voltas.length > 0 and not countOnly:
				itemPosList.append((pos-0.001, 'volta'))

			if duration is not None:
				pos = pos + duration
	itemPosList.sort()
	print("itemPosList", itemPosList)
	return itemPosList

def genItemPositionRanges(itemPosList, totalTime, initItem = [], countOnly = False):
	lastPos = None
	pos = -0.001
	retval = []
	prevItem = initItem
	item = ''
	if len(prevItem) > 0:
		item = prevItem[0]

	for itemPos in itemPosList:
		text = itemPos[1]
		pos = itemPos[0]
		if text == 'volta':
			if len(prevItem) < 2:
				prevItem.insert(0, item)
			else:
				oldItem = item
				item = prevItem[-1]
				prevItem = prevItem[1:]
				prevItem[0] = oldItem
			text = item

		try:
			print('text', text)
			item = text

			if len(prevItem) > 0:
				retval.append(((lastPos, pos), prevItem[:]))
			if len(prevItem) == 0:
				prevItem.append(item)
			else:
				prevItem[0] = item

			lastPos = pos
		except:
			print('CRASH!!!')
			pass

	if len(prevItem) > 0 and lastPos is not None:
		retval.append(((lastPos, totalTime), prevItem[:]))
	else:
		if not countOnly:
			retval.append(((0.0, totalTime), prevItem[:]))
	return retval

# Get Item at specific position
def getItemAtPos(itemRanges, pos):
	for itemRange in itemRanges:
		if pos >= itemRange[0][0] and pos < itemRange[0][1]:
			return itemRange[1]
	return ''

def hasItemAtPos(itemRanges, pos):
	for itemRange in itemRanges:
		if itemRange[0][0] is not None:
			if pos >= itemRange[0][0]-0.001 and pos < itemRange[0][0]+0.002:
				return itemRange[1]
	return None

def getSimpleTexts(noteObj):
	retval = []
	odrawObjs, drawObjs = getChildren(noteObj, 'drawObj')
	
	for drawObj in drawObjs:
		texts  = childElements(drawObj, 'text')
		if len(texts) > 0:
			for text in texts:
				try:
					content = text.getElementsByTagName('content')[0].firstChild.data
					fonts = text.getElementsByTagName('font')
					if fonts.length > 0:
						color = fonts[0].getAttribute('color')
						height = int(fonts[0].getAttribute('height'))
					content = re.sub('\n$', '', content)
					retval.append(content)
				except:
					pass
	return retval

def getObjTextScaledYcoordsByCriteria(noteObj, criteria):
	ys = []
	minY = None
	maxY = None
	if noteObj.localName == 'chord':
		odrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
		if drawObjs is not None and len(drawObjs) > 0:
			for drawObj in drawObjs:
				if isTextCriteriaDrawObj(drawObj, criteria):
					text = drawObj.gotoChild('text', False)
					# invert sign to treat higher text to higher note
					_y = -float(text.getAttribute('y'))
					if minY is None:
						minY = _y
					else:
						minY = min(minY, _y)
					if maxY is None:
						maxY = _y
					else:
						maxY = max(maxY, _y)
					ys.append(_y)
	scaled_ys = []
	if len(ys) < 2:
		return None
	for y in ys:
		scaled_ys.append(scale(y, minY, maxY, 0, 100))
	return scaled_ys

def deleteObjLyrics(noteObj):
	lyric = noteObj.gotoChild('lyric', False)
	if lyric is not None:
		noteObj.removeChild(lyric)

def deleteObjTextAttributesByCriteria(noteObj, criteria, keepDrawObjs=None):
	_keepDrawObjs = []
	if noteObj.localName in ['chord', 'rest']:
		odrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
		if drawObjs is not None and len(drawObjs) > 0:
			for drawObj in drawObjs:
				keepCount = 0
				if keepDrawObjs is not None:
					keepCount = keepDrawObjs.count(drawObj)
				if not isTextCriteriaDrawObj(drawObj, criteria) or keepCount > 0:
					_keepDrawObjs.append(drawObj)
		if len(drawObjs) > 0:
			noteObj.removeChild(odrawObjs)
		if len(_keepDrawObjs) > 0:
			drawObjects = noteObj.gotoChild('drawObjects', True)
			setChildren(drawObjects, _keepDrawObjs, 'drawObjects')

def deleteNoteObjTextObj(noteObj, textObj):
	odrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
	if drawObjs is not None and len(drawObjs) > 0:
		for drawObj in drawObjs:
			if drawObj == textObj:
				odrawObjs.removeChild(textObj)
	
def getObjTextObjsByCriteria(noteObj, criteria):
	texts = []
	if noteObj.localName == 'chord':
		odrawObjs, drawObjs = getChildren(noteObj, 'drawObjects')
		if drawObjs is not None and len(drawObjs) > 0:
			for drawObj in drawObjs:
				if isTextCriteriaDrawObj(drawObj, criteria):
					texts.append(drawObj)
	return texts

def isTextCriteriaDrawObj(drawObj, criteria):
	text = drawObj.gotoChild('text', False)
	richText = drawObj.gotoChild('richText', False)
	basic = drawObj.gotoChild('basic', False)
	transposable = drawObj.gotoChild('transposable', False)
	group = drawObj.gotoChild('group', False)
	if transposable is not None:
		if criteria == 'chord':
			return True
		else:
			return False
	if group is not None: 
		drOb = group.gotoChild('drawObj')
		if drOb is not None:
			if criteria == 'chord':
				return True
			else:
				return False
	if text is not None:
		font = text.gotoChild('font', False)
		if font is not None:
			face = font.getAttribute('face')
			# Music Symbol Criteria (capella3 font face)
			if face == 'capella3':
				if criteria == 'musicSymbol':
					return True
				else:
					return False
			height = font.getAttribute('height')
			try:
				height = int(height)
			except:
				height = 0
			color = font.getAttribute('color')
			# Global Fret Index Criteria (font color and size)
			if color == estFretIndexFontColor and height == estFretIndexFontSize:
				if criteria == 'globalFretIndex':
					return True
				else:
					return False
			# Local Fret Index Criteria (font color and size)
			if color == estFretIndexFontColor and height < estFretIndexFontSize:
				if criteria == 'localFretIndex':
					return True
				else:
					return False
		# Fingering criteria (placementHint)
		if basic is not None and basic.getAttribute('placementHint') == 'fingering':
			if criteria == 'fingering':
				return True
			else:
				return False 
		# Simple Text (without placementHint) or Performance Instruction (placementHint)
		if (basic is None) or (basic is not None and basic.getAttribute('placementHint') in ['', 'performanceInstruction']) :
			if criteria == 'simple':
				return True
			else:
				return False
	# Rich Text (richText Child is present)
	if richText is not None:
		if criteria == 'richText':
			return True
		else:
			return False
	return False

def setTextContent(drawObj, content):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		contentEl = text.gotoChild('content', False)
		if contentEl is not None:
			text.removeChild(contentEl)
		contentEl = text.gotoChild('content', True)
		textName = doc.createTextNode(content)
		contentEl.appendChild(textName)

def setTextSize(drawObj, height):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		font = text.gotoChild('font', True)
		font.setAttribute('height', height)

def setTextPos(drawObj, x, y):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		text.setAttribute('x', str(x))
		text.setAttribute('y', str(y))
	
def setPlacementHint(drawObj, hint):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		basic = text.gotoChild('basic', True)
		basic.setAttribute('placementHint', hint)

def getTextContent(drawObj):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		content = ''
		content_t = text.getElementsByTagName('content')[0].firstChild
		if content_t is not None:
			content = content_t.data
		return content
	return None

def getTextColor(drawObj):
	text = drawObj.gotoChild('text', False)
	if text is not None:
		font = text.gotoChild('font', False)
		if font is not None:
			color = font.getAttribute('color')
			return color
	return ''

def setTextColor(drawObj, new_color):
	text = drawObj.gotoChild('text', False)
	if text is None:
		return
	font = text.gotoChild('font', True)
	if new_color is None:
		color = font.getAttribute('color')
		if color is not None and color != '':
			font.removeAttribute('color')
	else:
		font.setAttribute('color', new_color)

def getPosByClefAndPitch(clef, pitch):
	treble_x_b5 = 1.2
	treble_y_b5 = 1.4
	treble_x_b6 = treble_x_b5
	treble_y_b6 = -2.03125
	ydiff_tone_step = (treble_y_b6 - treble_y_b5) / 8
	clef_mid_tone_map = {
		'treble': 'B4',
		'G2-': 'B3',
		'alto': 'C4',
		'tenor': 'A3',
		'bass': 'D3',
		'P3':'B4',
		'N2':'D5',
		'G2+': 'B5',
		'G1': 'D5',
		'C1': 'G4',
		'C2': 'E4',
		'C5': 'F3',
		'F3': 'F3',
		'F4-': 'D2',
		'F5': 'B2'
		}
	pitch_midline = clef_mid_tone_map.get(clef)
	f_pitch_midline = fullTonePitch(pitch_midline)
	f_pitch = fullTonePitch(pitch)
	f_pitch_offset = (f_pitch - f_pitch_midline) * ydiff_tone_step
	return(treble_x_b5, treble_y_b5+f_pitch_offset)

def getKey(noteObj):
	if noteObj.localName == 'keySign':
		fifths = noteObj.getAttribute('fifths')
		if fifths is not None:
			return int(fifths)
	return None

def getKeys(noteObj):
	key = getKey(noteObj)
	if key is not None:
		return str(key)
	return ''

def getKeyPositions(staff, countOnly = False):
	return getItemPositions(getKeys, staff, countOnly)
	
def getInstrumentMarkerPositions(staff, countOnly = False):
	return getItemPositions(getInstrumentMarker, staff, countOnly)

def getInstrumentMarker(noteObj):
	texts = getSimpleTexts(noteObj)
	instrumentNames = []
	for instrument in instruments:
		instrumentNames.append(instrument[0])
	for text in texts:
		if text in instrumentNames:
			return text
	return u''

def getStringNames(fretBoard, withOctave = False):
	retval = []

	openStrings = fretBoard[0]
	stringNames = []
	for string in openStrings:
		stringName = re.sub('(.*)?[0-9].*','\\1',string)
		stringNames.append(stringName)

	countedStrings = []
	for string in openStrings:
		stringName = re.sub('(.*)?[0-9].*','\\1',string)
		octave = re.sub('.*?([0-9].*)','\\1',string)
		count = stringNames.count(stringName)
		countedStrings.append(stringName)
		currentString = countedStrings.count(stringName)
		makeLower = False
		makeStroke = False
		if count <= 2:
			if currentString == 2:
				makeLower = True
		elif count == 3:
			if currentString == 1:
				makeStroke = True
			elif currentString == 3:
				makeLower = True
		elif count == 4:
			if currentString == 1:
				makeStroke = True
			elif currentString == 3:
				makeLower = True
			elif currentString == 4:
				makeStroke = True
				makeLower = True
		if makeStroke:
			stringName = stringName + "'"
		if makeLower:
			stringName = stringName.lower()
		if withOctave:
			stringName = stringName + octave
		retval.append(stringName)
	return retval

def setHeadPitch(head, pitch, step):
	head.setAttribute('pitch', pitch)
	if step is not None and step != 0:
		alter = head.gotoChild('alter', True)
		alter.setAttribute('step', str(step))
	else:
		alter = head.gotoChild('alter', False)
		if alter is not None:
			head.removeChild(alter)

def aca2capTone(tone, baseOct4):
	step = 0
	if len(tone) < 2:
		return (None, None)
	base = tone[0]
	tone = tone[1:]
	while tone[0] in [u'b', u'#']:
		if tone[0] == 'b':
			step = step - 1
		else:
			step = step + 1
		tone = tone[1:]
	if len(tone) < 1:
		return (None, None)
	octave = tone[0]
	if baseOct4:
		octave = str(int(octave) + 1)
	return (base+octave, step)

def sortTextObjTuples(textObjectTuples):
	ys = []
	for textObjectTuple in textObjectTuples:
		textObj, text = textObjectTuple 
		text_n = textObj.gotoChild('text', False)
		if text_n is not None:
			try:
				y = float(text_n.getAttribute('y'))
				ys.append((y, textObjectTuple))
			except:
				pass
	ys.sort(reverse=True)
	retval = []
	for y in ys:
		retval.append(y[1])
	return retval

def sortPitches(pitches):
	midiPitches = []
	for pitch in pitches:
		m_pitch = midiPitch(pitch)
		midiPitches.append((m_pitch, pitch))
	midiPitches.sort()
	retval = []
	for m_pitch in midiPitches:
		retval.append(m_pitch[1])
	return retval

def sortHeads(heads):
	midiPitches = []
	for head in heads:
		pitch = head.getAttribute('pitch')
		alter = head.gotoChild('alter')
		step = 0
		if alter is not None:
			tstep = alter.getAttribute('step')
			if tstep is not None and tstep != '':
				step = int(tstep)
		stepSigns = [ 'bb', 'b', '', '#', '##' ]
		if pitch is not None:
			tone = pitch[0]+stepSigns[step+2]+pitch[1]
			m_pitch = midiPitch(tone)
			midiPitches.append((m_pitch, head))

	midiPitches.sort()
	retval = []
	for m_pitch in midiPitches:
		retval.append(m_pitch[1])
	return retval

def getPitches(noteObj):
	retval = []
	oheads, heads = getChildren(noteObj, 'head')
	for head in heads:
		pitch = head.getAttribute('pitch')
		alter = head.gotoChild('alter')
		step = 0
		if alter is not None:
			tstep = alter.getAttribute('step')
			if tstep is not None and tstep != '':
				step = int(tstep)
		stepSigns = [ 'bb', 'b', '', '#', '##' ]
		if pitch is not None:
			retval.append(pitch[0]+stepSigns[step+2]+pitch[1])
	return retval

def transposeTone(tone, htOffset, use_sharp = True):
	m_pitch = midiPitch(tone) + htOffset
	return pitchFromMidi(m_pitch, use_sharp)

def force_sign(tone_s, sharp = True):
	flats  = ['Db', 'Eb', 'Gb', 'Ab', 'Bb']
	sharps = ['C#', 'D#', 'F#', 'G#', 'A#']
	srcs = flats
	tgts = sharps
	if sharp == False or sharp == 'b':
		srcs = sharps
		tgts = flats

	# in case of a list
	if type(tone_s) is list:
		retval = []
		for tone in tone_s:
			for i in range(0, 5):
				tone = re.sub('^'+srcs[i], tgts[i], tone)	
			retval.append(tone)
		return retval

	# in case of a string
	for i in range(0, 5):
		tone_s = re.sub('^'+srcs[i], tgts[i], tone_s)	
	return tone_s

def midiPitch(tone):
	key_notes = "CDEFGAB"
	step = 0
	i = key_notes.find(tone[0])
	tone = tone[1:]
	if len(tone) < 1:
		return None
	while tone[0] in [u'b', u'#']:
		if tone[0] == 'b':
			step = step - 1
		else:
			step = step + 1
		tone = tone[1:]
	n = (0,2,4,5,7,9,11)[i] + 12 * int(tone[0]) + step
	return n

def fullTonePitch(tone):
	key_notes = "CDEFGAB"
	step = 0
	i = key_notes.find(tone[0])
	tone = tone[1:]
	if len(tone) < 1:
		return None
	while tone[0] in [u'b', u'#']:
		if tone[0] == 'b':
			step = step - 1
		else:
			step = step + 1
		tone = tone[1:]
	n = i + 8 * int(tone[0])
	return n

def pitchFromMidi(n, use_sharp = True):
	octave = int(n)/12
	itone = int(n)%12
	tonesSharp = 'CCDDEFFGGAAB'
	tonesFlat  = 'CDDEEFGGAABB'
	steps      = '010100101010'
	toneSharp = tonesSharp[itone]
	toneFlat  = tonesFlat[itone]
	step = -int(steps[itone])
	tone = toneFlat

	# Use Sharp sign if set
	if use_sharp:
		tone = toneSharp
		step = -step

	sign = ''
	if step < 0:
		sign = 'b'
	elif step > 0:
		sign = '#'
	return tone+sign+str(octave)

def setInstrumentProperty(layout, tag, value):
	instrument = layout.gotoChild('instrument', True)
	names = instrument.getElementsByTagName(tag)
	for name in names:
		instrument.removeChild(name)
	instrument.setAttribute(tag, value)
	contentEl = doc.createElement(tag)
	textName = doc.createTextNode(value)
	contentEl.appendChild(textName)
	instrument.appendChild(contentEl)

# Determine FretIndex color and font size - keyword "Fret"
def getFretColorAndSize(score):
	# Search for Fret Keyword in Note Objects
	osystens, systems = getChildren(score, 'system')
	for system in systems:
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:	
			ovoices, voices = getChildren(staff, 'voice')
			staffName = staff.getAttribute('layout')
			for voice in voices:
				oObjs, objs = getChildren(voice, 'noteObjects')
				for i in range(0,len(objs)):
					obj = objs[i]
					texts = obj.getElementsByTagName('text')
					if texts.length > 0:
						for text in texts:
							try:
								content = text.getElementsByTagName('content')[0].firstChild.data
							except:
								content = ''
							fonts = text.getElementsByTagName('font')
							print('content=',content)
							if fonts.length > 0 and content.lower() in ['fret', 'fret!']:
								color = fonts[0].getAttribute('color')
								height = int(fonts[0].getAttribute('height'))
								return (color, height, staffName)

	return (u'', 0, u'-')

def analyzeScore(score):
	global estSourceStaffName
	global estHasFinalStaff
	global estHasALNstaff
	global estFretIndexFontSize	
	global estFretIndexFontColor
	global estFretIndexStaffName
	global estStringsAndPitches
	global isAln

	estStringsAndPitches = []
	estHasFinalStaff = False
	isAln = False

	estFretIndexFontColor, estFretIndexFontSize, estFretIndexStaffName = getFretColorAndSize(score)
	if estFretIndexStaffName != u'-':
		isAln = True
	alnStaves = getStavesByLayout(score, setGenStaffName)
	if len(alnStaves) > 0:
		estHasALNstaff = True

	selected_system = curSelection()[0][0]
	selected_staff = curSelection()[0][1]
	selected_voice = curSelection()[0][2]
	selected_obj = curSelection()[0][3]

	osystems, systems = getChildren(score, 'system')
	systemID = 0
	for system in systems:
		staffID = 0
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:
			staffName = staff.getAttribute('layout')
			if selected_staff == staffID and selected_system == systemID:
				estSourceStaffName = staffName
			if staffName == setGenStaffName:
				estHasFinalStaff = True
			staffID = staffID + 1
		systemID = systemID + 1
	

	sourceStaves = getStavesByLayout(score, estSourceStaffName)
	wholeSourceStaff = concatStaves(sourceStaves)
	wTotalTime = getStaffTotalTime(wholeSourceStaff)

	wInstrumentMarkerPositions = getInstrumentMarkerPositions(wholeSourceStaff)
	wInstrumentMarkerPosRanges = genItemPositionRanges(wInstrumentMarkerPositions, wTotalTime, [])
	print('whole instrument markers', wInstrumentMarkerPosRanges)

	wKeyPositions = getKeyPositions(wholeSourceStaff)
	wKeyPosRanges = genItemPositionRanges(wKeyPositions, wTotalTime, ['0'])
	print('whole keys', wKeyPosRanges)

	instrumentNames = []
	for instrument in instruments:
		instrumentNames.append(instrument[0])

	osystems, systems = getChildren(score, 'system')
	systemID = 0
	pos = 0.0
	for system in systems:
		staffID = 0
		ostaves, staves = getChildren(system, 'staff')
		pos_save_system = pos
		for staff in staves:
			pos = pos_save_system
			voiceID = 0
			ovoices, voices = getChildren(staff, 'voice')
			pos_save_staff = pos
			for voice in voices:
				objID = 0
				pos = pos_save_staff
				oNoteObjs, noteObjs = getChildren(voice, 'noteObjects')
				for obj in noteObjs:
					dura = getObjDuration(obj)
					if dura is None:
						dura = 0
					if selected_system == systemID and selected_staff == staffID and selected_voice == voiceID and selected_obj == objID:
						if obj.localName == 'chord':
							text_objs = getObjTextObjsByCriteria(obj, 'fingering')
							oHeads, heads = getChildren(obj, 'head')
							for text_obj in text_objs:
								text=getTextContent(text_obj).strip()
								if text.startswith(':'):
									text = re.sub(':(.*)','\\1',text)
								string = re.sub('(.*?)[0-9].*','\\1',text)
								rest = re.sub('.*?([0-9].*)','\\1',text)
								fret = re.sub('([0-9]*),*.*','\\1',rest)
								try:
									fret_i = int(fret)
								except:
									fret_i = 0
								instrument_t = getItemAtPos(wInstrumentMarkerPosRanges, pos)
								key = getItemAtPos(wKeyPosRanges, pos)
								if type(instrument_t) is list:
									if len(instrument_t)>0:
										instrument_t = instrument_t[0]
									else:
										instrument_t = ''
									if instrumentNames.count(instrument_t) > 0:
										instrument_index = instrumentNames.index(instrument_t)
										instrument = instruments[instrument_index]
										stringNames = getStringNames([instrument[1]])	
									else:
										instrument = instruments[setInstrumentIndex]
										stringNames = getStringNames([instrument[1]])	
									if stringNames.count(string) > 0:
										string_index = stringNames.index(string)
										base_tone = instrument[1][string_index]
										tone = transposeTone(base_tone, fret_i)
										use_sharps = True
										if key < 0:
											use_sharps = False
										tone = force_sign(tone, use_sharps)
										estStringsAndPitches.append((string+fret,tone))
					pos = pos + dura
					objID = objID + 1
				voiceID = voiceID + 1
			staffID = staffID + 1
		systemID = systemID + 1
	


# al.Editor main function
def aLEditorMain(score):
	sourceStaves = getStavesByLayout(score, estSourceStaffName)
	wholeSourceStaff = concatStaves(sourceStaves)
	wTotalTime = getStaffTotalTime(wholeSourceStaff)

	wInstrumentMarkerPositions = getInstrumentMarkerPositions(wholeSourceStaff)
	wInstrumentMarkerPosRanges = genItemPositionRanges(wInstrumentMarkerPositions, wTotalTime, [])
	print('whole instrument markers', wInstrumentMarkerPosRanges)

	wKeyPositions = getKeyPositions(wholeSourceStaff)
	wKeyPosRanges = genItemPositionRanges(wKeyPositions, wTotalTime, ['0'])
	print('whole keys', wKeyPosRanges)

	layout = score.gotoChild("layout")
	ostaffLayouts, staffLayouts = getChildren(layout, 'staffLayout', forcedPlural = 'staves')
	lastLayout = None
	finalStaffLayout = None
	for staffLayout in staffLayouts:
		description = staffLayout.getAttribute('description')	
		lastLayout = staffLayout
		if description == setGenStaffName:
			finalStaffLayout = staffLayout.cloneNode(deep=True)

	if finalStaffLayout == None:
		finalStaffLayout = lastLayout.cloneNode(deep=True)
		finalStaffLayout.removeAttribute('description')
		finalStaffLayout.setAttribute('description', setGenStaffName)
		setInstrumentProperty(finalStaffLayout, 'name', setInstrumentName)
		setInstrumentProperty(finalStaffLayout, 'abbrev', '')
		sound = finalStaffLayout.gotoChild('sound', True)
		sound.setAttribute('volume', str(80))

	reCreate = False
	if not estHasFinalStaff:
		staffLayouts.extend([finalStaffLayout])
	setChildren(ostaffLayouts, staffLayouts, 'staves')


	instrumentNames = []
	for instrument in instruments:
		instrumentNames.append(instrument[0])

	selected_staff = curSelection()[0][1]
	selected_system = curSelection()[0][0]
	osystems, systems = getChildren(score, 'system')
	systemID = 0
	pos = 0.0
	clef = 'treble'

	extraDistTopList = []
	for system in systems:
		staffID = 0
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:
			extraDistance = staff.gotoChild('extraDistance', False)
			if extraDistance is not None:
				top = extraDistance.getAttribute('top')
				if top is not None:
					extraDistTopList.append((systemID, staffID, top))
			else:
				extraDistTopList.append((systemID, staffID, 0))
			staffID = staffID + 1
		systemID = systemID + 1

	systemID = 0
	for system in systems:
		staffID = 0
		new_staves = []
		ostaves, staves = getChildren(system, 'staff')
		for staff in staves:
			layout = staff.getAttribute('layout')
			if selected_staff == staffID:
				origStaff = staff
				if layout == setGenStaffName:
					msgCursorInFinalStaff()
					return
			reCreate = False
			if layout == setGenStaffName:
				reCreate = True
			staffID = staffID + 1
		save_pos_system = pos
		staffID = 0
		for staff in staves:
			pos = save_pos_system
			voiceID = 0
			new_staff = None
			save_pos_staff = pos
			if selected_system == systemID or setCreateEditorStaffMode == 0:
				if not reCreate and selected_staff == staffID:
					new_staff = origStaff.cloneNode(deep = True)
					new_staff.setAttribute('layout', setGenStaffName)
					index = None
					idx = 0
					for distElem in extraDistTopList:
						if distElem[0]==systemID and distElem[1]==staffID:
							index = idx
						idx = idx + 1
					if index is not None:
						top = extraDistTopList[min(index + 1, len(extraDistTopList)-1)][2]
						extraDistance = new_staff.gotoChild('extraDistance', True)
						try:
							distance = int(top)+int(setDistAdditional)
						except:
							distance = 0
						extraDistance.setAttribute('top', str(distance))
				elif staff.getAttribute('layout') == setGenStaffName:
					new_staff = staff
					clearStaff(new_staff)
					origClone = origStaff.cloneNode(deep = True)
					copyStaff(origClone, new_staff)
				if new_staff is not None:
					ovoices, voices = getChildren(new_staff, 'voice')
					for voice in voices:
						pos = save_pos_staff
						onoteObjs, noteObjs = getChildren(voice, 'noteObjects')
						for obj in noteObjs:
							dura = 0.0
							if obj.localName == 'clefSign':
								clef = obj.getAttribute('clef')
							if obj.localName in [ 'rest', 'chord' ]:
								dura = getObjDuration(obj)
								texts = []
								textsToDelete = []
								pitches = []
								fingeringWithTextObjs = []
								localFretIndexWithTextObjs = []
								altNotesWithTextObjs = []
								fingering_text_objs = getObjTextObjsByCriteria(obj, 'fingering')
								local_fret_text_objs = getObjTextObjsByCriteria(obj, 'localFretIndex')

								for local_fret_text_obj in local_fret_text_objs:
									text=getTextContent(local_fret_text_obj).strip()
									localFretIndexWithTextObjs.append((local_fret_text_obj, text))

								for fingering_text_obj in fingering_text_objs:
									text=getTextContent(fingering_text_obj)
									text = text.replace(' ', '')
									altNote = ''
									color = getTextColor(fingering_text_obj)

									if text.startswith(':') or color != '':
										text = re.sub(':(.*)','\\1',text).strip()
										if bool(setAltNoteMarker):
											altNote = text
									stexts = text.split(',')

									string = ''
									fret = ''
									finger = ''
									localFretIndex = ''
									for stext in stexts:
										if len(stext) > 0 and len(stexts) in range(1,4):
											if ord(stext[0].lower()) in range(ord('a'),ord('h')+1):
												string = re.sub('(.*?)[0-9].*','\\1',stext)
												fret = re.sub('.*?([0-9]+).*','\\1',stext)
											elif stext[0] == '/':
												localFretIndex = stext[1:]
											elif ord(stext[0]) in range(ord('0'), ord('9')+1) or stext[0] == '-':
												finger = stext
									try:
										fret_i = int(fret)
									except:
										fret_i = 0
									texts.append(string)
									instrument_t = getItemAtPos(wInstrumentMarkerPosRanges, pos)
									key = getItemAtPos(wKeyPosRanges, pos)
									if type(instrument_t) is list:
										if len(instrument_t)>0:
											instrument_t = instrument_t[0]
										else:
											instrument_t = ''
									instrument_index = None
									if instrumentNames.count(instrument_t) > 0:
										instrument_index = instrumentNames.index(instrument_t)
										instrument = instruments[instrument_index]
										stringNames = getStringNames([instrument[1]])	
									else:
										instrument = instruments[setInstrumentIndex]
										stringNames = getStringNames([instrument[1]])	
									text_obj_ordered = False
									if stringNames.count(string) > 0:
										string_index = stringNames.index(string)
										base_tone = instrument[1][string_index]
										tone = transposeTone(base_tone, fret_i)
										delete = True
										#if altNote != '' and bool(setAltNoteMarker):
											#altNotesWithTextObjs.append((fingering_text_obj, altNote))
											#text_obj_ordered = True
											#delete = False
										if localFretIndex != '':
											if text_obj_ordered:
												fingering_text_obj = fingering_text_obj.cloneNode(deep=True)
												drawObjs = obj.gotoChild('drawObjects', False)
												drawObjs.appendChild(fingering_text_obj)
											localFretIndexWithTextObjs.append((fingering_text_obj, localFretIndex))
											text_obj_ordered = True
											delete = False
										if finger != '' or altNote != '':
											if text_obj_ordered:
												fingering_text_obj = fingering_text_obj.cloneNode(deep=True)
												drawObjs = obj.gotoChild('drawObjects', False)
												drawObjs.appendChild(fingering_text_obj)
											text = finger
											if altNote != '':
												text = altNote
											fingeringWithTextObjs.append((fingering_text_obj, text))
											text_obj_ordered = True
											delete = False
										if delete:
											textsToDelete.append(fingering_text_obj)
										use_sharps = True
										if key < 0:
											use_sharps = False
										tone = force_sign(tone, use_sharps)
										pitches.append(tone)
									elif finger != '':
										if text_obj_ordered:
											fingering_text_obj = fingering_text_obj.cloneNode(deep=True)
											drawObjs = obj.gotoChild('drawObjects', False)
											drawObjs.appendChild(fingering_text_obj)
										fingeringWithTextObjs.append((fingering_text_obj, finger))
								if not setChordsShow:
									deleteObjTextAttributesByCriteria(obj, 'chord')
								if not setSimpleTextShow:
									deleteObjTextAttributesByCriteria(obj, 'simple')
								if not setMusicSymbolsShow:
									deleteObjTextAttributesByCriteria(obj, 'musicSymbol')
								if not setRichTextShow:
									deleteObjTextAttributesByCriteria(obj, 'richText')
								if not setLyricsShow:
									deleteObjLyrics(obj)
								oheads, heads = getChildren(obj, 'head')
								pitches = sortPitches(pitches)
								fingeringWithTextObjs = sortTextObjTuples(fingeringWithTextObjs)
								localFretIndexWithTextObjs = sortTextObjTuples(localFretIndexWithTextObjs)
								heads = sortHeads(heads)
								if len(pitches) == len(heads):
									for textToDelete in textsToDelete:
										deleteNoteObjTextObj(obj, textToDelete)
									fingeringTextID = 0
									for fingeringWithTextObj in fingeringWithTextObjs:
										textObj, content = fingeringWithTextObj
										setTextContent(textObj, content)
										setPlacementHint(textObj, 'fingering')
										setTextSize(textObj, setFingeringFontSize)
										if len(pitches) == len(fingeringWithTextObjs):
											x,y = getPosByClefAndPitch(clef, pitches[fingeringTextID])
											if content == '-':
												y = y - 0.5
												x = x + 0.1
											setTextPos(textObj, x, y)
										fingeringTextID = fingeringTextID + 1
									localFretIndexTextID = 0
									for localFretIndexWithTextObj in localFretIndexWithTextObjs:
										textObj, content = localFretIndexWithTextObj
										setTextContent(textObj, content)
										setTextSize(textObj, setLocalFretIndexFontSize)
										setTextColor(textObj, estFretIndexFontColor)
										if len(pitches) == len(localFretIndexWithTextObjs):
											x,y = getPosByClefAndPitch(clef, pitches[localFretIndexTextID])
											if content == '-':
												y = y - 0.5
												x = x + 0.1
											setTextPos(textObj, x, y)
										localFretIndexTextID = localFretIndexTextID + 1
									for altNotesWithTextObj in altNotesWithTextObjs:
										textObj, content = altNotesWithTextObj
										setTextContent(textObj, content)
										setPlacementHint(textObj, 'fingering')
										setTextSize(textObj, setFingeringFontSize)
										setTextColor(textObj, None)
									pitchIdx = 0
									for head in heads:
										capTone = aca2capTone(pitches[pitchIdx], True)
										setHeadPitch(head, capTone[0], capTone[1])
										pitchIdx = pitchIdx + 1
							pos = pos + dura
						voiceID = voiceID + 1
					if not reCreate:
						new_staves.append(new_staff)
			staffID = staffID + 1
		staves.extend(new_staves)
		setChildren(ostaves, staves, 'staves')
		systemID = systemID + 1
	global embeddedFileCopyList
	embeddedFileCopyList = getReAlignFileCopyList(score)

def msgCursorInFinalStaff():
	messageBox('Zeilenauswahl falsch', 'Cursor darf nicht in der Ziel-Notenzeile stehen.')

# Analyze Phase
class ScoreAnalyze(ScoreChange):
	def changeScore(self, score):
		global doc
		doc = score.parentNode
		analyzeScore(score)

# Plugin Processing Phase
class ScoreChange(ScoreChange):
	def changeScore(self, score):
		global doc
		doc = score.parentNode
		aLEditorMain(score)

class Config:
	def __init__(self):
		self.file = ScriptOptions()
		self.dic = {}

	def get(self, varName, defValue = None):
		try:
			value = self.dic.get(varName)
			if defValue is not None:
				if value is None:
					value = defValue
					if type(defValue) is bool:
						defValue = str(value).lower
					elif type(defValue) is int:
						defValue = int(value)
					self.set(varName, defValue)
				else:
					if type(defValue) is bool:
						if value == 'true':
							value = True
						elif value == 'false':
							value = False
						elif value == '1':
							value = True
						elif value == '0':
							value = False
					if type(defValue) is int:
						value = int(value)
			return value
		except:
			pass
		return None
	
	def set(self, varName, value):
		if type(value) is bool:
			value = str(value).lower()
		if type(value) is int:
			value = str(value)
		self.dic.update({varName: value})

	def load(self):
		try:
			self.dic = self.file.get()
			for key in self.dic.keys():
				value = self.dic.get(key)
				if value == 'false':
					value = False
				elif value == 'true':
					value = True
				self.dic.update({key: value})
			print("loaded", self.dic)
		except:
			print("Could not open configuration file",self.file)

	def save(self):
		try:
			for key in self.dic.keys():
				value = self.dic.get(key)
				if type(value) is bool:
					value = str(value).lower()
				self.dic.update({key: value})
			self.file.set(self.dic)
		except:
			print("Could not save configuration file",self.file)

config = Config()
config.load()
setGenStaffName = config.get('setGenStaffName', 'Final')
setInstrumentIndex = config.get('setInstrumentIndex', 0)


instruments = [
	('S6 Gitarre Std', ['E3', 'A3', 'D4', 'G4', 'B4', 'E5'], 5),
	('S7 Gitarre Std', ['B2', 'E3', 'A3', 'D4', 'G4', 'B4', 'E5'], 5),
	('S4 Bass Std', ['E2', 'A2', 'D3', 'G3'], 5),
	('S5 Bass Std',['B1', 'E2', 'A2', 'D3', 'G3'], 5),
	('S4 acaBass-Low', ['E3', 'A3', 'D4', 'G4'], 5),
	('S4 acaBass-High', ['E4', 'A4', 'D5', 'G5'], 5),
	('S5 acaBass-Low', ['B2', 'E3', 'A3', 'D4', 'G4'], 5),
	('S5 acaBass-High', ['B3', 'E4', 'A4', 'D5', 'G5'], 5),
	('S4 Ukulele Std', ['G4', 'C4', 'E4', 'A4'], 5),
	('S4 Ukulele aLT', ['G5', 'C4', 'E4', 'A4'], 5),
	('S4 Mandoline Std', ['G3', 'D4', 'A4', 'E5'], 7),
	('S6 Gitarre DADGAD', ['D3', 'A3', 'D4', 'G4', 'A4', 'D5'], 7),
	('S6 Gitarre Open-C', ['C3', 'G3', 'C4', 'G4', 'C5', 'E5'], 7),
	('S6 Gitarre Drop-C', ['C3', 'G3', 'C4', 'F4', 'A4', 'D5'], 7),
	('S6 Gitarre Open-D', ['D3', 'A3', 'D4', 'F#4', 'A4', 'D5'], 7),
	('S6 Gitarre Open-Dm', ['D3', 'A3', 'D4', 'F4', 'A4', 'D5'], 7),
	('S6 Gitarre Drop-D', ['D3', 'A3', 'D4', 'G4', 'B4', 'E5'], 7),
	('S6 Gitarre Double-Drop-D', ['D3', 'A3', 'D4', 'G4', 'B4', 'D5'], 7),
	('S6 Gitarre Open-E', ['E3', 'B3', 'E4', 'G#4', 'B4', 'E5'], 7),
	('S6 Gitarre Open-Em', ['E3', 'B3', 'E4', 'G4', 'B4', 'E5'], 7),
	('S6 Gitarre Drop-E', ['E3', 'B3', 'E4', 'A4', 'C#5', 'F#5'], 7),
	('S6 Gitarre Open-G', ['D3', 'G3', 'D4', 'G4', 'B4', 'D5'], 7),
	('S6 Gitarre Open-Gm', ['D3', 'G3', 'D4', 'G4', 'Bb4', 'D5'], 7),
	('S6 Gitarre Open-A1', ['E3', 'A3', 'E4', 'A4', 'C#5', 'E5'], 7),
	('S6 Gitarre Open-A2', ['E3', 'A3', 'C#4', 'E4', 'A4', 'E5'], 7),
	('S6 Gitarre Open-Am1', ['E3', 'A3', 'E4', 'A4', 'C5', 'E5'], 7),
	('S6 Gitarre Open-Am2', ['E3', 'A3', 'C4', 'E4', 'A4', 'E5'], 7),
	('S7 Dummy Test', ['E2', 'E3', 'A3', 'E4', 'G4', 'B4', 'E5'], 5),
	('S7 Dummy TestB', ['B2', 'B3', 'D4', 'B4', 'D5', 'F5', 'B5'], 5),
	('S7 Dummy TestBb', ['Bb2', 'Bb3', 'D4', 'Bb4', 'D5', 'F5', 'Bb5'], 5),
]

# Plugin Dialog
def dialog():
	global config
	global setGenStaffName
	global setInstrumentName
	global setDistAdditional
	global setFingeringFontSize
	global setLocalFretIndexFontSize
	global setAltNoteMarker
	global setCreateEditorStaffMode
	global setInstrumentIndex
	global setRichTextShow
	global setMusicSymbolsShow
	global setSimpleTextShow
	global setChordsShow
	global setLyricsShow

	global estSourceStaffName
	global estHasFinalStaff
	global estStringsAndPitches

	global editGenStaffName
	global editInstrumentName
	global editDistAdditional
	global editFingeringFontSize
	global editLocalFretIndexFontSize
	global checkAltNoteMarker
	global radioCreateEditorStaff
	global comboInstrument
	global checkReset
	global checkSimpleTextShow
	global checkRichTextShow
	global checkMusicSymbolsShow
	global checkChordsShow
	global checkLyricsShow

	checkReset = CheckBox('reset', value = False)

	setColorVoice1 = 0
	setColorVoice2 = 0
	setColorVoice3 = 0
	config.load()
	setGenStaffName = config.get('setGenStaffName', 'Final')
	setInstrumentName = config.get('setInstrumentName', 'Instr.')
	setDistAdditional = config.get('setDistAdditional', 0)
	setFingeringFontSize = config.get('setFingeringFontSize', 8)
	setLocalFretIndexFontSize = config.get('setLocalFretIndexFontSize', 8)
	setAltNoteMarker = config.get('setAltNoteMarker', True)
	setCreateEditorStaffMode = config.get('setCreateEditorStaffMode', 0)
	setSimpleTextShow = config.get('setSimpleTextShow', True)
	setRichTextShow = config.get('setRichTextShow', True)
	setMusicSymbolsShow = config.get('setMusicSymbolsShow', True)
	setChordsShow = config.get('setChordsShow', True)
	setLyricsShow = config.get('setLyricsShow', True)

	wdt = 36
	wdtFoffs = -4 
	spacer = Label('')

	labelGenStaffName = Label('Ziel-Notenzeilenname', width = wdt / 2)
	editGenStaffName = Edit(value = setGenStaffName, width = wdt / 3)
	hBoxGenStaffName = HBox([labelGenStaffName, editGenStaffName])
	labelInstrumentName = Label('Instrumentenbezeichnung', width = wdt / 2)
	editInstrumentName = Edit(value = setInstrumentName, width = wdt / 3)
	hBoxInstrumentName = HBox([labelInstrumentName, editInstrumentName])
	sHasFinalStaff = 'nein'
	if estHasFinalStaff:
		sHasFinalStaff = 'ja'
	labelEstHasFinalStaff = Label('Zu erzeugende Notenzeile vorhanden: '+sHasFinalStaff, width = wdt-1)
	labelDists = Label('Nur bei Erst-Erzeugung der Notenzeile:', width = wdt + 5)
	labelDistAdditional = Label('Zusätzlicher Abstand zur Notenzeile nach oben', width = wdt + wdtFoffs)
	editDistAdditional = Edit(value = str(setDistAdditional), width = wdt/3-wdtFoffs - 8)
	hBoxDistAdditional = HBox([labelDistAdditional, editDistAdditional], width = wdt)
	labelFingeringFontSize = Label('Fingersatz-Fontgröße', width=wdt+wdtFoffs)
	editFingeringFontSize = Edit(value = str(setFingeringFontSize), width = wdt/3 - wdtFoffs - 8)
	hBoxFingeringFontSize = HBox([labelFingeringFontSize, editFingeringFontSize])
	labelLocalFretIndexFontSize = Label('Lokaler Fret-Index-Fontgröße', width=wdt+wdtFoffs)
	editLocalFretIndexFontSize = Edit(value = str(setLocalFretIndexFontSize), width = wdt/3 - wdtFoffs - 8)
	hBoxLocalFretIndexFontSize = HBox([labelLocalFretIndexFontSize, editLocalFretIndexFontSize])
	checkAltNoteMarker = CheckBox("Kennz.  von Alternativ-Ton-Positionen farbig oder mit ':'", value=setAltNoteMarker, width = wdt+3)
	vBoxDisplayProps = VBox([hBoxFingeringFontSize, hBoxLocalFretIndexFontSize, checkAltNoteMarker])
	hBoxDisplayProps = HBox([vBoxDisplayProps], text = 'Anzeige')
	vBoxProperties = VBox([hBoxGenStaffName, hBoxInstrumentName, labelEstHasFinalStaff, labelDists, hBoxDistAdditional])
	hBoxProperties = HBox([vBoxProperties], text = 'Einstellbare Eigenschaften für vom Editor erzeugte Notenzeile', width = wdt)

	labelSourceStaffName = Label('Quell-Notenzeilenname (an Cursor): ' + estSourceStaffName, width = wdt)
	optsCreateEditorStaff = ['In allen Systemen', 'Nur in Cursor-System']
	radioCreateEditorStaff = Radio(optsCreateEditorStaff, text = 'aL.Editor Notenzeile erstellen', value = setCreateEditorStaffMode)
	vBoxSettings = VBox([labelSourceStaffName, spacer, radioCreateEditorStaff])
	hBoxSettings = HBox([vBoxSettings], text = 'Einstellungen')
	instrumentOptions = []
	for instrument in instruments:
		stringNames = getStringNames([instrument[1]], withOctave=True)
		joinedStringNames = str.join(',', stringNames)
		instrumentOptions.append(instrument[0]+' ('+joinedStringNames+'), '+str(instrument[2]))
	labelInstrument = Label('Instrument / Stimmung / Saiten / Zyklus', width = wdt - 6)
	comboInstrument = ComboBox(instrumentOptions, value=setInstrumentIndex, width = wdt - 6)
	stringList = [item[0] for item in estStringsAndPitches]
	pitchList = [item[1] for item in estStringsAndPitches]
	stringPitchInfo = 'k.A.'
	if len(stringList) > 0:
		stringPitchInfo = str.join(',', stringList) + ' → ' + str.join(',', pitchList)
	labelStringsAndPitches = Label('Saite/Bund → Notenname (an Cursor): '+stringPitchInfo, width=wdt)
	checkReset = CheckBox('EINSTELLUNGEN ZURÜCKSETZEN', value=False, width=wdt)
	checkSimpleTextShow = CheckBox('Einfach-Text "T" anzeigen', value = setSimpleTextShow, width=wdt-6)
	checkRichTextShow = CheckBox('Textfeld "abc" anzeigen', value = setRichTextShow, width=wdt-6)
	checkMusicSymbolsShow = CheckBox('Capella3 Musik-Textzeichen anzeigen', value = setMusicSymbolsShow, width=wdt-6)
	checkChordsShow = CheckBox('Chords/Steps/"T"-Gruppen anzeigen', value = setChordsShow, width=wdt-6)
	checkLyricsShow = CheckBox('Liedtext anzeigen', value = setLyricsShow, width=wdt-6)
	vBoxPlugin = VBox([checkReset, checkSimpleTextShow, checkRichTextShow, checkMusicSymbolsShow, checkChordsShow, checkLyricsShow])
	hBoxPlugin = HBox([vBoxPlugin], text = 'Plugin-Anwendung')

	vBoxLeft = VBox([hBoxProperties, hBoxDisplayProps])
	vBoxRight = VBox([hBoxSettings, labelInstrument, spacer, comboInstrument, labelStringsAndPitches, spacer, spacer, hBoxPlugin])

	hBox = HBox([vBoxLeft, spacer, vBoxRight])
	vBox = VBox([hBox])
	dlg = Dialog('aL.Editor', vBox)
	return dlg

if activeScore():
	tempInput = tempfile.mktemp('.capx')
	tempOutput = tempfile.mktemp('.capx')
	activeScore().write(tempInput)
	ScoreAnalyze(tempInput, tempOutput)
	os.remove(tempInput)
	os.remove(tempOutput)

	dialogPass = False
	while not dialogPass:
		dialogOkClicked = dialog().run()
		setReset = checkReset.value()
		if setReset and dialogOkClicked:
			config.dic = {}
			dialogStatus = False
			dialogPass = False
			config.save()
		else:
			dialogPass = True

	if dialogOkClicked:
		setGenStaffName = editGenStaffName.value()
		setInstrumentName = editInstrumentName.value()
		setDistAdditional = editDistAdditional.value()
		setFingeringFontSize = editFingeringFontSize.value()
		setLocalFretIndexFontSize = editLocalFretIndexFontSize.value()
		setAltNoteMarker = checkAltNoteMarker.value()
		setCreateEditorStaffMode = radioCreateEditorStaff.value()
		setInstrumentIndex = comboInstrument.value()
		setSimpleTextShow = checkSimpleTextShow.value()
		setRichTextShow = checkRichTextShow.value()
		setMusicSymbolsShow = checkMusicSymbolsShow.value()
		setChordsShow = checkChordsShow.value()
		setLyricsShow = checkLyricsShow.value()
		config.set("setGenStaffName", setGenStaffName)
		config.set("setInstrumentName", setInstrumentName)
		config.set("setDistAdditional", setDistAdditional)
		config.set("setFingeringFontSize", setFingeringFontSize)
		config.set("setLocalFretIndexFontSize", setLocalFretIndexFontSize)
		config.set("setAltNoteMarker", setAltNoteMarker)
		config.set("setCreateEditorStaffMode", setCreateEditorStaffMode)
		config.set("setInstrumentIndex", setInstrumentIndex)
		config.set("setSimpleTextShow", setSimpleTextShow)
		config.set("setRichTextShow", setRichTextShow)
		config.set("setMusicSymbolsShow", setMusicSymbolsShow)
		config.set("setChordsShow", setChordsShow)
		config.set("setLyricsShow", setLyricsShow)

		print(config.dic)
		config.save()

		activeScore().registerUndo("aL.Editor")
		tempInput = tempfile.mktemp('.capx')
		tempOutput = tempfile.mktemp('.capx')
		activeScore().write(tempInput)
		global embeddedFileCopyList
		embeddedFileCopyList = []

		# embeddedFileCopyList is set after ScoreChange
		ScoreChange(tempInput, tempOutput)
		# tempOutput must be extended by
		# new (obj___X)-Files here (e.g. Rich Text)
		# listed in embeddedFileCopyList
		if len(embeddedFileCopyList) > 0:
			copyAndRepackFiles(tempOutput, embeddedFileCopyList)

		activeScore().read(tempOutput)
		os.remove(tempInput)
		os.remove(tempOutput)