Browse Source

various modifications and improvements:

show name of document in title bar
add keyboard shortcuts
improve UI resizability
make close menu item functional
add primitive about dialog
add a warning if the user tries to close without saving
make chords, blocks, document equatable
remember where the user last saved/opened a document
master 0.3
Ivan Holmes 5 years ago
parent
commit
36a86d280f
  1. 1
      README.md
  2. 32
      chordsheet/document.py
  3. 8
      chordsheet/render.py
  4. 2
      chordsheet/tableView.py
  5. 196
      gui.py
  6. 2
      ui/guitardialog.ui
  7. 97
      ui/mainwindow.ui

1
README.md

@ -21,6 +21,7 @@ Chordsheet is alpha-grade software. At present, the program will crash readily g
- PDF preview is blurry on high DPI monitors - 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
- Poor font handling (choice of either FreeSans or Helvetica Neue if installed) - Poor font handling (choice of either FreeSans or Helvetica Neue if installed)
- No support for printing
## Dependencies ## Dependencies
Chordsheet depends on pymupdf (to show the preview), reportlab (to generate the PDF), and PyQt5 (for the GUI). Chordsheet depends on pymupdf (to show the preview), reportlab (to generate the PDF), and PyQt5 (for the GUI).

32
chordsheet/document.py

@ -35,8 +35,15 @@ class Style:
class Chord: class Chord:
def __init__(self, name, **kwargs): def __init__(self, name, **kwargs):
self.name = name self.name = name
self.voicings = {}
for inst, fing in kwargs.items(): 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: class Block:
def __init__(self, length, **kwargs): def __init__(self, length, **kwargs):
@ -44,6 +51,11 @@ class Block:
self.chord = kwargs.get('chord', None) self.chord = kwargs.get('chord', None)
self.notes = kwargs.get('notes', None) self.notes = kwargs.get('notes', None)
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 Document: class Document:
def __init__(self, chordList=None, blockList=None, title=None, composer=None, arranger=None, timeSignature=defaultTimeSignature): def __init__(self, chordList=None, blockList=None, title=None, composer=None, arranger=None, timeSignature=defaultTimeSignature):
self.chordList = chordList or [] self.chordList = chordList or []
@ -52,6 +64,12 @@ class Document:
self.composer = composer self.composer = composer
self.arranger = arranger self.arranger = arranger
self.timeSignature = timeSignature self.timeSignature = timeSignature
def __eq__(self, other):
if isinstance(other, self.__class__):
textEqual = self.title == other.title and self.composer == other.composer and self.arranger == other.arranger and self.timeSignature == other.timeSignature # check all the text values for equality
return textEqual and self.chordList == other.chordList and self.blockList == other.blockList
return NotImplemented
def loadXML(self, filepath): def loadXML(self, filepath):
xmlDoc = ET.parse(filepath) xmlDoc = ET.parse(filepath)
@ -62,8 +80,7 @@ class Document:
for c in root.findall('chords/chord'): for c in root.findall('chords/chord'):
self.chordList.append(Chord(parseName(c.find('name').text))) self.chordList.append(Chord(parseName(c.find('name').text)))
for v in c.findall('voicing'): 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.blockList = [] self.blockList = []
if root.find('progression') is not None: if root.find('progression') is not None:
@ -110,10 +127,11 @@ class Document:
for c in self.chordList: for c in self.chordList:
chordElement = ET.SubElement(chordsElement, "chord") chordElement = ET.SubElement(chordsElement, "chord")
ET.SubElement(chordElement, "name").text = c.name 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 = c.voicings['piano'][0] # return first element of list as feature has not been implemented
progressionElement = ET.SubElement(root, "progression") progressionElement = ET.SubElement(root, "progression")

8
chordsheet/render.py

@ -48,9 +48,9 @@ def guitarChart(currentCanvas, style, chordList, cur_pos):
nstrings = 6 nstrings = 6
fontsize = 12 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')])
guitarChordList = [[chordList[q].voicings['guitar'][-(r+1)] for q in range(len(chordList)) if 'guitar' in chordList[q].voicings.keys()] for r in range(nstrings)]
guitarChordList.append([chordList[q].name for q in range(len(chordList)) if 'guitar' in chordList[q].voicings.keys()])
for i in range(nstrings+1): # i is the string currently being drawn 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') writeText(currentCanvas, style, ['e','B','G','D','A','E','Name'][i], fontsize, v_origin+(i*string_height), hpos=h_origin, align='right')
@ -126,7 +126,7 @@ def chordProgression(currentCanvas, style, document, cur_pos):
def guitarChartCheck(cL): def guitarChartCheck(cL):
chordsPresent = False chordsPresent = False
for c in cL: for c in cL:
if hasattr(c, 'guitar'):
if 'guitar' in c.voicings.keys():
chordsPresent = True chordsPresent = True
break break
return chordsPresent return chordsPresent

2
chordsheet/tableView.py

@ -55,7 +55,7 @@ class ChordTableView(MTableView):
def populate(self, cList): def populate(self, cList):
self.model.removeRows(0, self.model.rowCount()) self.model.removeRows(0, self.model.rowCount())
for c in cList: for c in cList:
rowList = [QtGui.QStandardItem(c.name), QtGui.QStandardItem(",".join(c.guitar if hasattr(c, 'guitar') else ""))]
rowList = [QtGui.QStandardItem(c.name), QtGui.QStandardItem(",".join(c.voicings['guitar'] if 'guitar' in c.voicings.keys() else ""))]
for item in rowList: for item in rowList:
item.setEditable(False) item.setEditable(False)
item.setDropEnabled(False) item.setDropEnabled(False)

196
gui.py

@ -7,10 +7,11 @@ Created on Wed May 29 00:02:24 2019
""" """
import sys, fitz, io, subprocess, os import sys, fitz, io, subprocess, os
from copy import copy
from PyQt5.QtWidgets import QApplication, QAction, QLabel, QDialogButtonBox, QDialog, QFileDialog, QMessageBox, QPushButton, QLineEdit, QCheckBox, QSpinBox, QDoubleSpinBox, QTableWidget, QTableWidgetItem, QTabWidget, QComboBox, QWidget, QScrollArea
from PyQt5.QtCore import QFile, QObject, Qt
from PyQt5.QtGui import QPixmap, QImage
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, pyqtSlot, QSettings
from PyQt5.QtGui import QPixmap, QImage, QKeySequence
from PyQt5 import uic from PyQt5 import uic
from chordsheet.tableView import ChordTableView, BlockTableView , MItemModel, MProxyStyle from chordsheet.tableView import ChordTableView, BlockTableView , MItemModel, MProxyStyle
@ -23,38 +24,78 @@ from chordsheet.document import Document, Style, Chord, Block
from chordsheet.render import savePDF from chordsheet.render import savePDF
from chordsheet.parsers import parseFingering, parseName from chordsheet.parsers import parseFingering, parseName
# set the directory where our files are depending on whether we're running a pyinstaller binary or not
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
scriptDir = sys._MEIPASS scriptDir = sys._MEIPASS
else: else:
scriptDir = os.path.abspath(os.path.dirname(os.path.abspath(__file__))) scriptDir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) # enable automatic high DPI scaling on Windows
QApplication.setOrganizationName("Ivan Holmes")
QApplication.setOrganizationDomain("ivanholmes.co.uk")
QApplication.setApplicationName("Chordsheet")
settings = QSettings()
pdfmetrics.registerFont(TTFont('FreeSans', os.path.join(scriptDir, 'fonts', 'FreeSans.ttf'))) pdfmetrics.registerFont(TTFont('FreeSans', os.path.join(scriptDir, 'fonts', 'FreeSans.ttf')))
if sys.platform == "darwin": if sys.platform == "darwin":
pdfmetrics.registerFont(TTFont('HelveticaNeue', 'HelveticaNeue.ttc', subfontIndex=0)) pdfmetrics.registerFont(TTFont('HelveticaNeue', 'HelveticaNeue.ttc', subfontIndex=0))
# dictionaries for combo boxes
pageSizeDict = {'A4':A4, 'A5':A5, 'Letter':LETTER, 'Legal':LEGAL} pageSizeDict = {'A4':A4, 'A5':A5, 'Letter':LETTER, 'Legal':LEGAL}
unitDict = {'mm':mm, 'cm':cm, 'inch':inch, 'point':1, 'pica':pica} # point is 1 because reportlab's native unit is points. unitDict = {'mm':mm, 'cm':cm, 'inch':inch, 'point':1, 'pica':pica} # point is 1 because reportlab's native unit is points.
class DocumentWindow(QWidget):
def __init__(self, doc, style, parent=None):
super().__init__(parent)
class DocumentWindow(QMainWindow):
"""
Class for the main window of the application.
"""
def __init__(self, doc, style, filename=None):
"""
Initialisation function for the main window of the application.
Arguments:
doc -- the Document object for the window to use
style -- the Style object for the window to use
"""
super().__init__()
self.doc = doc self.doc = doc
self.style = style self.style = style
self.lastDoc = copy(self.doc)
self.currentFilePath = filename
self.UIFileLoader(str(os.path.join(scriptDir, 'ui','mainwindow.ui'))) self.UIFileLoader(str(os.path.join(scriptDir, 'ui','mainwindow.ui')))
self.UIInitStyle() self.UIInitStyle()
# self.UIInitDocument()
self.setCentralWidget(self.window.centralWidget)
self.setMenuBar(self.window.menuBar)
self.setWindowTitle("Chordsheet")
if filename:
try:
self.doc.loadXML(filename)
except:
UnreadableMessageBox().exec()
def closeEvent(self, event):
"""
Reimplement the built in closeEvent to allow asking the user to save.
"""
self.saveWarning()
def UIFileLoader(self, ui_file): def UIFileLoader(self, ui_file):
"""
Loads the .ui file for this window and connects the UI elements to their actions.
"""
ui_file = QFile(ui_file) ui_file = QFile(ui_file)
ui_file.open(QFile.ReadOnly) ui_file.open(QFile.ReadOnly)
self.window = uic.loadUi(ui_file) self.window = uic.loadUi(ui_file)
ui_file.close() ui_file.close()
# link all the UI elements
self.window.actionAbout.triggered.connect(self.menuFileAboutAction)
self.window.actionNew.triggered.connect(self.menuFileNewAction) self.window.actionNew.triggered.connect(self.menuFileNewAction)
self.window.actionOpen.triggered.connect(self.menuFileOpenAction) self.window.actionOpen.triggered.connect(self.menuFileOpenAction)
self.window.actionSave.triggered.connect(self.menuFileSaveAction) self.window.actionSave.triggered.connect(self.menuFileSaveAction)
@ -62,7 +103,20 @@ class DocumentWindow(QWidget):
self.window.actionSave_PDF.triggered.connect(self.menuFileSavePDFAction) self.window.actionSave_PDF.triggered.connect(self.menuFileSavePDFAction)
self.window.actionPrint.triggered.connect(self.menuFilePrintAction) self.window.actionPrint.triggered.connect(self.menuFilePrintAction)
self.window.actionClose.triggered.connect(self.menuFileCloseAction) self.window.actionClose.triggered.connect(self.menuFileCloseAction)
self.window.actionNew.setShortcut(QKeySequence.New)
self.window.actionOpen.setShortcut(QKeySequence.Open)
self.window.actionSave.setShortcut(QKeySequence.Save)
self.window.actionSave_as.setShortcut(QKeySequence.SaveAs)
self.window.actionSave_PDF.setShortcut(QKeySequence("Ctrl+E"))
self.window.actionPrint.setShortcut(QKeySequence.Print)
self.window.actionClose.setShortcut(QKeySequence.Close)
self.window.actionUndo.setShortcut(QKeySequence.Undo)
self.window.actionRedo.setShortcut(QKeySequence.Redo)
self.window.actionCut.setShortcut(QKeySequence.Cut)
self.window.actionCopy.setShortcut(QKeySequence.Copy)
self.window.actionPaste.setShortcut(QKeySequence.Paste)
self.window.pageSizeComboBox.currentIndexChanged.connect(self.pageSizeAction) self.window.pageSizeComboBox.currentIndexChanged.connect(self.pageSizeAction)
self.window.documentUnitsComboBox.currentIndexChanged.connect(self.unitAction) self.window.documentUnitsComboBox.currentIndexChanged.connect(self.unitAction)
@ -83,21 +137,25 @@ class DocumentWindow(QWidget):
self.window.blockTableView.clicked.connect(self.blockClickedAction) self.window.blockTableView.clicked.connect(self.blockClickedAction)
def UIInitDocument(self): def UIInitDocument(self):
"""
Fills the window's fields with the values from its document.
"""
self.updateTitleBar()
# set all fields to appropriate values from document
self.window.titleLineEdit.setText(self.doc.title) self.window.titleLineEdit.setText(self.doc.title)
self.window.composerLineEdit.setText(self.doc.composer) self.window.composerLineEdit.setText(self.doc.composer)
self.window.arrangerLineEdit.setText(self.doc.arranger) self.window.arrangerLineEdit.setText(self.doc.arranger)
self.window.timeSignatureSpinBox.setValue(self.doc.timeSignature) self.window.timeSignatureSpinBox.setValue(self.doc.timeSignature)
# chord and block table lists here
self.window.chordTableView.populate(self.doc.chordList) self.window.chordTableView.populate(self.doc.chordList)
self.window.blockTableView.populate(self.doc.blockList) self.window.blockTableView.populate(self.doc.blockList)
self.updateChordDict() self.updateChordDict()
self.window.tabWidget.setCurrentWidget(self.window.tabWidget.findChild(QWidget, 'Overview'))
# self.updatePreview()
def UIInitStyle(self): def UIInitStyle(self):
"""
Fills the window's fields with the values from its style.
"""
self.window.pageSizeComboBox.addItems(list(pageSizeDict.keys())) self.window.pageSizeComboBox.addItems(list(pageSizeDict.keys()))
self.window.pageSizeComboBox.setCurrentText(list(pageSizeDict.keys())[0]) self.window.pageSizeComboBox.setCurrentText(list(pageSizeDict.keys())[0])
@ -133,37 +191,61 @@ class DocumentWindow(QWidget):
self.window.blockLengthLineEdit.setText(self.window.blockTableView.model.item(index.row(), 1).text()) self.window.blockLengthLineEdit.setText(self.window.blockTableView.model.item(index.row(), 1).text())
self.window.blockNotesLineEdit.setText(self.window.blockTableView.model.item(index.row(), 2).text()) self.window.blockNotesLineEdit.setText(self.window.blockTableView.model.item(index.row(), 2).text())
def getPath(self, value):
"""
Wrapper for Qt settings to return home directory if no setting exists.
"""
return str((settings.value(value) if settings.value(value) else os.path.expanduser("~")))
def setPath(self, value, fullpath):
"""
Wrapper for Qt settings to set path to open/save from next time from current file location.
"""
return settings.setValue(value, os.path.dirname(fullpath))
def menuFileNewAction(self): def menuFileNewAction(self):
self.doc = Document() self.doc = Document()
self.lastDoc = copy(self.doc)
self.currentFilePath = None
self.UIInitDocument()
self.updatePreview()
def menuFileOpenAction(self): def menuFileOpenAction(self):
filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', self.getPath("workingPath"), "Chordsheet ML files (*.xml *.cml)")
if filePath[0]: if filePath[0]:
self.currentFilePath = filePath[0] self.currentFilePath = filePath[0]
self.doc.loadXML(filePath[0]) self.doc.loadXML(filePath[0])
self.lastDoc = copy(self.doc)
self.setPath("workingPath", self.currentFilePath)
self.UIInitDocument() self.UIInitDocument()
self.updatePreview() self.updatePreview()
def menuFileSaveAction(self): def menuFileSaveAction(self):
self.updateDocument() self.updateDocument()
if not (hasattr(self, 'currentFilePath') and self.currentFilePath): if not (hasattr(self, 'currentFilePath') and self.currentFilePath):
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', self.getPath("workingPath"), "Chordsheet ML files (*.xml *.cml)")
self.currentFilePath = filePath[0] self.currentFilePath = filePath[0]
self.doc.saveXML(self.currentFilePath) self.doc.saveXML(self.currentFilePath)
self.lastDoc = copy(self.doc)
self.setPath("workingPath", self.currentFilePath)
def menuFileSaveAsAction(self): def menuFileSaveAsAction(self):
self.updateDocument() self.updateDocument()
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', self.getPath("workingPath"), "Chordsheet ML files (*.xml *.cml)")
if filePath[0]: if filePath[0]:
self.currentFilePath = filePath[0] self.currentFilePath = filePath[0]
self.doc.saveXML(self.currentFilePath) self.doc.saveXML(self.currentFilePath)
self.lastDoc = copy(self.doc)
self.setPath("workingPath", self.currentFilePath)
self.updateTitleBar() # as we now have a new filename
def menuFileSavePDFAction(self): def menuFileSavePDFAction(self):
self.updateDocument() self.updateDocument()
self.updatePreview() self.updatePreview()
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "PDF files (*.pdf)")
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', self.getPath("lastExportPath"), "PDF files (*.pdf)")
if filePath[0]: if filePath[0]:
savePDF(d, s, filePath[0]) savePDF(d, s, filePath[0])
self.setPath("lastExportPath", filePath[0])
def menuFilePrintAction(self): def menuFilePrintAction(self):
if sys.platform == "darwin": if sys.platform == "darwin":
@ -171,9 +253,35 @@ class DocumentWindow(QWidget):
# subprocess.call() # subprocess.call()
else: else:
pass pass
@pyqtSlot()
def menuFileCloseAction(self): def menuFileCloseAction(self):
pass
self.saveWarning()
def menuFileAboutAction(self):
aboutDialog = QMessageBox.information(self, "About", "Chordsheet © Ivan Holmes, 2019", buttons = QMessageBox.Ok, defaultButton = QMessageBox.Ok)
def saveWarning(self):
"""
Function to check if the document has unsaved data in it and offer to save it.
"""
self.updateDocument() # update the document to catch all changes
if (self.lastDoc == self.doc):
self.close()
else:
wantToSave = UnsavedMessageBox().exec()
if wantToSave == QMessageBox.Save:
if not (hasattr(self, 'currentFilePath') and self.currentFilePath):
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
self.currentFilePath = filePath[0]
self.doc.saveXML(self.currentFilePath)
self.close()
elif wantToSave == QMessageBox.Discard:
self.close()
# if cancel or anything else do nothing at all
def guitarVoicingAction(self): def guitarVoicingAction(self):
gdialog = GuitarDialog() gdialog = GuitarDialog()
@ -208,7 +316,7 @@ class DocumentWindow(QWidget):
self.doc.chordList.append(Chord(parseName(self.window.chordNameLineEdit.text()))) self.doc.chordList.append(Chord(parseName(self.window.chordNameLineEdit.text())))
if self.window.guitarVoicingLineEdit.text(): if self.window.guitarVoicingLineEdit.text():
setattr(self.doc.chordList[-1], 'guitar', parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar'))
self.doc.chordList[-1].voicings['guitar'] = parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar')
self.window.chordTableView.populate(self.doc.chordList) self.window.chordTableView.populate(self.doc.chordList)
self.clearChordLineEdits() self.clearChordLineEdits()
@ -220,7 +328,7 @@ class DocumentWindow(QWidget):
row = self.window.chordTableView.selectionModel().currentIndex().row() row = self.window.chordTableView.selectionModel().currentIndex().row()
self.doc.chordList[row] = Chord(parseName(self.window.chordNameLineEdit.text())) self.doc.chordList[row] = Chord(parseName(self.window.chordNameLineEdit.text()))
if self.window.guitarVoicingLineEdit.text(): if self.window.guitarVoicingLineEdit.text():
setattr(self.doc.chordList[row], 'guitar', parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar'))
self.doc.chordList[-1].voicings['guitar'] = parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar')
self.window.chordTableView.populate(self.doc.chordList) self.window.chordTableView.populate(self.doc.chordList)
self.clearChordLineEdits() self.clearChordLineEdits()
@ -277,13 +385,20 @@ class DocumentWindow(QWidget):
self.window.imageLabel.setPixmap(QPixmap.fromImage(qtimg)) self.window.imageLabel.setPixmap(QPixmap.fromImage(qtimg))
self.window.imageLabel.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown) self.window.imageLabel.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
def updateTitleBar(self):
appName = "Chordsheet"
if self.currentFilePath:
self.setWindowTitle(appName + " – " + os.path.basename(self.currentFilePath))
else:
self.setWindowTitle(appName)
def updateChords(self): def updateChords(self):
chordTableList = [] chordTableList = []
for i in range(self.window.chordTableView.model.rowCount()): for i in range(self.window.chordTableView.model.rowCount()):
chordTableList.append(Chord(parseName(self.window.chordTableView.model.item(i, 0).text()))), chordTableList.append(Chord(parseName(self.window.chordTableView.model.item(i, 0).text()))),
if self.window.chordTableView.model.item(i, 1).text(): if self.window.chordTableView.model.item(i, 1).text():
chordTableList[-1].guitar = parseFingering(self.window.chordTableView.model.item(i, 1).text(), 'guitar')
chordTableList[-1].voicings['guitar'] = parseFingering(self.window.chordTableView.model.item(i, 1).text(), 'guitar')
self.doc.chordList = chordTableList self.doc.chordList = chordTableList
@ -326,6 +441,10 @@ class DocumentWindow(QWidget):
# something for the font box here # something for the font box here
class GuitarDialog(QDialog): 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): def __init__(self):
super().__init__() super().__init__()
self.UIFileLoader(str(os.path.join(scriptDir, 'ui','guitardialog.ui'))) self.UIFileLoader(str(os.path.join(scriptDir, 'ui','guitardialog.ui')))
@ -350,13 +469,40 @@ class GuitarDialog(QDialog):
else: else:
return None return None
class UnsavedMessageBox(QMessageBox):
"""
Message box to alert the user of unsaved changes and allow them to choose how to act.
"""
def __init__(self):
super().__init__()
self.setWindowTitle("Unsaved changes")
self.setText("The document has been modified.")
self.setInformativeText("Do you want to save your changes?")
self.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
self.setDefaultButton(QMessageBox.Save)
class UnreadableMessageBox(QMessageBox):
"""
Message box to inform the user that the chosen file cannot be opened.
"""
def __init__(self):
super().__init__()
self.setWindowTitle("File 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)
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication(sys.argv) app = QApplication(sys.argv)
d = Document() d = Document()
s = Style() s = Style()
w = DocumentWindow(d, s)
w.window.show()
w = DocumentWindow(d, s, filename=(sys.argv[1] if len(sys.argv) > 1 else None)) # pass first argument as filename
w.show()
sys.exit(app.exec_()) sys.exit(app.exec_())

2
ui/guitardialog.ui

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

97
ui/mainwindow.ui

@ -6,14 +6,20 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1113</width>
<height>746</height>
<width>761</width>
<height>513</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Chordsheet</string> <string>Chordsheet</string>
</property> </property>
<widget class="QWidget" name="centralwidget">
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QSplitter" name="splitter"> <widget class="QSplitter" name="splitter">
@ -24,16 +30,19 @@
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<widget class="QWidget" name="leftPane" native="true"> <widget class="QWidget" name="leftPane" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>320</width>
<height>0</height>
<width>430</width>
<height>400</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
@ -51,17 +60,11 @@
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>300</width>
<height>400</height>
</size>
</property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>500</width> <width>500</width>
@ -69,7 +72,7 @@
</size> </size>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>2</number>
<number>0</number>
</property> </property>
<widget class="QWidget" name="tabWidgetOverview"> <widget class="QWidget" name="tabWidgetOverview">
<attribute name="title"> <attribute name="title">
@ -516,12 +519,6 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="editTriggers"> <property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set> <set>QAbstractItemView::NoEditTriggers</set>
</property> </property>
@ -614,6 +611,9 @@
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>40</width>
@ -719,6 +719,12 @@
</layout> </layout>
</widget> </widget>
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>300</width> <width>300</width>
@ -733,8 +739,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>580</width>
<height>698</height>
<width>298</width>
<height>465</height>
</rect> </rect>
</property> </property>
<property name="autoFillBackground"> <property name="autoFillBackground">
@ -776,12 +782,12 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QMenuBar" name="menubar">
<widget class="QMenuBar" name="menuBar">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1113</width>
<width>761</width>
<height>22</height> <height>22</height>
</rect> </rect>
</property> </property>
@ -800,7 +806,21 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionClose"/> <addaction name="actionClose"/>
</widget> </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"/>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuEdit"/>
</widget> </widget>
<action name="actionNew"> <action name="actionNew">
<property name="text"> <property name="text">
@ -832,6 +852,26 @@
<string>Close</string> <string>Close</string>
</property> </property>
</action> </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"> <action name="actionCut">
<property name="text"> <property name="text">
<string>Cut</string> <string>Cut</string>
@ -847,9 +887,14 @@
<string>Paste</string> <string>Paste</string>
</property> </property>
</action> </action>
<action name="actionSave_as">
<action name="actionPreferences">
<property name="text"> <property name="text">
<string>Save as...</string>
<string>Preferences</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property> </property>
</action> </action>
</widget> </widget>

Loading…
Cancel
Save