diff --git a/chordsheet/document.py b/chordsheet/document.py
index c636c92..41c2b89 100644
--- a/chordsheet/document.py
+++ b/chordsheet/document.py
@@ -22,16 +22,10 @@ class Style:
self.unitWidth = kwargs.get('unitWidth', 10)
self.useIncludedFont = True
+ self.numberPages = True
self.separatorSize = 5*self.unit
- self.stringHzSp = 20*self.unit
- self.stringHzGap = 2*self.unit
- self.stringHeight = 5*self.unit
-
- self.unitHeight = 20*self.unit
- self.beatsHeight = 5*self.unit
-
self.titleFontSize = 24
self.subtitleFontSize = 18
self.creditsFontSize = 12
@@ -190,9 +184,8 @@ class Document:
ET.SubElement(chordElement, "voicing", attrib={
'instrument': 'guitar'}).text = ','.join(c.voicings['guitar'])
if inst == 'piano':
- # return first element of list as feature has not been implemented
ET.SubElement(chordElement, "voicing", attrib={
- 'instrument': 'piano'}).text = c.voicings['piano'][0]
+ 'instrument': 'piano'}).text = ','.join(c.voicings['piano'])
for n, s in enumerate(self.sectionList):
sectionElement = ET.SubElement(root, "section", attrib={
diff --git a/chordsheet/parsers.py b/chordsheet/parsers.py
index 7f4f757..6cdc462 100644
--- a/chordsheet/parsers.py
+++ b/chordsheet/parsers.py
@@ -10,11 +10,13 @@ def parseFingering(fingering, instrument):
if len(fingering) == numStrings: # if the fingering is entered in concise format e.g. xx4455
output = list(fingering)
else: # if entered in long format e.g. x,x,10,10,11,11
- output = [x for x in fingering.split(',')]
+ output = fingering.split(",")
if len(output) == numStrings:
return output
else:
raise Exception("Voicing <{}> is malformed.".format(fingering))
+ elif instrument == 'piano':
+ return [parseName(note).upper() for note in fingering.split(",")]
else:
return [fingering]
@@ -22,7 +24,6 @@ def parseFingering(fingering, instrument):
# dictionary holding text to be replaced in chord names
nameReplacements = {"b": "♭", "#": "♯"}
-
def parseName(chordName):
"""
Replaces symbols in chord names.
@@ -30,4 +31,4 @@ def parseName(chordName):
parsedName = chordName
for i, j in nameReplacements.items():
parsedName = parsedName.replace(i, j)
- return parsedName
+ return parsedName
\ No newline at end of file
diff --git a/chordsheet/render.py b/chordsheet/render.py
index 35ddcd3..733f3ec 100644
--- a/chordsheet/render.py
+++ b/chordsheet/render.py
@@ -6,6 +6,7 @@ from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.colors import black, white
from reportlab.platypus import BaseDocTemplate, Spacer, Paragraph, Flowable, Frame, PageTemplate, PageBreak
from chordsheet.document import Block
@@ -71,9 +72,12 @@ class GuitarChart(Flowable):
self.style = style
self.guitarChordList = [
c for c in chordList if 'guitar' in c.voicings.keys()]
- self.chartMargin = 15*mm
+ self.chartMargin = 13*mm
self.nStrings = 6
- self.headingSize = 18
+
+ self.stringHzSp = 20*mm
+ self.stringHzGap = 2*mm
+ self.stringHeight = 5*mm
self.spaceAfter = self.style.separatorSize
@@ -84,21 +88,21 @@ class GuitarChart(Flowable):
def wrap(self, availWidth, availHeight):
self.nChords = trunc((availWidth - self.chartMargin -
- self.style.stringHzGap) / self.style.stringHzSp)
+ self.stringHzGap) / self.stringHzSp)
# the height of one layer of chart
- self.oneHeight = self.style.stringHeight * (self.nStrings+1)
+ self.oneHeight = self.stringHeight * (self.nStrings+1)
# only one line needed
if len(self.guitarChordList) <= self.nChords:
- self.width = self.chartMargin + self.style.stringHzGap + self.style.stringHzSp * \
+ self.width = self.chartMargin + self.stringHzGap + self.stringHzSp * \
len(self.guitarChordList) # calculate the width
self.height = self.oneHeight # and its height
# multiple lines needed
else:
- self.width = self.chartMargin + self.style.stringHzGap + \
- self.style.stringHzSp * self.nChords
+ self.width = self.chartMargin + self.stringHzGap + \
+ self.stringHzSp * self.nChords
self.height = self.oneHeight * ceil(len(self.guitarChordList) / self.nChords) + \
- (self.style.stringHeight *
- trunc(len(self.guitarChordList) / self.nChords))
+ (self.stringHeight *
+ trunc(len(self.guitarChordList) / self.nChords))
return (self.width, self.height)
def draw(self):
@@ -106,7 +110,8 @@ class GuitarChart(Flowable):
chartmargin = self.chartMargin
for count, gcl in enumerate(self.splitChordList(self.guitarChordList, self.nChords)):
- v_origin = self.height - count * (self.oneHeight + self.style.stringHeight)
+ v_origin = self.height - count * \
+ (self.oneHeight + self.stringHeight)
self.nStrings = 6
fontsize = 12
@@ -117,41 +122,235 @@ class GuitarChart(Flowable):
for i in range(self.nStrings+1): # i is the string line currently being drawn
writeText(canvas, self.style, ['e', 'B', 'G', 'D', 'A', 'E', 'Name'][i], fontsize, v_origin-(
- i*self.style.stringHeight), self.width, hpos=chartmargin, align='right')
+ i*self.stringHeight), self.width, hpos=chartmargin, align='right')
# j is which chord (0 is first chord, 1 is 2nd etc)
for j in range(len(stringList[-1])):
currentWidth = canvas.stringWidth(stringList[i][j])
if j == 0:
- x = self.style.stringHzGap + chartmargin
- l = self.style.stringHzSp/2 - self.style.stringHzGap - \
- ((currentWidth/2)) - self.style.stringHzGap
- y = v_origin-(self.style.stringHeight*i) - \
- self.style.stringHeight/2
+ x = self.stringHzGap + chartmargin
+ l = self.stringHzSp/2 - self.stringHzGap - \
+ ((currentWidth/2)) - self.stringHzGap
+ y = v_origin-(self.stringHeight*i) - \
+ self.stringHeight/2
canvas.line(x, y, x+l, y)
else:
- x = chartmargin + self.style.stringHzSp * \
- (j-0.5)+(lastWidth/2+self.style.stringHzGap)
- l = self.style.stringHzSp - currentWidth / \
- 2 - lastWidth/2 - self.style.stringHzGap*2
- y = v_origin-(self.style.stringHeight*i) - \
- self.style.stringHeight/2
+ x = chartmargin + self.stringHzSp * \
+ (j-0.5)+(lastWidth/2+self.stringHzGap)
+ l = self.stringHzSp - currentWidth / \
+ 2 - lastWidth/2 - self.stringHzGap*2
+ y = v_origin-(self.stringHeight*i) - \
+ self.stringHeight/2
canvas.line(x, y, x+l, y)
if j == len(stringList[-1])-1:
- x = chartmargin + self.style.stringHzSp * \
- (j+0.5) + currentWidth/2 + self.style.stringHzGap
- l = self.style.stringHzSp/2 - currentWidth/2 - self.style.stringHzGap
- y = v_origin-(self.style.stringHeight*i) - \
- self.style.stringHeight/2
+ x = chartmargin + self.stringHzSp * \
+ (j+0.5) + currentWidth/2 + self.stringHzGap
+ l = self.stringHzSp/2 - currentWidth/2 - self.stringHzGap
+ y = v_origin-(self.stringHeight*i) - \
+ self.stringHeight/2
canvas.line(x, y, x+l, y)
writeText(canvas, self.style, stringList[i][j], fontsize, v_origin-(
- i*self.style.stringHeight), self.width, hpos=chartmargin+self.style.stringHzSp*(j+0.5))
+ i*self.stringHeight), self.width, hpos=chartmargin+self.stringHzSp*(j+0.5))
lastWidth = currentWidth
+class PianoChart(Flowable):
+ """
+ Flowable that draws a series of piano chord charts.
+ """
+
+ def __init__(self, style, chordList):
+ self.style = style
+ self.pianoChordList = [
+ c for c in chordList if 'piano' in c.voicings.keys()]
+
+ self.whiteKeyWidth = 2.5 * mm
+ self.blackKeyWidth = 1.5 * mm
+ self.whiteKeyHeight = 10 * mm
+ self.blackKeyHeight = 5.5 * mm
+ self.dotRadius = 0.5 * mm
+
+ self.chartMargin = 0.7 * mm
+ self.iconHzSpacing = 5 * mm
+ self.vSpacing = 2*mm
+
+ self.indicatorFontSize = 8
+ self.chordNameFontSize = 12
+ self.lineSpacing = 1.15
+
+ self.keyDict = {'A': 'white', 'A♯': 'black', 'B': 'white', 'C': 'white', 'C♯': 'black', 'D': 'white',
+ 'D♯': 'black', 'E': 'white', 'F': 'white', 'F♯': 'black', 'G': 'white', 'G♯': 'black'}
+ self.keyList = list(self.keyDict.keys())
+
+ self.spaceAfter = self.style.separatorSize
+
+ def wrap(self, availWidth, availHeight):
+ self.availWidth = availWidth
+ vUnits = 1
+ currentWidth = self.chartMargin
+ widest = 0
+ for index, c in enumerate(self.pianoChordList):
+ cKL, vL, fKN, iconWidth = self.calculate(c)
+ if currentWidth + iconWidth >= availWidth + self.iconHzSpacing:
+ vUnits += 1
+ currentWidth = self.chartMargin
+ else:
+ currentWidth += self.iconHzSpacing
+ currentWidth += iconWidth
+
+ if currentWidth > widest:
+ widest = currentWidth
+ if vUnits == 1:
+ widest -= self.iconHzSpacing # chop off the trailing space
+ self.oneHeight = self.chordNameFontSize * self.lineSpacing + \
+ self.whiteKeyHeight + self.indicatorFontSize * self.lineSpacing
+
+ self.width = widest
+ self.height = self.oneHeight * vUnits + self.vSpacing * (vUnits - 1)
+ return (self.width, self.height)
+
+ def replaceFlats(self, fingering):
+ # note name replacements
+ noteReplacements = {"B♭": "A♯", "D♭": "C♯",
+ "E♭": "D♯", "G♭": "F♯", "A♭": "G♯"}
+
+ parsedFingering = []
+ for key in fingering:
+ parsedFingering.append(noteReplacements.get(key, key))
+
+ return parsedFingering
+
+ def splitChordList(self, chordList, width):
+ bigList = []
+ currentList = []
+ currentWidth = self.chartMargin
+ for c in self.pianoChordList:
+ cKL, vL, fKN, iconWidth = self.calculate(c)
+
+ if currentWidth + iconWidth >= width + self.iconHzSpacing:
+ bigList.append(currentList)
+ currentList = [c]
+ currentWidth = self.chartMargin
+ else:
+ currentList.append(c)
+ currentWidth += self.iconHzSpacing
+ currentWidth += iconWidth
+
+ bigList.append(currentList)
+ return bigList
+
+ def calculate(self, c):
+ voicingList = self.replaceFlats(c.voicings['piano'])
+ # save this as we convert all the flats to sharps, but the user would probably like to see the name they entered...
+ firstKeyName = c.voicings['piano'][0]
+ chartKeyList = []
+
+ # get the list of keys to be drawn for each chord
+ for count, note in enumerate(voicingList):
+ if count == 0:
+ curIndex = self.keyList.index(note)
+ if self.keyDict[self.keyList[curIndex-1]] == 'black':
+ chartKeyList.append(
+ self.keyList[curIndex-2]) # don't start on a black key
+ chartKeyList.append(self.keyList[curIndex-1]) # the key before
+
+ chartKeyList.append(note)
+ else:
+ lastIndex = self.keyList.index(lastNote)
+ curIndex = self.keyList.index(note)
+
+ if curIndex > lastIndex:
+ chartKeyList.extend(
+ self.keyList[lastIndex+1:((curIndex+1) % len(self.keyList))])
+ elif curIndex < lastIndex:
+ chartKeyList.extend(self.keyList[lastIndex+1:])
+ chartKeyList.extend(
+ self.keyList[0:((curIndex+1) % len(self.keyList))])
+ else:
+ chartKeyList.append(note)
+
+ if count == len(voicingList) - 1:
+ curIndex = self.keyList.index(note)
+ chartKeyList.append(
+ self.keyList[((curIndex+1) % len(self.keyList))])
+ # don't finish on a black key
+ if self.keyDict[self.keyList[((curIndex+1) % len(self.keyList))]] == 'black':
+ chartKeyList.append(
+ self.keyList[((curIndex+2) % len(self.keyList))])
+
+ lastNote = note
+
+ iconWidth = sum([self.whiteKeyWidth if self.keyDict[k]
+ == 'white' else 0 for k in chartKeyList])
+
+ return chartKeyList, voicingList, firstKeyName, iconWidth
+
+ def draw(self):
+ canvas = self.canv
+
+ for index, cL in enumerate(self.splitChordList(self.pianoChordList, self.width)):
+ h_offset = self.chartMargin
+ v_offset = self.height - self.oneHeight * index - self.vSpacing * \
+ index - self.chordNameFontSize * self.lineSpacing
+
+ for c in cL:
+ chartKeyList, voicingList, firstKeyName, iconWidth = self.calculate(
+ c)
+ # draw chord names
+ canvas.setFont(self.style.font, self.chordNameFontSize)
+ canvas.drawCentredString(h_offset + iconWidth/2, v_offset+(
+ 0.3*self.chordNameFontSize*self.lineSpacing), c.name)
+
+ # draw the keys
+ count = 0
+ for key in chartKeyList:
+ if self.keyDict[key] == 'white':
+ canvas.rect(h_offset + (count*self.whiteKeyWidth), v_offset -
+ self.whiteKeyHeight, self.whiteKeyWidth, self.whiteKeyHeight)
+ count += 1
+ elif self.keyDict[key] == 'black':
+ canvas.rect(h_offset + (count*self.whiteKeyWidth) - (self.blackKeyWidth/2),
+ v_offset-self.blackKeyHeight, self.blackKeyWidth, self.blackKeyHeight, fill=1)
+
+ # draw the indicator dots
+ count = 0
+ dotCount = 0
+ for key in chartKeyList:
+ if self.keyDict[key] == 'white':
+ count += 1
+ if len(voicingList) > dotCount and key == voicingList[dotCount]:
+ hpos = h_offset + \
+ (count*self.whiteKeyWidth) - \
+ (self.whiteKeyWidth/2)
+ if dotCount == 0:
+ canvas.setFont(self.style.font,
+ self.indicatorFontSize)
+ canvas.drawCentredString(
+ hpos, v_offset - self.whiteKeyHeight*1.3, firstKeyName)
+ dotCount += 1
+ canvas.circle(hpos, v_offset - self.whiteKeyHeight + (self.whiteKeyWidth/2),
+ self.dotRadius, stroke=0, fill=1)
+ elif self.keyDict[key] == 'black':
+ if len(voicingList) > dotCount and key == voicingList[dotCount]:
+ hpos = h_offset + \
+ (count*self.whiteKeyWidth)
+ if dotCount == 0:
+ canvas.setFont(self.style.font,
+ self.indicatorFontSize)
+ canvas.drawCentredString(
+ hpos, v_offset - self.whiteKeyHeight*1.3, firstKeyName)
+ dotCount += 1
+ canvas.setFillColor(white)
+ canvas.circle(hpos, v_offset - self.blackKeyHeight + (self.blackKeyWidth/2),
+ self.dotRadius, stroke=0, fill=1)
+ canvas.setFillColor(black)
+
+ h_offset += iconWidth + self.iconHzSpacing
+
+
class ChordProgression(Flowable):
"""
Flowable that draws a chord progression made up of blocks.
@@ -162,7 +361,10 @@ class ChordProgression(Flowable):
self.heading = heading # the title of the section
self.blockList = blockList
self.timeSignature = timeSignature
- self.headingSize = 18
+ self.chartMargin = 0.7*mm # kludge factor to account for line width
+
+ self.unitHeight = 20*mm
+ self.beatsHeight = 5*mm
self.spaceAfter = self.style.separatorSize
@@ -182,11 +384,9 @@ class ChordProgression(Flowable):
while sum(lengthList) < blockList[i].length:
if blockList[i].length - sum(lengthList) >= maxWidth:
lengthList.append(maxWidth)
- # print(lengthList)
else:
lengthList.append(
blockList[i].length - sum(lengthList))
- # print(lengthList)
for l in lengthList:
# create a block with the given length
@@ -218,7 +418,7 @@ class ChordProgression(Flowable):
trunc((availWidth/(self.style.unitWidth*self.style.unit)) /
(2*self.timeSignature)) # width of each line, in beats
self.width = self.widthInBeats * self.style.unitWidth * self.style.unit
- self.height = self.style.beatsHeight + self.style.unitHeight * \
+ self.height = self.beatsHeight + self.unitHeight * \
sum([b.length for b in self.blockList]) / self.widthInBeats
return(self.width, self.height)
@@ -227,7 +427,7 @@ class ChordProgression(Flowable):
return [self]
else:
vUnits = trunc(
- (availHeight - self.style.beatsHeight) / self.style.unitHeight)
+ (availHeight - self.beatsHeight) / self.unitHeight)
firstPart, secondPart = self.splitBlockList(
self.blockList, vUnits * self.widthInBeats)
@@ -239,7 +439,8 @@ class ChordProgression(Flowable):
canvas = self.canv
unitWidth = self.style.unitWidth*self.style.unit
- v_origin = self.height - self.style.beatsHeight
+ v_origin = self.height - self.beatsHeight
+ h_offset = self.chartMargin
h_loc = 0
v_loc = 0
@@ -248,16 +449,16 @@ class ChordProgression(Flowable):
for u in range(maxWidth+1):
y = v_origin
- x = u*unitWidth
+ x = u*unitWidth + h_offset
if u % self.timeSignature == 0:
- l = self.style.beatsHeight
+ l = self.beatsHeight
else:
- l = self.style.beatsHeight/2
+ l = self.beatsHeight/2
canvas.line(x, y, x, y+l)
if u == maxWidth: # Avoid writing beat number after the final line
break
writeText(canvas, self.style, str((u % self.timeSignature)+1), self.style.beatsFontSize,
- v_origin+self.style.beatsHeight, self.width, hpos=x+unitWidth/2)
+ v_origin+self.beatsHeight, self.width, hpos=x+unitWidth/2)
parsedBlockList = self.wrapBlocks(self.blockList, maxWidth)
@@ -265,23 +466,26 @@ class ChordProgression(Flowable):
if h_loc == maxWidth:
v_loc += 1
h_loc = 0
- canvas.rect(h_loc*unitWidth, v_origin-((v_loc+1)*self.style.unitHeight),
- b.length*unitWidth, self.style.unitHeight)
+ canvas.rect(h_offset+h_loc*unitWidth, v_origin-((v_loc+1)*self.unitHeight),
+ b.length*unitWidth, self.unitHeight)
if b.notes is not None:
- writeText(canvas, self.style, b.notes, self.style.notesFontSize, v_origin-((v_loc+1)*self.style.unitHeight)+(
- 1.3*self.style.notesFontSize), self.width, hpos=((h_loc+b.length/2)*unitWidth))
- v_offset = ((v_loc*self.style.unitHeight) +
- self.style.unitHeight/2)-self.style.chordNameFontSize/2
+ writeText(canvas, self.style, b.notes, self.style.notesFontSize, v_origin-((v_loc+1)*self.unitHeight)+(
+ 1.3*self.style.notesFontSize), self.width, hpos=h_offset+((h_loc+b.length/2)*unitWidth))
+ v_offset = ((v_loc*self.unitHeight) +
+ self.unitHeight/2)-self.style.chordNameFontSize/2
if b.chord is not None:
writeText(canvas, self.style, b.chord.name, self.style.chordNameFontSize,
- v_origin-v_offset, self.width, hpos=((h_loc+b.length/2)*unitWidth))
+ v_origin-v_offset, self.width, hpos=h_offset+((h_loc+b.length/2)*unitWidth))
h_loc += b.length
-def guitarChartCheck(cL):
+def instChartCheck(cL, inst):
+ """
+ Check if a file contains a chord chart for a certain instrument.
+ """
chordsPresent = False
for c in cL:
- if 'guitar' in c.voicings.keys():
+ if inst in c.voicings.keys():
chordsPresent = True
break
return chordsPresent
@@ -294,8 +498,10 @@ class Renderer:
def savePDF(self, pathToPDF):
template = PageTemplate(id='AllPages', frames=[Frame(self.style.leftMargin*mm, self.style.bottomMargin*mm,
- self.style.pageSize[0] - self.style.leftMargin*mm - self.style.rightMargin*mm,
- self.style.pageSize[1] - self.style.topMargin*mm - self.style.bottomMargin*mm,
+ self.style.pageSize[0] - self.style.leftMargin *
+ mm - self.style.rightMargin*mm,
+ self.style.pageSize[1] - self.style.topMargin *
+ mm - self.style.bottomMargin*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0)])
rlDocList = []
@@ -325,11 +531,16 @@ class Renderer:
if self.document.title or self.document.subtitle or self.document.composer or self.document.arranger or self.document.tempo:
rlDocList.append(Spacer(0, self.style.separatorSize))
- if guitarChartCheck(self.document.chordList):
+ if instChartCheck(self.document.chordList, 'guitar'):
rlDocList.extend([
Paragraph('Guitar chord voicings', styles['Heading']),
GuitarChart(self.style, self.document.chordList)])
+ if instChartCheck(self.document.chordList, 'piano'):
+ rlDocList.extend([
+ Paragraph('Piano chord voicings', styles['Heading']),
+ PianoChart(self.style, self.document.chordList)])
+
for s in self.document.sectionList:
rlDocList.append(Paragraph(s.name, styles['Heading']))
# only draw the chord progression if there are blocks
diff --git a/chordsheet/rlStylesheet.py b/chordsheet/rlStylesheet.py
index f561216..44cc1b7 100644
--- a/chordsheet/rlStylesheet.py
+++ b/chordsheet/rlStylesheet.py
@@ -47,64 +47,4 @@ def getStyleSheet(csStyle):
spaceAfter=2*mm)
)
- # stylesheet.add(ParagraphStyle(name='Heading3',
- # parent=stylesheet['Normal'],
- # fontName = csStyle.font,
- # fontSize=12,
- # leading=14,
- # spaceBefore=12,
- # spaceAfter=6),
- # alias='h3')
-
- # stylesheet.add(ParagraphStyle(name='Heading4',
- # parent=stylesheet['Normal'],
- # fontName = csStyle.font,
- # fontSize=10,
- # leading=12,
- # spaceBefore=10,
- # spaceAfter=4),
- # alias='h4')
-
- # stylesheet.add(ParagraphStyle(name='Heading5',
- # parent=stylesheet['Normal'],
- # fontName = csStyle.font,
- # fontSize=9,
- # leading=10.8,
- # spaceBefore=8,
- # spaceAfter=4),
- # alias='h5')
-
- # stylesheet.add(ParagraphStyle(name='Heading6',
- # parent=stylesheet['Normal'],
- # fontName = csStyle.font,
- # fontSize=7,
- # leading=8.4,
- # spaceBefore=6,
- # spaceAfter=2),
- # alias='h6')
-
- # stylesheet.add(ParagraphStyle(name='Bullet',
- # parent=stylesheet['Normal'],
- # firstLineIndent=0,
- # spaceBefore=3),
- # alias='bu')
-
- # stylesheet.add(ParagraphStyle(name='Definition',
- # parent=stylesheet['Normal'],
- # firstLineIndent=0,
- # leftIndent=36,
- # bulletIndent=0,
- # spaceBefore=6,
- # bulletFontName=csStyle.font),
- # alias='df')
-
- # stylesheet.add(ParagraphStyle(name='Code',
- # parent=stylesheet['Normal'],
- # fontName='Courier',
- # fontSize=8,
- # leading=8.8,
- # firstLineIndent=0,
- # leftIndent=36,
- # hyphenationLang=''))
-
return stylesheet
\ No newline at end of file
diff --git a/chordsheet/tableView.py b/chordsheet/tableView.py
index 4cd5e17..09e34f7 100644
--- a/chordsheet/tableView.py
+++ b/chordsheet/tableView.py
@@ -64,7 +64,7 @@ class ChordTableView(MTableView):
def __init__(self, parent):
super().__init__(parent)
- self.model.setHorizontalHeaderLabels(['Chord', 'Voicing'])
+ self.model.setHorizontalHeaderLabels(['Chord', 'Guitar voicing', 'Piano voicing'])
def populate(self, cList):
"""
@@ -73,7 +73,9 @@ class ChordTableView(MTableView):
self.model.removeRows(0, self.model.rowCount())
for c in cList:
rowList = [QtGui.QStandardItem(c.name), QtGui.QStandardItem(
- ",".join(c.voicings['guitar'] if 'guitar' in c.voicings.keys() else ""))]
+ ",".join(c.voicings['guitar'] if 'guitar' in c.voicings.keys() else "")),
+ QtGui.QStandardItem(
+ ",".join(c.voicings['piano'] if 'piano' in c.voicings.keys() else ""))]
for item in rowList:
item.setEditable(False)
item.setDropEnabled(False)
diff --git a/examples/example.xml b/examples/example.xml
index 70c62a9..3a6c738 100644
--- a/examples/example.xml
+++ b/examples/example.xml
@@ -1 +1,93 @@
-Example SongIvan Holmes4Cx,3,2,0,1,0F1,3,3,2,1,1G3,2,0,0,0,3C/G3,3,2,0,1,0Dmx,x,0,2,3,116CIntro, strum lightly4C4F8G4C4F8G4C4Dm4C/G4G4C4Contemplation time8CCrescendo until end
\ No newline at end of file
+
+
+ Example Song
+ Ivan Holmes
+ 4
+
+
+ C7
+ x,3,2,0,1,0
+ Bb,E,C,G
+
+
+ F
+ 1,3,3,2,1,1
+ A,C,F
+
+
+ G
+ 3,2,0,0,0,3
+ B,D,G
+
+
+ C/G
+ 3,3,2,0,1,0
+ G,C,E
+
+
+ Dm
+ x,x,0,2,3,1
+ A,D,F
+
+
+
+
+ 16
+ C7
+ Intro, strum lightly
+
+
+ 4
+ C7
+
+
+ 4
+ F
+
+
+ 8
+ G
+
+
+ 4
+ C7
+
+
+ 4
+ F
+
+
+ 8
+ G
+
+
+ 4
+ C7
+
+
+ 4
+ Dm
+
+
+ 4
+ C/G
+
+
+ 4
+ G
+
+
+ 4
+ C7
+
+
+ 4
+ Contemplation time
+
+
+ 8
+ C7
+ Crescendo until end
+
+
+
\ No newline at end of file
diff --git a/examples/examplelong.xml b/examples/examplelong.xml
new file mode 100644
index 0000000..100838f
--- /dev/null
+++ b/examples/examplelong.xml
@@ -0,0 +1,143 @@
+
+
+ Example Song
+ Ivan Holmes
+ 4
+
+
+ C7
+ x,3,2,0,1,0
+ Bb,E,C,G
+
+
+ F
+ 1,3,3,2,1,1
+ A,C,F
+
+
+ G
+ 3,2,0,0,0,3
+ B,D,G
+
+
+ C/G
+ 3,3,2,0,1,0
+ G,C,E
+
+
+ Dm
+ x,x,0,2,3,1
+ A,D,F
+
+
+ q7
+ x,3,2,0,1,0
+ Bb,E,C,G
+
+
+ q3
+ 1,3,3,2,1,1
+ A,C,F
+
+
+ q5
+ 3,2,0,0,0,3
+ B,D,G
+
+
+ q/G
+ 3,3,2,0,1,0
+ G,C,E
+
+
+ q
+ x,x,0,2,3,1
+ A,D,F
+
+
+ yeeq
+ x,3,2,0,1,0
+ Bb,E,C,G
+
+
+ quu
+ 1,3,3,2,1,1
+ A,C,F
+
+
+ b3
+ 3,2,0,0,0,3
+ B,D
+
+
+ aa
+ 3,3,2,0,1,0
+ G,C,E
+
+
+ z
+ x,x,0,2,3,1
+ A,D,F,G,E,B,C
+
+
+
+
+ 16
+ C7
+ Intro, strum lightly
+
+
+ 4
+ C7
+
+
+ 4
+ F
+
+
+ 8
+ G
+
+
+ 4
+ C7
+
+
+ 4
+ F
+
+
+ 8
+ G
+
+
+ 4
+ C7
+
+
+ 4
+ Dm
+
+
+ 4
+ C/G
+
+
+ 4
+ G
+
+
+ 4
+ C7
+
+
+ 4
+ Contemplation time
+
+
+ 8
+ C7
+ Crescendo until end
+
+
+
\ No newline at end of file
diff --git a/gui.py b/gui.py
index c3d83d0..83f8907 100755
--- a/gui.py
+++ b/gui.py
@@ -100,7 +100,8 @@ class DocumentWindow(QMainWindow):
"""
Reimplement the built in closeEvent to allow asking the user to save.
"""
- self.saveWarning()
+ if self.saveWarning():
+ self.close()
def UIFileLoader(self, ui_file):
"""
@@ -250,6 +251,8 @@ class DocumentWindow(QMainWindow):
self.window.chordTableView.model.item(index.row(), 0).text())
self.window.guitarVoicingLineEdit.setText(
self.window.chordTableView.model.item(index.row(), 1).text())
+ self.window.pianoVoicingLineEdit.setText(
+ self.window.chordTableView.model.item(index.row(), 2).text())
def sectionClickedAction(self, index):
# set the controls to the values from the selected section
@@ -297,21 +300,23 @@ class DocumentWindow(QMainWindow):
return settings.setValue(value, os.path.dirname(fullpath))
def menuFileNewAction(self):
- self.doc = Document() # new document object
- # copy this object as reference to check against on quitting
- self.lastDoc = copy(self.doc)
- # reset file path (this document hasn't been saved yet)
- self.currentFilePath = None
- # new renderer
- self.renderer = Renderer(self.doc, self.style)
- self.UIInitDocument()
- self.updatePreview()
+ if self.saveWarning(): # ask the user if they want to save
+ self.doc = Document() # new document object
+ # copy this object as reference to check against on quitting
+ self.lastDoc = copy(self.doc)
+ # reset file path (this document hasn't been saved yet)
+ self.currentFilePath = None
+ # new renderer
+ self.renderer = Renderer(self.doc, self.style)
+ self.UIInitDocument()
+ self.updatePreview()
def menuFileOpenAction(self):
- filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', self.getPath(
- "workingPath"), "Chordsheet ML files (*.xml *.cml)")[0]
- if filePath:
- self.openFile(filePath)
+ if self.saveWarning(): # ask the user if they want to save
+ filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', self.getPath(
+ "workingPath"), "Chordsheet ML files (*.xml *.cml)")[0]
+ if filePath:
+ self.openFile(filePath)
def openFile(self, filePath):
"""
@@ -410,7 +415,7 @@ class DocumentWindow(QMainWindow):
self.updateDocument() # update the document to catch all changes
if self.lastDoc == self.doc:
- self.close()
+ return True
else:
wantToSave = UnsavedMessageBox().exec()
@@ -420,11 +425,13 @@ class DocumentWindow(QMainWindow):
os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
self.currentFilePath = filePath[0]
self.doc.saveXML(self.currentFilePath)
- self.close()
+ return True
elif wantToSave == QMessageBox.Discard:
- self.close()
- # if cancel or anything else do nothing at all
+ return True
+
+ else:
+ return False
def guitarVoicingAction(self):
gdialog = GuitarDialog()
@@ -436,9 +443,11 @@ class DocumentWindow(QMainWindow):
def clearChordLineEdits(self):
self.window.chordNameLineEdit.clear()
self.window.guitarVoicingLineEdit.clear()
+ self.window.pianoVoicingLineEdit.clear()
# necessary on Mojave with PyInstaller (or previous contents will be shown)
self.window.chordNameLineEdit.repaint()
self.window.guitarVoicingLineEdit.repaint()
+ self.window.pianoVoicingLineEdit.repaint()
def clearSectionLineEdits(self):
self.window.sectionNameLineEdit.clear()
@@ -475,9 +484,17 @@ class DocumentWindow(QMainWindow):
self.updateChords()
row = self.window.chordTableView.selectionModel().currentIndex().row()
+ oldName = self.window.chordTableView.model.item(row, 0).text()
self.doc.chordList.pop(row)
self.window.chordTableView.populate(self.doc.chordList)
+ # remove the chord if any of the blocks have it attached
+ for s in self.doc.sectionList:
+ for b in s.blockList:
+ if b.chord:
+ if b.chord.name == oldName:
+ b.chord = None
+ self.window.blockTableView.populate(self.currentSection.blockList)
self.clearChordLineEdits()
self.updateChordDict()
@@ -488,13 +505,21 @@ class DocumentWindow(QMainWindow):
cName = parseName(self.window.chordNameLineEdit.text())
if cName:
self.doc.chordList.append(Chord(cName))
- if self.window.guitarVoicingLineEdit.text():
- try:
- self.doc.chordList[-1].voicings['guitar'] = parseFingering(
- self.window.guitarVoicingLineEdit.text(), 'guitar')
- success = True # chord successfully parsed
- except Exception:
- VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
+ if self.window.guitarVoicingLineEdit.text() or self.window.pianoVoicingLineEdit.text():
+ if self.window.guitarVoicingLineEdit.text():
+ try:
+ self.doc.chordList[-1].voicings['guitar'] = parseFingering(
+ self.window.guitarVoicingLineEdit.text(), 'guitar')
+ success = True # chord successfully parsed
+ except Exception:
+ VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
+ if self.window.pianoVoicingLineEdit.text():
+ try:
+ self.doc.chordList[-1].voicings['piano'] = parseFingering(
+ self.window.pianoVoicingLineEdit.text(), 'piano')
+ success = True # chord successfully parsed
+ except Exception:
+ VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
else:
success = True # chord successfully parsed
else:
@@ -514,15 +539,23 @@ class DocumentWindow(QMainWindow):
cName = parseName(self.window.chordNameLineEdit.text())
if cName:
self.doc.chordList[row].name = cName
- if self.window.guitarVoicingLineEdit.text():
- try:
- self.doc.chordList[row].voicings['guitar'] = parseFingering(
- self.window.guitarVoicingLineEdit.text(), 'guitar')
- success = True
- except Exception:
- VoicingWarningMessageBox().exec()
+ if self.window.guitarVoicingLineEdit.text() or self.window.pianoVoicingLineEdit.text():
+ if self.window.guitarVoicingLineEdit.text():
+ try:
+ self.doc.chordList[-1].voicings['guitar'] = parseFingering(
+ self.window.guitarVoicingLineEdit.text(), 'guitar')
+ success = True # chord successfully parsed
+ except Exception:
+ VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
+ if self.window.pianoVoicingLineEdit.text():
+ try:
+ self.doc.chordList[-1].voicings['piano'] = parseFingering(
+ self.window.pianoVoicingLineEdit.text(), 'piano')
+ success = True # chord successfully parsed
+ except Exception:
+ VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
else:
- success = True
+ success = True # chord successfully parsed
else:
ChordNameWarningMessageBox().exec()
@@ -665,6 +698,9 @@ class DocumentWindow(QMainWindow):
if self.window.chordTableView.model.item(i, 1).text():
chordTableList[-1].voicings['guitar'] = parseFingering(
self.window.chordTableView.model.item(i, 1).text(), 'guitar')
+ if self.window.chordTableView.model.item(i, 2).text():
+ chordTableList[-1].voicings['piano'] = parseFingering(
+ self.window.chordTableView.model.item(i, 2).text(), 'piano')
self.doc.chordList = chordTableList
diff --git a/test.py b/test.py
index f91d85c..f35a214 100644
--- a/test.py
+++ b/test.py
@@ -8,8 +8,8 @@ from chordsheet.render import Renderer
pdfmetrics.registerFont(TTFont('FreeSans', os.path.join('fonts', 'FreeSans.ttf')))
-doc = Document.newFromXML('examples/angela.xml')
-style = Style(unitWidth=20)
+doc = Document.newFromXML('examples/test.xml')
+style = Style(unitWidth=10)
ren = Renderer(doc, style)
-ren.savePDF('test.pdf')
\ No newline at end of file
+ren.savePDF('test.pdf')
\ No newline at end of file
diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui
index 6f12a3d..8327c84 100644
--- a/ui/mainwindow.ui
+++ b/ui/mainwindow.ui
@@ -72,7 +72,7 @@
- 0
+ 2
@@ -503,6 +503,30 @@
-
+
-
+
+
+ Chord name
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Guitar voicing
+
+
+
-
@@ -532,27 +556,13 @@
- -
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
- Guitar voicing
-
-
+
-
+
- -
-
+
-
+
- Chord name
+ Piano voicing