71 Commits
0.1 ... master

Author SHA1 Message Date
Ivan Holmes 70e2d4e7d3 remove debug print statements and make bg grey 2 years ago
Ivan Holmes de4b2bfe3d slightly improve pagesetup ui 3 years ago
Ivan Holmes 6fc46a46b8 change style of save warning dialog buttons on Haiku for better integration 3 years ago
Ivan Holmes 6f3da3b2c4 change icons on messageboxes to info rather than warning 3 years ago
Ivan Holmes 1d313f420e merge remote changes 3 years ago
Ivan Holmes 44147406c8 fix save warning crash on new document and change save warning dialog icon 3 years ago
Ivan Holmes 41a67c5e55 fix the fix and add vertical scroll position hack 3 years ago
Ivan Holmes c9a93b5e93 fix for flashing/cut off preview and pagesetup ui slimmed 3 years ago
Ivan Holmes d31a926f92 get MDI working properly, fix some longstanding bugs 3 years ago
Ivan Holmes 699312cb5c Update linux.spec to add pyqt5.sip hidden import 3 years ago
Ivan Holmes ce3ed11f12 add MDI 3 years ago
Ivan Holmes 022112fba5 Merge readme Merge branch 'master' of git.radivan.net:ivan/chordsheet 4 years ago
Ivan Holmes fcf1e72c96 MDI pretty much working 4 years ago
Ivan Holmes d315f13868 Update the readme 4 years ago
Ivan Holmes 6e9ab42997 update version 4 years ago
Ivan Holmes b340c1e64d fix crash on add block with no sections 4 years ago
Ivan Holmes 058d89cff6 actually fix 4 years ago
Ivan Holmes 7b2b793006 hopefully fix remove chord crash 4 years ago
Ivan Holmes db2d30f738 Fix top note of piano chord not being rendered 4 years ago
Ivan Holmes 7a5a89644f Fix control order in UI and clarify error messages 4 years ago
Ivan Holmes 5a078a1bca increment version 4 years ago
Ivan Holmes dc955cdf7c Fix crash on saving new file (severe) 4 years ago
Ivan Holmes 1e54f4c05a Update Linux spec file 4 years ago
Ivan Holmes b8aa49465a Edit win spec file once more 4 years ago
Ivan Holmes 5aafc6f604 Edit windows spec file 4 years ago
Ivan Holmes 9f1ad34719 Update readme (minor fix) 4 years ago
Ivan Holmes aa4228f436 Update readme 4 years ago
Ivan Holmes eb55561f31 Fix link in about window 4 years ago
Ivan Holmes f52e9679d6 Update to 0.4.2 4 years ago
Ivan Holmes 25779ddbbb Update readme 4 years ago
Ivan Holmes 1ce8757de4 fix 'fontName' issue 5 years ago
Ivan Holmes 67ed03f992 disable UPX for mac 5 years ago
Ivan Holmes ade2228bc7 fix save now that .cma files are supported 5 years ago
Ivan Holmes 0c556d098f update windows spec file 5 years ago
Ivan Holmes 0434fae8cf fix small spelling mistake 5 years ago
Ivan Holmes 522dfd5d1d update version.rc again 5 years ago
Ivan Holmes a2b173b2b6 update version.rc.template to actually work 5 years ago
Ivan Holmes f22fe5c33d add version.rc 5 years ago
Ivan Holmes 3e071312a0 script to generate version.rc for windows 5 years ago
Ivan Holmes 62fbbcb505 update windows spec file 5 years ago
Ivan Holmes dd6cda5348 update mac builld script to add version number 5 years ago
Ivan Holmes 73372066cc increment version number 5 years ago
Ivan Holmes 5aac931f51 add read support for simple macro language 5 years ago
Ivan Holmes 5291b6e404 add read support for simple macro language 5 years ago
Ivan Holmes c9c45f66d5 add space before credits 5 years ago
Ivan Holmes 1d526d3a09 strip whitespace off entered fingering notes 5 years ago
Ivan Holmes 3fa216a149 update heading style to make sure it's attached 5 years ago
Ivan Holmes ce3d79bb6c update piano chord wrapping 5 years ago
Ivan Holmes b5ab599e15 update example 5 years ago
Ivan Holmes 055ff0479e fix crash when you choose not to save 5 years ago
Ivan Holmes abaa251010 fix regression in updateChordAction 5 years ago
Ivan Holmes 2c662716f6 Merge branch 'master' of https://github.com/ivanholmes/chordsheet 5 years ago
Ivan Holmes 3230dc71b7 add piano chord chart and piano voicings 5 years ago
Ivan Holmes 49512214dc
update readme to reflect changes 5 years ago
Ivan Holmes 5b2f1d84ab added Sections, multi-page capability 5 years ago
Ivan Holmes e38c6ebdab rewrite to use reportlab flowables for rendering 5 years ago
Ivan Holmes 7d038f9973 update version 5 years ago
Ivan Holmes 977817a293 Add subtitle and tempo fields 5 years ago
Ivan Holmes 79eca296ca fixed updating chord 5 years ago
Ivan Holmes c50c6b9d3c fix crash when 'remove chord' is pressed but there 5 years ago
Ivan Holmes a60b96d003 correct version 5 years ago
Ivan Holmes 7899aee96e Update examples and fix preview crash 5 years ago
Ivan Holmes 6cf5b3230d Fix typo calling saveFile function 5 years ago
Ivan Holmes e2d4132b6e Correctly set the chord combo box on the block 5 years ago
Ivan Holmes 17e14e6eeb Vastly improve commenting 5 years ago
Ivan Holmes 5f334d1f14 input validation for chord and block editors 5 years ago
Ivan Holmes 36a86d280f various modifications and improvements: 5 years ago
Ivan Holmes b0aa4621fb sort out column resizing by user and program 5 years ago
Ivan Holmes a54d704ada Merge branch 'master' of https://github.com/ivanholmes/chordsheet 5 years ago
Ivan Holmes e6bc73d9c7 enable high dpi scaling 5 years ago
Ivan Holmes b86f693e42
updated README 5 years ago
  1. 3
      .gitignore
  2. 41
      README.md
  3. 4
      _version.py
  4. 7
      chordsheet/common.py
  5. 61
      chordsheet/components/chordprogression.py
  6. 56
      chordsheet/components/guitarchart.py
  7. 4
      chordsheet/components/header.py
  8. 247
      chordsheet/document.py
  9. 24
      chordsheet/parsers.py
  10. 39
      chordsheet/primitives.py
  11. 583
      chordsheet/render.py
  12. 51
      chordsheet/rlStylesheet.py
  13. 78
      chordsheet/tableView.py
  14. 43
      cli.py
  15. 12
      csgui/comboBox.py
  16. 81
      csgui/dialogs.py
  17. 124
      csgui/messageBox.py
  18. 64
      csgui/panels.py
  19. 96
      csgui/pdfViewer.py
  20. 153
      csgui/tableView.py
  21. 6
      examples/ah.xml
  22. 309
      examples/ahlong.xml
  23. 1
      examples/angela.xml
  24. 89
      examples/example.xml
  25. 143
      examples/examplelong.xml
  26. 24
      examples/kissoflife.cma
  27. 1
      examples/kissoflife.xml
  28. 1
      examples/test.xml
  29. 15
      generate_version_rc.py
  30. 1247
      gui.py
  31. 47
      linux.spec
  32. 19
      mac.spec
  33. 4
      requirements.txt
  34. 142
      ui/aboutdialog.ui
  35. 291
      ui/blocks.ui
  36. 237
      ui/chords.ui
  37. 162
      ui/docinfo.ui
  38. 57
      ui/document.ui
  39. 2
      ui/guitardialog.ui
  40. BIN
      ui/icon.afdesign
  41. BIN
      ui/icon.icns
  42. BIN
      ui/icon.ico
  43. BIN
      ui/icon.png
  44. 896
      ui/mainwindow.ui
  45. 83
      ui/preview.ui
  46. 326
      ui/psetup.ui
  47. 165
      ui/sections.ui
  48. 43
      version.rc
  49. 43
      version.rc.template
  50. 20
      win.spec

3
.gitignore

@ -52,3 +52,6 @@ docs/_build/
# Mac stuff
.DS_Store
.vscode/
# Syncthing conflicts
*.sync-conflict-*

41
README.md

@ -1,23 +1,52 @@
# Chordsheet
Chordsheet is a piece of software that generates a chord sheet, with a simple GUI.
It can load and save chordsheets in its own XML-based format, and can save them in PDF format for printing.
It's now possible to write a chordsheet in a custom macro language, and import this for further editing in the GUI. See the examples folder for details.
I wrote Chordsheet because no other software offered what I was looking for. I did not want to have to enter lyrics (most of the music I am charting is instrumental anyway), needed definite timing information, and wanted to be able to represent guitar chords (with a view to supporting other instruments in future).
I wrote Chordsheet because no other software offered what I was looking for. I did not want to have to enter lyrics (most of the music I am charting is instrumental anyway), needed definite timing information, and wanted to be able to represent guitar and piano chords (with a view to supporting other instruments in future).
Chordsheet works on a system of blocks, each with a certain length. Blocks may have an assigned chord, or additional information. These blocks can then be ordered and the output generated.
## Get started
To run Chordsheet, go to the Releases tab and download the most recent version for your OS. Releases are currently provided for macOS, Linux and Windows. These binaries are created with PyInstaller and so can't be used for development. You do not need to install anything else to run Chordsheet this way.
To develop Chordsheet, clone this repository and run gui.py using Python 3.7 or newer. Make sure you have the dependencies installed!
## Current status
Chordsheet is alpha-grade software. Most things should work, but I haven't tested it too extensively.
### Features
- Guitar and piano chords can be entered and rendered
- Multiple sections of the same piece are supported
- Supports reading Chordsheet macro files (custom macro language designed to make it easy to quickly generate chordsheets, see examples folder)
- High quality PDF output
Chordsheet is alpha-grade software. At present, the program will crash readily given user input it doesn't expect.
### Limitations
- No support for multiple pages
- Only guitar chords can be entered and shown
- No support for lyrics or melody (use something else!)
- PDF preview is blurry on high DPI monitors
- Chord names and notes can spill out of their block if it's not big enough
- Chord names and notes can spill out of their block if it's not big enough (partially remedied by allowing the user to change the beat width)
- Poor font handling (choice of either FreeSans or Helvetica Neue if installed)
- No internal support for printing
## Dependencies
Chordsheet depends on pymupdf (to show the preview), reportlab (to generate the PDF), and PyQt5 (for the GUI).
Also, a font that supports musical symbols is required. A copy of FreeSans is bundled (in the fonts folder).
This command should sort you out:
```bash
pip3 install pymupdf reportlab pyqt5
```
Note that if you have downloaded a binary from the Releases tab, these libraries are included already so you don't need to install them separately.
## Building (freezing)
To build the binary distribution using PyInstaller, simply run:
```bash
pyinstaller {mac|linux|win}.spec
```
The generated binary will be in the 'dist' subfolder.
### Version numbering on Windows
If you are building on Windows and alter the version number, make sure to run `generate_version_rc.py` to update the version.rc file. This file is included in the generated executable and contains the version information viewable in Windows Explorer properties.
## License
Chordsheet is licensed under the AGPLv3, included in full in 'LICENSE'.

4
_version.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
appName = "Chordsheet"
version = '0.5.0'

7
chordsheet/common.py

@ -0,0 +1,7 @@
import sys, os
# set the directory where our files are depending on whether we're running a pyinstaller binary or not
if getattr(sys, 'frozen', False):
scriptDir = sys._MEIPASS
else:
scriptDir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir))

61
chordsheet/components/chordprogression.py

@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.graphics.shapes import *
from graphics.primitives import *
class ChordProgression:
def __init__(self, currentCanvas, blockList, **kwargs):
self.currentCanvas = currentCanvas
self.beatsHeight = kwargs.get('beatsHeight', 5*mm)
self.timeFontSize = kwargs.get('timeFontSize', 12)
self.chordNameFontSize = kwargs.get('chordNameFontSize', 18)
self.notesFontSize = kwargs.get('notesFontSize', 12)
self.unitWidth = kwargs.get('unitWidth', 10*mm)
self.unitHeight = kwargs.get('unitHeight', 20*mm)
self.timeSignature = kwargs.get('timeSignature', 4)
def draw(self):
global cur_pos, margin, pagesize
writeText(self.currentCanvas, "Chord progression", size=18, align="left")
v_origin = cur_pos + 2*mm + self.beatsHeight
h_origin = margin
h_loc = 0
v_loc = 0
maxWidth = int((((pagesize[0]-(2*margin))/self.unitWidth)//(self.timeSignature*2))*(self.timeSignature*2)) # use integer division to round maxWidth to nearest multiple of time signature
for u in range(maxWidth+1):
s = 0
x = u*self.unitWidth+margin
if u % self.timeSignature == 0:
e = -self.beatsHeight
else:
e = -self.beatsHeight/2
drawVertLine(self.currentCanvas, s, e, x, h_origin, v_origin)
if u == maxWidth: # Avoid writing beat number after the final line
break
writeText(str((u % self.timeSignature)+1),size=self.timeFontSize, hpos=x+self.unitWidth/2, vpos=v_origin-self.beatsHeight)
blockList = parseBlockList(self.blockList, maxWidth)
for b in blockList:
if h_loc == maxWidth:
v_loc += 1
h_loc = 0
currentCanvas.rect(h_origin+(h_loc*self.unitWidth), v_origin+(v_loc*self.unitHeight), b[0]*self.unitWidth, self.unitHeight)
if b[2]:
writeText(currentCanvas, b[2], size=self.notesFontSize, hpos=h_origin+((h_loc+b[0]/2)*self.unitWidth), vpos=v_origin+((v_loc+1)*self.unitHeight)-(1.3*self.notesFontSize))
v_offset = (v_loc*self.unitHeight+self.unitHeight/2)-self.chordNameFontSize/2
writeText(currentCanvas, parseName(b[1]), size=self.chordNameFontSize, hpos=h_origin+((h_loc+b[0]/2)*self.unitWidth), vpos=v_origin+v_offset)
h_loc += b[0]
cur_pos = v_origin+(v_loc+1)*self.unitHeight+self.beatsHeight

56
chordsheet/components/guitarchart.py

@ -1,56 +0,0 @@
# -*- coding: utf-8 -*-
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.graphics.shapes import *
from graphics.primitives import *
string_hz_sp = 20*mm
string_hz_gap = 2*mm
string_height = 5*mm
def guitarChart(currentCanvas, string_hz_sp, string_hz_gap, string_height):
global cur_pos, margin, pagesize
writeText("Guitar chord voicings", size=18, align="left")
chartmargin = 15*mm
v_origin = cur_pos + 2*mm
h_origin = margin + chartmargin
nstrings = 6
fontsize = 12
guitarChordList = [[chordList[q].guitar[r] for q in range(len(chordList)) if hasattr(chordList[q], 'guitar')] for r in range(6)]
guitarChordList.append([chordList[q].name for q in range(len(chordList))])
for i in range(nstrings+1): # i is the string currently being drawn
writeText(['e','B','G','D','A','E','Name'][i], size=fontsize, hpos=(h_origin), vpos=v_origin+(i*string_height), align='right')
for j in range(len(ls.chords)): # j is which chord (0 is first chord, 1 is 2nd etc)
if j == 0:
charpos = string_hz_sp/2
s = string_hz_gap
e = charpos-((c.stringWidth(chordList[i][j])/2)+string_hz_gap)
y = v_origin+(string_height*i)+string_height/2
drawHorizLine(currentCanvas, s, e, y, h_origin, v_origin)
else:
charpos = string_hz_sp*(j+0.5)
s = charpos-string_hz_sp+(lastWidth/2+string_hz_gap)
e = charpos-((currentCanvas.stringWidth(chordList[i][j])/2)+string_hz_gap)
y = v_origin+(string_height*i)+string_height/2
drawHorizLine(currentCanvas, s, e, y, h_origin, v_origin)
if j == len(ls.chords)-1:
s = charpos+(currentCanvas.stringWidth(chordList[i][j])/2+string_hz_gap)
e = charpos+string_hz_sp/2
y = v_origin+(string_height*i)+string_height/2
drawHorizLine(currentCanvas, s, e, y, h_origin, v_origin)
writeText(chordList[i][j], size=fontsize, hpos=h_origin+charpos, vpos=v_origin+(i*string_height))
lastWidth = currentCanvas.stringWidth(chordList[i][j])
cur_pos += (string_height*(nstrings+2))

4
chordsheet/components/header.py

@ -1,4 +0,0 @@
def header():
writeText(ls.title.cdata, size=24)
writeText("Composer: {c}".format(c = ls.composer.cdata), size=12)
writeText("Arranger: {a}".format(a = ls.arranger.cdata), size=12)

247
chordsheet/document.py

@ -9,66 +9,108 @@ defaultTimeSignature = 4
class Style:
def __init__(self, **kwargs):
# set up the style using sane defaults
self.unit = kwargs.get('unit', mm)
self.pageSize = kwargs.get('pageSize', A4)
self.leftMargin = kwargs.get('leftMargin', 10)
self.topMargin = kwargs.get('topMargin', 10)
self.rightMargin = kwargs.get('rightMargin', 10)
self.bottomMargin = kwargs.get('bottomMargin', 10)
self.font = kwargs.get('font', 'FreeSans')
self.lineSpacing = kwargs.get('lineSpacing', 1.15)
self.separatorSize = kwargs.get('separatorSize', 5)
self.useIncludedFont = False
self.unitWidth = kwargs.get('unitWidth', 10)
self.stringHzSp = 20*self.unit
self.stringHzGap = 2*self.unit
self.stringHeight = 5*self.unit
self.useIncludedFont = True
self.numberPages = True
self.unitWidth = 10*self.unit
self.unitHeight = 20*self.unit
self.beatsHeight = 5*self.unit
self.separatorSize = 5*self.unit
self.titleFontSize = 24
self.subtitleFontSize = 18
self.creditsFontSize = 12
self.tempoFontSize = 12
self.headingFontSize = 18
self.notesFontSize = 12
self.chordNameFontSize = 18
self.beatsFontSize = 12
class Chord:
def __init__(self, name, **kwargs):
self.name = name
self.voicings = {}
for inst, fing in kwargs.items():
setattr(self, inst, fing)
self.voicings[inst] = fing
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.name == other.name and self.voicings == other.voicings
return NotImplemented
class Block:
def __init__(self, length, **kwargs):
def __init__(self, length, chord=None, notes=None):
self.length = length
self.chord = kwargs.get('chord', None)
self.notes = kwargs.get('notes', None)
self.chord = chord
self.notes = notes
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.length == other.length and self.chord == other.chord and self.notes == other.notes
return NotImplemented
class Section:
def __init__(self, blockList=None, name=None):
self.blockList = blockList or []
self.name = name
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.blockList == other.blockList and self.name == other.name
return NotImplemented
class Document:
def __init__(self, chordList=None, blockList=None, title=None, composer=None, arranger=None, timeSignature=defaultTimeSignature):
def __init__(self, chordList=None, sectionList=None, title=None, subtitle=None, composer=None, arranger=None, timeSignature=defaultTimeSignature, tempo=None):
self.chordList = chordList or []
self.blockList = blockList or []
self.sectionList = sectionList 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.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.sectionList == other.sectionList
return NotImplemented
def loadXML(self, filepath):
"""
Read an XML file and import its contents.
"""
xmlDoc = ET.parse(filepath)
root = xmlDoc.getroot()
self.chordList = []
if root.find('chords') is not None:
if root.find('chords'):
for c in root.findall('chords/chord'):
self.chordList.append(Chord(parseName(c.find('name').text)))
for v in c.findall('voicing'):
setattr(self.chordList[-1], v.attrib['instrument'],
parseFingering(v.text, v.attrib['instrument']))
self.chordList[-1].voicings[v.attrib['instrument']
] = parseFingering(v.text, v.attrib['instrument'])
self.sectionList = []
if root.find('section'):
for n, s in enumerate(root.findall('section')):
blockList = []
self.blockList = []
if root.find('progression') is not None:
for b in root.findall('progression/block'):
blockChordName = parseName(b.find('chord').text) if b.find('chord') is not None else None
for b in s.findall('block'):
blockChordName = parseName(b.find('chord').text) if b.find(
'chord') is not None else None
if blockChordName:
blockChord = None
for c in self.chordList:
@ -76,27 +118,42 @@ class Document:
blockChord = c
break
if blockChord is None:
exit("Chord {c} does not match any chord in {l}.".format(c=blockChordName, l=self.chordList))
raise ValueError("Chord {c} does not match any chord in {l}.".format(
c=blockChordName, l=self.chordList))
else:
blockChord = None
blockNotes = (b.find('notes').text if b.find('notes') is not None else None)
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.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)
blockNotes = (b.find('notes').text if b.find(
'notes') is not None else None)
blockList.append(
Block(float(b.find('length').text), chord=blockChord, notes=blockNotes))
# automatically name the section by its index if a name isn't given. The +1 is because indexing starts from 0.
self.sectionList.append(Section(blockList=blockList, name=(
s.attrib['name'] if 'name' in s.attrib else "Section {}".format(n + 1))))
def newFromXML(filepath):
doc = Document()
doc.loadXML(filepath)
return doc
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 saveXML(self, filepath):
def toXML(self):
"""
Write the contents of the Document object to an XML file.
"""
root = ET.Element("chordsheet")
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
@ -105,20 +162,27 @@ class Document:
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")
for c in self.chordList:
chordElement = ET.SubElement(chordsElement, "chord")
ET.SubElement(chordElement, "name").text = c.name
if hasattr(c, 'guitar'):
ET.SubElement(chordElement, "voicing", attrib={'instrument':'guitar'}).text = ','.join(c.guitar)
if hasattr(c, 'piano'):
ET.SubElement(chordElement, "voicing", attrib={'instrument':'piano'}).text = c.piano[0] # return first element of list as feature has not been implemented
for inst in c.voicings.keys():
if inst == 'guitar':
ET.SubElement(chordElement, "voicing", attrib={
'instrument': 'guitar'}).text = ','.join(c.voicings['guitar'])
if inst == 'piano':
ET.SubElement(chordElement, "voicing", attrib={
'instrument': 'piano'}).text = ','.join(c.voicings['piano'])
progressionElement = ET.SubElement(root, "progression")
for b in self.blockList:
blockElement = ET.SubElement(progressionElement, "block")
for n, s in enumerate(self.sectionList):
sectionElement = ET.SubElement(root, "section", attrib={
'name': s.name if s.name else "Section {}".format(n + 1)})
for b in s.blockList:
blockElement = ET.SubElement(sectionElement, "block")
ET.SubElement(blockElement, "length").text = str(b.length)
if b.chord is not None:
ET.SubElement(blockElement, "chord").text = b.chord.name
@ -126,7 +190,102 @@ class Document:
ET.SubElement(blockElement, "notes").text = b.notes
tree = ET.ElementTree(root)
return tree
def saveXML(self, filepath):
tree = self.toXML()
tree.write(filepath)
return hash(ET.tostring(tree.getroot()))
def getHash(self):
tree = self.toXML()
return hash(ET.tostring(tree.getroot()))
def loadCSMacro(self, filepath):
"""
Read a Chordsheet Macro file and import its contents.
"""
self.chordList = []
self.sectionList = []
aliasTable = {}
def chord(args):
argList = args.split(" ")
chordName = argList.pop(0)
self.chordList.append(Chord(parseName(chordName)))
argIter = iter(argList)
subCmdsArgs = list(zip(argIter, argIter))
for subCmd, arg in subCmdsArgs:
if subCmd == "alias":
aliasTable[arg] = chordName
else:
self.chordList[-1].voicings[subCmd] = parseFingering(arg, subCmd)
def section(args):
blockList = []
sectionName, blocks = [arg.strip() for arg in args.split("\n", 1)]
self.sectionList.append(Section(name=sectionName))
for b in blocks.split():
blockParams = b.split(",")
blockLength = float(blockParams[1])
if blockParams[0] in aliasTable:
blockChordName = aliasTable[blockParams[0]]
else:
blockChordName = blockParams[0]
blockChordName = parseName(blockChordName) if blockChordName not in ["NC", "X"] else None
blockChord = None
if blockChordName:
for c in self.chordList:
if c.name == blockChordName:
blockChord = c
break
if blockChord is None:
raise ValueError("Chord {c} does not match any chord in {l}.".format(
c=blockChordName, l=self.chordList))
blockList.append(Block(blockLength, chord=blockChord))
self.sectionList[-1].blockList = blockList
with open(filepath, 'r') as f:
cmatext = f.read()
cmaCmdsArgs = [statement.split(" ", 1) for statement in \
(rawStatement.strip() for rawStatement in cmatext.split("\\")[1:])]
for cmd, args in cmaCmdsArgs:
if cmd == "chordsheet":
# There's only one version so no need to do anything with this
pass
elif cmd == "title":
self.title = args
elif cmd == "subtitle":
self.subtitle = args
elif cmd == "arranger":
self.arranger = args
elif cmd == "composer":
self.composer = args
elif cmd == "timesig":
self.timeSignature = int(args)
elif cmd == "tempo":
self.tempo = args
elif cmd == "chord":
chord(args)
elif cmd == "section":
section(args)
elif cmd in ["!", "rem"]:
# Simply ignore comments
pass
else:
raise ValueError(f"Command {cmd} not understood.")

24
chordsheet/parsers.py

@ -1,24 +1,32 @@
# -*- coding: utf-8 -*-
from sys import exit
def parseFingering(fingering, instrument):
"""
Converts fingerings into the list format that Chord objects understand.
"""
if instrument == 'guitar':
numStrings = 6
if len(fingering) == numStrings:
if "," not in fingering and len(fingering) == numStrings: # if the fingering is entered in concise format e.g. xx4455
output = list(fingering)
else:
output = [x for x in fingering.split(',')]
if len(output) == numStrings:
else: # if entered in long format e.g. x,x,10,10,11,11
output = [f.strip() for f in fingering.split(",")]
if len(output) != numStrings:
print("Voicing <{}> is malformed.".format(fingering))
return output
else:
exit("Voicing <{v}> is malformed.".format(v=fingering))
elif instrument == 'piano':
return [parseName(note).upper().strip() for note in fingering.split(",")]
else:
return [fingering]
nameReplacements = { "b":"", "#":"" }
# dictionary holding text to be replaced in chord names
nameReplacements = {"b": "", "#": ""}
def parseName(chordName):
"""
Replaces symbols in chord names.
"""
parsedName = chordName
for i, j in nameReplacements.items():
parsedName = parsedName.replace(i, j)

39
chordsheet/primitives.py

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.graphics.shapes import *
def writeText(currentCanvas, style, string, size, vpos, **kwargs):
margin = style.leftMargin*style.unit
align = kwargs.get('align', 'centre')
if align == 'centre' or align == 'center':
hpos = kwargs.get('hpos', style.pageSize[0]/2)
elif align == 'left':
hpos = kwargs.get('hpos', margin)
elif align == 'right':
hpos = kwargs.get('hpos', style.pageSize[0]-margin)
spacing = kwargs.get('spacing', style.lineSpacing)
currentCanvas.setFont(style.font, size)
if align == 'centre' or align == 'center':
currentCanvas.drawCentredString(hpos, vpos+(0.75*size*spacing),string)
elif align == 'left':
currentCanvas.drawString(hpos, vpos+(0.75*size*spacing),string)
elif align == 'right':
currentCanvas.drawString(hpos-currentCanvas.stringWidth(string), vpos+(0.75*size*spacing),string)
return size*style.lineSpacing
def drawHorizLine(currentCanvas, startpoint, endpoint, v_pos, h_origin, v_origin):
x1 = h_origin+startpoint
x2 = h_origin+endpoint
currentCanvas.line(x1, v_pos, x2, v_pos)
def drawVertLine(currentCanvas, startpoint, endpoint, h_pos, h_origin, v_origin):
y1 = v_origin+startpoint
y2 = v_origin+endpoint
currentCanvas.line(h_pos, y1, h_pos, y2)

583
chordsheet/render.py

@ -1,12 +1,378 @@
# -*- coding: utf-8 -*-
from math import trunc, ceil
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.graphics.shapes import *
from chordsheet.primitives import writeText, drawVertLine, drawHorizLine
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
from chordsheet.rlStylesheet import getStyleSheet
defaultSpacing = 1.15
def writeText(canvas, style, string, size, vpos, width, **kwargs):
"""
Wrapper function to conveniently write text and return how much vertical space it took up.
"""
align = kwargs.get('align', 'centre')
if align == 'centre' or align == 'center':
hpos = kwargs.get('hpos', width/2)
elif align == 'left':
hpos = kwargs.get('hpos', 0)
elif align == 'right':
hpos = kwargs.get('hpos', width)
spacing = kwargs.get('spacing', style.lineSpacing)
canvas.setFont(style.font, size)
if align == 'centre' or align == 'center':
canvas.drawCentredString(hpos, vpos-(0.75*size*spacing), string)
elif align == 'left':
canvas.drawString(hpos, vpos-(0.75*size*spacing), string)
elif align == 'right':
canvas.drawString(hpos-canvas.stringWidth(string),
vpos-(0.75*size*spacing), string)
return size*style.lineSpacing
class Tempo(Flowable):
"""
Flowable that draws the tempo. Necessary because Paragraph does not support the crotchet character.
"""
def __init__(self, tempo, paraStyle):
self.tempo = tempo
self.text = "♩ = {t} bpm".format(t=self.tempo)
self.fontSize = paraStyle.fontSize
self.fontName = paraStyle.fontName
self.leading = paraStyle.leading
def wrap(self, availWidth, availHeight):
self.width = availWidth
self.height = self.leading
return (self.width, self.height)
def draw(self):
canvas = self.canv
canvas.setFont(self.fontName, self.fontSize)
canvas.drawString(0, self.leading * 0.25, self.text)
class GuitarChart(Flowable):
"""
Flowable that draws a guitar chord voicing chart.
"""
def __init__(self, style, chordList):
self.style = style
self.guitarChordList = [
c for c in chordList if 'guitar' in c.voicings.keys()]
self.chartMargin = 13*mm
self.nStrings = 6
self.stringHzSp = 20*mm
self.stringHzGap = 2*mm
self.stringHeight = 5*mm
self.spaceAfter = self.style.separatorSize
def splitChordList(self, l, n):
"""Yield successive n-sized chunks from l."""
for i in range(0, len(l), n):
yield l[i:i + n]
def wrap(self, availWidth, availHeight):
self.nChords = trunc((availWidth - self.chartMargin -
self.stringHzGap) / self.stringHzSp)
# the height of one layer of chart
self.oneHeight = self.stringHeight * (self.nStrings+1)
# only one line needed
if len(self.guitarChordList) <= self.nChords:
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.stringHzGap + \
self.stringHzSp * self.nChords
self.height = self.oneHeight * ceil(len(self.guitarChordList) / self.nChords) + \
(self.stringHeight *
trunc(len(self.guitarChordList) / self.nChords))
return (self.width, self.height)
def draw(self):
canvas = self.canv
chartmargin = self.chartMargin
for count, gcl in enumerate(self.splitChordList(self.guitarChordList, self.nChords)):
v_origin = self.height - count * \
(self.oneHeight + self.stringHeight)
self.nStrings = 6
fontsize = 12
stringList = [
[c.voicings['guitar'][-(r+1)] for c in gcl] for r in range(self.nStrings)]
stringList.append([c.name for c in gcl])
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.stringHeight), self.width, hpos=chartmargin, align='right', spacing=defaultSpacing)
# 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.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.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.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.stringHeight), self.width, hpos=chartmargin+self.stringHzSp*(j+0.5), spacing=defaultSpacing)
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:
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 and curIndex != 11:
chartKeyList.extend(
self.keyList[lastIndex+1:((curIndex+1) % len(self.keyList))])
elif curIndex < lastIndex or curIndex == 11:
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
def splitBlocks(blockList, maxWidth):
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.
"""
def __init__(self, style, heading, blockList, timeSignature):
self.style = style
self.heading = heading # the title of the section
self.blockList = blockList
self.timeSignature = timeSignature
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
def wrapBlocks(self, blockList, maxWidth):
"""
Splits any blocks that won't fit in the remaining space on the line.
"""
h_loc = 0
splitBlockList = []
for i in range(len(blockList)):
@ -20,10 +386,12 @@ def splitBlocks(blockList, maxWidth):
if blockList[i].length - sum(lengthList) >= maxWidth:
lengthList.append(maxWidth)
else:
lengthList.append(blockList[i].length - sum(lengthList))
lengthList.append(
blockList[i].length - sum(lengthList))
for l in lengthList:
splitBlockList.append(Block(l, chord=c_orig, notes=n_orig)) # create a block with the given length
# create a block with the given length
splitBlockList.append(Block(l, chord=c_orig, notes=n_orig))
h_loc = lengthList[-1]
else:
@ -31,131 +399,162 @@ def splitBlocks(blockList, maxWidth):
h_loc += blockList[i].length
return splitBlockList
def guitarChart(currentCanvas, style, chordList, cur_pos):
title_height = writeText(currentCanvas, style, "Guitar chord voicings", 18, cur_pos, align="left")
cur_pos += title_height
string_hz_sp = style.stringHzSp
string_hz_gap = style.stringHzGap
string_height = style.stringHeight
margin = style.leftMargin*style.unit
pagesize = style.pageSize
chartmargin = 15*mm
v_origin = cur_pos + 2*mm
h_origin = margin + chartmargin
nstrings = 6
fontsize = 12
guitarChordList = [[chordList[q].guitar[-(r+1)] for q in range(len(chordList)) if hasattr(chordList[q], 'guitar')] for r in range(6)]
guitarChordList.append([chordList[q].name for q in range(len(chordList)) if hasattr(chordList[q], 'guitar')])
for i in range(nstrings+1): # i is the string currently being drawn
writeText(currentCanvas, style, ['e','B','G','D','A','E','Name'][i], fontsize, v_origin+(i*string_height), hpos=h_origin, align='right')
for j in range(len(guitarChordList[-1])): # j is which chord (0 is first chord, 1 is 2nd etc)
if j == 0:
charpos = string_hz_sp/2
s = string_hz_gap
e = charpos-((currentCanvas.stringWidth(guitarChordList[i][j])/2)+string_hz_gap)
y = v_origin+(string_height*i)+string_height/2
drawHorizLine(currentCanvas, s, e, y, h_origin, v_origin)
def splitBlockList(self, blockList, length):
"""
Splits a blockList into two lists, one of the given length (in beats) and one for the rest. Also wraps the blocks to
given length in case the split would fall in the middle of one.
"""
secondPart = self.wrapBlocks(blockList, length)
firstPart = []
currentBeat = 0
while currentBeat != length:
block = secondPart.pop(0)
firstPart.append(block)
currentBeat += block.length
return firstPart, secondPart
def wrap(self, availWidth, availHeight):
self.widthInBeats = 2 * self.timeSignature * \
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.beatsHeight + self.unitHeight * \
sum([b.length for b in self.blockList]) / self.widthInBeats
return(self.width, self.height)
def split(self, availWidth, availHeight):
if availHeight >= self.height:
return [self]
else:
charpos = string_hz_sp*(j+0.5)
s = charpos-string_hz_sp+(lastWidth/2+string_hz_gap)
e = charpos-((currentCanvas.stringWidth(guitarChordList[i][j])/2)+string_hz_gap)
y = v_origin+(string_height*i)+string_height/2
drawHorizLine(currentCanvas, s, e, y, h_origin, v_origin)
vUnits = trunc(
(availHeight - self.beatsHeight) / self.unitHeight)
firstPart, secondPart = self.splitBlockList(
self.blockList, vUnits * self.widthInBeats)
if j == len(guitarChordList[-1])-1:
s = charpos+(currentCanvas.stringWidth(guitarChordList[i][j])/2+string_hz_gap)
e = charpos+string_hz_sp/2
y = v_origin+(string_height*i)+string_height/2
drawHorizLine(currentCanvas, s, e, y, h_origin, v_origin)
return [ChordProgression(self.style, self.heading, firstPart, self.timeSignature),
PageBreak(),
ChordProgression(self.style, self.heading, secondPart, self.timeSignature)]
writeText(currentCanvas, style, guitarChordList[i][j], fontsize, v_origin+(i*string_height), hpos=h_origin+charpos)
def draw(self):
canvas = self.canv
unitWidth = self.style.unitWidth*self.style.unit
lastWidth = currentCanvas.stringWidth(guitarChordList[i][j])
return (string_height*(nstrings+1)) + title_height # calculate the height of the block
def chordProgression(currentCanvas, style, document, cur_pos):
margin = style.leftMargin*style.unit
pagesize = style.pageSize
title_height = writeText(currentCanvas, style, "Chord progression", 18, cur_pos, align="left")
cur_pos += title_height
v_origin = cur_pos + 2*mm + style.beatsHeight
h_origin = margin
v_origin = self.height - self.beatsHeight
h_offset = self.chartMargin
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
maxWidth = self.widthInBeats
for u in range(maxWidth+1):
s = 0
x = u*style.unitWidth+margin
if u % document.timeSignature == 0:
e = -style.beatsHeight
y = v_origin
x = u*unitWidth + h_offset
if u % self.timeSignature == 0:
l = self.beatsHeight
else:
e = -style.beatsHeight/2
drawVertLine(currentCanvas, s, e, x, h_origin, v_origin)
l = self.beatsHeight/2
canvas.line(x, y, x, y+l)
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(canvas, self.style, str((u % self.timeSignature)+1), self.style.beatsFontSize,
v_origin+self.beatsHeight, self.width, hpos=x+unitWidth/2, spacing=defaultSpacing)
parsedBlockList = splitBlocks(document.blockList, maxWidth)
parsedBlockList = self.wrapBlocks(self.blockList, maxWidth)
for b in parsedBlockList:
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)
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(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))
v_offset = ((v_loc*style.unitHeight)+style.unitHeight/2)-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), spacing=defaultSpacing)
v_offset = ((v_loc*self.unitHeight) +
self.unitHeight/2)-self.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(canvas, self.style, b.chord.name, self.style.chordNameFontSize,
v_origin-v_offset, self.width, hpos=h_offset+((h_loc+b.length/2)*unitWidth), spacing=defaultSpacing)
h_loc += b.length
return v_origin + (v_loc+1)*style.unitHeight + style.beatsHeight + title_height # calculate the height of the generated chart
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 hasattr(c, 'guitar'):
if inst in c.voicings.keys():
chordsPresent = True
break
return chordsPresent
def savePDF(document, style, pathToPDF):
c = canvas.Canvas(pathToPDF, pagesize=style.pageSize, bottomup=0)
class Renderer:
def __init__(self, document, style):
self.document = document
self.style = style
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,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0)])
rlDocList = []
rlDoc = BaseDocTemplate(
pathToPDF, pagesize=self.style.pageSize, pageTemplates=[template])
styles = getStyleSheet(self.style)
if self.document.title:
rlDocList.append(Paragraph(self.document.title, styles['Title']))
if self.document.subtitle:
rlDocList.append(
Paragraph(self.document.subtitle, styles['Subtitle']))
curPos = style.topMargin*style.unit
if self.document.composer or self.document.arranger:
rlDocList.append(Spacer(0, 2*mm))
if document.title is not None:
curPos += writeText(c, style, document.title, 24, curPos)
if self.document.composer:
rlDocList.append(Paragraph("Composer: {c}".format(
c=self.document.composer), styles['Credits']))
if document.composer is not None:
curPos += writeText(c, style, "Composer: {c}".format(c = document.composer), 12, curPos)
if self.document.arranger:
rlDocList.append(Paragraph("Arranger: {a}".format(
a=self.document.arranger), styles['Credits']))
if document.arranger is not None:
curPos += writeText(c, style, "Arranger: {a}".format(a = document.arranger), 12, curPos)
if self.document.tempo:
rlDocList.append(Tempo(self.document.tempo, styles['Tempo']))
curPos += style.separatorSize*style.unit
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(document.chordList):
curPos += guitarChart(c, style, document.chordList, curPos)
if instChartCheck(self.document.chordList, 'guitar'):
rlDocList.extend([
Paragraph('Guitar chord voicings', styles['Heading']),
GuitarChart(self.style, self.document.chordList)])
curPos += style.separatorSize*style.unit
if instChartCheck(self.document.chordList, 'piano'):
rlDocList.extend([
Paragraph('Piano chord voicings', styles['Heading']),
PianoChart(self.style, self.document.chordList)])
if document.blockList:
curPos += chordProgression(c, style, document, curPos)
for s in self.document.sectionList:
rlDocList.append(Paragraph(s.name, styles['Heading']))
# only draw the chord progression if there are blocks
if s.blockList:
rlDocList.append(ChordProgression(
self.style, s.name, s.blockList, self.document.timeSignature))
curPos += style.separatorSize*style.unit
rlDoc.build(rlDocList)
c.save()
def stream(self):
virtualFile = BytesIO()
self.savePDF(virtualFile)
return virtualFile

51
chordsheet/rlStylesheet.py

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from reportlab.lib.styles import StyleSheet1, ParagraphStyle
from reportlab.lib.enums import *
from reportlab.lib.units import mm
from reportlab.lib.colors import black
def getStyleSheet(csStyle):
"""Returns a stylesheet object"""
stylesheet = StyleSheet1()
stylesheet.add(ParagraphStyle(name='Master',
fontName=csStyle.font))
stylesheet.add(ParagraphStyle(name='Title',
leading=csStyle.lineSpacing*csStyle.titleFontSize,
fontSize=csStyle.titleFontSize,
alignment=TA_CENTER,
parent=stylesheet['Master'])
)
stylesheet.add(ParagraphStyle(name='Subtitle',
leading=csStyle.lineSpacing*csStyle.subtitleFontSize,
fontSize=csStyle.subtitleFontSize,
alignment=TA_CENTER,
parent=stylesheet['Master'])
)
stylesheet.add(ParagraphStyle(name='Credits',
leading=csStyle.lineSpacing*csStyle.creditsFontSize,
fontSize=csStyle.creditsFontSize,
alignment=TA_CENTER,
parent=stylesheet['Master'])
)
stylesheet.add(ParagraphStyle(name='Tempo',
leading=csStyle.lineSpacing*csStyle.tempoFontSize,
fontSize=csStyle.tempoFontSize,
alignment=TA_LEFT,
parent=stylesheet['Master'])
)
stylesheet.add(ParagraphStyle(name='Heading',
leading=csStyle.lineSpacing*csStyle.headingFontSize,
fontSize=csStyle.headingFontSize,
alignment=TA_LEFT,
parent=stylesheet['Master'],
spaceAfter=2*mm,
keepWithNext=1)
)
return stylesheet

78
chordsheet/tableView.py

@ -1,78 +0,0 @@
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MItemModel(QtGui.QStandardItemModel):
def dropMimeData(self, data, action, row, col, parent):
"""
Always move the entire row, and don't allow column "shifting"
"""
return super().dropMimeData(data, action, row, 0, parent)
class MProxyStyle(QtWidgets.QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
"""
Draw a line across the entire row rather than just the column
we're hovering over. This may not always work depending on global
style.
"""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
class MTableView(QtWidgets.QTableView):
def __init__(self, parent):
super().__init__(parent)
self.verticalHeader().hide()
self.horizontalHeader().show()
self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
self.setShowGrid(False)
#self.setDragDropMode(self.InternalMove)
#self.setDragDropOverwriteMode(False)
# Set our custom style - this draws the drop indicator across the whole row
self.setStyle(MProxyStyle())
self.model = MItemModel()
self.setModel(self.model)
class ChordTableView(MTableView):
def __init__(self, parent):
super().__init__(parent)
self.model.setHorizontalHeaderLabels(['Chord', 'Voicing'])
def populate(self, cList):
self.model.removeRows(0, self.model.rowCount())
for c in cList:
rowList = [QtGui.QStandardItem(c.name), QtGui.QStandardItem(",".join(c.guitar if hasattr(c, 'guitar') else ""))]
for item in rowList:
item.setEditable(False)
item.setDropEnabled(False)
self.model.appendRow(rowList)
class BlockTableView(MTableView):
def __init__(self, parent):
super().__init__(parent)
self.model.setHorizontalHeaderLabels(['Chord', 'Length', 'Notes'])
def populate(self, bList):
self.model.removeRows(0, self.model.rowCount())
for b in bList:
rowList = [QtGui.QStandardItem((b.chord.name if b.chord else "")), QtGui.QStandardItem(str(b.length)), QtGui.QStandardItem(b.notes)]
for item in rowList:
item.setEditable(False)
item.setDropEnabled(False)
self.model.appendRow(rowList)

43
cli.py

@ -0,0 +1,43 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import time
from reportlab.lib.units import mm, cm, inch, pica
from reportlab.lib.pagesizes import A4, A5, LETTER, LEGAL
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from chordsheet.common import scriptDir
from chordsheet.document import Document, Style, Chord, Block, Section
from chordsheet.render import Renderer
from chordsheet.parsers import parseFingering, parseName
import _version
pdfmetrics.registerFont(
TTFont('FreeSans', os.path.join(scriptDir, 'fonts', 'FreeSans.ttf')))
if sys.platform == "darwin":
pdfmetrics.registerFont(
TTFont('HelveticaNeue', 'HelveticaNeue.ttc', subfontIndex=0))
if len(sys.argv) == 2:
inputFilePath = sys.argv[1]
else:
print("Please provide a .cml, .xml, or .cma file to process.")
sys.exit()
doc = Document()
if inputFilePath[-4:] == ".cma":
doc.loadCSMacro(inputFilePath)
else:
doc.loadXML(inputFilePath)
style = Style()
renderer = Renderer(doc, style)
outputFilePath = ".".join(inputFilePath.split(".")[:-1]) + ".pdf"
renderer.savePDF(outputFilePath)

12
csgui/comboBox.py

@ -0,0 +1,12 @@
from PyQt5.QtWidgets import QComboBox
from PyQt5.QtCore import pyqtSignal
class MComboBox(QComboBox):
"""
Modified version of combobox that emits a signal with the current item when clicked.
"""
clicked = pyqtSignal(str)
def showPopup(self):
self.clicked.emit(self.currentText())
super().showPopup()

81
csgui/dialogs.py

@ -0,0 +1,81 @@
import os
from PyQt5.QtWidgets import QApplication, QAction, QLabel, QDialogButtonBox, QDialog, QFileDialog, QMessageBox, QPushButton, QLineEdit, QCheckBox, QSpinBox, QDoubleSpinBox, QTableWidgetItem, QTabWidget, QComboBox, QWidget, QScrollArea, QMainWindow, QShortcut
from PyQt5.QtCore import QFile, QObject, Qt
from PyQt5.QtGui import QImage, QPixmap
from PyQt5 import uic
from chordsheet.common import scriptDir
import _version
class GuitarDialog(QDialog):
"""
Dialogue to allow the user to enter a guitar chord voicing. Not particularly advanced at present!
May be extended in future.
"""
def __init__(self):
super().__init__()
self.UIFileLoader(
str(os.path.join(scriptDir, 'ui', 'guitardialog.ui')))
def UIFileLoader(self, ui_file):
ui_file = QFile(ui_file)
ui_file.open(QFile.ReadOnly)
self.dialog = uic.loadUi(ui_file)
ui_file.close()
def getVoicing(self, existingVoicing):
"""
Show the dialogue and return the voicing that has been entered.
"""
lineEditsList = [
self.dialog.ELineEdit,
self.dialog.ALineEdit,
self.dialog.DLineEdit,
self.dialog.GLineEdit,
self.dialog.BLineEdit,
self.dialog.eLineEdit
]
# Read the present voicing
if type(existingVoicing) == list:
for count in range(len(existingVoicing)):
lineEditsList[count].setText(existingVoicing[count])
if self.dialog.exec_() == QDialog.Accepted:
result = [self.dialog.ELineEdit.text() or 'x',
self.dialog.ALineEdit.text() or 'x',
self.dialog.DLineEdit.text() or 'x',
self.dialog.GLineEdit.text() or 'x',
self.dialog.BLineEdit.text() or 'x',
self.dialog.eLineEdit.text() or 'x']
resultJoined = ",".join(result)
return resultJoined
else:
return None
class AboutDialog(QDialog):
"""
Dialogue showing information about the program.
"""
def __init__(self):
super().__init__()
self.UIFileLoader(str(os.path.join(scriptDir, 'ui', 'aboutdialog.ui')))
icon = QImage(str(os.path.join(scriptDir, 'ui', 'icon.png')))
self.dialog.iconLabel.setPixmap(QPixmap.fromImage(icon).scaled(self.dialog.iconLabel.width(
), self.dialog.iconLabel.height(), Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation))
self.dialog.versionLabel.setText("Version " + _version.version)
self.dialog.exec()
def UIFileLoader(self, ui_file):
ui_file = QFile(ui_file)
ui_file.open(QFile.ReadOnly)
self.dialog = uic.loadUi(ui_file)
ui_file.close()

124
csgui/messageBox.py

@ -0,0 +1,124 @@
from PyQt5.QtWidgets import QApplication, QAction, QLabel, QDialogButtonBox, QDialog, QFileDialog, QMessageBox, QPushButton, QLineEdit, QCheckBox, QSpinBox, QDoubleSpinBox, QTableWidgetItem, QTabWidget, QComboBox, QWidget, QScrollArea, QMainWindow, QShortcut, QDialogButtonBox
import sys
class UnsavedMessageBox(QMessageBox):
"""
Message box to alert the user of unsaved changes and allow them to choose how to act.
"""
def __init__(self, fileName):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle("Unsaved changes")
self.setText(f"The document \"{fileName}\" has been modified.")
self.setInformativeText("Do you want to save your changes?")
self.setStandardButtons(
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
self.setDefaultButton(QMessageBox.Save)
# If we are running on Haiku, use the MacOS button style to fit in more
# with native applications
if sys.platform.startswith("haiku"):
buttonBox = self.findChild(QDialogButtonBox)
buttonBox.setStyleSheet("* { button-layout: 1}")
class UnreadableMessageBox(QMessageBox):
"""
Message box to warn the user that the chosen file cannot be opened.
"""
def __init__(self, fileName):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle(f"File \"{fileName}\" cannot be opened")
self.setText("The file you have selected cannot be opened.")
self.setInformativeText("Please make sure it is in the right format.")
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)
class ChordNameWarningMessageBox(QMessageBox):
"""
Message box to warn the user that a chord must have a name
"""
def __init__(self):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle("Unnamed chord")
self.setText("Chords must have a name.")
self.setInformativeText("Please give your chord a name and try again.")
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)
class SectionNameWarningMessageBox(QMessageBox):
"""
Message box to warn the user that a section must have a name
"""
def __init__(self):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle("Unnamed section")
self.setText("Sections must have a unique name.")
self.setInformativeText(
"Please give your section a unique name and try again.")
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)
class BlockMustHaveSectionWarningMessageBox(QMessageBox):
"""
Message box to warn the user that a block must belong to a section
"""
def __init__(self):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle("No sections found")
self.setText("Each block must belong to a section, but no sections have yet been created.")
self.setInformativeText(
"Please create a section before adding blocks.")
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)
class VoicingWarningMessageBox(QMessageBox):
"""
Message box to warn the user that the voicing entered could not be parsed
"""
def __init__(self):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle("Malformed voicing")
self.setText(
"The voicing you entered was not understood and has not been applied.")
self.setInformativeText(
"Please try re-entering it in the correct format.")
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)
class LengthWarningMessageBox(QMessageBox):
"""
Message box to warn the user that a block must have a length
"""
def __init__(self):
super().__init__()
self.setIcon(QMessageBox.Information)
self.setWindowTitle("Block without valid length")
self.setText("Blocks must have a length.")
self.setInformativeText(
"Please enter a valid length for your block and try again.")
self.setStandardButtons(QMessageBox.Ok)
self.setDefaultButton(QMessageBox.Ok)

64
csgui/panels.py

@ -0,0 +1,64 @@
import os
from PyQt5.QtWidgets import QApplication, QAction, QLabel, QDialogButtonBox, QDialog, QFileDialog, QMessageBox, QPushButton, QLineEdit, QCheckBox, QSpinBox, QDoubleSpinBox, QTableWidgetItem, QTabWidget, QComboBox, QWidget, QScrollArea, QMainWindow, QShortcut, QDockWidget, QLineEdit, QTableView
from PyQt5.QtCore import QFile, QObject, Qt
from PyQt5.QtGui import QImage, QPixmap
from PyQt5 import uic
from chordsheet.common import scriptDir
from csgui.tableView import MTableView
class UIFileDockWidget(QDockWidget):
def __init__(self):
super().__init__()
def UIFileLoader(self, ui_file):
ui_file = QFile(os.path.join(scriptDir, 'ui', ui_file))
ui_file.open(QFile.ReadOnly)
self.setWidget(uic.loadUi(ui_file))
ui_file.close()
def clear(self):
# Clear all the fields
for lineEdit in self.findChildren(QLineEdit):
lineEdit.clear()
for comboBox in self.findChildren(QComboBox):
comboBox.clear()
for tableView in self.findChildren(MTableView):
tableView.clear()
class DocInfoDockWidget(UIFileDockWidget):
def __init__(self):
super().__init__()
self.UIFileLoader('docinfo.ui')
self.setWindowTitle("Document information")
class PageSetupDockWidget(UIFileDockWidget):
def __init__(self):
super().__init__()
self.UIFileLoader('psetup.ui')
self.setWindowTitle("Page setup")
class ChordsDockWidget(UIFileDockWidget):
def __init__(self):
super().__init__()
self.UIFileLoader('chords.ui')
self.setWindowTitle("Chords")
class SectionsDockWidget(UIFileDockWidget):
def __init__(self):
super().__init__()
self.UIFileLoader('sections.ui')
self.setWindowTitle("Sections")
class BlocksDockWidget(UIFileDockWidget):
def __init__(self):
super().__init__()
self.UIFileLoader('blocks.ui')
self.setWindowTitle("Blocks")
class PreviewDockWidget(UIFileDockWidget):
def __init__(self):
super().__init__()
self.UIFileLoader('preview.ui')
self.setWindowTitle("Preview")

96
csgui/pdfViewer.py

@ -0,0 +1,96 @@
from PyQt5.QtWidgets import QScrollArea, QLabel, QVBoxLayout, QWidget, QSizePolicy
from PyQt5.QtCore import Qt, QPoint, QSize
from PyQt5.QtGui import QPixmap, QImage, QResizeEvent, QPainter
import fitz
class PDFLabel(QLabel):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
def paintEvent(self, event):
self.adjustSize()
if self.pixmap() is not None:
painter = QPainter(self)
# painter.setRenderHint(QPainter.Antialiasing)
parentLayoutMargins = self.parent.scrollAreaLayout.getContentsMargins()
parentMargins = self.parent.getContentsMargins()
if self.parent.verticalScrollBar().isVisible():
scrollBarWidth = self.parent.verticalScrollBar().sizeHint().width()
else:
scrollBarWidth = 0
totalMargin = parentLayoutMargins[0] + parentLayoutMargins[2] + \
parentMargins[0]*2 + scrollBarWidth
idealWidth = self.parent.width() - totalMargin
# print(idealWidth)
pixSize = self.pixmap().size()
# print(pixSize)
# print((self.parent.scrollAreaContents.size()))
pixSize.scale(idealWidth, 1000000, Qt.KeepAspectRatio)
scaledPix = self.pixmap().scaled(pixSize, Qt.KeepAspectRatio, Qt.SmoothTransformation)
painter.drawPixmap(QPoint(), scaledPix)
self.setMaximumSize(pixSize)
class PDFViewer(QScrollArea):
def __init__(self, parent):
super().__init__(parent)
self.scrollAreaContents = QWidget()
self.scrollAreaLayout = QVBoxLayout()
self.setWidget(self.scrollAreaContents)
self.setWidgetResizable(True)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# set a fixed background colour
# on some OSes (Mac, Haiku) it is grey automatically but not on KDE
self.setStyleSheet('PDFViewer {background-color: #D8D8D8}')
self.scrollAreaContents.setLayout(self.scrollAreaLayout)
self.pixmapList = []
self.lastVScrollPosition = None
def update_pdf(self, pdf):
self.render(pdf)
self.clear()
self.show()
def render(self, pdf):
"""
Update the preview shown by rendering a new PDF and drawing it to the scroll area.
"""
self.pixmapList = []
pdfView = fitz.Document(stream=pdf, filetype='pdf')
for page in pdfView:
# needs to be high enough for big monitors. 300 should do it...
self.pixmapList.append(page.get_pixmap(dpi=300, alpha=False))
def clear(self):
self.lastVScrollPosition = self.verticalScrollBar().value()
while self.scrollAreaLayout.count():
item = self.scrollAreaLayout.takeAt(0)
w = item.widget()
if w:
w.deleteLater()
def show(self):
for p in self.pixmapList:
label = PDFLabel(parent=self)
label.setAlignment(Qt.AlignHCenter)
qtimg = QImage(p.samples, p.width, p.height, p.stride, QImage.Format_RGB888)
label.setPixmap(QPixmap.fromImage(qtimg))
self.scrollAreaLayout.addWidget(label)
self.scrollAreaLayout.addStretch(1)
# Somewhat of a hack. Should really replace pixmaps in place
# rather than removing labels elsewhere in code.
if self.lastVScrollPosition:
self.verticalScrollBar().setValue(self.lastVScrollPosition)

153
csgui/tableView.py

@ -0,0 +1,153 @@
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import pyqtSignal, Qt, QModelIndex
class MItemModel(QtGui.QStandardItemModel):
"""
Special item model to ensure whole row is moved.
"""
itemsDropped = pyqtSignal()
def __init__(self):
super().__init__()
self.pendingRemoveRows = False
def dropMimeData(self, data, action, row, col, parent):
"""
Always move the entire row, and don't allow column "shifting"
"""
ret = super().dropMimeData(data, Qt.MoveAction, row, 0, parent)
if ret:
self.pendingRemoveRows = True
return ret
def removeRows(self, row, count, index=QModelIndex()):
"""
Emit a signal after rows have been moved
"""
ret = super().removeRows(row, count, index)
if self.pendingRemoveRows:
self.itemsDropped.emit()
self.pendingRemoveRows = False
return ret
class MProxyStyle(QtWidgets.QProxyStyle):
"""
Proxy style to change the appearance of the TableView.
"""
def drawPrimitive(self, element, option, painter, widget=None):
"""
Draw a line across the entire row rather than just the column
we're hovering over.
"""
if element == self.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
class MTableView(QtWidgets.QTableView):
"""
Subclass the built in TableView to customise it.
"""
def __init__(self, parent):
super().__init__(parent)
self.model = MItemModel()
self.setModel(self.model)
self.verticalHeader().hide()
self.horizontalHeader().show()
self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Interactive)
self.horizontalHeader().setStretchLastSection(True)
self.setShowGrid(False)
# self.setDragDropMode(self.InternalMove)
self.setDragDropOverwriteMode(False)
# Set our custom style - this draws the drop indicator across the whole row
self.setStyle(MProxyStyle())
def clear(self):
self.model.removeRows(0, self.model.rowCount())
class ChordTableView(MTableView):
"""
Subclass MTableView to add properties just for the chord table.
"""
def __init__(self, parent):
super().__init__(parent)
self.model.setHorizontalHeaderLabels(['Chord', 'Guitar voicing', 'Piano voicing'])
def populate(self, cList):
"""
Fill the table from a list of Chord objects.
"""
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 "")),
QtGui.QStandardItem(
",".join(c.voicings['piano'] if 'piano' in c.voicings.keys() else ""))]
for item in rowList:
item.setEditable(False)
item.setDropEnabled(False)
self.model.appendRow(rowList)
class SectionTableView(MTableView):
"""
Subclass MTableView to add properties just for the section table.
"""
def __init__(self, parent):
super().__init__(parent)
self.model.setHorizontalHeaderLabels(['Name'])
def populate(self, sList):
"""
Fill the table from a list of Section objects.
"""
self.model.removeRows(0, self.model.rowCount())
for s in sList:
rowList = [QtGui.QStandardItem(s.name)]
for item in rowList:
item.setEditable(False)
item.setDropEnabled(False)
self.model.appendRow(rowList)
class BlockTableView(MTableView):
"""
Subclass MTableView to add properties just for the block table.
"""
def __init__(self, parent):
super().__init__(parent)
self.model.setHorizontalHeaderLabels(['Chord', 'Length', 'Notes'])
def populate(self, bList):
"""
Fill the table from a list of Block objects.
"""
self.model.removeRows(0, self.model.rowCount())
for b in bList:
rowList = [QtGui.QStandardItem((b.chord.name if b.chord else "")), QtGui.QStandardItem(
str(b.length)), QtGui.QStandardItem(b.notes)]
for item in rowList:
item.setEditable(False)
item.setDropEnabled(False)
self.model.appendRow(rowList)

6
examples/ah.xml

@ -1,7 +1,9 @@
<chordsheet>
<title>"African Heritage"</title>
<subtitle>A corroboration</subtitle>
<composer>Ivan Holmes</composer>
<arranger>Ivan Holmes and Joe Buckley</arranger>
<tempo>120</tempo>
<timesignature>6</timesignature>
<chords>
<chord>
@ -25,7 +27,7 @@
<voicing instrument="guitar">x69676</voicing>
</chord>
</chords>
<progression>
<section name="A section">
<block>
<length>9</length>
<chord>Gm9</chord>
@ -71,5 +73,5 @@
<chord>D7#5#9</chord>
<notes>over Ab</notes>
</block>
</progression>
</section>
</chordsheet>

309
examples/ahlong.xml

@ -0,0 +1,309 @@
<chordsheet>
<title>"African Heritage"</title>
<subtitle>A corroboration</subtitle>
<composer>Ivan Holmes</composer>
<arranger>Ivan Holmes and Joe Buckley</arranger>
<tempo>120</tempo>
<timesignature>6</timesignature>
<chords>
<chord>
<name>Gm9</name>
<voicing instrument="guitar">x,10,8,10,10,x</voicing>
</chord>
<chord>
<name>Abm9</name>
<voicing instrument="guitar">x,11,9,11,11,x</voicing>
</chord>
<chord>
<name>Cm9</name>
<voicing instrument="guitar">x,x,8,8,8,10</voicing>
</chord>
<chord>
<name>D7#5#9</name>
<voicing instrument="guitar">x58565</voicing>
</chord>
<chord>
<name>Eb7#5#9</name>
<voicing instrument="guitar">x69676</voicing>
</chord>
</chords>
<section name="A section">
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>9</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
<notes>over Ab</notes>
</block>
</section>
<section name="B section">
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Cm9</chord>
</block>
</section>
<section name="C section">
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Cm9</chord>
</block>
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>9</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
<notes>over Ab</notes>
</block>
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>9</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
<notes>over Ab</notes>
</block>
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>9</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
<notes>over Ab</notes>
</block>
<block>
<length>9</length>
<chord>Gm9</chord>
</block>
<block>
<length>3</length>
<chord>Abm9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>9</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>12</length>
<chord>Gm9</chord>
</block>
<block>
<length>6</length>
<chord>Eb7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>6</length>
<chord>Cm9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
</block>
<block>
<length>3</length>
<chord>D7#5#9</chord>
<notes>over Ab</notes>
</block>
</section>
</chordsheet>

1
examples/angela.xml

@ -0,0 +1 @@
<chordsheet><title>Angela</title><subtitle>Theme from 'Taxi'</subtitle><arranger>Max</arranger><composer>Bob James</composer><timesignature>4</timesignature><chords><chord><name>E&#9837;maj7</name><voicing instrument="guitar">x,x,1,3,3,3</voicing></chord><chord><name>A&#9837;ma7</name><voicing instrument="guitar">x,x,1,1,1,3</voicing></chord><chord><name>Gm7</name><voicing instrument="guitar">3,x,3,3,3,x</voicing></chord><chord><name>B&#9837;/D</name><voicing instrument="guitar">x,x,0,3,3,1</voicing></chord><chord><name>A&#9837;maj7/C</name><voicing instrument="guitar">x,3,1,1,1,3</voicing></chord><chord><name>B&#9837;</name><voicing instrument="guitar">x,1,3,3,3,1</voicing></chord><chord><name>A&#9837;</name><voicing instrument="guitar">4,6,6,5,4,4</voicing></chord><chord><name>E&#9837;/G</name><voicing instrument="guitar">x,x,5,3,4,3</voicing></chord><chord><name>Fm</name><voicing instrument="guitar">x,x,3,1,1,1</voicing></chord><chord><name>A&#9837;/B&#9837;</name><voicing instrument="guitar">6,x,6,5,4,4</voicing></chord><chord><name>E&#9837;</name><voicing instrument="guitar">x,6,5,3,4,3</voicing></chord><chord><name>E&#9837;7</name><voicing instrument="guitar">x,x,1,3,2,3</voicing></chord><chord><name>A&#9837;maj9</name><voicing instrument="guitar">4,x,5,3,4,3</voicing></chord><chord><name>Fm7</name><voicing instrument="guitar">1,x,1,1,1,x</voicing></chord></chords><section name="Intro"><block><length>2.0</length><chord>E&#9837;maj7</chord></block><block><length>2.0</length><chord>A&#9837;ma7</chord></block><block><length>2.0</length><chord>Gm7</chord></block><block><length>2.0</length><chord>B&#9837;/D</chord></block><block><length>2.0</length><chord>A&#9837;maj7/C</chord></block><block><length>2.0</length><chord>B&#9837;</chord></block><block><length>1.0</length><chord>A&#9837;</chord></block><block><length>1.0</length><chord>E&#9837;/G</chord></block><block><length>1.0</length><chord>Fm</chord></block><block><length>1.0</length><chord>A&#9837;/B&#9837;</chord></block><block><length>2.0</length><chord>E&#9837;</chord></block><block><length>2.0</length><chord>E&#9837;7</chord></block><block><length>2.0</length><chord>A&#9837;maj9</chord></block><block><length>2.0</length><chord>E&#9837;/G</chord></block><block><length>2.0</length><chord>Fm7</chord></block><block><length>2.0</length><chord>B&#9837;</chord></block><block><length>1.0</length><chord>A&#9837;</chord></block><block><length>1.0</length><chord>E&#9837;/G</chord></block><block><length>1.0</length><chord>Fm</chord></block><block><length>1.0</length><chord>A&#9837;/B&#9837;</chord></block></section></chordsheet>

89
examples/example.xml

@ -1,46 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<chordsheet>
<title>Composition</title>
<composer>A. Person</composer>
<title>Example Song</title>
<composer>Ivan Holmes</composer>
<timesignature>4</timesignature>
<chords>
<chord>
<name>B</name>
<voicing instrument="guitar">xx2341</voicing>
<voicing instrument="piano">abcdefg</voicing>
<name>C7</name>
<voicing instrument="guitar">x,3,2,0,1,0</voicing>
<voicing instrument="piano">Bb,E,C,G</voicing>
</chord>
<chord>
<name>E</name>
<voicing instrument="guitar">022100</voicing>
<name>F</name>
<voicing instrument="guitar">1,3,3,2,1,1</voicing>
<voicing instrument="piano">A,C,F</voicing>
</chord>
<chord>
<name>Cm9</name>
<voicing instrument="guitar">x,x,8,8,8,10</voicing>
<name>G</name>
<voicing instrument="guitar">3,2,0,0,0,3</voicing>
<voicing instrument="piano">B,D,G</voicing>
</chord>
<chord>
<name>D7b5#9</name>
<name>C/G</name>
<voicing instrument="guitar">3,3,2,0,1,0</voicing>
<voicing instrument="piano">G,C,E</voicing>
</chord>
<chord>
<name>Dm</name>
<voicing instrument="guitar">x,x,0,2,3,1</voicing>
<voicing instrument="piano">A,D,F</voicing>
</chord>
</chords>
<progression>
<section name="Example section">
<block>
<length>16</length>
<chord>C7</chord>
<notes>Intro, strum lightly</notes>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<chord>F</chord>
</block>
<block>
<length>8</length>
<chord>G</chord>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<chord>F</chord>
</block>
<block>
<length>8</length>
<chord>G</chord>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<chord>B</chord>
<notes>These are notes</notes>
<chord>Dm</chord>
</block>
<block>
<length>4</length>
<chord>E</chord>
<chord>C/G</chord>
</block>
<block>
<length>12</length>
<chord>Cm9</chord>
<length>4</length>
<chord>G</chord>
</block>
<block>
<length>6</length>
<chord>D7b5#9</chord>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<notes>Contemplation time</notes>
</block>
<block>
<length>6</length>
<notes>For quiet contemplation.</notes>
<length>8</length>
<chord>C7</chord>
<notes>Crescendo until end</notes>
</block>
</progression>
</section>
</chordsheet>

143
examples/examplelong.xml

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<chordsheet>
<title>Example Song</title>
<composer>Ivan Holmes</composer>
<timesignature>4</timesignature>
<chords>
<chord>
<name>C7</name>
<voicing instrument="guitar">x,3,2,0,1,0</voicing>
<voicing instrument="piano">Bb,E,C,G</voicing>
</chord>
<chord>
<name>F</name>
<voicing instrument="guitar">1,3,3,2,1,1</voicing>
<voicing instrument="piano">A,C,F</voicing>
</chord>
<chord>
<name>G</name>
<voicing instrument="guitar">3,2,0,0,0,3</voicing>
<voicing instrument="piano">B,D,G</voicing>
</chord>
<chord>
<name>C/G</name>
<voicing instrument="guitar">3,3,2,0,1,0</voicing>
<voicing instrument="piano">G,C,E</voicing>
</chord>
<chord>
<name>Dm</name>
<voicing instrument="guitar">x,x,0,2,3,1</voicing>
<voicing instrument="piano">A,D,F</voicing>
</chord>
<chord>
<name>q7</name>
<voicing instrument="guitar">x,3,2,0,1,0</voicing>
<voicing instrument="piano">Bb,E,C,G</voicing>
</chord>
<chord>
<name>q3</name>
<voicing instrument="guitar">1,3,3,2,1,1</voicing>
<voicing instrument="piano">A,C,F</voicing>
</chord>
<chord>
<name>q5</name>
<voicing instrument="guitar">3,2,0,0,0,3</voicing>
<voicing instrument="piano">B,D,G</voicing>
</chord>
<chord>
<name>q/G</name>
<voicing instrument="guitar">3,3,2,0,1,0</voicing>
<voicing instrument="piano">G,C,E</voicing>
</chord>
<chord>
<name>q</name>
<voicing instrument="guitar">x,x,0,2,3,1</voicing>
<voicing instrument="piano">A,D,F</voicing>
</chord>
<chord>
<name>yeeq</name>
<voicing instrument="guitar">x,3,2,0,1,0</voicing>
<voicing instrument="piano">Bb,E,C,G</voicing>
</chord>
<chord>
<name>quu</name>
<voicing instrument="guitar">1,3,3,2,1,1</voicing>
<voicing instrument="piano">A,C,F</voicing>
</chord>
<chord>
<name>b3</name>
<voicing instrument="guitar">3,2,0,0,0,3</voicing>
<voicing instrument="piano">B,D</voicing>
</chord>
<chord>
<name>aa</name>
<voicing instrument="guitar">3,3,2,0,1,0</voicing>
<voicing instrument="piano">G,C,E</voicing>
</chord>
<chord>
<name>z</name>
<voicing instrument="guitar">x,x,0,2,3,1</voicing>
<voicing instrument="piano">A,D,F,G,E,B,C</voicing>
</chord>
</chords>
<section name="Example section">
<block>
<length>16</length>
<chord>C7</chord>
<notes>Intro, strum lightly</notes>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<chord>F</chord>
</block>
<block>
<length>8</length>
<chord>G</chord>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<chord>F</chord>
</block>
<block>
<length>8</length>
<chord>G</chord>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<chord>Dm</chord>
</block>
<block>
<length>4</length>
<chord>C/G</chord>
</block>
<block>
<length>4</length>
<chord>G</chord>
</block>
<block>
<length>4</length>
<chord>C7</chord>
</block>
<block>
<length>4</length>
<notes>Contemplation time</notes>
</block>
<block>
<length>8</length>
<chord>C7</chord>
<notes>Crescendo until end</notes>
</block>
</section>
</chordsheet>

24
examples/kissoflife.cma

@ -0,0 +1,24 @@
\chordsheet 1
\rem This is a comment to say that the first line isn't really needed (as there's only one version of Chordsheet Macro so far)
\title Kiss of Life
\subtitle Sade
\arranger Ivan Holmes
\composer Sade Adu, Paul S. Denman, Andrew Hale, Stuart Matthewman
\timesig 4
\tempo 120
\chord AM9 piano A,B,C#,E
\chord F#m11 alias F piano A,B,C#,E
\chord DM7 piano A,C#,F#
\chord C#m7 piano G#,B,E
\chord Bm7 piano A,D,F#
\section Intro and verse
AM9,8 F#m11,8 DM7,1.5
C#m7,2 Bm7,4.5 F#m11,8
\! This is another comment. Comments may not exist in the middle of other statements.
\section Chorus and bridge
Bm7,3.5 F,4.5 Bm7,3.5 F#m11,4.5

1
examples/kissoflife.xml

@ -0,0 +1 @@
<chordsheet><title>Kiss of Life</title><subtitle>Sade</subtitle><arranger>Ivan Holmes</arranger><composer>Sade Adu, Paul S. Denman, Andrew Hale, Stuart Matthewman</composer><timesignature>4</timesignature><tempo>120</tempo><chords><chord><name>AM9</name><voicing instrument="piano">A,B,C&#9839;,E</voicing></chord><chord><name>F&#9839;m11</name><voicing instrument="piano">A,B,C&#9839;,E</voicing></chord><chord><name>DM7</name><voicing instrument="piano">A,C&#9839;,F&#9839;</voicing></chord><chord><name>C&#9839;m7</name><voicing instrument="piano">G&#9839;,B,E</voicing></chord><chord><name>Bm7</name><voicing instrument="piano">A,D,F&#9839;</voicing></chord></chords><section name="Intro and verse"><block><length>8.0</length><chord>AM9</chord></block><block><length>8.0</length><chord>F&#9839;m11</chord></block><block><length>1.5</length><chord>DM7</chord></block><block><length>2.0</length><chord>C&#9839;m7</chord></block><block><length>4.5</length><chord>Bm7</chord></block><block><length>8.0</length><chord>F&#9839;m11</chord></block></section><section name="Chorus and bridge"><block><length>3.5</length><chord>Bm7</chord></block><block><length>4.5</length><chord>F&#9839;m11</chord></block><block><length>3.5</length><chord>Bm7</chord></block><block><length>4.5</length><chord>F&#9839;m11</chord></block></section></chordsheet>

1
examples/test.xml

@ -0,0 +1 @@
<chordsheet><title>Composition</title><composer>A. Person</composer><timesignature>4</timesignature><chords><chord><name>B</name><voicing instrument="guitar">x,x,2,3,4,1</voicing></chord><chord><name>E</name><voicing instrument="guitar">0,2,2,1,0,0</voicing></chord><chord><name>Cm9</name><voicing instrument="guitar">x,x,8,8,8,10</voicing></chord><chord><name>D7&#9837;5&#9839;9</name></chord></chords><section name="Test section"><block><length>4</length><chord>B</chord><notes>These are notes.</notes></block><block><length>4</length><chord>E</chord></block><block><length>12</length><chord>Cm9</chord></block><block><length>6</length><chord>D7&#9837;5&#9839;9</chord></block><block><length>6</length><notes>For quiet contemplation.</notes></block><block><length>46</length><chord>D7&#9837;5&#9839;9</chord><notes>A very long block to test wrapping!</notes></block></section></chordsheet>

15
generate_version_rc.py

@ -0,0 +1,15 @@
import _version
with open('version.rc.template', 'r') as verstf:
verstemp = verstf.read()
major, minor, patch = _version.version.split(".")
verstemp = verstemp.replace("%APPNAME%", _version.appName)
verstemp = verstemp.replace("%VERSION%", _version.version)
verstemp = verstemp.replace("%MAJOR_VERSION%", major)
verstemp = verstemp.replace("%MINOR_VERSION%", minor)
verstemp = verstemp.replace("%PATCH_VERSION%", patch)
with open('version.rc', 'w') as versf:
versf.write(verstemp)

1247
gui.py
File diff suppressed because it is too large
View File

47
linux.spec

@ -0,0 +1,47 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
with open('_version.py', 'r') as versinfo:
exec(versinfo.read())
a = Analysis(['gui.py'],
pathex=['/home/ivan/Code/chordsheet'],
binaries=[],
datas=[
('fonts', 'fonts'),
('ui', 'ui')
],
hiddenimports=['PyQt5.sip'],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='chordsheet',
icon='ui/icon.png',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
version='version.rc')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='chordsheet-'+version)

19
mac.spec

@ -1,7 +1,10 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# Nasty hack to get the version number included automatically
with open('_version.py', 'r') as versinfo:
exec(versinfo.read())
block_cipher = None
a = Analysis(['/Users/ivan/Code/chordsheet/gui.py'],
pathex=['/Users/ivan/Code/chordsheet'],
@ -26,20 +29,22 @@ exe = EXE(pyz,
a.zipfiles,
a.datas,
[],
name='Chordsheet',
name=appName,
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False )
app = BUNDLE(exe,
name='Chordsheet.app',
icon=None,
bundle_identifier=None,
name=exe.name + '.app',
icon='ui/icon.icns',
bundle_identifier="uk.co.ivanholmes.chordsheet",
info_plist={
'CFBundleShortVersionString': version,
'NSPrincipalClass': 'NSApplication',
'NSHighResolutionCapable': 'True'
'NSHighResolutionCapable': 'True',
'NSHumanReadableCopyright': "© Ivan Holmes, 2020. Some rights reserved."
}
)

4
requirements.txt

@ -0,0 +1,4 @@
reportlab>=3.5.67
PyMuPDF>=1.18.13
PyQt5>=5.15.4
PyQt5.Sip

142
ui/aboutdialog.ui

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>200</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>436</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>About Chordsheet</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QVBoxLayout" name="leftPane">
<item>
<widget class="QLabel" name="iconLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="rightPane">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Chordsheet&lt;/span&gt;&lt;br/&gt;by Ivan Holmes&lt;/p&gt;&lt;p&gt;Chordsheet is a piece of software that generates a chord sheet with a simple GUI. &lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://git.radivan.net/ivan/chordsheet&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Git repository&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="versionLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Version not set</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

291
ui/blocks.ui

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>blocksWidget</class>
<widget class="QWidget" name="blocksWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>387</width>
<height>206</height>
</rect>
</property>
<property name="windowTitle">
<string>Blocks</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QFormLayout" name="formLayout_5">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="blockSectionLabel">
<property name="text">
<string>Section</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="MComboBox" name="blockSectionComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="BlockTableView" name="blockTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::TargetMoveAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="blockGridLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="blockLengthLabel">
<property name="text">
<string>Length</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QComboBox" name="blockChordComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="blockNotesLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Notes</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="blockChordLabel">
<property name="text">
<string>Chord</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="blockLengthLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
<widget class="QLineEdit" name="blockNotesLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="bottomBlockHorizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="removeBlockButton">
<property name="text">
<string>Remove block</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="updateBlockButton">
<property name="text">
<string>Update block</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addBlockButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Add block</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BlockTableView</class>
<extends>QTableView</extends>
<header>csgui/tableView.h</header>
</customwidget>
<customwidget>
<class>MComboBox</class>
<extends>QComboBox</extends>
<header>csgui/comboBox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>blockLengthLineEdit</tabstop>
<tabstop>blockChordComboBox</tabstop>
<tabstop>blockNotesLineEdit</tabstop>
<tabstop>removeBlockButton</tabstop>
<tabstop>updateBlockButton</tabstop>
<tabstop>addBlockButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

237
ui/chords.ui

@ -0,0 +1,237 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>chordsWidget</class>
<widget class="QWidget" name="chordsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>393</width>
<height>236</height>
</rect>
</property>
<property name="windowTitle">
<string>Chords</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ChordTableView" name="chordTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QGridLayout" name="chordGridLayout">
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="1">
<widget class="QLineEdit" name="guitarVoicingLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="guitarVoicingLabel">
<property name="text">
<string>Guitar voicing</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="pianoVoicingLineEdit"/>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="chordNameLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="pianoVoicingLabel">
<property name="text">
<string>Piano voicing</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="chordNameLabel">
<property name="text">
<string>Chord name</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="guitarVoicingButton">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Editor...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="bottomChordHorizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="removeChordButton">
<property name="text">
<string>Remove chord</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="updateChordButton">
<property name="text">
<string>Update chord</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addChordButton">
<property name="text">
<string>Add chord</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ChordTableView</class>
<extends>QTableView</extends>
<header>csgui/tableView.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>chordNameLineEdit</tabstop>
<tabstop>guitarVoicingLineEdit</tabstop>
<tabstop>guitarVoicingButton</tabstop>
<tabstop>pianoVoicingLineEdit</tabstop>
<tabstop>removeChordButton</tabstop>
<tabstop>updateChordButton</tabstop>
<tabstop>addChordButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

162
ui/docinfo.ui

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>docInfoWidget</class>
<widget class="QWidget" name="docInfoWidget">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>291</width>
<height>170</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Document information</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QFormLayout" name="formLayoutOverview">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="titleLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="subtitleLabel">
<property name="text">
<string>Subtitle</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="subtitleLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="composerLabel">
<property name="text">
<string>Composer</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="composerLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="arrangerLabel">
<property name="text">
<string>Arranger</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="arrangerLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="tempoLabel">
<property name="text">
<string>Tempo</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="tempoLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="timeSignatureLabel">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="timeSignatureSpinBox">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="specialValueText">
<string/>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

57
ui/document.ui

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>docWindow</class>
<widget class="QWidget" name="docWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>424</width>
<height>324</height>
</rect>
</property>
<property name="windowTitle">
<string>Unsaved</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="PDFViewer" name="pdfArea" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>300</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PDFViewer</class>
<extends>QWidget</extends>
<header>csgui/pdfViewer.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

2
ui/guitardialog.ui

@ -23,7 +23,7 @@
</size>
</property>
<property name="windowTitle">
<string>Guitar chord</string>
<string>Guitar chord editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>

BIN
ui/icon.afdesign

BIN
ui/icon.icns

BIN
ui/icon.ico

BIN
ui/icon.png

After

Width: 1024  |  Height: 1024  |  Size: 236 KiB

896
ui/mainwindow.ui

@ -6,782 +6,57 @@
<rect>
<x>0</x>
<y>0</y>
<width>1113</width>
<height>746</height>
<width>1061</width>
<height>639</height>
</rect>
</property>
<property name="windowTitle">
<string>Chordsheet</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="leftPane" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>16777215</height>
</size>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="tabWidgetOverview">
<attribute name="title">
<string>Overview</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QFormLayout" name="formLayoutOverview">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="titleLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="composerLabel">
<property name="text">
<string>Composer</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="composerLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="arrangerLabel">
<property name="text">
<string>Arranger</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="arrangerLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayoutTimeSignature">
<item row="0" column="0">
<widget class="QLabel" name="timeSignatureLabel">
<property name="text">
<string>Time signature</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="timeSignatureSpinBox">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="specialValueText">
<string/>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWidgetPage">
<attribute name="title">
<string>Page</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QGroupBox" name="pageGroupBox">
<property name="title">
<string>Page options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="pageSizeLabel">
<property name="text">
<string>Page size</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="pageSizeComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="documentUnitsLabel">
<property name="text">
<string>Document units</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="documentUnitsComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="leftMarginLabel">
<property name="text">
<string>Left margin</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="leftMarginLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="topMarginLabel">
<property name="text">
<string>Top margin</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="topMarginLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="textGroupBox">
<property name="title">
<string>Text options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="lineSpacingLabel">
<property name="text">
<string>Line spacing</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="lineSpacingDoubleSpinBox">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="fontGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Font options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="fontLabel">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="fontComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QCheckBox" name="includedFontCheckBox">
<property name="text">
<string>Use included FreeSans</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWidgetChords">
<attribute name="title">
<string>Chords</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="chordTabLayout">
<item>
<widget class="ChordTableView" name="chordTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<property name="documentMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="chordGridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="chordNameLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="guitarVoicingButton">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Editor...</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="guitarVoicingLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="guitarVoicingLabel">
<property name="text">
<string>Guitar voicing</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="chordNameLabel">
<property name="text">
<string>Chord name</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="bottomChordHorizontalLayout">
<item>
<widget class="QPushButton" name="removeChordButton">
<property name="text">
<string>Remove chord</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="updateChordButton">
<property name="text">
<string>Update chord</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addChordButton">
<property name="text">
<string>Add chord</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWidgetBlocks">
<attribute name="title">
<string>Blocks</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QVBoxLayout" name="blockTabLayout">
<item>
<widget class="BlockTableView" name="blockTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::TargetMoveAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="blockGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="blockLengthLabel">
<property name="text">
<string>Length</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QComboBox" name="blockChordComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="blockNotesLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Notes</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="blockChordLabel">
<property name="text">
<string>Chord</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="blockLengthLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
<widget class="QLineEdit" name="blockNotesLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="bottomBlockHorizontalLayout">
<item>
<widget class="QPushButton" name="removeBlockButton">
<property name="text">
<string>Remove block</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="updateBlockButton">
<property name="text">
<string>Update block</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addBlockButton">
<property name="text">
<string>Add block</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="generateButton">
<property name="text">
<string>Generate chordsheet</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QScrollArea" name="scrollArea">
<property name="minimumSize">
<size>
<width>300</width>
<height>400</height>
</size>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>580</width>
<height>698</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<widget class="QWidget" name="centralWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>12</number>
<number>0</number>
</property>
<property name="topMargin">
<number>12</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>12</number>
<number>0</number>
</property>
<property name="bottomMargin">
<number>12</number>
<number>0</number>
</property>
<item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QLabel" name="imageLabel">
<property name="autoFillBackground">
<item>
<widget class="QMdiArea" name="mdiArea">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
<property name="documentMode">
<bool>true</bool>
</property>
<property name="scaledContents">
<bool>false</bool>
<property name="tabsClosable">
<bool>true</bool>
</property>
<property name="margin">
<number>0</number>
<property name="tabsMovable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1113</width>
<width>1061</width>
<height>22</height>
</rect>
</property>
@ -799,8 +74,44 @@
<addaction name="actionPrint"/>
<addaction name="separator"/>
<addaction name="actionClose"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>Edit</string>
</property>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="separator"/>
<addaction name="actionCut"/>
<addaction name="actionCopy"/>
<addaction name="actionPaste"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuWindow">
<property name="title">
<string>Window</string>
</property>
<addaction name="actionCascadeSubWindows"/>
<addaction name="actionTileSubWindows"/>
<addaction name="separator"/>
<addaction name="actionActivateNextSubWindow"/>
<addaction name="actionActivatePreviousSubWindow"/>
<addaction name="separator"/>
<addaction name="actionRedockAllPanels"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuHelo">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuWindow"/>
<addaction name="menuHelo"/>
</widget>
<action name="actionNew">
<property name="text">
@ -832,6 +143,26 @@
<string>Close</string>
</property>
</action>
<action name="actionSave_as">
<property name="text">
<string>Save as...</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
<action name="actionUndo">
<property name="text">
<string>Undo</string>
</property>
</action>
<action name="actionRedo">
<property name="text">
<string>Redo</string>
</property>
</action>
<action name="actionCut">
<property name="text">
<string>Cut</string>
@ -847,49 +178,42 @@
<string>Paste</string>
</property>
</action>
<action name="actionSave_as">
<action name="actionPreferences">
<property name="text">
<string>Save as...</string>
<string>Preferences</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionCascadeSubWindows">
<property name="text">
<string>Cascade windows</string>
</property>
</action>
<action name="actionTileSubWindows">
<property name="text">
<string>Tile windows</string>
</property>
</action>
<action name="actionActivateNextSubWindow">
<property name="text">
<string>Next window</string>
</property>
</action>
<action name="actionActivatePreviousSubWindow">
<property name="text">
<string>Previous window</string>
</property>
</action>
<action name="actionRedockAllPanels">
<property name="text">
<string>Redock all panels</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>ChordTableView</class>
<extends>QTableView</extends>
<header>chordsheet/tableView.h</header>
</customwidget>
<customwidget>
<class>BlockTableView</class>
<extends>QTableView</extends>
<header>chordsheet/tableView.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>generateButton</tabstop>
<tabstop>tabWidget</tabstop>
<tabstop>titleLineEdit</tabstop>
<tabstop>composerLineEdit</tabstop>
<tabstop>arrangerLineEdit</tabstop>
<tabstop>timeSignatureSpinBox</tabstop>
<tabstop>pageSizeComboBox</tabstop>
<tabstop>documentUnitsComboBox</tabstop>
<tabstop>leftMarginLineEdit</tabstop>
<tabstop>topMarginLineEdit</tabstop>
<tabstop>lineSpacingDoubleSpinBox</tabstop>
<tabstop>fontComboBox</tabstop>
<tabstop>includedFontCheckBox</tabstop>
<tabstop>chordTableView</tabstop>
<tabstop>chordNameLineEdit</tabstop>
<tabstop>guitarVoicingLineEdit</tabstop>
<tabstop>guitarVoicingButton</tabstop>
<tabstop>addChordButton</tabstop>
<tabstop>removeChordButton</tabstop>
<tabstop>blockTableView</tabstop>
<tabstop>addBlockButton</tabstop>
<tabstop>removeBlockButton</tabstop>
<tabstop>scrollArea</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

83
ui/preview.ui

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>previewPanel</class>
<widget class="QWidget" name="previewPanel">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>202</width>
<height>72</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>202</width>
<height>72</height>
</size>
</property>
<property name="windowTitle">
<string>Preview</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item alignment="Qt::AlignHCenter">
<widget class="QCheckBox" name="autoUpdatePreviewCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Automatically update preview</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QPushButton" name="updatePreviewButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Update preview</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

326
ui/psetup.ui

@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>psetupWidget</class>
<widget class="QWidget" name="psetupWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>173</width>
<height>306</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Page setup</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0,0,0" columnstretch="0,0">
<property name="spacing">
<number>6</number>
</property>
<item row="6" column="0" colspan="2">
<widget class="QFontComboBox" name="fontComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="maxVisibleItems">
<number>12</number>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<property name="frame">
<bool>true</bool>
</property>
<property name="writingSystem">
<enum>QFontDatabase::Any</enum>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="includedFontCheckBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>FreeSans</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="rightMarginLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="bottomMarginLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="fontLabel">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="pageSizeComboBox">
<property name="maximumSize">
<size>
<width>70</width>
<height>26</height>
</size>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="beatWidthLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Beat width</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="topMarginLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Top margin</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="documentUnitsComboBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>26</height>
</size>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="leftMarginLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="topMarginLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="pageSizeLabel">
<property name="text">
<string>Page size</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="beatWidthLineEdit">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="leftMarginLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Left margin</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="bottomMarginLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Bottom margin</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="lineSpacingLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Line spacing</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="rightMarginLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Right margin</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="documentUnitsLabel">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDoubleSpinBox" name="lineSpacingDoubleSpinBox">
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>70</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>pageSizeComboBox</tabstop>
<tabstop>documentUnitsComboBox</tabstop>
<tabstop>leftMarginLineEdit</tabstop>
<tabstop>rightMarginLineEdit</tabstop>
<tabstop>topMarginLineEdit</tabstop>
<tabstop>bottomMarginLineEdit</tabstop>
<tabstop>fontComboBox</tabstop>
<tabstop>includedFontCheckBox</tabstop>
<tabstop>lineSpacingDoubleSpinBox</tabstop>
<tabstop>beatWidthLineEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

165
ui/sections.ui

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>sectionsWidget</class>
<widget class="QWidget" name="sectionsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>405</width>
<height>170</height>
</rect>
</property>
<property name="windowTitle">
<string>Sections</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="SectionTableView" name="sectionTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::TargetMoveAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="sectionNameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="sectionNameLineEdit"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="bottomSectionHorizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="removeSectionButton">
<property name="text">
<string>Remove section</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="updateSectionButton">
<property name="text">
<string>Update section</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addSectionButton">
<property name="text">
<string>Add section</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SectionTableView</class>
<extends>QTableView</extends>
<header>csgui/tableView.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

43
version.rc

@ -0,0 +1,43 @@
# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(0, 5, 0, 0),
prodvers=(0, 5, 0, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x4,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'Ivan Holmes'),
StringStruct(u'FileDescription', u'Chordsheet'),
StringStruct(u'FileVersion', u'0.5.0'),
StringStruct(u'InternalName', u'Chordsheet'),
StringStruct(u'LegalCopyright', u'Copyright (c) Ivan Holmes, 2020. Some rights reserved.'),
StringStruct(u'OriginalFilename', u'chordsheet.exe'),
StringStruct(u'ProductName', u'Chordsheet'),
StringStruct(u'ProductVersion', u'0.5.0')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)

43
version.rc.template

@ -0,0 +1,43 @@
# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(%MAJOR_VERSION%, %MINOR_VERSION%, %PATCH_VERSION%, 0),
prodvers=(%MAJOR_VERSION%, %MINOR_VERSION%, %PATCH_VERSION%, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x4,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'Ivan Holmes'),
StringStruct(u'FileDescription', u'%APPNAME%'),
StringStruct(u'FileVersion', u'%VERSION%'),
StringStruct(u'InternalName', u'%APPNAME%'),
StringStruct(u'LegalCopyright', u'Copyright (c) Ivan Holmes, 2020. Some rights reserved.'),
StringStruct(u'OriginalFilename', u'chordsheet.exe'),
StringStruct(u'ProductName', u'%APPNAME%'),
StringStruct(u'ProductVersion', u'%VERSION%')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)

20
win.spec

@ -2,7 +2,6 @@
block_cipher = None
a = Analysis(['gui.py'],
pathex=['C:\\Users\\Ivan Holmes\\code\\chordsheet'],
binaries=[],
@ -22,15 +21,24 @@ pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
exclude_binaries=True,
name='Chordsheet',
icon='ui\\icon.ico',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False )
console=False,
version='version.rc')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name='Chordsheet')
Loading…
Cancel
Save