def capture(self): if not self._portmidiinput: self.open() doc = self.widget().mainwindow().currentDocument() self._language = documentinfo.docinfo(doc).language() or 'nederlands' self._activenotes = 0 self._listener.start()
def getModeShifter(document, mainwindow): """Show a dialog and return the desired mode shifter. Returns None if the dialog was cancelled. """ language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text.lower()): r = ly.pitch.pitchReader(language)(pitch) if r: result.append(ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Validates text by checking if it contains a defined mode.""" return len(readpitches(text)) == 1 from . import dialog dlg = dialog.ModeShiftDialog(mainwindow) dlg.addAction(mainwindow.actionCollection.help_whatsthis) dlg.setWindowModality(Qt.WindowModal) dlg.setKeyValidator(validate) if dlg.exec_(): key, scale = dlg.getMode() key = readpitches(key)[0] dlg.saveSettings() return ly.pitch.transpose.ModeShifter(key, scale)
def changeLanguage(cursor, language): """Changes the language of the pitch names.""" c = lydocument.cursor(cursor, select_all=True) try: with qutil.busyCursor(): changed = ly.pitch.translate.translate(c, language) except ly.pitch.PitchNameNotAvailable: QMessageBox.critical( None, app.caption(_("Pitch Name Language")), _("Can't perform the requested translation.\n\n" "The music contains quarter-tone alterations, but " "those are not available in the pitch language \"{name}\"."). format(name=language)) return if changed: return if not cursor.hasSelection(): # there was no selection and no language command, so insert one version = (documentinfo.docinfo(cursor.document()).version() or lilypondinfo.preferred().version()) ly.pitch.translate.insert_language(c.document, language, version) return # there was a selection but no command, user must insert manually. QMessageBox.information( None, app.caption(_("Pitch Name Language")), '<p>{0}</p>' '<p><code>\\include "{1}.ly"</code> {2}</p>' '<p><code>\\language "{1}"</code> {3}</p>'.format( _("The pitch language of the selected text has been " "updated, but you need to manually add the following " "command to your document:"), language, _("(for LilyPond below 2.14), or"), _("(for LilyPond 2.14 and higher.)")))
def info(document): """Returns a LilyPondInfo instance that should be used by default to engrave the document.""" version = documentinfo.docinfo(document).version() if version and QSettings().value("lilypond_settings/autoversion", False, bool): return lilypondinfo.suitable(version) return lilypondinfo.preferred()
def getTransposer(document, mainwindow): """Show a dialog and return the desired transposer. Returns None if the dialog was cancelled. """ language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text): r = ly.pitch.pitchReader(language)(pitch) if r: result.append(ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Returns whether the text contains exactly two pitches.""" return len(readpitches(text)) == 2 text = inputdialog.getText(mainwindow, _("Transpose"), _( "Please enter two absolute pitches, separated by a space, " "using the pitch name language \"{language}\"." ).format(language=language), icon = icons.get('tools-transpose'), help = "transpose", validate = validate) if text: return ly.pitch.transpose.Transposer(*readpitches(text))
def changeLanguage(cursor, language): """Changes the language of the pitch names.""" c = lydocument.cursor(cursor, select_all=True) try: with qutil.busyCursor(): changed = ly.pitch.translate.translate(c, language) except ly.pitch.PitchNameNotAvailable: QMessageBox.critical(None, app.caption(_("Pitch Name Language")), _( "Can't perform the requested translation.\n\n" "The music contains quarter-tone alterations, but " "those are not available in the pitch language \"{name}\"." ).format(name=language)) return if changed: return if not cursor.hasSelection(): # there was no selection and no language command, so insert one version = (documentinfo.docinfo(cursor.document()).version() or lilypondinfo.preferred().version()) ly.pitch.translate.insert_language(c.document, language, version) return # there was a selection but no command, user must insert manually. QMessageBox.information(None, app.caption(_("Pitch Name Language")), '<p>{0}</p>' '<p><code>\\include "{1}.ly"</code> {2}</p>' '<p><code>\\language "{1}"</code> {3}</p>'.format( _("The pitch language of the selected text has been " "updated, but you need to manually add the following " "command to your document:"), language, _("(for LilyPond below 2.14), or"), _("(for LilyPond 2.14 and higher.)")))
def getTransposer(document, mainwindow): """Show a dialog and return the desired transposer. Returns None if the dialog was cancelled. """ language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text): r = ly.pitch.pitchReader(language)(pitch) if r: result.append( ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Returns whether the text contains exactly two pitches.""" return len(readpitches(text)) == 2 text = inputdialog.getText( mainwindow, _("Transpose"), _("Please enter two absolute pitches, separated by a space, " "using the pitch name language \"{language}\".").format( language=language), icon=icons.get('tools-transpose'), help="transpose", validate=validate) if text: return ly.pitch.transpose.Transposer(*readpitches(text))
def getModeShifter(document, mainwindow): """Show a dialog and return the desired mode shifter. Returns None if the dialog was cancelled. """ language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text.lower()): r = ly.pitch.pitchReader(language)(pitch) if r: result.append( ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Validates text by checking if it contains a defined mode.""" return len(readpitches(text)) == 1 from . import dialog dlg = dialog.ModeShiftDialog(mainwindow) dlg.addAction(mainwindow.actionCollection.help_whatsthis) dlg.setWindowModality(Qt.WindowModal) dlg.setKeyValidator(validate) if dlg.exec_(): key, scale = dlg.getMode() key = readpitches(key)[0] dlg.saveSettings() return ly.pitch.transpose.ModeShifter(key, scale)
def get_absolute(self, document): """Return True when the first pitch in a \\relative expression without startpitch may be considered absolute. """ import documentinfo return (self.actionCollection.pitch_relative_assume_first_pitch_absolute.isChecked() or documentinfo.docinfo(document).version() >= (2, 18))
def actionTriggered(self, name): version = documentinfo.docinfo(self.mainwindow().currentDocument()).version() glyphs = self._barlines[name] if version and version < (2, 18): glyph = glyphs[0] or glyphs[1] else: glyph = glyphs[1] text = '\\bar "{0}"'.format(glyph) self.insertText(text)
def setLanguageMenu(self): """Called when the menu is shown; selects the correct language.""" import documentinfo doc = self.mainwindow().currentDocument() lang = documentinfo.docinfo(doc).language() or 'nederlands' for a in self.language_group.actions(): if a.objectName() == lang: a.setChecked(True) break
def token_hash(self): """Return a hash for all non-whitespace tokens. Used to determine non-whitespace changes. """ dinfo = documentinfo.docinfo(self.document()) return hash(tuple(t for t in dinfo.tokens if not isinstance(t, (ly.lex.Space, ly.lex.Comment))))
def setDocument(self, doc): v = documentinfo.docinfo(doc).version_string() if v: self.fromVersion.setText(v) self.reason.setText(_("(set in document)")) else: self.reason.clear() self._text = doc.toPlainText() self._encoding = doc.encoding() or 'UTF-8' self.setConvertedText()
def may_compile(self): """Return True if we could need to compile the document.""" if self._dirty: dinfo = documentinfo.docinfo(self.document()) if (dinfo.mode() == "lilypond" and dinfo.complete()): h = self.token_hash() if h != self._hash: self._hash = h if h != hash(tuple()): return True self._dirty = False
def may_compile(self): """Return True if we could need to compile the document.""" if self._dirty: dinfo = documentinfo.docinfo(self.document()) if (dinfo.mode() == "lilypond" and dinfo.complete() and documentinfo.music(self.document()).has_output()): h = dinfo.token_hash() if h != self._hash: self._hash = h if h != hash(tuple()): return True self._dirty = False
def save(mainwindow): titles = dict((snippets.title(name), name) for name in model.model().names() if 'template' in snippets.get(name).variables) title = inputdialog.getText(mainwindow, _("Save as Template"), _("Please enter a template name:"), regexp=r"\w(.*\w)?", complete=sorted(titles)) if not title: return if title in titles: if QMessageBox.critical( mainwindow, _("Overwrite Template?"), _("A template named \"{name}\" already exists.\n\n" "Do you want to overwrite it?").format(name=title), QMessageBox.Yes | QMessageBox.Cancel) != QMessageBox.Yes: return name = titles[title] else: name = None # get the text and insert cursor position or selection cursor = mainwindow.textCursor() text = cursor.document().toPlainText() repls = [(cursor.position(), '${CURSOR}')] if cursor.hasSelection(): repls.append((cursor.anchor(), '${ANCHOR}')) repls.sort() result = [] prev = 0 for pos, what in repls: result.append(text[prev:pos].replace('$', '$$')) result.append(what) prev = pos result.append(text[prev:].replace('$', '$$')) text = ''.join(result) # add header line, if it is lilypond, enable autorun headerline = '-*- template; indent: no;' if documentinfo.mode(cursor.document()) == 'lilypond': dinfo = documentinfo.docinfo(cursor.document()) if dinfo.complete() and dinfo.has_output(): headerline += ' template-run;' text = headerline + '\n' + text # save the new snippet model.model().saveSnippet(name, text, title)
def initialize(self): document = self.document() if document.isModified(): self._dirty = True else: # look for existing result files in the default output format s = QSettings() s.beginGroup("lilypond_settings") if s.value("default_output_target", "pdf", type("")) == "svg": ext = '.svg*' else: ext = '.pdf' self._dirty = not resultfiles.results(document).files(ext) self._hash = None if self._dirty else documentinfo.docinfo(document).token_hash()
def initialize(self): document = self.document() if document.isModified(): self._dirty = True else: # look for existing result files in the default output format s = QSettings() s.beginGroup("lilypond_settings") if s.value("default_output_target", "pdf", type("")) == "svg": ext = '.svg*' else: ext = '.pdf' self._dirty = not resultfiles.results(document).files(ext) self._hash = None if self._dirty else documentinfo.docinfo( document).token_hash()
def may_compile(self): """Return True if we could need to compile the document.""" if self._dirty: path = self.document().url().path() dinfo = documentinfo.docinfo(self.document()) if (dinfo.mode() == "lilypond" and (path.endswith('.ly') or path == '') and dinfo.complete() and documentinfo.music(self.document()).has_output()): h = dinfo.token_hash() if h != self._hash: self._hash = h if h != hash(tuple()): return True self._dirty = False
def __init__(self, document): document.contentsChanged.connect(self.slotDocumentContentsChanged) document.saved.connect(self.slotDocumentSaved) if document.isModified(): self._dirty = True else: # look for existing result files in the default output format s = QSettings() s.beginGroup("lilypond_settings") if s.value("default_output_target", "pdf", type("")) == "svg": ext = '.svg*' else: ext = '.pdf' self._dirty = not resultfiles.results(document).files(ext) self._hash = None if self._dirty else documentinfo.docinfo(document).token_hash() jobmanager.manager(document).started.connect(self.slotJobStarted)
def getModeShifter(document, mainwindow): """Show a dialog and return the desired mode shifter. Returns None if the dialog was cancelled. TODO: 1. Create a dialog where you can choose the mode from a dropdown list. 2. Define more modes/scales. """ from fractions import Fraction # Mode definitions modes = { 'Major': (0, 1, 2, Fraction(5, 2), Fraction(7, 2), Fraction(9, 2), Fraction(11, 2)), 'Minor': (0, 1, Fraction(3, 2), Fraction(5, 2), Fraction(7, 2), 4, Fraction(11, 2)), 'Natminor': (0, 1, Fraction(3, 2), Fraction(5, 2), Fraction(7, 2), 4, 5), 'Dorian': (0, 1, Fraction(3, 2), Fraction(5, 2), Fraction(7, 2), Fraction(9, 2), 5) } language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text): r = ly.pitch.pitchReader(language)(pitch) if r: result.append(ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Validates text by checking if it contains a defined mode.""" words = text.split() one = bool(words) and len(readpitches(words[0])) == 1 sec = len(words) > 1 and words[1].capitalize() in modes return one and sec text = inputdialog.getText(mainwindow, _("Shift mode"), _( "Please enter the mode to shift to. (i.e. \"D major\")" ), icon = icons.get('tools-transpose'), help = "mode_shift", validate = validate) if text: words = text.split() key = readpitches(words[0])[0] scale = modes[words[1].capitalize()] return ly.pitch.transpose.ModeShifter(key, scale)
def getModalTransposer(document, mainwindow): """Show a dialog and return the desired modal transposer. Returns None if the dialog was cancelled. """ language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text): r = ly.pitch.pitchReader(language)(pitch) if r: result.append( ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Returns whether the text is an integer followed by the name of a key.""" words = text.split() if len(words) != 2: return False try: steps = int(words[0]) keyIndex = ly.pitch.transpose.ModalTransposer.getKeyIndex(words[1]) return True except ValueError: return False text = inputdialog.getText( mainwindow, _("Transpose"), _("Please enter the number of steps to alter by, followed by a key signature. (i.e. \"5 F\")" ), icon=icons.get('tools-transpose'), help="modal_transpose", validate=validate) if text: words = text.split() return ly.pitch.transpose.ModalTransposer( int(words[0]), ly.pitch.transpose.ModalTransposer.getKeyIndex(words[1]))
def move_to_include_file(cursor, parent_widget=None): """Opens a dialog to save the cursor's selection to a file. The cursor's selection is then replaced with an \\include statement. This function does its best to supply a good default filename and use it correctly in a relative \\include statement. Of course it only works well if the document already has a filename. """ doc = cursor.document() text = cursor.selection().toPlainText() mode = fileinfo.textmode(text) caption = app.caption(_("dialog title", "Move to include file")) filetypes = app.filetypes(ly.lex.extensions[mode]) name, ext = os.path.splitext(os.path.basename(doc.url().path())) if not ext or mode == "lilypond": ext = ".ily" version = documentinfo.docinfo(doc).version_string() if version: text = '\\version "{0}"\n\n{1}'.format(version, text) docname = name + "-include" + ext dirname = os.path.dirname(doc.url().toLocalFile()) or app.basedir() filename = os.path.join(dirname, docname) filename = QFileDialog.getSaveFileName(parent_widget, caption, filename, filetypes)[0] if not filename: return # cancelled data = util.encode(util.platform_newlines(text)) try: with open(filename, "wb") as f: f.write(data) except IOError as e: msg = _("{message}\n\n{strerror} ({errno})").format( message=_("Could not write to: {url}").format(url=filename), strerror=e.strerror, errno=e.errno) QMessageBox.critical(parent_widget, app.caption(_("Error")), msg) return filename = os.path.relpath(filename, dirname) command = '\\include "{0}"\n'.format(filename) cursor.insertText(command)
def move_to_include_file(cursor, parent_widget=None): """Opens a dialog to save the cursor's selection to a file. The cursor's selection is then replaced with an \\include statement. This function does its best to supply a good default filename and use it correctly in a relative \\include statement. Of course it only works well if the document already has a filename. """ doc = cursor.document() text = cursor.selection().toPlainText() mode = fileinfo.textmode(text) caption = app.caption(_("dialog title", "Move to include file")) filetypes = app.filetypes(ly.lex.extensions[mode]) name, ext = os.path.splitext(os.path.basename(doc.url().path())) if not ext or mode == "lilypond": ext = ".ily" version = documentinfo.docinfo(doc).version_string() if version: text = '\\version "{0}"\n\n{1}'.format(version, text) docname = name + "-include" + ext dirname = os.path.dirname(doc.url().toLocalFile()) or app.basedir() filename = os.path.join(dirname, docname) filename = QFileDialog.getSaveFileName(parent_widget, caption, filename, filetypes)[0] if not filename: return # cancelled data = util.encode(util.platform_newlines(text)) try: with open(filename, "wb") as f: f.write(data) except IOError as e: msg = _("{message}\n\n{strerror} ({errno})").format( message = _("Could not write to: {url}").format(url=filename), strerror = e.strerror, errno = e.errno) QMessageBox.critical(self, app.caption(_("Error")), msg) return filename = os.path.relpath(filename, dirname) command = '\\include "{0}"\n'.format(filename) cursor.insertText(command)
def actionTriggered(self, name): if self.tool().shorthands.isChecked() and name in shorthands: short = shorthands[name] # LilyPond >= 2.17.25 changed -| to -! if name == 'staccatissimo': version = documentinfo.docinfo(self.mainwindow().currentDocument()).version() if version >= (2, 17, 25): short = '!' text = '_-^'[self.direction()+1] + short else: text = ('_', '', '^')[self.direction()+1] + '\\' + name cursor = self.mainwindow().textCursor() selection = cursor.hasSelection() cursors = articulation_positions(cursor) if cursors: with cursortools.compress_undo(cursor): for c in cursors: c.insertText(text) if not selection: self.mainwindow().currentView().setTextCursor(c) elif not selection: cursor.insertText(text)
def actionTriggered(self, name): if self.tool().shorthands.isChecked() and name in shorthands: short = shorthands[name] # LilyPond >= 2.17.25 changed -| to -! if name == "staccatissimo": version = documentinfo.docinfo(self.mainwindow().currentDocument()).version() if version >= (2, 17, 25): short = "!" text = "_-^"[self.direction() + 1] + short else: text = ("_", "", "^")[self.direction() + 1] + "\\" + name cursor = self.mainwindow().textCursor() selection = cursor.hasSelection() cursors = articulation_positions(cursor) if cursors: with cursortools.compress_undo(cursor): for c in cursors: c.insertText(text) if not selection: self.mainwindow().currentView().setTextCursor(c) elif not selection: cursor.insertText(text)
def getModalTransposer(document, mainwindow): """Show a dialog and return the desired modal transposer. Returns None if the dialog was cancelled. """ language = documentinfo.docinfo(document).language() or 'nederlands' def readpitches(text): """Reads pitches from text.""" result = [] for pitch, octave in re.findall(r"([a-z]+)([,']*)", text): r = ly.pitch.pitchReader(language)(pitch) if r: result.append(ly.pitch.Pitch(*r, octave=ly.pitch.octaveToNum(octave))) return result def validate(text): """Returns whether the text is an integer followed by the name of a key.""" words = text.split() if len(words) != 2: return False try: steps = int(words[0]) keyIndex = ly.pitch.transpose.ModalTransposer.getKeyIndex(words[1]) return True except ValueError: return False text = inputdialog.getText(mainwindow, _("Transpose"), _( "Please enter the number of steps to alter by, followed by a key signature. (i.e. \"5 F\")" ), icon = icons.get('tools-transpose'), help = "modal_transpose", validate = validate) if text: words = text.split() return ly.pitch.transpose.ModalTransposer(int(words[0]), ly.pitch.transpose.ModalTransposer.getKeyIndex(words[1]))
def save(mainwindow): titles = dict((snippets.title(name), name) for name in model.model().names() if 'template' in snippets.get(name).variables) # would it make sense to run LilyPond after creating a document from this # template? cursor = mainwindow.textCursor() template_run = False if documentinfo.mode(cursor.document()) == 'lilypond': dinfo = documentinfo.docinfo(cursor.document()) if dinfo.complete() and dinfo.has_output(): template_run = True dlg = TemplateDialog(mainwindow) c = QCompleter(sorted(titles), dlg.lineEdit()) dlg.lineEdit().setCompleter(c) dlg.runCheck().setChecked(template_run) result = dlg.exec_() dlg.deleteLater() if not result: return # cancelled title = dlg.text() template_run = dlg.runCheck().isChecked() if title in titles: if QMessageBox.critical( mainwindow, _("Overwrite Template?"), _("A template named \"{name}\" already exists.\n\n" "Do you want to overwrite it?").format(name=title), QMessageBox.Yes | QMessageBox.Cancel) != QMessageBox.Yes: return name = titles[title] else: name = None # get the text and insert cursor position or selection text = cursor.document().toPlainText() repls = [(cursor.position(), '${CURSOR}')] if cursor.hasSelection(): repls.append((cursor.anchor(), '${ANCHOR}')) repls.sort() result = [] prev = 0 for pos, what in repls: result.append(text[prev:pos].replace('$', '$$')) result.append(what) prev = pos result.append(text[prev:].replace('$', '$$')) text = ''.join(result) # add header line, if desired enable autorun headerline = '-*- template; indent: no;' if template_run: headerline += ' template-run;' text = headerline + '\n' + text # save the new snippet model.model().saveSnippet(name, text, title)
def slotJobStarted(self): """Called when an engraving job is started on this document.""" if self._dirty: self._dirty = False self._hash = documentinfo.docinfo(self.document()).token_hash()
def save(mainwindow): titles = dict((snippets.title(name), name) for name in model.model().names() if 'template' in snippets.get(name).variables) # would it make sense to run LilyPond after creating a document from this # template? cursor = mainwindow.textCursor() template_run = False if documentinfo.mode(cursor.document()) == 'lilypond': dinfo = documentinfo.docinfo(cursor.document()) if dinfo.complete() and dinfo.has_output(): template_run = True dlg = TemplateDialog(mainwindow) c = QCompleter(sorted(titles), dlg.lineEdit()) dlg.lineEdit().setCompleter(c) dlg.runCheck().setChecked(template_run) result = dlg.exec_() dlg.deleteLater() if not result: return # cancelled title = dlg.text() template_run = dlg.runCheck().isChecked() if title in titles: if QMessageBox.critical(mainwindow, _("Overwrite Template?"), _("A template named \"{name}\" already exists.\n\n" "Do you want to overwrite it?").format(name=title), QMessageBox.Yes | QMessageBox.Cancel) != QMessageBox.Yes: return name = titles[title] else: name = None # get the text and insert cursor position or selection text = cursor.document().toPlainText() repls = [(cursor.position(), '${CURSOR}')] if cursor.hasSelection(): repls.append((cursor.anchor(), '${ANCHOR}')) repls.sort() result = [] prev = 0 for pos, what in repls: result.append(text[prev:pos].replace('$', '$$')) result.append(what) prev = pos result.append(text[prev:].replace('$', '$$')) text = ''.join(result) # add header line, if desired enable autorun headerline = '-*- template; indent: no;' if template_run: headerline += ' template-run;' text = headerline + '\n' + text # save the new snippet model.model().saveSnippet(name, text, title)