From 977817a293fa8251f788b4ba8c41b18c1d535f99 Mon Sep 17 00:00:00 2001 From: Ivan Holmes Date: Mon, 4 Nov 2019 22:20:21 +0000 Subject: [PATCH] Add subtitle and tempo fields Allow changing block width --- chordsheet/document.py | 18 +++++-- chordsheet/primitives.py | 2 +- chordsheet/render.py | 24 ++++++--- gui.py | 23 +++++++-- ui/aboutdialog.ui | 3 ++ ui/mainwindow.ui | 107 +++++++++++++++++++++++++++++++-------- 6 files changed, 138 insertions(+), 39 deletions(-) diff --git a/chordsheet/document.py b/chordsheet/document.py index 3dd2927..a47a329 100644 --- a/chordsheet/document.py +++ b/chordsheet/document.py @@ -18,14 +18,14 @@ class Style: self.font = kwargs.get('font', 'FreeSans') self.lineSpacing = kwargs.get('lineSpacing', 1.15) self.separatorSize = kwargs.get('separatorSize', 5) + self.unitWidth = kwargs.get('unitWidth', 10) self.useIncludedFont = True self.stringHzSp = 20*self.unit self.stringHzGap = 2*self.unit self.stringHeight = 5*self.unit - - self.unitWidth = 10*self.unit + self.unitHeight = 20*self.unit self.beatsHeight = 5*self.unit @@ -58,17 +58,19 @@ class Block: return NotImplemented class Document: - def __init__(self, chordList=None, blockList=None, title=None, composer=None, arranger=None, timeSignature=defaultTimeSignature): + def __init__(self, chordList=None, blockList=None, title=None, subtitle=None, composer=None, arranger=None, timeSignature=defaultTimeSignature, tempo=None): self.chordList = chordList or [] self.blockList = blockList or [] self.title = title or '' # Do not initialise title empty + self.subtitle = subtitle self.composer = composer self.arranger = arranger self.timeSignature = timeSignature + self.tempo = tempo def __eq__(self, other): if isinstance(other, self.__class__): - textEqual = self.title == other.title and self.composer == other.composer and self.arranger == other.arranger and self.timeSignature == other.timeSignature # check all the text values for equality + textEqual = self.title == other.title and self.subtitle == other.subtitle and self.composer == other.composer and self.arranger == other.arranger and self.timeSignature == other.timeSignature and self.tempo == other.tempo # check all the text values for equality return textEqual and self.chordList == other.chordList and self.blockList == other.blockList return NotImplemented @@ -104,9 +106,11 @@ class Document: self.blockList.append(Block(int(b.find('length').text), chord=blockChord, notes=blockNotes)) self.title = (root.find('title').text if root.find('title') is not None else '') # Do not initialise title empty + self.subtitle = (root.find('subtitle').text if root.find('subtitle') is not None else None) self.composer = (root.find('composer').text if root.find('composer') is not None else None) self.arranger = (root.find('arranger').text if root.find('arranger') is not None else None) self.timeSignature = (int(root.find('timesignature').text) if root.find('timesignature') is not None else defaultTimeSignature) + self.tempo = (root.find('tempo').text if root.find('tempo') is not None else None) def newFromXML(filepath): """ @@ -124,6 +128,9 @@ class Document: ET.SubElement(root, "title").text = self.title + if self.subtitle is not None: + ET.SubElement(root, "subtitle").text = self.subtitle + if self.arranger is not None: ET.SubElement(root, "arranger").text = self.arranger @@ -131,6 +138,9 @@ class Document: ET.SubElement(root, "composer").text = self.composer ET.SubElement(root, "timesignature").text = str(self.timeSignature) + + if self.tempo is not None: + ET.SubElement(root, "tempo").text = self.tempo chordsElement = ET.SubElement(root, "chords") diff --git a/chordsheet/primitives.py b/chordsheet/primitives.py index 67bac51..9c6a934 100644 --- a/chordsheet/primitives.py +++ b/chordsheet/primitives.py @@ -6,7 +6,7 @@ from reportlab.graphics.shapes import * def writeText(currentCanvas, style, string, size, vpos, **kwargs): """ - Wrapper function to conveniently write text according to my requirements... + Wrapper function to conveniently write text and return how much vertical space it took up. """ margin = style.leftMargin*style.unit diff --git a/chordsheet/render.py b/chordsheet/render.py index aebf210..2cec25c 100644 --- a/chordsheet/render.py +++ b/chordsheet/render.py @@ -82,6 +82,7 @@ def guitarChart(currentCanvas, style, chordList, cur_pos): def chordProgression(currentCanvas, style, document, cur_pos): margin = style.leftMargin*style.unit + unitWidth = style.unitWidth*style.unit pagesize = style.pageSize title_height = writeText(currentCanvas, style, "Chord progression", 18, cur_pos, align="left") @@ -93,11 +94,14 @@ def chordProgression(currentCanvas, style, document, cur_pos): h_loc = 0 v_loc = 0 - maxWidth = int((((pagesize[0]-(2*margin))/style.unitWidth)//(document.timeSignature*2))*(document.timeSignature*2)) # use integer division to round maxWidth to nearest two bars - + if (unitWidth * document.timeSignature * 2) >= ((pagesize[0]-(2*margin) + 1)): # adding 1 to allow for rounding errors + raise Exception("Beat width (unitWidth) is too high. It is {current} pt and can be a maximum of {max} pt".format(current = unitWidth, max = ((pagesize[0]-(2*margin)/(document.timeSignature * 2))))) + + maxWidth = int((((pagesize[0]-(2*margin))/unitWidth)//(document.timeSignature*2))*(document.timeSignature*2)) # use integer division to round maxWidth to nearest two bars + for u in range(maxWidth+1): s = 0 - x = u*style.unitWidth+margin + x = u*unitWidth+margin if u % document.timeSignature == 0: e = -style.beatsHeight else: @@ -105,7 +109,7 @@ def chordProgression(currentCanvas, style, document, cur_pos): drawVertLine(currentCanvas, s, e, x, h_origin, v_origin) if u == maxWidth: # Avoid writing beat number after the final line break - writeText(currentCanvas, style, str((u % document.timeSignature)+1), style.beatsFontSize, v_origin-style.beatsHeight, hpos=x+style.unitWidth/2) + writeText(currentCanvas, style, str((u % document.timeSignature)+1), style.beatsFontSize, v_origin-style.beatsHeight, hpos=x+unitWidth/2) parsedBlockList = splitBlocks(document.blockList, maxWidth) @@ -113,12 +117,12 @@ def chordProgression(currentCanvas, style, document, cur_pos): if h_loc == maxWidth: v_loc += 1 h_loc = 0 - currentCanvas.rect(h_origin+(h_loc*style.unitWidth), v_origin+(v_loc*style.unitHeight), b.length*style.unitWidth, style.unitHeight) + currentCanvas.rect(h_origin+(h_loc*unitWidth), v_origin+(v_loc*style.unitHeight), b.length*unitWidth, style.unitHeight) if b.notes is not None: - writeText(currentCanvas, style, b.notes, style.notesFontSize, v_origin+((v_loc+1)*style.unitHeight)-(1.3*style.notesFontSize), hpos=h_origin+((h_loc+b.length/2)*style.unitWidth)) + writeText(currentCanvas, style, b.notes, style.notesFontSize, v_origin+((v_loc+1)*style.unitHeight)-(1.3*style.notesFontSize), hpos=h_origin+((h_loc+b.length/2)*unitWidth)) v_offset = ((v_loc*style.unitHeight)+style.unitHeight/2)-style.chordNameFontSize/2 if b.chord is not None: - writeText(currentCanvas, style, b.chord.name, style.chordNameFontSize, v_origin+v_offset, hpos=h_origin+((h_loc+b.length/2)*style.unitWidth)) + writeText(currentCanvas, style, b.chord.name, style.chordNameFontSize, v_origin+v_offset, hpos=h_origin+((h_loc+b.length/2)*unitWidth)) h_loc += b.length return v_origin + (v_loc+1)*style.unitHeight + style.beatsHeight + title_height # calculate the height of the generated chart @@ -139,6 +143,9 @@ def savePDF(document, style, pathToPDF): if document.title is not None: curPos += writeText(c, style, document.title, 24, curPos) + + if document.subtitle is not None: + curPos += writeText(c, style, document.subtitle, 18, curPos) if document.composer is not None: curPos += writeText(c, style, "Composer: {c}".format(c = document.composer), 12, curPos) @@ -146,6 +153,9 @@ def savePDF(document, style, pathToPDF): if document.arranger is not None: curPos += writeText(c, style, "Arranger: {a}".format(a = document.arranger), 12, curPos) + if document.tempo is not None: + curPos += writeText(c, style, "♩ = {t} bpm".format(t = document.tempo), 12, curPos, align = "left") + curPos += style.separatorSize*style.unit if guitarChartCheck(document.chordList): diff --git a/gui.py b/gui.py index 881fa70..b84532b 100755 --- a/gui.py +++ b/gui.py @@ -155,6 +155,7 @@ class DocumentWindow(QMainWindow): self.window.composerLineEdit.setText(self.doc.composer) self.window.arrangerLineEdit.setText(self.doc.arranger) self.window.timeSignatureSpinBox.setValue(self.doc.timeSignature) + self.window.tempoLineEdit.setText(self.doc.tempo) self.window.chordTableView.populate(self.doc.chordList) self.window.blockTableView.populate(self.doc.blockList) @@ -177,6 +178,8 @@ class DocumentWindow(QMainWindow): self.window.fontComboBox.setDisabled(True) self.window.includedFontCheckBox.setChecked(True) + + self.window.beatWidthLineEdit.setText(str(self.style.unitWidth)) def pageSizeAction(self, index): self.pageSizeSelected = self.window.pageSizeComboBox.itemText(index) @@ -529,15 +532,25 @@ class DocumentWindow(QMainWindow): Update the Document object by reading values from the UI. """ self.doc.title = self.window.titleLineEdit.text() # Title can be empty string but not None + self.doc.subtitle = (self.window.subtitleLineEdit.text() if self.window.subtitleLineEdit.text() else None) self.doc.composer = (self.window.composerLineEdit.text() if self.window.composerLineEdit.text() else None) self.doc.arranger = (self.window.arrangerLineEdit.text() if self.window.arrangerLineEdit.text() else None) - - self.doc.timeSignature = int(self.window.timeSignatureSpinBox.value()) + self.doc.tempo = (self.window.tempoLineEdit.text() if self.window.tempoLineEdit.text() else None) + self.doc.timeSignature = int(self.window.timeSignatureSpinBox.value()) if self.window.timeSignatureSpinBox.value() else self.doc.timeSignature + self.style.pageSize = pageSizeDict[self.pageSizeSelected] self.style.unit = unitDict[self.unitSelected] - self.style.leftMargin = int(self.window.leftMarginLineEdit.text()) - self.style.topMargin = int(self.window.topMarginLineEdit.text()) - self.style.lineSpacing = float(self.window.lineSpacingDoubleSpinBox.value()) + self.style.leftMargin = float(self.window.leftMarginLineEdit.text()) if self.window.leftMarginLineEdit.text() else self.style.leftMargin + self.style.topMargin = float(self.window.topMarginLineEdit.text()) if self.window.topMarginLineEdit.text() else self.style.topMargin + self.style.lineSpacing = float(self.window.lineSpacingDoubleSpinBox.value()) if self.window.lineSpacingDoubleSpinBox.value() else self.style.lineSpacing + + # make sure the unit width isn't too wide to draw! + if self.window.beatWidthLineEdit.text(): + if (self.style.pageSize[0] - 2 * self.style.leftMargin * mm) >= (float(self.window.beatWidthLineEdit.text()) * 2 * self.doc.timeSignature * mm): + self.style.unitWidth = float(self.window.beatWidthLineEdit.text()) + else: + maxBeatWidth = (self.style.pageSize[0] - 2 * self.style.leftMargin * mm) / (2 * self.doc.timeSignature * mm) + warning = QMessageBox.warning(self, "Out of range", "Beat width is out of range. It can be a maximum of {}.".format(maxBeatWidth), buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok) self.updateChords() self.updateBlocks() diff --git a/ui/aboutdialog.ui b/ui/aboutdialog.ui index 85dd956..f8bc3cc 100644 --- a/ui/aboutdialog.ui +++ b/ui/aboutdialog.ui @@ -51,6 +51,9 @@ true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 69370f3..c52fd9d 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 761 - 513 + 1061 + 646 @@ -42,7 +42,7 @@ 430 - 400 + 600 @@ -104,13 +104,23 @@ + + + Subtitle + + + + + + + Composer - + @@ -120,14 +130,14 @@ - + Arranger - + @@ -137,18 +147,37 @@ - - - - - + + + + Tempo + + + + + + + + 0 + 0 + + + + + 60 + 16777215 + + + + + - Time signature + Time - + @@ -190,6 +219,12 @@ + + + 0 + 0 + + Page options @@ -330,14 +365,44 @@ + + + + Use included FreeSans + + + - - - - Use included FreeSans + + + + Block options + + + + + + + Beat width + + + + + + + + 60 + 16777215 + + + + + + + @@ -739,8 +804,8 @@ 0 0 - 298 - 465 + 598 + 598 @@ -787,7 +852,7 @@ 0 0 - 761 + 1061 22 @@ -916,14 +981,12 @@ titleLineEdit composerLineEdit arrangerLineEdit - timeSignatureSpinBox pageSizeComboBox documentUnitsComboBox leftMarginLineEdit topMarginLineEdit lineSpacingDoubleSpinBox fontComboBox - includedFontCheckBox chordTableView chordNameLineEdit guitarVoicingLineEdit