You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

362 lines
15 KiB

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed May 29 00:02:24 2019
@author: ivan
"""
import sys, fitz, io, subprocess, os
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 import uic
from chordsheet.tableView import ChordTableView, BlockTableView , MItemModel, MProxyStyle
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.document import Document, Style, Chord, Block
from chordsheet.render import savePDF
from chordsheet.parsers import parseFingering, parseName
if getattr(sys, 'frozen', False):
scriptDir = sys._MEIPASS
else:
scriptDir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
pdfmetrics.registerFont(TTFont('FreeSans', os.path.join(scriptDir, 'fonts', 'FreeSans.ttf')))
if sys.platform == "darwin":
pdfmetrics.registerFont(TTFont('HelveticaNeue', 'HelveticaNeue.ttc', subfontIndex=0))
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.
class DocumentWindow(QWidget):
def __init__(self, doc, style, parent=None):
super().__init__(parent)
self.doc = doc
self.style = style
self.UIFileLoader(str(os.path.join(scriptDir, 'ui','mainwindow.ui')))
self.UIInitStyle()
# self.UIInitDocument()
def UIFileLoader(self, ui_file):
ui_file = QFile(ui_file)
ui_file.open(QFile.ReadOnly)
self.window = uic.loadUi(ui_file)
ui_file.close()
self.window.actionNew.triggered.connect(self.menuFileNewAction)
self.window.actionOpen.triggered.connect(self.menuFileOpenAction)
self.window.actionSave.triggered.connect(self.menuFileSaveAction)
self.window.actionSave_as.triggered.connect(self.menuFileSaveAsAction)
self.window.actionSave_PDF.triggered.connect(self.menuFileSavePDFAction)
self.window.actionPrint.triggered.connect(self.menuFilePrintAction)
self.window.actionClose.triggered.connect(self.menuFileCloseAction)
self.window.pageSizeComboBox.currentIndexChanged.connect(self.pageSizeAction)
self.window.documentUnitsComboBox.currentIndexChanged.connect(self.unitAction)
self.window.includedFontCheckBox.stateChanged.connect(self.includedFontAction)
self.window.generateButton.clicked.connect(self.generateAction)
self.window.guitarVoicingButton.clicked.connect(self.guitarVoicingAction)
self.window.addChordButton.clicked.connect(self.addChordAction)
self.window.removeChordButton.clicked.connect(self.removeChordAction)
self.window.updateChordButton.clicked.connect(self.updateChordAction)
self.window.addBlockButton.clicked.connect(self.addBlockAction)
self.window.removeBlockButton.clicked.connect(self.removeBlockAction)
self.window.updateBlockButton.clicked.connect(self.updateBlockAction)
self.window.chordTableView.clicked.connect(self.chordClickedAction)
self.window.blockTableView.clicked.connect(self.blockClickedAction)
def UIInitDocument(self):
self.window.titleLineEdit.setText(self.doc.title)
self.window.composerLineEdit.setText(self.doc.composer)
self.window.arrangerLineEdit.setText(self.doc.arranger)
self.window.timeSignatureSpinBox.setValue(self.doc.timeSignature)
# chord and block table lists here
self.window.chordTableView.populate(self.doc.chordList)
self.window.blockTableView.populate(self.doc.blockList)
self.updateChordDict()
self.window.tabWidget.setCurrentWidget(self.window.tabWidget.findChild(QWidget, 'Overview'))
# self.updatePreview()
def UIInitStyle(self):
self.window.pageSizeComboBox.addItems(list(pageSizeDict.keys()))
self.window.pageSizeComboBox.setCurrentText(list(pageSizeDict.keys())[0])
self.window.documentUnitsComboBox.addItems(list(unitDict.keys()))
self.window.documentUnitsComboBox.setCurrentText(list(unitDict.keys())[0])
self.window.lineSpacingDoubleSpinBox.setValue(self.style.lineSpacing)
self.window.leftMarginLineEdit.setText(str(self.style.leftMargin))
self.window.topMarginLineEdit.setText(str(self.style.topMargin))
self.window.fontComboBox.setDisabled(True)
self.window.includedFontCheckBox.setChecked(True)
def pageSizeAction(self, index):
self.pageSizeSelected = self.window.pageSizeComboBox.itemText(index)
def unitAction(self, index):
self.unitSelected = self.window.documentUnitsComboBox.itemText(index)
def includedFontAction(self):
if self.window.includedFontCheckBox.isChecked():
self.style.useIncludedFont = True
else:
self.style.useIncludedFont = False
def chordClickedAction(self, index):
self.window.chordNameLineEdit.setText(self.window.chordTableView.model.item(index.row(), 0).text())
self.window.guitarVoicingLineEdit.setText(self.window.chordTableView.model.item(index.row(), 1).text())
def blockClickedAction(self, index):
self.window.blockChordComboBox.setCurrentText(self.window.blockTableView.model.item(index.row(), 0).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())
def menuFileNewAction(self):
self.doc = Document()
def menuFileOpenAction(self):
filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
if filePath[0]:
self.currentFilePath = filePath[0]
self.doc.loadXML(filePath[0])
self.UIInitDocument()
self.updatePreview()
def menuFileSaveAction(self):
self.updateDocument()
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)
def menuFileSaveAsAction(self):
self.updateDocument()
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
if filePath[0]:
self.currentFilePath = filePath[0]
self.doc.saveXML(self.currentFilePath)
def menuFileSavePDFAction(self):
self.updateDocument()
self.updatePreview()
filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "PDF files (*.pdf)")
if filePath[0]:
savePDF(d, s, filePath[0])
def menuFilePrintAction(self):
if sys.platform == "darwin":
pass
# subprocess.call()
else:
pass
def menuFileCloseAction(self):
pass
def guitarVoicingAction(self):
gdialog = GuitarDialog()
voicing = gdialog.getVoicing()
if voicing:
self.window.guitarVoicingLineEdit.setText(voicing)
def clearChordLineEdits(self):
self.window.chordNameLineEdit.clear()
self.window.guitarVoicingLineEdit.clear()
self.window.chordNameLineEdit.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
self.window.guitarVoicingLineEdit.clear()
def updateChordDict(self):
self.chordDict = {c.name:c for c in self.doc.chordList}
self.window.blockChordComboBox.clear()
self.window.blockChordComboBox.addItems(list(self.chordDict.keys()))
def removeChordAction(self):
self.updateChords()
row = self.window.chordTableView.selectionModel().currentIndex().row()
self.doc.chordList.pop(row)
self.window.chordTableView.populate(self.doc.chordList)
self.clearChordLineEdits()
self.updateChordDict()
def addChordAction(self):
self.updateChords()
self.doc.chordList.append(Chord(parseName(self.window.chordNameLineEdit.text())))
if self.window.guitarVoicingLineEdit.text():
setattr(self.doc.chordList[-1], 'guitar', parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar'))
self.window.chordTableView.populate(self.doc.chordList)
self.clearChordLineEdits()
self.updateChordDict()
def updateChordAction(self):
if self.window.chordTableView.selectionModel().hasSelection():
self.updateChords()
row = self.window.chordTableView.selectionModel().currentIndex().row()
self.doc.chordList[row] = Chord(parseName(self.window.chordNameLineEdit.text()))
if self.window.guitarVoicingLineEdit.text():
setattr(self.doc.chordList[row], 'guitar', parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar'))
self.window.chordTableView.populate(self.doc.chordList)
self.clearChordLineEdits()
self.updateChordDict()
def clearBlockLineEdits(self):
self.window.blockLengthLineEdit.clear()
self.window.blockNotesLineEdit.clear()
self.window.blockLengthLineEdit.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
self.window.blockNotesLineEdit.repaint()
def removeBlockAction(self):
self.updateBlocks()
row = self.window.blockTableView.selectionModel().currentIndex().row()
self.doc.blockList.pop(row)
self.window.blockTableView.populate(self.doc.blockList)
def addBlockAction(self):
self.updateBlocks()
self.doc.blockList.append(Block(self.window.blockLengthLineEdit.text(),
chord = self.chordDict[self.window.blockChordComboBox.currentText()],
notes = (self.window.blockNotesLineEdit.text() if not "" else None)))
self.window.blockTableView.populate(self.doc.blockList)
self.clearBlockLineEdits()
def updateBlockAction(self):
if self.window.blockTableView.selectionModel().hasSelection():
self.updateBlocks()
row = self.window.blockTableView.selectionModel().currentIndex().row()
self.doc.blockList[row] = (Block(self.window.blockLengthLineEdit.text(),
chord = self.chordDict[self.window.blockChordComboBox.currentText()],
notes = (self.window.blockNotesLineEdit.text() if not "" else None)))
self.window.blockTableView.populate(self.doc.blockList)
self.clearBlockLineEdits()
def generateAction(self):
self.updateDocument()
self.updatePreview()
def updatePreview(self):
self.currentPreview = io.BytesIO()
savePDF(self.doc, self.style, self.currentPreview)
pdfView = fitz.Document(stream=self.currentPreview, filetype='pdf')
pix = pdfView[0].getPixmap(alpha = False)
fmt = QImage.Format_RGB888
qtimg = QImage(pix.samples, pix.width, pix.height, pix.stride, fmt)
self.window.imageLabel.setPixmap(QPixmap.fromImage(qtimg))
self.window.imageLabel.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
def updateChords(self):
chordTableList = []
for i in range(self.window.chordTableView.model.rowCount()):
chordTableList.append(Chord(parseName(self.window.chordTableView.model.item(i, 0).text()))),
if self.window.chordTableView.model.item(i, 1).text():
chordTableList[-1].guitar = parseFingering(self.window.chordTableView.model.item(i, 1).text(), 'guitar')
self.doc.chordList = chordTableList
def updateBlocks(self):
blockTableList = []
for i in range(self.window.blockTableView.model.rowCount()):
blockLength = int(self.window.blockTableView.model.item(i, 1).text())
blockChordName = parseName(self.window.blockTableView.model.item(i, 0).text())
if blockChordName:
blockChord = None
for c in self.doc.chordList:
if c.name == blockChordName:
blockChord = c
break
if blockChord is None:
exit("Chord {c} does not match any chord in {l}.".format(c=blockChordName, l=self.doc.chordList))
else:
blockChord = None
blockNotes = self.window.blockTableView.model.item(i, 2).text() if self.window.blockTableView.model.item(i, 2).text() else None
blockTableList.append(Block(blockLength, chord=blockChord, notes=blockNotes))
self.doc.blockList = blockTableList
def updateDocument(self):
self.doc.title = self.window.titleLineEdit.text() # Title can be empty string but not None
self.doc.composer = (self.window.composerLineEdit.text() if self.window.composerLineEdit.text() else None)
self.doc.arranger = (self.window.arrangerLineEdit.text() if self.window.arrangerLineEdit.text() else None)
self.doc.timeSignature = int(self.window.timeSignatureSpinBox.value())
self.style.pageSize = pageSizeDict[self.pageSizeSelected]
self.style.unit = unitDict[self.unitSelected]
self.style.leftMargin = int(self.window.leftMarginLineEdit.text())
self.style.topMargin = int(self.window.topMarginLineEdit.text())
self.style.lineSpacing = float(self.window.lineSpacingDoubleSpinBox.value())
self.updateChords()
self.updateBlocks()
self.style.font = ('FreeSans' if self.style.useIncludedFont else 'HelveticaNeue')
# something for the font box here
class GuitarDialog(QDialog):
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):
if self.dialog.exec_() == QDialog.Accepted:
result = [self.dialog.ELineEdit.text(),
self.dialog.ALineEdit.text(),
self.dialog.DLineEdit.text(),
self.dialog.GLineEdit.text(),
self.dialog.BLineEdit.text(),
self.dialog.eLineEdit.text()]
resultJoined = ",".join(result)
return resultJoined
else:
return None
if __name__ == '__main__':
app = QApplication(sys.argv)
d = Document()
s = Style()
w = DocumentWindow(d, s)
w.window.show()
sys.exit(app.exec_())