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.
1137 lines
48 KiB
1137 lines
48 KiB
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Created on Wed May 29 00:02:24 2019
|
|
|
|
@author: ivan
|
|
"""
|
|
|
|
import sys
|
|
import fitz
|
|
import io
|
|
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, pyqtSignal, QSettings
|
|
from PyQt5.QtGui import QPixmap, QImage, QKeySequence, QFontInfo, QFont, QRawFont
|
|
from PyQt5 import uic
|
|
|
|
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
|
|
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
|
|
|
|
# enable automatic high DPI scaling on Windows
|
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|
QApplication.setOrganizationName("Ivan Holmes")
|
|
QApplication.setOrganizationDomain("ivanholmes.co.uk")
|
|
QApplication.setApplicationName("Chordsheet")
|
|
settings = QSettings()
|
|
|
|
pdfmetrics.registerFont(
|
|
TTFont('FreeSans', os.path.join(scriptDir, 'fonts', 'FreeSans.ttf')))
|
|
if sys.platform == "darwin":
|
|
pdfmetrics.registerFont(
|
|
TTFont('HelveticaNeue', 'HelveticaNeue.ttc', subfontIndex=0))
|
|
|
|
# dictionaries for combo boxes
|
|
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):
|
|
"""
|
|
Class for the main window of the application.
|
|
"""
|
|
|
|
def __init__(self, filename=None):
|
|
"""
|
|
Initialisation function for the main window of the application.
|
|
|
|
Arguments:
|
|
doc -- the Document object for the window to use
|
|
style -- the Style object for the window to use
|
|
"""
|
|
super().__init__()
|
|
|
|
self.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.window.mdiArea.setActivationOrder(self.window.mdiArea.ActivationHistoryOrder)
|
|
|
|
if filename:
|
|
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():
|
|
event.accept()
|
|
else:
|
|
for subWindow in self.window.mdiArea.subWindowList():
|
|
if not subWindow.close():
|
|
event.ignore()
|
|
return
|
|
event.accept()
|
|
|
|
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)
|
|
|
|
self.previeww.hide()
|
|
|
|
# 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.psetup.widget().pageSizeComboBox.addItems(list(pageSizeDict.keys()))
|
|
self.psetup.widget().pageSizeComboBox.setCurrentText(
|
|
list(pageSizeDict.keys())[0])
|
|
|
|
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
|
|
# self.window.tabWidget.tabBarClicked.connect(self.tabBarUpdateAction)
|
|
|
|
self.chordsw.widget().guitarVoicingButton.clicked.connect(
|
|
self.guitarVoicingAction)
|
|
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(
|
|
self.blockSectionClickedAction)
|
|
self.blocksw.widget().blockSectionComboBox.currentIndexChanged.connect(
|
|
self.blockSectionChangedAction)
|
|
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 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.
|
|
"""
|
|
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)
|
|
self.docinfo.widget().composerLineEdit.setText(doc.composer)
|
|
self.docinfo.widget().arrangerLineEdit.setText(doc.arranger)
|
|
self.docinfo.widget().timeSignatureSpinBox.setValue(doc.timeSignature)
|
|
self.docinfo.widget().tempoLineEdit.setText(doc.tempo)
|
|
|
|
self.chordsw.widget().chordTableView.populate(doc.chordList)
|
|
self.sectionsw.widget().sectionTableView.populate(doc.sectionList)
|
|
|
|
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.csw().currentSection is None:
|
|
self.csw().currentSection = doc.sectionList[0] if doc.sectionList else None
|
|
else:
|
|
self.blocksw.widget().blockSectionComboBox.setCurrentText(
|
|
self.csw().currentSection.name)
|
|
self.blocksw.widget().blockTableView.populate(
|
|
self.csw().currentSection.blockList if self.csw().currentSection else [])
|
|
|
|
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])
|
|
|
|
self.psetup.widget().documentUnitsComboBox.setCurrentText(
|
|
[k for k, v in unitDict.items() if v==style.unit][0])
|
|
|
|
self.psetup.widget().lineSpacingDoubleSpinBox.setValue(style.lineSpacing)
|
|
|
|
self.psetup.widget().leftMarginLineEdit.setText(str(style.leftMargin))
|
|
self.psetup.widget().rightMarginLineEdit.setText(str(style.rightMargin))
|
|
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().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 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):
|
|
"""
|
|
Update the chord list by reading the table.
|
|
"""
|
|
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')
|
|
|
|
if self.csw():
|
|
self.csw().doc.chordList = chordTableList
|
|
self.autoUpdatePreview()
|
|
|
|
def updateSections(self):
|
|
"""
|
|
Update the section list by reading the table
|
|
"""
|
|
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 updateBlocksCurrentSection(self):
|
|
if self.csw():
|
|
section = self.csw().currentSection
|
|
else:
|
|
section = None
|
|
|
|
self.updateBlocks(section)
|
|
|
|
def autoUpdatePreview(self):
|
|
if self.previeww.widget().autoUpdatePreviewCheckBox.isChecked():
|
|
self.csw().updatePreview()
|
|
|
|
def cascadeSubWindowsAction(self):
|
|
self.window.mdiArea.cascadeSubWindows()
|
|
|
|
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.updateChords()
|
|
|
|
row = self.chordsw.widget().chordTableView.selectionModel().currentIndex().row()
|
|
oldName = self.chordsw.widget().chordTableView.model.item(row, 0).text()
|
|
self.csw().doc.chordList.pop(row)
|
|
|
|
self.chordsw.widget().chordTableView.populate(self.csw().doc.chordList)
|
|
# remove the chord if any of the blocks have it attached
|
|
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.csw().currentSection.blockList)
|
|
self.clearChordLineEdits()
|
|
self.csw().updateChordDict()
|
|
self.updateBlockChordComboBox()
|
|
self.autoUpdatePreview()
|
|
|
|
def addChordAction(self):
|
|
success = False # initialise
|
|
self.updateChords()
|
|
|
|
cName = parseName(self.chordsw.widget().chordNameLineEdit.text())
|
|
if 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.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.csw().doc.chordList[-1].voicings['piano'] = parseFingering(
|
|
self.chordsw.widget().pianoVoicingLineEdit.text(), 'piano')
|
|
success = True # chord successfully parsed
|
|
except Exception:
|
|
VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
|
|
else:
|
|
success = True # chord successfully parsed
|
|
else:
|
|
ChordNameWarningMessageBox().exec() # Chord has no name, warn user
|
|
|
|
if success == True: # if chord was parsed properly
|
|
self.chordsw.widget().chordTableView.populate(self.csw().doc.chordList)
|
|
self.clearChordLineEdits()
|
|
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.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.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.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.csw().doc.chordList[row].voicings['piano'] = parseFingering(
|
|
self.chordsw.widget().pianoVoicingLineEdit.text(), 'piano')
|
|
success = True # chord successfully parsed
|
|
except Exception:
|
|
VoicingWarningMessageBox().exec() # Voicing is malformed, warn user
|
|
else:
|
|
success = True # chord successfully parsed
|
|
else:
|
|
ChordNameWarningMessageBox().exec()
|
|
|
|
if success == True:
|
|
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.csw().doc.sectionList:
|
|
for b in s.blockList:
|
|
if b.chord:
|
|
if b.chord.name == oldName:
|
|
b.chord.name = cName
|
|
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.updateSections()
|
|
|
|
row = self.sectionsw.widget().sectionTableView.selectionModel().currentIndex().row()
|
|
self.csw().doc.sectionList.pop(row)
|
|
|
|
self.sectionsw.widget().sectionTableView.populate(self.csw().doc.sectionList)
|
|
self.clearSectionLineEdits()
|
|
self.csw().updateSectionDict()
|
|
self.updateBlockSectionComboBox()
|
|
self.autoUpdatePreview()
|
|
|
|
def addSectionAction(self):
|
|
self.updateSections()
|
|
|
|
sName = self.sectionsw.widget().sectionNameLineEdit.text()
|
|
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.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.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.csw().doc.sectionList]:
|
|
self.csw().doc.sectionList[row].name = sName
|
|
self.sectionsw.widget().sectionTableView.populate(self.csw().doc.sectionList)
|
|
self.clearSectionLineEdits()
|
|
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.updateBlocks(self.csw().currentSection)
|
|
|
|
row = self.blocksw.widget().blockTableView.selectionModel().currentIndex().row()
|
|
self.csw().currentSection.blockList.pop(row)
|
|
|
|
self.blocksw.widget().blockTableView.populate(self.csw().currentSection.blockList)
|
|
self.autoUpdatePreview()
|
|
|
|
def addBlockAction(self):
|
|
self.updateBlocks(self.csw().currentSection)
|
|
|
|
try:
|
|
# can the value entered for block length be cast as a float
|
|
bLength = float(self.blocksw.widget().blockLengthLineEdit.text())
|
|
except Exception:
|
|
bLength = False
|
|
|
|
if bLength: # create the block
|
|
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.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.updateBlocks(self.csw().currentSection)
|
|
|
|
try:
|
|
# can the value entered for block length be cast as a float
|
|
bLength = float(self.blocksw.widget().blockLengthLineEdit.text())
|
|
except Exception:
|
|
bLength = False
|
|
|
|
row = self.blocksw.widget().blockTableView.selectionModel().currentIndex().row()
|
|
if bLength:
|
|
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.csw().currentSection.blockList)
|
|
self.clearBlockLineEdits()
|
|
self.autoUpdatePreview()
|
|
else:
|
|
LengthWarningMessageBox().exec()
|
|
|
|
def includedFontAction(self):
|
|
# 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(
|
|
self.chordsw.widget().chordTableView.model.item(index.row(), 0).text())
|
|
self.chordsw.widget().guitarVoicingLineEdit.setText(
|
|
self.chordsw.widget().chordTableView.model.item(index.row(), 1).text())
|
|
self.chordsw.widget().pianoVoicingLineEdit.setText(
|
|
self.chordsw.widget().chordTableView.model.item(index.row(), 2).text())
|
|
|
|
def sectionClickedAction(self, index):
|
|
# set the controls to the values from the selected section
|
|
self.sectionsw.widget().sectionNameLineEdit.setText(
|
|
self.sectionsw.widget().sectionTableView.model.item(index.row(), 0).text())
|
|
# also set the combo box on the block page to make it flow well
|
|
curSecName = self.sectionsw.widget().sectionTableView.model.item(
|
|
index.row(), 0).text()
|
|
if curSecName:
|
|
self.blocksw.widget().blockSectionComboBox.setCurrentText(
|
|
curSecName)
|
|
|
|
def blockSectionClickedAction(self, text):
|
|
if text:
|
|
self.updateBlocks(self.csw().sectionDict[text])
|
|
|
|
def blockSectionChangedAction(self, index):
|
|
sName = self.blocksw.widget().blockSectionComboBox.currentText()
|
|
if sName:
|
|
# 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
|
|
|
|
def blockClickedAction(self, index):
|
|
# set the controls to the values from the selected block
|
|
bChord = self.blocksw.widget().blockTableView.model.item(index.row(), 0).text()
|
|
self.blocksw.widget().blockChordComboBox.setCurrentText(
|
|
bChord if bChord else "None")
|
|
self.blocksw.widget().blockLengthLineEdit.setText(
|
|
self.blocksw.widget().blockTableView.model.item(index.row(), 1).text())
|
|
self.blocksw.widget().blockNotesLineEdit.setText(
|
|
self.blocksw.widget().blockTableView.model.item(index.row(), 2).text())
|
|
|
|
def menuFileNewAction(self):
|
|
dw = DocumentWindow(Document(), Style(), None)
|
|
self.window.mdiArea.addSubWindow(dw)
|
|
dw.show()
|
|
|
|
def menuFileOpenAction(self):
|
|
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)
|
|
if dw:
|
|
self.window.mdiArea.addSubWindow(dw)
|
|
dw.show()
|
|
|
|
def menuFileSaveAction(self):
|
|
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:
|
|
pathRequired = True
|
|
else:
|
|
pathRequired = True
|
|
|
|
if pathRequired:
|
|
self.csw().saveFileChoosePath()
|
|
|
|
def menuFileSaveAsAction(self):
|
|
self.csw().saveFileChoosePath()
|
|
|
|
def menuFileSavePDFAction(self):
|
|
filePath = self.csw().savePDFFileChoosePath()
|
|
if filePath:
|
|
setPath("lastExportPath", filePath)
|
|
|
|
def menuFilePrintAction(self):
|
|
if sys.platform == "darwin":
|
|
pass
|
|
# subprocess.call()
|
|
else:
|
|
pass
|
|
|
|
def menuFileCloseAction(self):
|
|
self.csw().close()
|
|
|
|
def menuFileQuitAction(self):
|
|
self.close()
|
|
|
|
def menuHelpAboutAction(self):
|
|
AboutDialog()
|
|
|
|
def menuEditUndoAction(self):
|
|
try:
|
|
QApplication.focusWidget().undo() # see if the built in widget supports it
|
|
except Exception:
|
|
pass # if not just fail silently
|
|
|
|
def menuEditRedoAction(self):
|
|
try:
|
|
QApplication.focusWidget().redo()
|
|
except Exception:
|
|
pass
|
|
|
|
def menuEditCutAction(self):
|
|
try:
|
|
QApplication.focusWidget().cut()
|
|
except Exception:
|
|
pass
|
|
|
|
def menuEditCopyAction(self):
|
|
try:
|
|
QApplication.focusWidget().copy()
|
|
except Exception:
|
|
pass
|
|
|
|
def menuEditPasteAction(self):
|
|
try:
|
|
QApplication.focusWidget().paste()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def guitarVoicingAction(self):
|
|
gdialog = GuitarDialog()
|
|
|
|
if self.chordsw.widget().guitarVoicingLineEdit.text():
|
|
existingVoicing = parseFingering(
|
|
self.chordsw.widget().guitarVoicingLineEdit.text(), 'guitar'
|
|
)
|
|
else:
|
|
existingVoicing = None
|
|
|
|
voicing = gdialog.getVoicing(existingVoicing)
|
|
|
|
if voicing:
|
|
self.chordsw.widget().guitarVoicingLineEdit.setText(voicing)
|
|
|
|
def clearChordLineEdits(self):
|
|
self.chordsw.widget().chordNameLineEdit.clear()
|
|
self.chordsw.widget().guitarVoicingLineEdit.clear()
|
|
self.chordsw.widget().pianoVoicingLineEdit.clear()
|
|
# necessary on Mojave with PyInstaller (or previous contents will be shown)
|
|
self.chordsw.widget().chordNameLineEdit.repaint()
|
|
self.chordsw.widget().guitarVoicingLineEdit.repaint()
|
|
self.chordsw.widget().pianoVoicingLineEdit.repaint()
|
|
|
|
def clearSectionLineEdits(self):
|
|
self.sectionsw.widget().sectionNameLineEdit.clear()
|
|
# necessary on Mojave with PyInstaller (or previous contents will be shown)
|
|
self.sectionsw.widget().sectionNameLineEdit.repaint()
|
|
|
|
def clearBlockLineEdits(self):
|
|
self.blocksw.widget().blockLengthLineEdit.clear()
|
|
self.blocksw.widget().blockNotesLineEdit.clear()
|
|
# necessary on Mojave with PyInstaller (or previous contents will be shown)
|
|
self.blocksw.widget().blockLengthLineEdit.repaint()
|
|
self.blocksw.widget().blockNotesLineEdit.repaint()
|
|
|
|
|
|
|
|
|
|
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.lastHash = self.doc.getHash()
|
|
self.currentFilePath = filename
|
|
|
|
self.currentSection = None
|
|
|
|
self.updateChordDict()
|
|
self.updateSectionDict()
|
|
|
|
def UIFileLoader(self, ui_file):
|
|
ui_file = QFile(ui_file)
|
|
ui_file.open(QFile.ReadOnly)
|
|
|
|
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):
|
|
"""
|
|
Opens a file from a file path and sets up the window accordingly.
|
|
"""
|
|
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()
|
|
|
|
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.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.
|
|
"""
|
|
if self.lastHash == self.doc.getHash():
|
|
return True
|
|
else:
|
|
if self.currentFilePath:
|
|
fileName = os.path.basename(self.currentFilePath)
|
|
else:
|
|
fileName = "Unsaved"
|
|
|
|
wantToSave = UnsavedMessageBox(fileName).exec()
|
|
|
|
if wantToSave == QMessageBox.Save:
|
|
if not self.currentFilePath:
|
|
self.saveFileChoosePath()
|
|
else:
|
|
self.saveFile(self.currentFilePath)
|
|
return True
|
|
|
|
elif wantToSave == QMessageBox.Discard:
|
|
return True
|
|
|
|
else:
|
|
return False
|
|
|
|
def updatePreview(self):
|
|
"""
|
|
Update the preview shown by rendering a new PDF and drawing it to the scroll area.
|
|
"""
|
|
try:
|
|
self.currentPreview = self.renderer.stream()
|
|
except Exception:
|
|
QMessageBox.warning(self, "Preview failed", "Could not update the preview.",
|
|
buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok)
|
|
|
|
self.widget().pdfArea.update_pdf(self.currentPreview)
|
|
|
|
def updateTitleBar(self):
|
|
"""
|
|
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 matchSection(self, nameToMatch):
|
|
"""
|
|
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
|
|
for s in self.doc.sectionList:
|
|
if s.name == nameToMatch:
|
|
section = s
|
|
break
|
|
if section is None:
|
|
section = Section(name=nameToMatch)
|
|
return section
|
|
|
|
def updateChordDict(self):
|
|
"""
|
|
Updates the dictionary used to generate the Chord menu (on the block tab)
|
|
"""
|
|
self.chordDict = {'None': None}
|
|
self.chordDict.update({c.name: c for c in self.doc.chordList})
|
|
|
|
def updateSectionDict(self):
|
|
"""
|
|
Updates the dictionary used to generate the Section menu (on the block tab)
|
|
"""
|
|
self.sectionDict = {s.name: s for s in self.doc.sectionList}
|
|
|
|
if __name__ == '__main__':
|
|
app = QApplication(sys.argv)
|
|
|
|
# pass first argument as filename
|
|
mw = MainWindow()
|
|
mw.show()
|
|
|
|
sys.exit(app.exec_())
|