diff --git a/chordsheet/document.py b/chordsheet/document.py index f76bb77..e7632ea 100644 --- a/chordsheet/document.py +++ b/chordsheet/document.py @@ -7,7 +7,6 @@ from reportlab.lib.pagesizes import A4 defaultTimeSignature = 4 - class Style: def __init__(self, **kwargs): # set up the style using sane defaults @@ -143,17 +142,8 @@ class Document: 'timesignature') is not None else defaultTimeSignature) self.tempo = (root.find('tempo').text if root.find( 'tempo') is not None else None) - - @classmethod - def newFromXML(cls, filepath): - """ - Create a new Document object directly from an XML file. - """ - doc = cls() - doc.loadXML(filepath) - return doc - - def saveXML(self, filepath): + + def toXML(self): """ Write the contents of the Document object to an XML file. """ @@ -200,7 +190,16 @@ 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): """ @@ -289,4 +288,4 @@ class Document: # Simply ignore comments pass else: - raise ValueError(f"Command {cmd} not understood.") \ No newline at end of file + raise ValueError(f"Command {cmd} not understood.") diff --git a/chordsheet/parsers.py b/chordsheet/parsers.py index 06d5067..0759dc9 100644 --- a/chordsheet/parsers.py +++ b/chordsheet/parsers.py @@ -7,14 +7,13 @@ def parseFingering(fingering, instrument): """ if instrument == 'guitar': numStrings = 6 - if len(fingering) == numStrings: # if the fingering is entered in concise format e.g. xx4455 + if "," not in fingering and len(fingering) == numStrings: # if the fingering is entered in concise format e.g. xx4455 output = list(fingering) 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: - return output - else: - raise Exception("Voicing <{}> is malformed.".format(fingering)) + if len(output) != numStrings: + print("Voicing <{}> is malformed.".format(fingering)) + return output elif instrument == 'piano': return [parseName(note).upper().strip() for note in fingering.split(",")] else: @@ -31,4 +30,4 @@ def parseName(chordName): parsedName = chordName for i, j in nameReplacements.items(): parsedName = parsedName.replace(i, j) - return parsedName \ No newline at end of file + return parsedName diff --git a/chordsheet/render.py b/chordsheet/render.py index 8fc3620..1adee2b 100644 --- a/chordsheet/render.py +++ b/chordsheet/render.py @@ -12,6 +12,7 @@ from reportlab.platypus import BaseDocTemplate, Spacer, Paragraph, Flowable, Fra from chordsheet.document import Block from chordsheet.rlStylesheet import getStyleSheet +defaultSpacing = 1.15 def writeText(canvas, style, string, size, vpos, width, **kwargs): """ @@ -122,7 +123,7 @@ class GuitarChart(Flowable): 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') + 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])): @@ -152,7 +153,7 @@ class GuitarChart(Flowable): 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)) + i*self.stringHeight), self.width, hpos=chartmargin+self.stringHzSp*(j+0.5), spacing=defaultSpacing) lastWidth = currentWidth @@ -458,7 +459,7 @@ class ChordProgression(Flowable): if u == maxWidth: # Avoid writing beat number after the final line break writeText(canvas, self.style, str((u % self.timeSignature)+1), self.style.beatsFontSize, - v_origin+self.beatsHeight, self.width, hpos=x+unitWidth/2) + v_origin+self.beatsHeight, self.width, hpos=x+unitWidth/2, spacing=defaultSpacing) parsedBlockList = self.wrapBlocks(self.blockList, maxWidth) @@ -470,12 +471,12 @@ class ChordProgression(Flowable): b.length*unitWidth, self.unitHeight) if b.notes is not None: 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)) + 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(canvas, self.style, b.chord.name, self.style.chordNameFontSize, - v_origin-v_offset, self.width, hpos=h_offset+((h_loc+b.length/2)*unitWidth)) + v_origin-v_offset, self.width, hpos=h_offset+((h_loc+b.length/2)*unitWidth), spacing=defaultSpacing) h_loc += b.length diff --git a/cli.py b/cli.py new file mode 100755 index 0000000..e90eada --- /dev/null +++ b/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) diff --git a/chordsheet/comboBox.py b/csgui/comboBox.py similarity index 100% rename from chordsheet/comboBox.py rename to csgui/comboBox.py diff --git a/chordsheet/dialogs.py b/csgui/dialogs.py similarity index 69% rename from chordsheet/dialogs.py rename to csgui/dialogs.py index dcc03ed..3a0551a 100644 --- a/chordsheet/dialogs.py +++ b/csgui/dialogs.py @@ -25,17 +25,31 @@ class GuitarDialog(QDialog): self.dialog = uic.loadUi(ui_file) ui_file.close() - def getVoicing(self): + 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(), - self.dialog.ALineEdit.text(), - self.dialog.DLineEdit.text(), - self.dialog.GLineEdit.text(), - self.dialog.BLineEdit.text(), - self.dialog.eLineEdit.text()] + 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: @@ -64,4 +78,4 @@ class AboutDialog(QDialog): ui_file.open(QFile.ReadOnly) self.dialog = uic.loadUi(ui_file) - ui_file.close() \ No newline at end of file + ui_file.close() diff --git a/chordsheet/messageBox.py b/csgui/messageBox.py similarity index 94% rename from chordsheet/messageBox.py rename to csgui/messageBox.py index 87a620e..8bd65a0 100644 --- a/chordsheet/messageBox.py +++ b/csgui/messageBox.py @@ -5,12 +5,12 @@ class UnsavedMessageBox(QMessageBox): Message box to alert the user of unsaved changes and allow them to choose how to act. """ - def __init__(self): + def __init__(self, fileName): super().__init__() self.setIcon(QMessageBox.Question) self.setWindowTitle("Unsaved changes") - self.setText("The document has been modified.") + 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) @@ -22,11 +22,11 @@ class UnreadableMessageBox(QMessageBox): Message box to warn the user that the chosen file cannot be opened. """ - def __init__(self): + def __init__(self, fileName): super().__init__() self.setIcon(QMessageBox.Warning) - self.setWindowTitle("File cannot be opened") + 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) diff --git a/chordsheet/panels.py b/csgui/panels.py similarity index 80% rename from chordsheet/panels.py rename to csgui/panels.py index 41f0427..4ffea6e 100644 --- a/chordsheet/panels.py +++ b/csgui/panels.py @@ -1,10 +1,11 @@ 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 +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): @@ -17,6 +18,15 @@ class UIFileDockWidget(QDockWidget): 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__() @@ -51,4 +61,4 @@ class PreviewDockWidget(UIFileDockWidget): def __init__(self): super().__init__() self.UIFileLoader('preview.ui') - self.setWindowTitle("Preview") \ No newline at end of file + self.setWindowTitle("Preview") diff --git a/chordsheet/pdfViewer.py b/csgui/pdfViewer.py similarity index 72% rename from chordsheet/pdfViewer.py rename to csgui/pdfViewer.py index 0db75df..9f30c31 100644 --- a/chordsheet/pdfViewer.py +++ b/csgui/pdfViewer.py @@ -14,8 +14,20 @@ class PDFLabel(QLabel): self.adjustSize() if self.pixmap() is not None: painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing) - idealWidth = self.parent.width()-45 + # 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 pixSize = self.pixmap().size() pixSize.scale(idealWidth, 1000000, Qt.KeepAspectRatio) @@ -32,6 +44,7 @@ class PDFViewer(QScrollArea): self.setWidget(self.scrollAreaContents) self.setWidgetResizable(True) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollAreaContents.setLayout(self.scrollAreaLayout) self.pixmapList = [] @@ -48,9 +61,8 @@ class PDFViewer(QScrollArea): self.pixmapList = [] pdfView = fitz.Document(stream=pdf, filetype='pdf') - # render at 8x resolution and scale for page in pdfView: - self.pixmapList.append(page.getPixmap(matrix=fitz.Matrix(8, 8), alpha=False)) + self.pixmapList.append(page.get_pixmap(alpha=False)) def clear(self): while self.scrollAreaLayout.count(): @@ -64,11 +76,7 @@ class PDFViewer(QScrollArea): label = PDFLabel(parent=self) label.setAlignment(Qt.AlignHCenter) qtimg = QImage(p.samples, p.width, p.height, p.stride, QImage.Format_RGB888) - # -45 because of various margins... value obtained by trial and error. label.setPixmap(QPixmap.fromImage(qtimg)) self.scrollAreaLayout.addWidget(label) - self.scrollAreaLayout.addStretch(1) - - # necessary on Mojave with PyInstaller (or previous contents will be shown) - self.repaint() \ No newline at end of file + # self.scrollAreaLayout.addStretch(1) diff --git a/chordsheet/tableView.py b/csgui/tableView.py similarity index 83% rename from chordsheet/tableView.py rename to csgui/tableView.py index 09e34f7..7a9a7fd 100644 --- a/chordsheet/tableView.py +++ b/csgui/tableView.py @@ -1,16 +1,35 @@ 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" """ - return super().dropMimeData(data, action, row, 0, parent) + 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): @@ -50,11 +69,13 @@ class MTableView(QtWidgets.QTableView): self.setShowGrid(False) # self.setDragDropMode(self.InternalMove) - # self.setDragDropOverwriteMode(False) + 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): """ diff --git a/gui.py b/gui.py index baa08c7..92972e8 100755 --- a/gui.py +++ b/gui.py @@ -13,14 +13,19 @@ import subprocess import os import time from copy import copy +from xml.etree.ElementTree import ParseError from PyQt5.QtWidgets import QApplication, QAction, QLabel, QDialogButtonBox, QDialog, QFileDialog, QMessageBox, QPushButton, QLineEdit, QCheckBox, QFontComboBox, QSpinBox, QDoubleSpinBox, QTableWidgetItem, QTabWidget, QComboBox, QWidget, QScrollArea, QMainWindow, QShortcut, QMdiSubWindow -from PyQt5.QtCore import QFile, QObject, Qt, pyqtSlot, QSettings +from PyQt5.QtCore import QFile, QObject, Qt, pyqtSlot, pyqtSignal, QSettings from PyQt5.QtGui import QPixmap, QImage, QKeySequence, QFontInfo, QFont, QRawFont from PyQt5 import uic -from chordsheet.tableView import ChordTableView, BlockTableView -from chordsheet.comboBox import MComboBox -from chordsheet.pdfViewer import PDFViewer + +from csgui.tableView import ChordTableView, BlockTableView +from csgui.comboBox import MComboBox +from csgui.pdfViewer import PDFViewer +from csgui.messageBox import UnsavedMessageBox, UnreadableMessageBox, ChordNameWarningMessageBox, SectionNameWarningMessageBox, BlockMustHaveSectionWarningMessageBox, VoicingWarningMessageBox, LengthWarningMessageBox +from csgui.dialogs import GuitarDialog, AboutDialog +from csgui.panels import DocInfoDockWidget, PageSetupDockWidget, ChordsDockWidget, SectionsDockWidget, BlocksDockWidget, PreviewDockWidget from reportlab.lib.units import mm, cm, inch, pica from reportlab.lib.pagesizes import A4, A5, LETTER, LEGAL @@ -31,9 +36,7 @@ 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 -from chordsheet.messageBox import UnsavedMessageBox, UnreadableMessageBox, ChordNameWarningMessageBox, SectionNameWarningMessageBox, BlockMustHaveSectionWarningMessageBox, VoicingWarningMessageBox, LengthWarningMessageBox -from chordsheet.dialogs import GuitarDialog, AboutDialog -from chordsheet.panels import DocInfoDockWidget, PageSetupDockWidget, ChordsDockWidget, SectionsDockWidget, BlocksDockWidget, PreviewDockWidget + import _version @@ -55,6 +58,17 @@ pageSizeDict = {'A4': A4, 'A5': A5, 'Letter': LETTER, 'Legal': LEGAL} # point is 1 because reportlab's native unit is points. unitDict = {'mm': mm, 'cm': cm, 'inch': inch, 'point': 1, 'pica': pica} +def getPath(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(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)) class MainWindow(QMainWindow): """ @@ -71,104 +85,90 @@ class MainWindow(QMainWindow): """ super().__init__() - self.UIFileLoader(str(os.path.join(scriptDir, 'ui', 'new.ui'))) + self.UIFileLoader(str(os.path.join(scriptDir, 'ui', 'mainwindow.ui'))) self.setCentralWidget(self.window.centralWidget) self.setMenuBar(self.window.menuBar) self.setWindowTitle("Chordsheet") - + self.lastSubWindow = None - self.currentSection = None + + self.window.mdiArea.setActivationOrder(self.window.mdiArea.ActivationHistoryOrder) if filename: - try: - self.openFile(filename) - except Exception: - UnreadableMessageBox().exec() + dw = DocumentWindow.openFile(filename) + if dw: + self.window.mdiArea.addSubWindow(dw) + # self.UIInitDocument(dw.doc) + # self.UIInitStyle(dw.style) + # self.currentSection = None + + dw.show() def closeEvent(self, event): """ Reimplement the built in closeEvent to allow asking the user to save. """ if not self.window.mdiArea.subWindowList(): - self.close() - - 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.open(QFile.ReadOnly) + event.accept() + else: + for subWindow in self.window.mdiArea.subWindowList(): + if not subWindow.close(): + event.ignore() + return + event.accept() - self.window = uic.loadUi(ui_file) - ui_file.close() - + def UISetupPanels(self): self.docinfo = DocInfoDockWidget() self.psetup = PageSetupDockWidget() self.chordsw = ChordsDockWidget() self.sectionsw = SectionsDockWidget() self.blocksw = BlocksDockWidget() self.previeww = PreviewDockWidget() - + + self.panels_list = [self.docinfo, self.psetup, self.chordsw, self.sectionsw, + self.blocksw, self.previeww] + self.addDockWidget(Qt.LeftDockWidgetArea, self.docinfo) self.addDockWidget(Qt.LeftDockWidgetArea, self.psetup) self.addDockWidget(Qt.LeftDockWidgetArea, self.previeww) self.addDockWidget(Qt.RightDockWidgetArea, self.chordsw) self.addDockWidget(Qt.RightDockWidgetArea, self.sectionsw) self.addDockWidget(Qt.RightDockWidgetArea, self.blocksw) - - # Check all the boxes in the window menu - self.window.actionDocument_information.setChecked(True) - self.window.actionPage_setup.setChecked(True) - self.window.actionChords.setChecked(True) - self.window.actionSections.setChecked(True) - self.window.actionBlocks.setChecked(True) - self.window.actionPreview.setChecked(True) - # link all the UI elements - self.window.mdiArea.subWindowActivated.connect(self.switchDocument) + self.previeww.hide() - self.window.actionAbout.triggered.connect(self.menuFileAboutAction) - 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.actionUndo.triggered.connect(self.menuEditUndoAction) - self.window.actionRedo.triggered.connect(self.menuEditRedoAction) - self.window.actionCut.triggered.connect(self.menuEditCutAction) - self.window.actionCopy.triggered.connect(self.menuEditCopyAction) - self.window.actionPaste.triggered.connect(self.menuEditPasteAction) + # connect the change signals to handlers + self.docinfo.widget().titleLineEdit.editingFinished.connect( + self.updateTitle) + self.docinfo.widget().subtitleLineEdit.editingFinished.connect( + self.updateSubtitle) + self.docinfo.widget().composerLineEdit.editingFinished.connect( + self.updateComposer) + self.docinfo.widget().arrangerLineEdit.editingFinished.connect( + self.updateArranger) + self.docinfo.widget().timeSignatureSpinBox.valueChanged.connect( + self.updateTimeSignature) + self.docinfo.widget().tempoLineEdit.editingFinished.connect( + self.updateTempo) + + self.psetup.widget().pageSizeComboBox.textActivated.connect( + self.updatePageSize) + self.psetup.widget().documentUnitsComboBox.textActivated.connect( + self.updateUnit) + self.psetup.widget().leftMarginLineEdit.editingFinished.connect( + self.updateLeftMargin) + self.psetup.widget().rightMarginLineEdit.editingFinished.connect( + self.updateRightMargin) + self.psetup.widget().topMarginLineEdit.editingFinished.connect( + self.updateTopMargin) + self.psetup.widget().bottomMarginLineEdit.editingFinished.connect( + self.updateBottomMargin) + self.psetup.widget().lineSpacingDoubleSpinBox.valueChanged.connect( + self.updateLineSpacing) + self.psetup.widget().beatWidthLineEdit.editingFinished.connect( + self.updateBeatWidth) - 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.actionDocument_information.triggered.connect(self.menuWindowDocInfoAction) - self.window.actionPage_setup.triggered.connect(self.menuWindowPageSetupAction) - self.window.actionChords.triggered.connect(self.menuWindowChordsAction) - self.window.actionSections.triggered.connect(self.menuWindowSectionsAction) - self.window.actionBlocks.triggered.connect(self.menuWindowBlocksAction) - self.window.actionPreview.triggered.connect(self.menuWindowPreviewAction) - - - self.psetup.widget().pageSizeComboBox.currentIndexChanged.connect( - self.pageSizeAction) - self.psetup.widget().documentUnitsComboBox.currentIndexChanged.connect( - self.unitAction) - self.psetup.widget().pageSizeComboBox.addItems(list(pageSizeDict.keys())) self.psetup.widget().pageSizeComboBox.setCurrentText( list(pageSizeDict.keys())[0]) @@ -176,11 +176,12 @@ class MainWindow(QMainWindow): self.psetup.widget().documentUnitsComboBox.addItems(list(unitDict.keys())) self.psetup.widget().documentUnitsComboBox.setCurrentText( list(unitDict.keys())[0]) - + self.psetup.widget().fontComboBox.currentFontChanged.connect(self.fontChangeAction) self.psetup.widget().includedFontCheckBox.stateChanged.connect( self.includedFontAction) - + + self.previeww.widget().autoUpdatePreviewCheckBox.stateChanged.connect(self.autoUpdateEnabledAction) self.previeww.widget().updatePreviewButton.clicked.connect(self.generateAction) # update whole document when any tab is selected @@ -191,6 +192,7 @@ class MainWindow(QMainWindow): self.chordsw.widget().addChordButton.clicked.connect(self.addChordAction) self.chordsw.widget().removeChordButton.clicked.connect(self.removeChordAction) self.chordsw.widget().updateChordButton.clicked.connect(self.updateChordAction) + self.chordsw.widget().chordTableView.model.itemsDropped.connect(self.updateChords) # connecting clicked only works for this combo box because it's my own modified version (MComboBox) self.blocksw.widget().blockSectionComboBox.clicked.connect( @@ -200,26 +202,131 @@ class MainWindow(QMainWindow): self.blocksw.widget().addBlockButton.clicked.connect(self.addBlockAction) self.blocksw.widget().removeBlockButton.clicked.connect(self.removeBlockAction) self.blocksw.widget().updateBlockButton.clicked.connect(self.updateBlockAction) + self.blocksw.widget().blockTableView.model.itemsDropped.connect(self.updateBlocksCurrentSection) self.sectionsw.widget().addSectionButton.clicked.connect(self.addSectionAction) self.sectionsw.widget().removeSectionButton.clicked.connect( self.removeSectionAction) self.sectionsw.widget().updateSectionButton.clicked.connect( self.updateSectionAction) + self.sectionsw.widget().sectionTableView.model.itemsDropped.connect(self.updateSections) self.chordsw.widget().chordTableView.clicked.connect(self.chordClickedAction) self.sectionsw.widget().sectionTableView.clicked.connect(self.sectionClickedAction) self.blocksw.widget().blockTableView.clicked.connect(self.blockClickedAction) + self.disablePanels() + + def unfloatPanels(self): + for panel in self.panels_list: + panel.setFloating(False) + + def disablePanels(self): + for panel in self.panels_list: + panel.setEnabled(False) + panel.clear() + + def enablePanels(self): + for panel in self.panels_list: + panel.setEnabled(True) + + 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.open(QFile.ReadOnly) + + self.window = uic.loadUi(ui_file) + ui_file.close() + + self.csw = self.window.mdiArea.currentSubWindow + + self.UISetupPanels() + self.disableMenuItems() + + # link all the UI elements + self.window.mdiArea.subWindowActivated.connect(self.switchDocument) + + self.window.actionAbout.triggered.connect(self.menuHelpAboutAction) + + 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.actionQuit.triggered.connect(self.menuFileQuitAction) + + self.window.actionUndo.triggered.connect(self.menuEditUndoAction) + self.window.actionRedo.triggered.connect(self.menuEditRedoAction) + self.window.actionCut.triggered.connect(self.menuEditCutAction) + self.window.actionCopy.triggered.connect(self.menuEditCopyAction) + self.window.actionPaste.triggered.connect(self.menuEditPasteAction) + + 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.actionCascadeSubWindows.triggered.connect(self.cascadeSubWindowsAction) + self.window.actionTileSubWindows.triggered.connect(self.tileSubWindowsAction) + self.window.actionActivateNextSubWindow.triggered.connect(self.activatePreviousSubWindowAction) + self.window.actionActivatePreviousSubWindow.triggered.connect(self.activateNextSubWindowAction) + self.window.actionRedockAllPanels.triggered.connect(self.unfloatPanels) + self.window.menuWindow.addSeparator() + self.window.menuWindow.addAction(self.docinfo.toggleViewAction()) + self.window.menuWindow.addAction(self.psetup.toggleViewAction()) + self.window.menuWindow.addAction(self.chordsw.toggleViewAction()) + self.window.menuWindow.addAction(self.sectionsw.toggleViewAction()) + self.window.menuWindow.addAction(self.blocksw.toggleViewAction()) + self.window.menuWindow.addSeparator() + self.window.menuWindow.addAction(self.previeww.toggleViewAction()) + # Set the tab widget to Overview tab # self.window.tabWidget.setCurrentIndex(0) - - def UIInitDocument(self, doc): + + def enableMenuItems(self): + self.window.actionSave.setEnabled(True) + self.window.actionSave_as.setEnabled(True) + self.window.actionSave_PDF.setEnabled(True) + # Comment this because we can't print + # self.window.actionPrint.setEnabled(True) + self.window.actionClose.setEnabled(True) + self.window.actionUndo.setEnabled(True) + self.window.actionRedo.setEnabled(True) + self.window.actionCut.setEnabled(True) + self.window.actionCopy.setEnabled(True) + self.window.actionPaste.setEnabled(True) + + def disableMenuItems(self): + self.window.actionSave.setEnabled(False) + self.window.actionSave_as.setEnabled(False) + self.window.actionSave_PDF.setEnabled(False) + self.window.actionPrint.setEnabled(False) + self.window.actionClose.setEnabled(False) + self.window.actionUndo.setEnabled(False) + self.window.actionRedo.setEnabled(False) + self.window.actionCut.setEnabled(False) + self.window.actionCopy.setEnabled(False) + self.window.actionPaste.setEnabled(False) + + def UIInitDocument(self): """ Fills the window's fields with the values from its document. """ - # self.updateTitleBar() - + doc = self.csw().doc + # set all fields to appropriate values from document self.docinfo.widget().titleLineEdit.setText(doc.title) self.docinfo.widget().subtitleLineEdit.setText(doc.subtitle) @@ -231,21 +338,27 @@ class MainWindow(QMainWindow): self.chordsw.widget().chordTableView.populate(doc.chordList) self.sectionsw.widget().sectionTableView.populate(doc.sectionList) - self.updateChordDict(doc) - self.updateSectionDict(doc) + self.csw().updateChordDict() + self.csw().updateSectionDict() + + self.updateBlockChordComboBox() + self.updateBlockSectionComboBox() + # populate the block table with the first section, account for a document with no sections - if self.currentSection is None: - self.currentSection = doc.sectionList[0] if doc.sectionList else None + if self.csw().currentSection is None: + self.csw().currentSection = doc.sectionList[0] if doc.sectionList else None else: self.blocksw.widget().blockSectionComboBox.setCurrentText( - self.currentSection.name) + self.csw().currentSection.name) self.blocksw.widget().blockTableView.populate( - self.currentSection.blockList if self.currentSection else []) + self.csw().currentSection.blockList if self.csw().currentSection else []) - def UIInitStyle(self, style): + def UIInitStyle(self): """ Fills the window's fields with the values from its style. """ + style = self.csw().style + self.psetup.widget().pageSizeComboBox.setCurrentText( [k for k, v in pageSizeDict.items() if v==style.pageSize][0]) @@ -259,94 +372,244 @@ class MainWindow(QMainWindow): self.psetup.widget().topMarginLineEdit.setText(str(style.topMargin)) self.psetup.widget().bottomMarginLineEdit.setText(str(style.bottomMargin)) - # self.psetup.widget().fontComboBox.setDisabled(True) + self.psetup.widget().fontComboBox.setDisabled(True) self.psetup.widget().includedFontCheckBox.setChecked(True) self.psetup.widget().beatWidthLineEdit.setText(str(style.unitWidth)) + + def fontChangeAction(self): +# if self.csw() is not None: + qFont = self.psetup.widget().fontComboBox.currentFont() + qFontReal = QRawFont.fromFont(qFont) + #fontInfo = QFontInfo(qFont) + #qFontReal = QRawFont(fontInfo.family()) + print(qFont.rawName()) - def updateChordDict(self, doc): + def autoUpdateEnabledAction(self, state): + self.previeww.widget().updatePreviewButton.setEnabled(not state) + + def switchDocument(self, curWindow): + if curWindow is not None: + self.enablePanels() + self.enableMenuItems() + + self.UIInitDocument() + self.UIInitStyle() + if self.csw().currentSection is not None: + self.blocksw.widget().blockSectionComboBox.setCurrentText( + self.csw().currentSection.name) + + self.lastSubWindow = curWindow + else: + self.disablePanels() + self.disableMenuItems() + + def updateBlockChordComboBox(self): + self.blocksw.widget().blockChordComboBox.clear() + self.blocksw.widget().blockChordComboBox.addItems(list(self.csw().chordDict.keys())) + + def updateBlockSectionComboBox(self): + self.blocksw.widget().blockSectionComboBox.clear() + self.blocksw.widget().blockSectionComboBox.addItems(list(self.csw().sectionDict.keys())) + + def updateTitle(self): + self.csw().doc.title = self.docinfo.widget().titleLineEdit.text() + # Title can be empty string but not None + self.autoUpdatePreview() + + def updateSubtitle(self): + self.csw().doc.subtitle = (self.docinfo.widget().subtitleLineEdit.text() \ + if self.docinfo.widget().subtitleLineEdit.text() else None) + self.autoUpdatePreview() + + def updateComposer(self): + self.csw().doc.composer = (self.docinfo.widget().composerLineEdit.text() \ + if self.docinfo.widget().composerLineEdit.text() else None) + self.autoUpdatePreview() + + def updateArranger(self): + self.csw().doc.arranger = (self.docinfo.widget().arrangerLineEdit.text() \ + if self.docinfo.widget().arrangerLineEdit.text() else None) + self.autoUpdatePreview() + + def updateTempo(self): + self.csw().doc.tempo = (self.docinfo.widget().tempoLineEdit.text() \ + if self.docinfo.widget().tempoLineEdit.text() else None) + self.autoUpdatePreview() + + def updateTimeSignature(self): + if self.csw(): + self.csw().doc.timeSignature = (int(self.docinfo.widget().timeSignatureSpinBox.value()) if self.docinfo.widget().timeSignatureSpinBox.value() else self.doc.timeSignature) + self.autoUpdatePreview() + + def updatePageSize(self, index): + if self.csw(): + self.csw().style.pageSize = pageSizeDict[self.psetup.widget().pageSizeComboBox.currentText()] + self.autoUpdatePreview() + + def updateUnit(self, index): + self.csw().style.unit = unitDict[self.psetup.widget().documentUnitsComboBox.currentText()] + self.autoUpdatePreview() + + def updateLeftMargin(self): + self.csw().style.leftMargin = float(self.psetup.widget().leftMarginLineEdit.text( + )) if self.psetup.widget().leftMarginLineEdit.text() else self.csw().style.leftMargin + self.autoUpdatePreview() + + def updateRightMargin(self): + self.csw().style.rightMargin = float(self.psetup.widget().rightMarginLineEdit.text( + )) if self.psetup.widget().rightMarginLineEdit.text() else self.csw().style.rightMargin + self.autoUpdatePreview() + + def updateTopMargin(self): + self.csw().style.topMargin = float(self.psetup.widget().topMarginLineEdit.text( + )) if self.psetup.widget().topMarginLineEdit.text() else self.csw().style.topMargin + self.autoUpdatePreview() + + def updateBottomMargin(self): + self.csw().style.bottomMargin = float(self.psetup.widget().bottomMarginLineEdit.text( + )) if self.psetup.widget().bottomMarginLineEdit.text() else self.csw().style.bottomMargin + self.autoUpdatePreview() + + def updateLineSpacing(self): + if self.csw(): + self.csw().style.lineSpacing = float(self.psetup.widget().lineSpacingDoubleSpinBox.value()) if self.psetup.widget().lineSpacingDoubleSpinBox.value() else self.csw().style.lineSpacing + self.autoUpdatePreview() + + def updateBeatWidth(self): + if self.psetup.widget().beatWidthLineEdit.text(): + if (self.csw().style.pageSize[0] - 2 * self.csw().style.leftMargin * mm) >= (float(self.psetup.widget().beatWidthLineEdit.text()) * 2 * self.csw().doc.timeSignature * mm): + self.csw().style.unitWidth = float( + self.psetup.widget().beatWidthLineEdit.text()) + else: + maxBeatWidth = ( + self.csw().style.pageSize[0] - 2 * self.csw().style.leftMargin * mm) / (2 * self.csw().doc.timeSignature * mm) + QMessageBox.warning(self, "Out of range", "Beat width is out of range. It can be a maximum of {}.".format( + maxBeatWidth), buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok) + self.autoUpdatePreview() + + def updateChords(self): """ - Updates the dictionary used to generate the Chord menu (on the block tab) + Update the chord list by reading the table. """ - self.chordDict = {'None': None} - self.chordDict.update({c.name: c for c in doc.chordList}) - self.blocksw.widget().blockChordComboBox.clear() - self.blocksw.widget().blockChordComboBox.addItems(list(self.chordDict.keys())) + chordTableList = [] + for i in range(self.chordsw.widget().chordTableView.model.rowCount()): + chordTableList.append( + Chord(parseName(self.chordsw.widget().chordTableView.model.item(i, 0).text()))), + if self.chordsw.widget().chordTableView.model.item(i, 1).text(): + chordTableList[-1].voicings['guitar'] = parseFingering( + self.chordsw.widget().chordTableView.model.item(i, 1).text(), 'guitar') + if self.chordsw.widget().chordTableView.model.item(i, 2).text(): + chordTableList[-1].voicings['piano'] = parseFingering( + self.chordsw.widget().chordTableView.model.item(i, 2).text(), 'piano') - def updateSectionDict(self, doc): + if self.csw(): + self.csw().doc.chordList = chordTableList + self.autoUpdatePreview() + + def updateSections(self): """ - Updates the dictionary used to generate the Section menu (on the block tab) + Update the section list by reading the table """ - self.sectionDict = {s.name: s for s in doc.sectionList} - self.blocksw.widget().blockSectionComboBox.clear() - self.blocksw.widget().blockSectionComboBox.addItems( - list(self.sectionDict.keys())) + sectionTableList = [] + for i in range(self.sectionsw.widget().sectionTableView.model.rowCount()): + sectionTableList.append(self.csw().matchSection( + self.sectionsw.widget().sectionTableView.model.item(i, 0).text())) + + self.csw().doc.sectionList = sectionTableList + self.autoUpdatePreview() + + def updateBlocks(self, section): + """ + Update the block list by reading the table. + """ + + if section is None: + BlockMustHaveSectionWarningMessageBox().exec() + else: + blockTableList = [] + for i in range(self.blocksw.widget().blockTableView.model.rowCount()): + blockLength = float( + self.blocksw.widget().blockTableView.model.item(i, 1).text()) + blockChord = self.csw().chordDict[(self.blocksw.widget().blockTableView.model.item( + i, 0).text() if self.blocksw.widget().blockTableView.model.item(i, 0).text() else "None")] + blockNotes = self.blocksw.widget().blockTableView.model.item(i, 2).text( + ) if self.blocksw.widget().blockTableView.model.item(i, 2).text() else None + blockTableList.append( + Block(blockLength, chord=blockChord, notes=blockNotes)) + + section.blockList = blockTableList + self.autoUpdatePreview() - def fontChangeAction(self): - if self.window.mdiArea.currentSubWindow() is not None: - qFont = self.psetup.widget().fontComboBox.currentFont() - qFontReal = QRawFont.fromFont(qFont) - #fontInfo = QFontInfo(qFont) - #qFontReal = QRawFont(fontInfo.family()) - print(qFont.rawName()) + def updateBlocksCurrentSection(self): + if self.csw(): + section = self.csw().currentSection + else: + section = None + self.updateBlocks(section) - def switchDocument(self, curWindow): - if curWindow is not None: - if self.lastSubWindow is not None: - self.lastSubWindow.currentSection = self.currentSection - - self.UIInitDocument(curWindow.doc) - self.UIInitStyle(curWindow.style) - self.currentSection = curWindow.currentSection - if self.currentSection is not None: - self.blocksw.widget().blockSectionComboBox.setCurrentText( - self.currentSection.name) - self.lastSubWindow = curWindow + def autoUpdatePreview(self): + if self.previeww.widget().autoUpdatePreviewCheckBox.isChecked(): + self.csw().updatePreview() + + def cascadeSubWindowsAction(self): + self.window.mdiArea.cascadeSubWindows() - def generateAction(self): - if self.window.mdiArea.currentSubWindow() is not None: - self.window.mdiArea.currentSubWindow().updateDocument() - self.window.mdiArea.currentSubWindow().updatePreview() + def tileSubWindowsAction(self): + self.window.mdiArea.tileSubWindows() + + def activatePreviousSubWindowAction(self): + self.window.mdiArea.activatePreviousSubWindow() + def activateNextSubWindowAction(self): + self.window.mdiArea.activateNextSubWindow() + + def generateAction(self): + # if self.csw() is not None: +# self.csw().updateDocument() + self.csw().updatePreview() + def removeChordAction(self): if self.chordsw.widget().chordTableView.selectionModel().hasSelection(): #  check for selection - self.window.mdiArea.currentSubWindow().updateChords() + self.updateChords() row = self.chordsw.widget().chordTableView.selectionModel().currentIndex().row() oldName = self.chordsw.widget().chordTableView.model.item(row, 0).text() - self.window.mdiArea.currentSubWindow().doc.chordList.pop(row) + self.csw().doc.chordList.pop(row) - self.chordsw.widget().chordTableView.populate(self.window.mdiArea.currentSubWindow().doc.chordList) + self.chordsw.widget().chordTableView.populate(self.csw().doc.chordList) # remove the chord if any of the blocks have it attached - if self.currentSection is not None: - for s in self.window.mdiArea.currentSubWindow().doc.sectionList: + if self.csw().currentSection is not None: + for s in self.csw().doc.sectionList: for b in s.blockList: if b.chord: if b.chord.name == oldName: b.chord = None - self.blocksw.widget().blockTableView.populate(self.currentSection.blockList) + self.blocksw.widget().blockTableView.populate(self.csw().currentSection.blockList) self.clearChordLineEdits() - self.updateChordDict(self.window.mdiArea.currentSubWindow().doc) - + self.csw().updateChordDict() + self.updateBlockChordComboBox() + self.autoUpdatePreview() + def addChordAction(self): success = False # initialise - self.window.mdiArea.currentSubWindow().updateChords() + self.updateChords() cName = parseName(self.chordsw.widget().chordNameLineEdit.text()) if cName: - self.window.mdiArea.currentSubWindow().doc.chordList.append(Chord(cName)) + self.csw().doc.chordList.append(Chord(cName)) if self.chordsw.widget().guitarVoicingLineEdit.text() or self.chordsw.widget().pianoVoicingLineEdit.text(): if self.chordsw.widget().guitarVoicingLineEdit.text(): try: - self.window.mdiArea.currentSubWindow().doc.chordList[-1].voicings['guitar'] = parseFingering( + self.csw().doc.chordList[-1].voicings['guitar'] = parseFingering( self.chordsw.widget().guitarVoicingLineEdit.text(), 'guitar') success = True #  chord successfully parsed except Exception: VoicingWarningMessageBox().exec() # Voicing is malformed, warn user if self.chordsw.widget().pianoVoicingLineEdit.text(): try: - self.window.mdiArea.currentSubWindow().doc.chordList[-1].voicings['piano'] = parseFingering( + self.csw().doc.chordList[-1].voicings['piano'] = parseFingering( self.chordsw.widget().pianoVoicingLineEdit.text(), 'piano') success = True #  chord successfully parsed except Exception: @@ -357,30 +620,32 @@ class MainWindow(QMainWindow): ChordNameWarningMessageBox().exec() # Chord has no name, warn user if success == True: # if chord was parsed properly - self.chordsw.widget().chordTableView.populate(self.window.mdiArea.currentSubWindow().doc.chordList) + self.chordsw.widget().chordTableView.populate(self.csw().doc.chordList) self.clearChordLineEdits() - self.updateChordDict(self.window.mdiArea.currentSubWindow().doc) + self.csw().updateChordDict() + self.updateBlockChordComboBox() + self.autoUpdatePreview() def updateChordAction(self): success = False # see comments above if self.chordsw.widget().chordTableView.selectionModel().hasSelection(): #  check for selection - self.window.mdiArea.currentSubWindow().updateChords() + self.updateChords() row = self.chordsw.widget().chordTableView.selectionModel().currentIndex().row() oldName = self.chordsw.widget().chordTableView.model.item(row, 0).text() cName = parseName(self.chordsw.widget().chordNameLineEdit.text()) if cName: - self.window.mdiArea.currentSubWindow().doc.chordList[row].name = cName + self.csw().doc.chordList[row].name = cName if self.chordsw.widget().guitarVoicingLineEdit.text() or self.chordsw.widget().pianoVoicingLineEdit.text(): if self.chordsw.widget().guitarVoicingLineEdit.text(): try: - self.window.mdiArea.currentSubWindow().doc.chordList[row].voicings['guitar'] = parseFingering( + self.csw().doc.chordList[row].voicings['guitar'] = parseFingering( self.chordsw.widget().guitarVoicingLineEdit.text(), 'guitar') success = True #  chord successfully parsed except Exception: VoicingWarningMessageBox().exec() # Voicing is malformed, warn user if self.chordsw.widget().pianoVoicingLineEdit.text(): try: - self.window.mdiArea.currentSubWindow().doc.chordList[row].voicings['piano'] = parseFingering( + self.csw().doc.chordList[row].voicings['piano'] = parseFingering( self.chordsw.widget().pianoVoicingLineEdit.text(), 'piano') success = True #  chord successfully parsed except Exception: @@ -391,68 +656,77 @@ class MainWindow(QMainWindow): ChordNameWarningMessageBox().exec() if success == True: - self.updateChordDict(self.window.mdiArea.currentSubWindow().doc) - self.chordsw.widget().chordTableView.populate(self.window.mdiArea.currentSubWindow().doc.chordList) + self.csw().updateChordDict() + self.updateBlockChordComboBox() + self.chordsw.widget().chordTableView.populate(self.csw().doc.chordList) # update the names of chords in all blocklists in case they've already been used - for s in self.window.mdiArea.currentSubWindow().doc.sectionList: + for s in self.csw().doc.sectionList: for b in s.blockList: if b.chord: if b.chord.name == oldName: b.chord.name = cName - if self.currentSection and self.currentSection.blockList: - self.blocksw.widget().blockTableView.populate(self.currentSection.blockList) + if self.csw().currentSection and self.csw().currentSection.blockList: + self.blocksw.widget().blockTableView.populate(self.csw().currentSection.blockList) self.clearChordLineEdits() + self.autoUpdatePreview() def removeSectionAction(self): if self.sectionsw.widget().sectionTableView.selectionModel().hasSelection(): #  check for selection - self.window.mdiArea.currentSubWindow().updateSections() + self.updateSections() row = self.sectionsw.widget().sectionTableView.selectionModel().currentIndex().row() - self.window.mdiArea.currentSubWindow().doc.sectionList.pop(row) + self.csw().doc.sectionList.pop(row) - self.sectionsw.widget().sectionTableView.populate(self.window.mdiArea.currentSubWindow().doc.sectionList) + self.sectionsw.widget().sectionTableView.populate(self.csw().doc.sectionList) self.clearSectionLineEdits() - self.updateSectionDict(self.window.mdiArea.currentSubWindow().doc) + self.csw().updateSectionDict() + self.updateBlockSectionComboBox() + self.autoUpdatePreview() def addSectionAction(self): - self.window.mdiArea.currentSubWindow().updateSections() + self.updateSections() sName = self.sectionsw.widget().sectionNameLineEdit.text() - if sName and sName not in [s.name for s in self.window.mdiArea.currentSubWindow().doc.sectionList]: - self.window.mdiArea.currentSubWindow().doc.sectionList.append(Section(name=sName)) - self.sectionsw.widget().sectionTableView.populate(self.window.mdiArea.currentSubWindow().doc.sectionList) + if sName and sName not in [s.name for s in self.csw().doc.sectionList]: + self.csw().doc.sectionList.append(Section(name=sName)) + self.sectionsw.widget().sectionTableView.populate(self.csw().doc.sectionList) self.clearSectionLineEdits() - self.updateSectionDict(self.window.mdiArea.currentSubWindow().doc) + self.csw().updateSectionDict() + self.updateBlockSectionComboBox() + self.autoUpdatePreview() else: # Section has no name or non unique, warn user SectionNameWarningMessageBox().exec() def updateSectionAction(self): if self.sectionsw.widget().sectionTableView.selectionModel().hasSelection(): #  check for selection - self.window.mdiArea.currentSubWindow().updateSections() + self.updateSections() row = self.sectionsw.widget().sectionTableView.selectionModel().currentIndex().row() sName = self.sectionsw.widget().sectionNameLineEdit.text() - if sName and sName not in [s.name for s in self.window.mdiArea.currentSubWindow().doc.sectionList]: - self.window.mdiArea.currentSubWindow().doc.sectionList[row].name = sName - self.sectionsw.widget().sectionTableView.populate(self.window.mdiArea.currentSubWindow().doc.sectionList) + if sName and sName not in [s.name for s in self.csw().doc.sectionList]: + self.csw().doc.sectionList[row].name = sName + self.sectionsw.widget().sectionTableView.populate(self.csw().doc.sectionList) self.clearSectionLineEdits() - self.updateSectionDict(self.window.mdiArea.currentSubWindow().doc) + self.csw().updateSectionDict() + self.updateBlockSectionComboBox() + self.autoUpdatePreview() else: # Section has no name or non unique, warn user SectionNameWarningMessageBox().exec() def removeBlockAction(self): if self.blocksw.widget().blockTableView.selectionModel().hasSelection(): #  check for selection - self.window.mdiArea.currentSubWindow().updateBlocks(self.currentSection) + self.updateBlocks(self.csw().currentSection) row = self.blocksw.widget().blockTableView.selectionModel().currentIndex().row() - self.currentSection.blockList.pop(row) + self.csw().currentSection.blockList.pop(row) - self.blocksw.widget().blockTableView.populate(self.currentSection.blockList) + self.blocksw.widget().blockTableView.populate(self.csw().currentSection.blockList) + self.autoUpdatePreview() def addBlockAction(self): - self.window.mdiArea.currentSubWindow().updateBlocks(self.currentSection) + self.updateBlocks(self.csw().currentSection) try: #  can the value entered for block length be cast as a float @@ -461,19 +735,20 @@ class MainWindow(QMainWindow): bLength = False if bLength: # create the block - self.currentSection.blockList.append(Block(bLength, - chord=self.chordDict[self.blocksw.widget().blockChordComboBox.currentText( + self.csw().currentSection.blockList.append(Block(bLength, + chord=self.csw().chordDict[self.blocksw.widget().blockChordComboBox.currentText( )], notes=(self.blocksw.widget().blockNotesLineEdit.text() if not "" else None))) - self.blocksw.widget().blockTableView.populate(self.currentSection.blockList) + self.blocksw.widget().blockTableView.populate(self.csw().currentSection.blockList) self.clearBlockLineEdits() + self.autoUpdatePreview() else: # show warning that length was not entered or in wrong format LengthWarningMessageBox().exec() def updateBlockAction(self): if self.blocksw.widget().blockTableView.selectionModel().hasSelection(): #  check for selection - self.window.mdiArea.currentSubWindow().updateBlocks(self.currentSection) + self.updateBlocks(self.csw().currentSection) try: #  can the value entered for block length be cast as a float @@ -483,29 +758,24 @@ class MainWindow(QMainWindow): row = self.blocksw.widget().blockTableView.selectionModel().currentIndex().row() if bLength: - self.currentSection.blockList[row] = (Block(bLength, - chord=self.chordDict[self.blocksw.widget().blockChordComboBox.currentText( + self.csw().currentSection.blockList[row] = (Block(bLength, + chord=self.csw().chordDict[self.blocksw.widget().blockChordComboBox.currentText( )], notes=(self.blocksw.widget().blockNotesLineEdit.text() if not "" else None))) self.blocksw.widget().blockTableView.populate( - self.currentSection.blockList) + self.csw().currentSection.blockList) self.clearBlockLineEdits() + self.autoUpdatePreview() else: LengthWarningMessageBox().exec() - - def pageSizeAction(self, index): - self.pageSizeSelected = self.psetup.widget().pageSizeComboBox.itemText(index) - - def unitAction(self, index): - self.unitSelected = self.psetup.widget().documentUnitsComboBox.itemText(index) def includedFontAction(self): - if self.window.mdiArea.currentSubWindow() is not None: - if self.psetup.widget().includedFontCheckBox.isChecked(): - self.window.mdiArea.currentSubWindow().style.useIncludedFont = True - else: - self.window.mdiArea.currentSubWindow().style.useIncludedFont = False - + # if self.csw() is not None: + if self.psetup.widget().includedFontCheckBox.isChecked(): + self.csw().style.font = 'FreeSans' + else: + self.csw().style.font = 'HelveticaNeue' + def chordClickedAction(self, index): # set the controls to the values from the selected chord self.chordsw.widget().chordNameLineEdit.setText( @@ -528,16 +798,16 @@ class MainWindow(QMainWindow): def blockSectionClickedAction(self, text): if text: - self.window.mdiArea.currentSubWindow().updateBlocks(self.sectionDict[text]) + self.updateBlocks(self.csw().sectionDict[text]) def blockSectionChangedAction(self, index): sName = self.blocksw.widget().blockSectionComboBox.currentText() if sName: - if self.window.mdiArea.currentSubWindow() is not None: - self.currentSection = self.sectionDict.get(sName, None) - # self.window.mdiArea.currentSubWindow().currentSection = self.currentSection - if self.currentSection is not None: - self.blocksw.widget().blockTableView.populate(self.currentSection.blockList) + # if self.csw() is not None: + self.csw().currentSection = self.csw().sectionDict.get(sName, None) + # self.csw().currentSection = self.currentSection + if self.csw().currentSection is not None: + self.blocksw.widget().blockTableView.populate(self.csw().currentSection.blockList) else: pass # self.currentSection = None @@ -551,69 +821,43 @@ class MainWindow(QMainWindow): self.blocksw.widget().blockNotesLineEdit.setText( self.blocksw.widget().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): dw = DocumentWindow(Document(), Style(), None) self.window.mdiArea.addSubWindow(dw) - self.UIInitDocument(dw.doc) dw.show() - def menuFileOpenAction(self): - filePath = QFileDialog.getOpenFileName(self.window, 'Open file', self.getPath( - "workingPath"), "Chordsheet Markup Language files (*.xml *.cml);;Chordsheet Macro files (*.cma)")[0] + filePath = QFileDialog.getOpenFileName(self.window, 'Open file', getPath( + "workingPath"), "Chordsheet Markup Language files (*.cml *.xml);;Chordsheet Macro files (*.cma)")[0] if filePath: dw = DocumentWindow.openFile(filePath) - self.window.mdiArea.addSubWindow(dw) - self.UIInitDocument(dw.doc) - self.UIInitStyle(dw.style) - # self.currentSection = None - - dw.show() + if dw: + self.window.mdiArea.addSubWindow(dw) + dw.show() def menuFileSaveAction(self): - if self.window.mdiArea.currentSubWindow() is not None: - self.window.mdiArea.currentSubWindow().updateDocument() - - if self.window.mdiArea.currentSubWindow().currentFilePath: - fileExt = os.path.splitext(self.window.mdiArea.currentSubWindow().currentFilePath)[1].lower() - if fileExt != ".cma": - # Chordsheet Macro files can't be saved at this time - self.window.mdiArea.currentSubWindow().saveFile(self.window.mdiArea.currentSubWindow().currentFilePath) + pathRequired = False + + if self.csw().currentFilePath: + fileExt = os.path.splitext(self.csw().currentFilePath)[1].lower() + if fileExt != ".cma": + # Chordsheet Macro files can't be saved at this time + self.csw().saveFile(self.csw().currentFilePath) else: - filePath = QFileDialog.getSaveFileName(self.window, 'Save file', self.getPath( - "workingPath"), "Chordsheet ML files (*.xml *.cml)")[0] - if filePath: - self.window.mdiArea.currentSubWindow().saveFile(filePath) + pathRequired = True + else: + pathRequired = True + + if pathRequired: + self.csw().saveFileChoosePath() def menuFileSaveAsAction(self): - if self.window.mdiArea.currentSubWindow() is not None: - self.window.mdiArea.currentSubWindow().updateDocument() - filePath = QFileDialog.getSaveFileName(self.window, 'Save file', self.getPath( - "workingPath"), "Chordsheet ML files (*.xml *.cml)")[0] - if filePath: - self.window.mdiArea.currentSubWindow().saveFile(filePath) + self.csw().saveFileChoosePath() def menuFileSavePDFAction(self): - if self.window.mdiArea.currentSubWindow() is not None: - self.window.mdiArea.currentSubWindow().updateDocument() - self.window.mdiArea.currentSubWindow().updatePreview() - filePath = QFileDialog.getSaveFileName(self.window, 'Save file', self.getPath( - "lastExportPath"), "PDF files (*.pdf)")[0] - if filePath: - self.window.mdiArea.currentSubWindow().renderer.savePDF(filePath) - self.window.mdiArea.currentSubWindow().setPath("lastExportPath", filePath) + filePath = self.csw().savePDFFileChoosePath() + if filePath: + setPath("lastExportPath", filePath) def menuFilePrintAction(self): if sys.platform == "darwin": @@ -622,11 +866,13 @@ class MainWindow(QMainWindow): else: pass - @pyqtSlot() def menuFileCloseAction(self): - self.saveWarning() + self.csw().close() + + def menuFileQuitAction(self): + self.close() - def menuFileAboutAction(self): + def menuHelpAboutAction(self): AboutDialog() def menuEditUndoAction(self): @@ -660,57 +906,18 @@ class MainWindow(QMainWindow): pass - # self.docinfo = DocInfoDockWidget() - # self.psetup = PageSetupDockWidget() - # self.chordsw = ChordsDockWidget() - # self.sectionsw = SectionsDockWidget() - # self.blocksw = BlocksDockWidget() - # self.previeww = PreviewDockWidget() - - def menuWindowDocInfoAction(self): - if self.window.actionDocument_information.isChecked(): - self.docinfo.show() - else: - self.docinfo.hide() - - def menuWindowPageSetupAction(self): - if self.window.actionPage_setup.isChecked(): - self.psetup.show() - else: - self.psetup.hide() - - def menuWindowChordsAction(self): - if self.window.actionChords.isChecked(): - self.chordsw.show() - else: - self.chordsw.hide() - - - def menuWindowSectionsAction(self): - if self.window.actionSections.isChecked(): - self.sectionsw.show() - else: - self.sectionsw.hide() - - - def menuWindowBlocksAction(self): - if self.window.actionBlocks.isChecked(): - self.blocksw.show() - else: - self.blocksw.hide() - + def guitarVoicingAction(self): + gdialog = GuitarDialog() - def menuWindowPreviewAction(self): - if self.window.actionPreview.isChecked(): - self.previeww.show() + if self.chordsw.widget().guitarVoicingLineEdit.text(): + existingVoicing = parseFingering( + self.chordsw.widget().guitarVoicingLineEdit.text(), 'guitar' + ) else: - self.previeww.hide() + existingVoicing = None + voicing = gdialog.getVoicing(existingVoicing) - def guitarVoicingAction(self): - gdialog = GuitarDialog() - - voicing = gdialog.getVoicing() if voicing: self.chordsw.widget().guitarVoicingLineEdit.setText(voicing) @@ -738,28 +945,27 @@ class MainWindow(QMainWindow): - - - - class DocumentWindow(QMdiSubWindow): + def __init__(self, doc, style, filename): super().__init__() - + self.UIFileLoader(str(os.path.join(scriptDir, 'ui', 'document.ui'))) - + # Set to delete on close + self.setAttribute(Qt.WA_DeleteOnClose) + self.doc = doc self.style = style self.renderer = Renderer(self.doc, self.style) - self.lastDoc = copy(self.doc) + self.lastHash = self.doc.getHash() self.currentFilePath = filename - + self.currentSection = None - - mw.updateChordDict(self.doc) - mw.updateSectionDict(self.doc) - + + self.updateChordDict() + self.updateSectionDict() + def UIFileLoader(self, ui_file): ui_file = QFile(ui_file) ui_file.open(QFile.ReadOnly) @@ -767,77 +973,104 @@ class DocumentWindow(QMdiSubWindow): self.setWidget(uic.loadUi(ui_file)) ui_file.close() + def closeEvent(self, event): + if self.saveWarning(): + event.accept() + else: + event.ignore() + @classmethod def openFile(cls, filePath): - dw = cls(Document(), Style(), None) - dw.loadFile(filePath) - return dw - - def loadFile(self, filePath): """ Opens a file from a file path and sets up the window accordingly. """ - self.currentFilePath = filePath - - fileExt = os.path.splitext(self.currentFilePath)[1].lower() - - if fileExt == ".cma": - self.doc.loadCSMacro(self.currentFilePath) - else: # if fileExt in [".xml", ".cml"]: - self.doc.loadXML(self.currentFilePath) - - self.updatePreview() + dw = cls(Document(), Style(), None) + dw.currentFilePath = filePath + + fileExt = os.path.splitext(dw.currentFilePath)[1].lower() + + try: + if fileExt == ".cma": + dw.doc.loadCSMacro(dw.currentFilePath) + else: # if fileExt in [".xml", ".cml"]: + dw.doc.loadXML(dw.currentFilePath) + except (ParseError, ValueError): + UnreadableMessageBox(os.path.basename(dw.currentFilePath)).exec() + dw.close() + return None + else: + dw.updatePreview() + + setPath("workingPath", dw.currentFilePath) + + dw.updateChordDict() + dw.updateSectionDict() + + dw.currentSection = (dw.doc.sectionList[0] if dw.doc.sectionList else None) + + dw.updateTitleBar() - self.lastDoc = copy(self.doc) - mw.setPath("workingPath", self.currentFilePath) - - mw.updateChordDict(self.doc) - mw.updateSectionDict(self.doc) - - self.currentSection = (self.doc.sectionList[0] if self.doc.sectionList else None) - - self.updateTitleBar() - + dw.lastHash = dw.doc.getHash() + return dw + def saveFile(self, filePath): """ Saves a file to given file path and sets up environment. """ self.currentFilePath = filePath - + fileExt = os.path.splitext(self.currentFilePath)[1].lower() - + if fileExt == ".cma": # At this stage we should never get here pass else: # if fileExt in [".xml", ".cml"]: - self.doc.saveXML(self.currentFilePath) - - self.lastDoc = copy(self.doc) - mw.setPath("workingPath", self.currentFilePath) + self.lastHash = self.doc.saveXML(self.currentFilePath) + + setPath("workingPath", self.currentFilePath) self.updateTitleBar() # as we may have a new filename + def saveFileChoosePath(self): + filePath, selectedFilter = QFileDialog.getSaveFileName(self.parent(), 'Save file', getPath( + "workingPath"), "Chordsheet ML files (*.cml *.xml)", ".cml") + if filePath: + if filePath.split(".")[-1] not in ["cml", "xml"]: + filePath += ".cml" + self.saveFile(filePath) + return filePath + else: + return None + + def savePDFFileChoosePath(self): + filePath, selectedFilter = QFileDialog.getSaveFileName(self.parent(), 'Save file', getPath( + "lastExportPath"), "PDF files (*.pdf)") + if filePath: + if filePath.split(".")[-1] != "pdf": + filePath += ".pdf" + self.renderer.savePDF(filePath) + return filePath + else: + return None + 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: + if self.lastHash == self.doc.getHash(): return True else: - wantToSave = UnsavedMessageBox().exec() + wantToSave = UnsavedMessageBox(os.path.basename(self.currentFilePath)).exec() if wantToSave == QMessageBox.Save: if not self.currentFilePath: - filePath = QFileDialog.getSaveFileName(self.window, 'Save file', str( - os.path.expanduser("~")), "Chordsheet ML files (*.xml *.cml)") - self.currentFilePath = filePath[0] - self.doc.saveXML(self.currentFilePath) + self.saveFileChoosePath() + else: + self.saveFile(self.currentFilePath) return True elif wantToSave == QMessageBox.Discard: return True - + else: return False @@ -850,42 +1083,21 @@ class DocumentWindow(QMdiSubWindow): except Exception: QMessageBox.warning(self, "Preview failed", "Could not update the preview.", buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok) - - - with open('preview.pdf', 'wb') as f: - f.write(self.currentPreview.getbuffer()) self.widget().pdfArea.update_pdf(self.currentPreview) def updateTitleBar(self): """ - Update the application's title bar to reflect the current document. + Update the window's title bar to reflect the current document. """ if self.currentFilePath: self.widget().setWindowTitle(os.path.basename(self.currentFilePath)) else: self.widget().setWindowTitle("Unsaved") - def updateChords(self): - """ - Update the chord list by reading the table. - """ - chordTableList = [] - for i in range(mw.chordsw.widget().chordTableView.model.rowCount()): - chordTableList.append( - Chord(parseName(mw.chordsw.widget().chordTableView.model.item(i, 0).text()))), - if mw.chordsw.widget().chordTableView.model.item(i, 1).text(): - chordTableList[-1].voicings['guitar'] = parseFingering( - mw.chordsw.widget().chordTableView.model.item(i, 1).text(), 'guitar') - if mw.chordsw.widget().chordTableView.model.item(i, 2).text(): - chordTableList[-1].voicings['piano'] = parseFingering( - mw.chordsw.widget().chordTableView.model.item(i, 2).text(), 'piano') - - self.doc.chordList = chordTableList - def matchSection(self, nameToMatch): """ - Given the name of a section, this function checks if it is already present in the document. + Given the name of a section, this function checks if it is already present in the document. If it is, it's returned. If not, a new section with the given name is returned. """ section = None @@ -897,86 +1109,18 @@ class DocumentWindow(QMdiSubWindow): section = Section(name=nameToMatch) return section - def updateSections(self): + def updateChordDict(self): """ - Update the section list by reading the table + Updates the dictionary used to generate the Chord menu (on the block tab) """ - sectionTableList = [] - for i in range(mw.sectionsw.widget().sectionTableView.model.rowCount()): - sectionTableList.append(self.matchSection( - mw.sectionsw.widget().sectionTableView.model.item(i, 0).text())) - - self.doc.sectionList = sectionTableList + self.chordDict = {'None': None} + self.chordDict.update({c.name: c for c in self.doc.chordList}) - def updateBlocks(self, section): + def updateSectionDict(self): """ - Update the block list by reading the table. + Updates the dictionary used to generate the Section menu (on the block tab) """ - if section is None: - BlockMustHaveSectionWarningMessageBox().exec() - else: - blockTableList = [] - for i in range(mw.blocksw.widget().blockTableView.model.rowCount()): - blockLength = float( - mw.blocksw.widget().blockTableView.model.item(i, 1).text()) - blockChord = mw.chordDict[(mw.blocksw.widget().blockTableView.model.item( - i, 0).text() if mw.blocksw.widget().blockTableView.model.item(i, 0).text() else "None")] - blockNotes = mw.blocksw.widget().blockTableView.model.item(i, 2).text( - ) if mw.blocksw.widget().blockTableView.model.item(i, 2).text() else None - blockTableList.append( - Block(blockLength, chord=blockChord, notes=blockNotes)) - - section.blockList = blockTableList - def updateDocument(self): - """ - Update the Document object by reading values from the UI. - """ - self.doc.title = mw.docinfo.widget().titleLineEdit.text( - ) # Title can be empty string but not None - self.doc.subtitle = (mw.docinfo.widget().subtitleLineEdit.text( - ) if mw.docinfo.widget().subtitleLineEdit.text() else None) - self.doc.composer = (mw.docinfo.widget().composerLineEdit.text( - ) if mw.docinfo.widget().composerLineEdit.text() else None) - self.doc.arranger = (mw.docinfo.widget().arrangerLineEdit.text( - ) if mw.docinfo.widget().arrangerLineEdit.text() else None) - self.doc.tempo = (mw.docinfo.widget().tempoLineEdit.text( - ) if mw.docinfo.widget().tempoLineEdit.text() else None) - self.doc.timeSignature = int(mw.docinfo.widget().timeSignatureSpinBox.value( - )) if mw.docinfo.widget().timeSignatureSpinBox.value() else self.doc.timeSignature - - self.style.pageSize = pageSizeDict[mw.pageSizeSelected] - self.style.unit = unitDict[mw.unitSelected] - self.style.leftMargin = float(mw.psetup.widget().leftMarginLineEdit.text( - )) if mw.psetup.widget().leftMarginLineEdit.text() else self.style.leftMargin - self.style.rightMargin = float(mw.psetup.widget().rightMarginLineEdit.text( - )) if mw.psetup.widget().rightMarginLineEdit.text() else self.style.rightMargin - self.style.topMargin = float(mw.psetup.widget().topMarginLineEdit.text( - )) if mw.psetup.widget().topMarginLineEdit.text() else self.style.topMargin - self.style.bottomMargin = float(mw.psetup.widget().bottomMarginLineEdit.text( - )) if mw.psetup.widget().bottomMarginLineEdit.text() else self.style.bottomMargin - self.style.lineSpacing = float(mw.psetup.widget().lineSpacingDoubleSpinBox.value( - )) if mw.psetup.widget().lineSpacingDoubleSpinBox.value() else self.style.lineSpacing - - # make sure the unit width isn't too wide to draw! - if mw.psetup.widget().beatWidthLineEdit.text(): - if (self.style.pageSize[0] - 2 * self.style.leftMargin * mm) >= (float(mw.psetup.widget().beatWidthLineEdit.text()) * 2 * self.doc.timeSignature * mm): - self.style.unitWidth = float( - mw.psetup.widget().beatWidthLineEdit.text()) - else: - maxBeatWidth = ( - self.style.pageSize[0] - 2 * self.style.leftMargin * mm) / (2 * self.doc.timeSignature * mm) - QMessageBox.warning(self, "Out of range", "Beat width is out of range. It can be a maximum of {}.".format( - maxBeatWidth), buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok) - - # update chords, sections, blocks - self.updateChords() - self.updateSections() - if mw.currentSection: - self.updateBlocks(mw.currentSection) - - self.style.font = ( - 'FreeSans' if self.style.useIncludedFont else 'HelveticaNeue') - # something for the font box here + self.sectionDict = {s.name: s for s in self.doc.sectionList} if __name__ == '__main__': app = QApplication(sys.argv) diff --git a/preview.pdf b/preview.pdf deleted file mode 100644 index e946d38..0000000 Binary files a/preview.pdf and /dev/null differ diff --git a/ui/aboutdialog.ui b/ui/aboutdialog.ui index 8b57c97..14280ee 100644 --- a/ui/aboutdialog.ui +++ b/ui/aboutdialog.ui @@ -14,7 +14,7 @@ - + 0 0 @@ -25,10 +25,25 @@ 200 + + + 436 + 200 + + About Chordsheet + + false + + + true + + + QLayout::SetFixedSize + diff --git a/ui/blocks.ui b/ui/blocks.ui index 5040468..2eb5bf0 100644 --- a/ui/blocks.ui +++ b/ui/blocks.ui @@ -270,12 +270,12 @@ BlockTableView QTableView -
chordsheet/tableView.h
+
csgui/tableView.h
MComboBox QComboBox -
chordsheet/comboBox.h
+
csgui/comboBox.h
diff --git a/ui/chords.ui b/ui/chords.ui index 40a4f67..e8ad895 100644 --- a/ui/chords.ui +++ b/ui/chords.ui @@ -220,7 +220,7 @@ ChordTableView QTableView -
chordsheet/tableView.h
+
csgui/tableView.h
diff --git a/ui/document.ui b/ui/document.ui index 3577480..aa177b1 100644 --- a/ui/document.ui +++ b/ui/document.ui @@ -36,7 +36,7 @@ - 400 + 200 300 @@ -48,7 +48,7 @@ PDFViewer QWidget -
chordsheet/pdfViewer.h
+
csgui/pdfViewer.h
1
diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 34c8f20..df9365b 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 1061 - 659 + 639 @@ -20,960 +20,33 @@ QTabWidget::Rounded - - - - - QFrame::NoFrame + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true - - Qt::Horizontal + + true + + + true + + + true - - - true - - - - 0 - 0 - - - - - 430 - 600 - - - - - 500 - 16777215 - - - - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - - 500 - 16777215 - - - - 4 - - - - Overview - - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Title - - - - - - - - 0 - 0 - - - - - - - - Subtitle - - - - - - - - - - Composer - - - - - - - - 0 - 0 - - - - - - - - Arranger - - - - - - - - 0 - 0 - - - - - - - - Tempo - - - - - - - - 0 - 0 - - - - - 60 - 16777215 - - - - - - - - Time - - - - - - - - 40 - 16777215 - - - - - - - 4 - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Page - - - - - - - 0 - 0 - - - - Page options - - - - - - - - Page size - - - - - - - - - - Document units - - - - - - - - - - Left margin - - - - - - - - 60 - 16777215 - - - - - - - - Top margin - - - - - - - - 60 - 16777215 - - - - - - - - Right margin - - - - - - - - 60 - 16777215 - - - - - - - - Bottom margin - - - - - - - - 60 - 16777215 - - - - - - - - - - - - - Text options - - - - - - - - Line spacing - - - - - - - - 70 - 0 - - - - - 70 - 16777215 - - - - - - - - - - - - - - 0 - 0 - - - - Font options - - - - - - - - - 40 - 16777215 - - - - Font - - - - - - - - 0 - 0 - - - - - - - - - - Use included FreeSans - - - - - - - - - - Block options - - - - - - - - Beat width - - - - - - - - 60 - 16777215 - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Chords - - - - - - - - - 0 - 0 - - - - QAbstractItemView::NoEditTriggers - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::IgnoreAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - false - - - false - - - - - - - - - Chord name - - - - - - - - 0 - 0 - - - - - - - - Guitar voicing - - - - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - - - - - - 16777215 - 16777215 - - - - Editor... - - - - - - - - - - Piano voicing - - - - - - - - - - - Remove chord - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - Update chord - - - - - - - Add chord - - - - - - - - - - - - Sections - - - - - - - - - 0 - 0 - - - - QAbstractItemView::NoEditTriggers - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::TargetMoveAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Name - - - - - - - - - - - - - - Remove section - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 0 - 20 - - - - - - - - Update section - - - - - - - Add section - - - - - - - - - - - - Blocks - - - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Section - - - - - - - - 0 - 0 - - - - - - - - - - - 0 - 0 - - - - QAbstractItemView::NoEditTriggers - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::TargetMoveAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - Length - - - - - - - - - - - 0 - 0 - - - - Notes - - - - - - - Chord - - - - - - - - 0 - 0 - - - - - 40 - 16777215 - - - - - - - - - 0 - 0 - - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - - - - - Remove block - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - Update block - - - - - - - Add block - - - - - - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - Generate chordsheet - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - - - - - - - 1 - 0 - - - - - 300 - 400 - - - - true - - @@ -1001,6 +74,8 @@ + + @@ -1013,10 +88,30 @@ + + + + Window + + + + + + + + + + + + + Help + + + @@ -1093,76 +188,32 @@ About + + + Cascade windows + + + + + Tile windows + + + + + Next window + + + + + Previous window + + + + + Redock all panels + + - - - ChordTableView - QTableView -
chordsheet/tableView.h
-
- - BlockTableView - QTableView -
chordsheet/tableView.h
-
- - SectionTableView - QTableView -
chordsheet/tableView.h
-
- - MComboBox - QComboBox -
chordsheet/comboBox.h
-
- - PDFViewer - QWidget -
chordsheet/pdfViewer.h
- 1 -
-
- - generateButton - tabWidget - titleLineEdit - subtitleLineEdit - composerLineEdit - arrangerLineEdit - tempoLineEdit - timeSignatureSpinBox - pageSizeComboBox - documentUnitsComboBox - leftMarginLineEdit - rightMarginLineEdit - topMarginLineEdit - bottomMarginLineEdit - lineSpacingDoubleSpinBox - fontComboBox - includedFontCheckBox - beatWidthLineEdit - chordTableView - chordNameLineEdit - guitarVoicingLineEdit - pianoVoicingLineEdit - guitarVoicingButton - removeChordButton - updateChordButton - addChordButton - sectionTableView - sectionNameLineEdit - removeSectionButton - updateSectionButton - addSectionButton - blockSectionComboBox - blockTableView - blockLengthLineEdit - blockChordComboBox - blockNotesLineEdit - removeBlockButton - updateBlockButton - addBlockButton - diff --git a/ui/new.ui b/ui/new.ui deleted file mode 100644 index eb82610..0000000 --- a/ui/new.ui +++ /dev/null @@ -1,233 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 1061 - 639 - - - - Chordsheet - - - false - - - QTabWidget::Rounded - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - true - - - true - - - true - - - - - - - - - 0 - 0 - 1061 - 22 - - - - - File - - - - - - - - - - - - - - - Edit - - - - - - - - - - - - - Window - - - - - - - - - - - - - - - - New... - - - - - Open... - - - - - Save - - - - - Save PDF... - - - - - Print... - - - - - Close - - - - - Save as... - - - - - Quit - - - - - Undo - - - - - Redo - - - - - Cut - - - - - Copy - - - - - Paste - - - - - Preferences - - - - - About - - - - - true - - - Document information - - - - - true - - - Page setup - - - - - true - - - Chords - - - - - true - - - Sections - - - - - true - - - Blocks - - - - - true - - - Preview - - - - - - diff --git a/ui/pdfarea.ui b/ui/pdfarea.ui deleted file mode 100644 index ce109c2..0000000 --- a/ui/pdfarea.ui +++ /dev/null @@ -1,32 +0,0 @@ - - - Form - - - - 0 - 0 - 400 - 300 - - - - PDF Viewer - - - - - - - - - - PDFViewer - QWidget -
chordsheet/pdfViewer.h
- 1 -
-
- - -
diff --git a/ui/preview.ui b/ui/preview.ui index 712ac9d..beef3bf 100644 --- a/ui/preview.ui +++ b/ui/preview.ui @@ -6,8 +6,8 @@ 0 0 - 248 - 40 + 202 + 72 @@ -18,31 +18,50 @@ - 0 - 40 + 202 + 72 Preview - + 6 - 0 + 6 - 0 + 6 - 0 + 6 - 0 + 6 - + + + + + 0 + 0 + + + + Automatically update preview + + + true + + + + + + false + 0 @@ -52,6 +71,9 @@ Update preview + + false + diff --git a/ui/sections.ui b/ui/sections.ui index 80d35d2..b04ddbb 100644 --- a/ui/sections.ui +++ b/ui/sections.ui @@ -157,7 +157,7 @@ SectionTableView QTableView -
chordsheet/tableView.h
+
csgui/tableView.h