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.

360 lines
15 KiB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Created on Wed May 29 00:02:24 2019
  5. @author: ivan
  6. """
  7. import sys, fitz, io, subprocess, os
  8. from PyQt5.QtWidgets import QApplication, QAction, QLabel, QDialogButtonBox, QDialog, QFileDialog, QMessageBox, QPushButton, QLineEdit, QCheckBox, QSpinBox, QDoubleSpinBox, QTableWidget, QTableWidgetItem, QTabWidget, QComboBox, QWidget, QScrollArea
  9. from PyQt5.QtCore import QFile, QObject
  10. from PyQt5.QtGui import QPixmap, QImage
  11. from PyQt5 import uic
  12. from chordsheet.tableView import ChordTableView, BlockTableView , MItemModel, MProxyStyle
  13. from reportlab.lib.units import mm, cm, inch, pica
  14. from reportlab.lib.pagesizes import A4, A5, LETTER, LEGAL
  15. from reportlab.pdfbase import pdfmetrics
  16. from reportlab.pdfbase.ttfonts import TTFont
  17. from chordsheet.document import Document, Style, Chord, Block
  18. from chordsheet.render import savePDF
  19. from chordsheet.parsers import parseFingering, parseName
  20. if getattr(sys, 'frozen', False):
  21. scriptDir = sys._MEIPASS
  22. else:
  23. scriptDir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
  24. pdfmetrics.registerFont(TTFont('FreeSans', os.path.join(scriptDir, 'fonts', 'FreeSans.ttf')))
  25. if sys.platform == "darwin":
  26. pdfmetrics.registerFont(TTFont('HelveticaNeue', 'HelveticaNeue.ttc', subfontIndex=0))
  27. pageSizeDict = {'A4':A4, 'A5':A5, 'Letter':LETTER, 'Legal':LEGAL}
  28. unitDict = {'mm':mm, 'cm':cm, 'inch':inch, 'point':1, 'pica':pica} # point is 1 because reportlab's native unit is points.
  29. class DocumentWindow(QWidget):
  30. def __init__(self, doc, style, parent=None):
  31. super().__init__(parent)
  32. self.doc = doc
  33. self.style = style
  34. self.UIFileLoader(str(os.path.join(scriptDir, 'ui','mainwindow.ui')))
  35. self.UIInitStyle()
  36. # self.UIInitDocument()
  37. def UIFileLoader(self, ui_file):
  38. ui_file = QFile(ui_file)
  39. ui_file.open(QFile.ReadOnly)
  40. self.window = uic.loadUi(ui_file)
  41. ui_file.close()
  42. self.window.actionNew.triggered.connect(self.menuFileNewAction)
  43. self.window.actionOpen.triggered.connect(self.menuFileOpenAction)
  44. self.window.actionSave.triggered.connect(self.menuFileSaveAction)
  45. self.window.actionSave_as.triggered.connect(self.menuFileSaveAsAction)
  46. self.window.actionSave_PDF.triggered.connect(self.menuFileSavePDFAction)
  47. self.window.actionPrint.triggered.connect(self.menuFilePrintAction)
  48. self.window.actionClose.triggered.connect(self.menuFileCloseAction)
  49. self.window.pageSizeComboBox.currentIndexChanged.connect(self.pageSizeAction)
  50. self.window.documentUnitsComboBox.currentIndexChanged.connect(self.unitAction)
  51. self.window.includedFontCheckBox.stateChanged.connect(self.includedFontAction)
  52. self.window.generateButton.clicked.connect(self.generateAction)
  53. self.window.guitarVoicingButton.clicked.connect(self.guitarVoicingAction)
  54. self.window.addChordButton.clicked.connect(self.addChordAction)
  55. self.window.removeChordButton.clicked.connect(self.removeChordAction)
  56. self.window.updateChordButton.clicked.connect(self.updateChordAction)
  57. self.window.addBlockButton.clicked.connect(self.addBlockAction)
  58. self.window.removeBlockButton.clicked.connect(self.removeBlockAction)
  59. self.window.updateBlockButton.clicked.connect(self.updateBlockAction)
  60. self.window.chordTableView.clicked.connect(self.chordClickedAction)
  61. self.window.blockTableView.clicked.connect(self.blockClickedAction)
  62. def UIInitDocument(self):
  63. self.window.titleLineEdit.setText(self.doc.title)
  64. self.window.composerLineEdit.setText(self.doc.composer)
  65. self.window.arrangerLineEdit.setText(self.doc.arranger)
  66. self.window.timeSignatureSpinBox.setValue(self.doc.timeSignature)
  67. # chord and block table lists here
  68. self.window.chordTableView.populate(self.doc.chordList)
  69. self.window.blockTableView.populate(self.doc.blockList)
  70. self.updateChordDict()
  71. self.window.tabWidget.setCurrentWidget(self.window.tabWidget.findChild(QWidget, 'Overview'))
  72. # self.updatePreview()
  73. def UIInitStyle(self):
  74. self.window.pageSizeComboBox.addItems(list(pageSizeDict.keys()))
  75. self.window.pageSizeComboBox.setCurrentText(list(pageSizeDict.keys())[0])
  76. self.window.documentUnitsComboBox.addItems(list(unitDict.keys()))
  77. self.window.documentUnitsComboBox.setCurrentText(list(unitDict.keys())[0])
  78. self.window.lineSpacingDoubleSpinBox.setValue(self.style.lineSpacing)
  79. self.window.leftMarginLineEdit.setText(str(self.style.leftMargin))
  80. self.window.topMarginLineEdit.setText(str(self.style.topMargin))
  81. self.window.fontComboBox.setDisabled(True)
  82. self.window.includedFontCheckBox.setChecked(True)
  83. def pageSizeAction(self, index):
  84. self.pageSizeSelected = self.window.pageSizeComboBox.itemText(index)
  85. def unitAction(self, index):
  86. self.unitSelected = self.window.documentUnitsComboBox.itemText(index)
  87. def includedFontAction(self):
  88. if self.window.includedFontCheckBox.isChecked():
  89. self.style.useIncludedFont = True
  90. else:
  91. self.style.useIncludedFont = False
  92. def chordClickedAction(self, index):
  93. self.window.chordNameLineEdit.setText(self.window.chordTableView.model.item(index.row(), 0).text())
  94. self.window.guitarVoicingLineEdit.setText(self.window.chordTableView.model.item(index.row(), 1).text())
  95. def blockClickedAction(self, index):
  96. self.window.blockChordComboBox.setCurrentText(self.window.blockTableView.model.item(index.row(), 0).text())
  97. self.window.blockLengthLineEdit.setText(self.window.blockTableView.model.item(index.row(), 1).text())
  98. self.window.blockNotesLineEdit.setText(self.window.blockTableView.model.item(index.row(), 2).text())
  99. def menuFileNewAction(self):
  100. self.doc = Document()
  101. def menuFileOpenAction(self):
  102. filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
  103. if filePath[0]:
  104. self.currentFilePath = filePath[0]
  105. self.doc.loadXML(filePath[0])
  106. self.UIInitDocument()
  107. self.updatePreview()
  108. def menuFileSaveAction(self):
  109. self.updateDocument()
  110. if not (hasattr(self, 'currentFilePath') and self.currentFilePath):
  111. filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
  112. self.currentFilePath = filePath[0]
  113. self.doc.saveXML(self.currentFilePath)
  114. def menuFileSaveAsAction(self):
  115. self.updateDocument()
  116. filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)")
  117. if filePath[0]:
  118. self.currentFilePath = filePath[0]
  119. self.doc.saveXML(self.currentFilePath)
  120. def menuFileSavePDFAction(self):
  121. self.updateDocument()
  122. self.updatePreview()
  123. filePath = QFileDialog.getSaveFileName(self.window.tabWidget, 'Save file', str(os.path.expanduser("~")), "PDF files (*.pdf)")
  124. if filePath[0]:
  125. savePDF(d, s, filePath[0])
  126. def menuFilePrintAction(self):
  127. if sys.platform == "darwin":
  128. pass
  129. # subprocess.call()
  130. else:
  131. pass
  132. def menuFileCloseAction(self):
  133. pass
  134. def guitarVoicingAction(self):
  135. gdialog = GuitarDialog()
  136. voicing = gdialog.getVoicing()
  137. if voicing:
  138. self.window.guitarVoicingLineEdit.setText(voicing)
  139. def clearChordLineEdits(self):
  140. self.window.chordNameLineEdit.clear()
  141. self.window.guitarVoicingLineEdit.clear()
  142. self.window.chordNameLineEdit.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
  143. self.window.guitarVoicingLineEdit.clear()
  144. def updateChordDict(self):
  145. self.chordDict = {c.name:c for c in self.doc.chordList}
  146. self.window.blockChordComboBox.clear()
  147. self.window.blockChordComboBox.addItems(list(self.chordDict.keys()))
  148. def removeChordAction(self):
  149. self.updateChords()
  150. row = self.window.chordTableView.selectionModel().currentIndex().row()
  151. self.doc.chordList.pop(row)
  152. self.window.chordTableView.populate(self.doc.chordList)
  153. self.clearChordLineEdits()
  154. self.updateChordDict()
  155. def addChordAction(self):
  156. self.updateChords()
  157. self.doc.chordList.append(Chord(parseName(self.window.chordNameLineEdit.text())))
  158. if self.window.guitarVoicingLineEdit.text():
  159. setattr(self.doc.chordList[-1], 'guitar', parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar'))
  160. self.window.chordTableView.populate(self.doc.chordList)
  161. self.clearChordLineEdits()
  162. self.updateChordDict()
  163. def updateChordAction(self):
  164. if self.window.chordTableView.selectionModel().hasSelection():
  165. self.updateChords()
  166. row = self.window.chordTableView.selectionModel().currentIndex().row()
  167. self.doc.chordList[row] = Chord(parseName(self.window.chordNameLineEdit.text()))
  168. if self.window.guitarVoicingLineEdit.text():
  169. setattr(self.doc.chordList[row], 'guitar', parseFingering(self.window.guitarVoicingLineEdit.text(), 'guitar'))
  170. self.window.chordTableView.populate(self.doc.chordList)
  171. self.clearChordLineEdits()
  172. self.updateChordDict()
  173. def clearBlockLineEdits(self):
  174. self.window.blockLengthLineEdit.clear()
  175. self.window.blockNotesLineEdit.clear()
  176. self.window.blockLengthLineEdit.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
  177. self.window.blockNotesLineEdit.repaint()
  178. def removeBlockAction(self):
  179. self.updateBlocks()
  180. row = self.window.blockTableView.selectionModel().currentIndex().row()
  181. self.doc.blockList.pop(row)
  182. self.window.blockTableView.populate(self.doc.blockList)
  183. def addBlockAction(self):
  184. self.updateBlocks()
  185. self.doc.blockList.append(Block(self.window.blockLengthLineEdit.text(),
  186. chord = self.chordDict[self.window.blockChordComboBox.currentText()],
  187. notes = (self.window.blockNotesLineEdit.text() if not "" else None)))
  188. self.window.blockTableView.populate(self.doc.blockList)
  189. self.clearBlockLineEdits()
  190. def updateBlockAction(self):
  191. if self.window.blockTableView.selectionModel().hasSelection():
  192. self.updateBlocks()
  193. row = self.window.blockTableView.selectionModel().currentIndex().row()
  194. self.doc.blockList[row] = (Block(self.window.blockLengthLineEdit.text(),
  195. chord = self.chordDict[self.window.blockChordComboBox.currentText()],
  196. notes = (self.window.blockNotesLineEdit.text() if not "" else None)))
  197. self.window.blockTableView.populate(self.doc.blockList)
  198. self.clearBlockLineEdits()
  199. def generateAction(self):
  200. self.updateDocument()
  201. self.updatePreview()
  202. def updatePreview(self):
  203. self.currentPreview = io.BytesIO()
  204. savePDF(self.doc, self.style, self.currentPreview)
  205. pdfView = fitz.Document(stream=self.currentPreview, filetype='pdf')
  206. pix = pdfView[0].getPixmap(alpha = False)
  207. fmt = QImage.Format_RGB888
  208. qtimg = QImage(pix.samples, pix.width, pix.height, pix.stride, fmt)
  209. self.window.imageLabel.setPixmap(QPixmap.fromImage(qtimg))
  210. self.window.imageLabel.repaint() # necessary on Mojave with PyInstaller (or previous contents will be shown)
  211. def updateChords(self):
  212. chordTableList = []
  213. for i in range(self.window.chordTableView.model.rowCount()):
  214. chordTableList.append(Chord(parseName(self.window.chordTableView.model.item(i, 0).text()))),
  215. if self.window.chordTableView.model.item(i, 1).text():
  216. chordTableList[-1].guitar = parseFingering(self.window.chordTableView.model.item(i, 1).text(), 'guitar')
  217. self.doc.chordList = chordTableList
  218. def updateBlocks(self):
  219. blockTableList = []
  220. for i in range(self.window.blockTableView.model.rowCount()):
  221. blockLength = int(self.window.blockTableView.model.item(i, 1).text())
  222. blockChordName = parseName(self.window.blockTableView.model.item(i, 0).text())
  223. if blockChordName:
  224. blockChord = None
  225. for c in self.doc.chordList:
  226. if c.name == blockChordName:
  227. blockChord = c
  228. break
  229. if blockChord is None:
  230. exit("Chord {c} does not match any chord in {l}.".format(c=blockChordName, l=self.doc.chordList))
  231. else:
  232. blockChord = None
  233. blockNotes = self.window.blockTableView.model.item(i, 2).text() if self.window.blockTableView.model.item(i, 2).text() else None
  234. blockTableList.append(Block(blockLength, chord=blockChord, notes=blockNotes))
  235. self.doc.blockList = blockTableList
  236. def updateDocument(self):
  237. self.doc.title = self.window.titleLineEdit.text() # Title can be empty string but not None
  238. self.doc.composer = (self.window.composerLineEdit.text() if self.window.composerLineEdit.text() else None)
  239. self.doc.arranger = (self.window.arrangerLineEdit.text() if self.window.arrangerLineEdit.text() else None)
  240. self.doc.timeSignature = int(self.window.timeSignatureSpinBox.value())
  241. self.style.pageSize = pageSizeDict[self.pageSizeSelected]
  242. self.style.unit = unitDict[self.unitSelected]
  243. self.style.leftMargin = int(self.window.leftMarginLineEdit.text())
  244. self.style.topMargin = int(self.window.topMarginLineEdit.text())
  245. self.style.lineSpacing = float(self.window.lineSpacingDoubleSpinBox.value())
  246. self.updateChords()
  247. self.updateBlocks()
  248. self.style.font = ('FreeSans' if self.style.useIncludedFont else 'HelveticaNeue')
  249. # something for the font box here
  250. class GuitarDialog(QDialog):
  251. def __init__(self):
  252. super().__init__()
  253. self.UIFileLoader(str(os.path.join(scriptDir, 'ui','guitardialog.ui')))
  254. def UIFileLoader(self, ui_file):
  255. ui_file = QFile(ui_file)
  256. ui_file.open(QFile.ReadOnly)
  257. self.dialog = uic.loadUi(ui_file)
  258. ui_file.close()
  259. def getVoicing(self):
  260. if self.dialog.exec_() == QDialog.Accepted:
  261. result = [self.dialog.ELineEdit.text(),
  262. self.dialog.ALineEdit.text(),
  263. self.dialog.DLineEdit.text(),
  264. self.dialog.GLineEdit.text(),
  265. self.dialog.BLineEdit.text(),
  266. self.dialog.eLineEdit.text()]
  267. resultJoined = ",".join(result)
  268. return resultJoined
  269. else:
  270. return None
  271. if __name__ == '__main__':
  272. app = QApplication(sys.argv)
  273. d = Document()
  274. s = Style()
  275. w = DocumentWindow(d, s)
  276. w.window.show()
  277. sys.exit(app.exec_())