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 importMusicXML(self): """ Opens a MusicXML file. Converts it to ly by using musicxml2ly """ filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files")) caption = app.caption(_("dialog title", "Import a MusicXML file")) directory = os.path.dirname(self.mainwindow().currentDocument().url().toLocalFile()) or app.basedir() importfile = QFileDialog.getOpenFileName(self.mainwindow(), caption, directory, filetypes) if not importfile: return # the dialog was cancelled by user try: dlg = self._importDialog except AttributeError: from . import musicxml dlg = self._importDialog = musicxml.Dialog(self.mainwindow()) dlg.addAction(self.mainwindow().actionCollection.help_whatsthis) dlg.setWindowModality(Qt.WindowModal) dlg.setDocument(importfile) if dlg.exec_(): with qutil.busyCursor(): stdout, stderr = dlg.run_command() if stdout: # success dlg.saveSettings() lyfile = os.path.splitext(importfile)[0] + ".ly" doc = self.createDocument(lyfile, stdout.decode("utf-8")) self.postImport(dlg.getPostSettings(), doc) self.mainwindow().saveDocument(doc) else: # failure to convert QMessageBox.critical( None, _("Error"), _("The file couldn't be converted. Error message:\n") + stderr.decode("utf-8") )
def changeLanguage(cursor, language): """Changes the language of the pitch names.""" selection = cursor.hasSelection() if selection: start = cursor.selectionStart() cursor.setPosition(cursor.selectionEnd()) cursor.setPosition(0, QTextCursor.KeepAnchor) source = tokeniter.Source.selection(cursor) else: source = tokeniter.Source.document(cursor) pitches = PitchIterator(source) tokens = pitches.tokens() writer = ly.pitch.pitchWriter(language) if selection: # consume tokens before the selection, following the language source.consume(tokens, start) changed = False # track change of \language or \include language command with cursortools.compress_undo(cursor): try: with qutil.busyCursor(): with cursortools.Editor() as e: for t in tokens: if isinstance(t, ly.lex.lilypond.Note): # translate the pitch name p = pitches.read(t) if p: n = writer(*p) if n != t: e.insertText(source.cursor(t), n) elif isinstance(t, LanguageName) and t != language: # change the language name in a command e.insertText(source.cursor(t), language) changed = True 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 selection: # there was no selection and no language command, so insert one insertLanguage(cursor.document(), language) 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 importMusicXML(self): """ Opens a MusicXML file. Converts it to ly by using musicxml2ly """ filetypes = '{0} (*.xml);;{1} (*)'.format(_("XML Files"), _("All Files")) caption = app.caption(_("dialog title", "Import a MusicXML file")) directory = os.path.dirname(self.mainwindow().currentDocument().url(). toLocalFile()) or app.basedir() importfile = QFileDialog.getOpenFileName(self.mainwindow(), caption, directory, filetypes) if not importfile: return # the dialog was cancelled by user try: dlg = self._importDialog except AttributeError: from . import musicxml dlg = self._importDialog = musicxml.Dialog(self.mainwindow()) dlg.addAction(self.mainwindow().actionCollection.help_whatsthis) dlg.setWindowModality(Qt.WindowModal) dlg.setDocument(importfile) if dlg.exec_(): with qutil.busyCursor(): stdout, stderr = dlg.run_command() if stdout: #success dlg.saveSettings() lyfile = os.path.splitext(importfile)[0] + ".ly" doc = self.createDocument(lyfile, stdout.decode('utf-8')) self.postImport(dlg.getPostSettings(), doc) self.mainwindow().saveDocument(doc) else: #failure to convert QMessageBox.critical( None, _("Error"), _("The file couldn't be converted. Error message:\n") + stderr.decode('utf-8'))
def abs2rel(cursor, startpitch, first_pitch_absolute): """Converts pitches from absolute to relative.""" with qutil.busyCursor(): c = lydocument.cursor(cursor, select_all=True) ly.pitch.abs2rel.abs2rel(c, startpitch=startpitch, first_pitch_absolute=first_pitch_absolute)
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 run(self): """Runs convert-ly (again).""" fromVersion = self.fromVersion.text() toVersion = self.toVersion.text() if not fromVersion or not toVersion: self.messages.setPlainText( _("Both 'from' and 'to' versions need to be set.")) return info = self._info command = info.toolcommand(info.ly_tool('convert-ly')) command += ['-f', fromVersion, '-t', toVersion, '-'] # if the user wants english messages, do it also here: LANGUAGE=C env = None if os.name == "nt": # Python 2.7 subprocess on Windows chokes on unicode in env env = util.bytes_environ() else: env = dict(os.environ) if sys.platform.startswith('darwin'): try: del env['PYTHONHOME'] except KeyError: pass try: del env['PYTHONPATH'] except KeyError: pass if QSettings().value("lilypond_settings/no_translation", False, bool): if os.name == "nt": # Python 2.7 subprocess on Windows chokes on unicode in env env[b'LANGUAGE'] = b'C' else: env['LANGUAGE'] = 'C' with qutil.busyCursor(): try: proc = subprocess.Popen(command, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate( util.platform_newlines(self._text).encode(self._encoding)) except OSError as e: self.messages.setPlainText( _("Could not start {convert_ly}:\n\n" "{message}\n").format(convert_ly=command[0], message=e)) return out = util.universal_newlines(out.decode('UTF-8')) err = util.universal_newlines(err.decode('UTF-8')) self.messages.setPlainText(err) self.setConvertedText(out) self.setDiffText(out) if not out or self._convertedtext == self._text: self.messages.append('\n' + _("The document has not been changed."))
def run(self): """Runs convert-ly (again).""" fromVersion = self.fromVersion.text() toVersion = self.toVersion.text() if not fromVersion or not toVersion: self.messages.setPlainText(_( "Both 'from' and 'to' versions need to be set.")) return info = self._info command = info.toolcommand(info.ly_tool('convert-ly')) command += ['-f', fromVersion, '-t', toVersion, '-'] # if the user wants english messages, do it also here: LANGUAGE=C env = None if os.name == "nt": # Python 2.7 subprocess on Windows chokes on unicode in env env = util.bytes_environ() else: env = dict(os.environ) if sys.platform.startswith('darwin'): try: del env['PYTHONHOME'] except KeyError: pass try: del env['PYTHONPATH'] except KeyError: pass if QSettings().value("lilypond_settings/no_translation", False, bool): if os.name == "nt": # Python 2.7 subprocess on Windows chokes on unicode in env env[b'LANGUAGE'] = b'C' else: env['LANGUAGE'] = 'C' with qutil.busyCursor(): try: proc = subprocess.Popen(command, env = env, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) out, err = proc.communicate(util.platform_newlines(self._text).encode(self._encoding)) except OSError as e: self.messages.setPlainText(_( "Could not start {convert_ly}:\n\n" "{message}\n").format(convert_ly = command[0], message = e)) return out = util.universal_newlines(out.decode('UTF-8')) err = util.universal_newlines(err.decode('UTF-8')) self.messages.setPlainText(err) self.setConvertedText(out) self.setDiffText(out) if not out or self._convertedtext == self._text: self.messages.append('\n' + _("The document has not been changed."))
def transpose(cursor, transposer, mainwindow=None): """Transpose pitches using the specified transposer.""" c = lydocument.cursor(cursor, select_all=True) try: with qutil.busyCursor(): ly.pitch.transpose.transpose(c, transposer) except ly.pitch.PitchNameNotAvailable as e: QMessageBox.critical(mainwindow, app.caption(_("Transpose")), _( "Can't perform the requested transposition.\n\n" "The transposed music would contain quarter-tone alterations " "that are not available in the pitch language \"{language}\"." ).format(language = e.language))
def transpose(cursor, transposer, mainwindow=None): """Transpose pitches using the specified transposer.""" c = lydocument.cursor(cursor, select_all=True) try: with qutil.busyCursor(): ly.pitch.transpose.transpose(c, transposer) except ly.pitch.PitchNameNotAvailable as e: QMessageBox.critical( mainwindow, app.caption(_("Transpose")), _("Can't perform the requested transposition.\n\n" "The transposed music would contain quarter-tone alterations " "that are not available in the pitch language \"{language}\"."). format(language=e.language))
def run(self): """Runs convert-ly (again).""" fromVersion = self.fromVersion.text() toVersion = self.toVersion.text() if not fromVersion or not toVersion: self.messages.setPlainText(_( "Both 'from' and 'to' versions need to be set.")) return info = self._info convert_ly = os.path.join(info.bindir(), info.convert_ly) # on Windows the convert-ly command is not directly executable, but # must be started using the LilyPond-provided Python interpreter if os.name == "nt": if not os.access(convert_ly, os.R_OK) and not convert_ly.endswith('.py'): convert_ly += '.py' command = [info.python(), convert_ly] else: command = [convert_ly] command += ['-f', fromVersion, '-t', toVersion, '-'] # if the user wants english messages, do it also here: LANGUAGE=C env = None if QSettings().value("lilypond_settings/no_translation", False) in (True, "true"): if os.name == "nt": # Python 2.7 subprocess on Windows chokes on unicode in env env = util.bytes_environ() env[b'LANGUAGE'] = b'C' else: env = dict(os.environ) env['LANGUAGE'] = 'C' with qutil.busyCursor(): try: proc = subprocess.Popen(command, universal_newlines = True, env = env, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE) out, err = proc.communicate(self._text.encode(self._encoding)) except OSError as e: self.messages.setPlainText(_( "Could not start {convert_ly}:\n\n" "{message}\n").format(convert_ly = convert_ly, message = e)) return self.messages.setPlainText(err.decode('UTF-8')) self.setConvertedText(out.decode('UTF-8')) if not out or self._convertedtext == self._text: self.messages.append('\n' + _("The document has not been changed."))
def runImport(self): """Generic execution of all import dialogs.""" dlg = self._importDialog dlg.setDocument(self.importfile) if dlg.exec_(): with qutil.busyCursor(): stdout, stderr = dlg.run_command() if stdout: #success dlg.saveSettings() lyfile = os.path.splitext(self.importfile)[0] + ".ly" doc = self.createDocument(lyfile, stdout.decode('utf-8')) self.postImport(dlg.getPostSettings(), doc) self.mainwindow().saveDocument(doc) else: #failure to convert QMessageBox.critical(None, _("Error"), _("The file couldn't be converted. Error message:\n") + stderr.decode('utf-8'))
def importMusicXML(self): """ Opens a MusicXML file. Converts it to ly by using musicxml2ly """ filetypes = app.filetypes('*.xml') caption = app.caption(_("dialog title", "Import a MusicXML file")) directory = os.path.dirname(self.mainwindow().currentDocument().url().toLocalFile()) or app.basedir() importfile = QFileDialog.getOpenFileName(self.mainwindow(), caption, directory, filetypes) if not importfile: return # the dialog was cancelled by user try: dlg = self._importDialog except AttributeError: from . import musicxml dlg = self._importDialog = musicxml.Dialog(self.mainwindow()) dlg.addAction(self.mainwindow().actionCollection.help_whatsthis) dlg.setWindowModality(Qt.WindowModal) dlg.setDocument(importfile) if dlg.exec_(): with qutil.busyCursor(): stdout, stderr = dlg.run_command() print stderr #put this in log window instead lyfile = os.path.splitext(importfile)[0] + ".ly" self.createDocument(lyfile, stdout.decode('utf-8'))
def transpose(cursor, transposer, mainwindow=None): """Transpose pitches using the specified transposer.""" selection = cursor.hasSelection() if selection: start = cursor.selectionStart() cursor.setPosition(cursor.selectionEnd()) cursor.setPosition(0, QTextCursor.KeepAnchor) source = tokeniter.Source.selection(cursor, True) else: source = tokeniter.Source.document(cursor, True) pitches = PitchIterator(source) psource = pitches.pitches() class gen(object): def __init__(self): self.inSelection = not selection def __iter__(self): return self def __next__(self): while True: t = next(psource) if isinstance(t, (ly.lex.Space, ly.lex.Comment)): continue elif not self.inSelection and pitches.position(t) >= start: self.inSelection = True # Handle stuff that's the same in relative and absolute here if t == "\\relative": relative() elif isinstance(t, ly.lex.lilypond.MarkupScore): absolute(context()) elif isinstance(t, ly.lex.lilypond.ChordMode): chordmode() elif isinstance(t, ly.lex.lilypond.PitchCommand): if t == "\\transposition": next(psource) # skip pitch elif t == "\\transpose": for p in getpitches(context()): transpose(p) elif t == "\\key": for p in getpitches(context()): transpose(p, 0) else: return t else: return t next = __next__ tsource = gen() def context(): """Consume tokens till the level drops (we exit a construct).""" depth = source.state.depth() for t in tsource: yield t if source.state.depth() < depth: return def consume(): """Consume tokens from context() returning the last token, if any.""" t = None for t in context(): pass return t def transpose(p, resetOctave = None): """Transpose absolute pitch, using octave if given.""" transposer.transpose(p) if resetOctave is not None: p.octave = resetOctave if tsource.inSelection: pitches.write(p, editor) def chordmode(): """Called inside \\chordmode or \\chords.""" for p in getpitches(context()): transpose(p, 0) def absolute(tokens): """Called when outside a possible \\relative environment.""" for p in getpitches(tokens): transpose(p) def relative(): """Called when \\relative is encountered.""" def transposeRelative(p, lastPitch): """Transposes a relative pitch; returns the pitch in absolute form.""" # absolute pitch determined from untransposed pitch of lastPitch p.makeAbsolute(lastPitch) if not tsource.inSelection: return p # we may change this pitch. Make it relative against the # transposed lastPitch. try: last = lastPitch.transposed except AttributeError: last = lastPitch # transpose a copy and store that in the transposed # attribute of lastPitch. Next time that is used for # making the next pitch relative correctly. newLastPitch = p.copy() transposer.transpose(p) newLastPitch.transposed = p.copy() if p.octaveCheck is not None: p.octaveCheck = p.octave p.makeRelative(last) if relPitch: # we are allowed to change the pitch after the # \relative command. lastPitch contains this pitch. lastPitch.octave += p.octave p.octave = 0 pitches.write(lastPitch, editor) del relPitch[:] pitches.write(p, editor) return newLastPitch lastPitch = None relPitch = [] # we use a list so it can be changed from inside functions # find the pitch after the \relative command t = next(tsource) if isinstance(t, Pitch): lastPitch = t if tsource.inSelection: relPitch.append(lastPitch) t = next(tsource) else: lastPitch = Pitch.c1() while True: # eat stuff like \new Staff == "bla" \new Voice \notes etc. if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator): t = consume() elif isinstance(t, ly.lex.lilypond.NoteMode): t = next(tsource) else: break # now transpose the relative expression if t in ('{', '<<'): # Handle full music expression { ... } or << ... >> for t in context(): if t == '\\octaveCheck': for p in getpitches(context()): lastPitch = p.copy() del relPitch[:] if tsource.inSelection: transposer.transpose(p) lastPitch.transposed = p pitches.write(p, editor) elif isinstance(t, ly.lex.lilypond.ChordStart): chord = [lastPitch] for p in getpitches(context()): chord.append(transposeRelative(p, chord[-1])) lastPitch = chord[:2][-1] # same or first elif isinstance(t, Pitch): lastPitch = transposeRelative(t, lastPitch) elif isinstance(t, ly.lex.lilypond.ChordStart): # Handle just one chord for p in getpitches(context()): lastPitch = transposeRelative(p, lastPitch) elif isinstance(t, Pitch): # Handle just one pitch transposeRelative(token, lastPitch) # Do it! try: with qutil.busyCursor(): with cursortools.Editor() as editor: absolute(tsource) except ly.pitch.PitchNameNotAvailable: QMessageBox.critical(mainwindow, app.caption(_("Transpose")), _( "Can't perform the requested transposition.\n\n" "The transposed music would contain quarter-tone alterations " "that are not available in the pitch language \"{language}\"." ).format(language = pitches.language))
def load(filename, widget): """Loads snippets from a file, displaying them in a list. The user can then choose: - overwrite builtin snippets or not - overwrite own snippets with same title or not - select and view snippets contents. """ try: d = ET.parse(filename) elements = list(d.findall('snippet')) if not elements: raise ValueError(_("No snippets found.")) except Exception as e: QMessageBox.critical( widget, app.caption(_("Error")), _("Can't read from source:\n\n{url}\n\n{error}").format( url=filename, error=e)) return dlg = widgets.dialog.Dialog(widget) dlg.setWindowModality(Qt.WindowModal) dlg.setWindowTitle(app.caption(_("dialog title", "Import Snippets"))) tree = QTreeWidget(headerHidden=True, rootIsDecorated=False) dlg.setMainWidget(tree) userguide.addButton(dlg.buttonBox(), "snippet_import_export") allnames = frozenset(snippets.names()) builtins = frozenset(builtin.builtin_snippets) titles = dict( (snippets.title(n), n) for n in allnames if n not in builtins) new = QTreeWidgetItem(tree, [_("New Snippets")]) updated = QTreeWidgetItem(tree, [_("Updated Snippets")]) unchanged = QTreeWidgetItem(tree, [_("Unchanged Snippets")]) new.setFlags(Qt.ItemIsEnabled) updated.setFlags(Qt.ItemIsEnabled) unchanged.setFlags(Qt.ItemIsEnabled) new.setExpanded(True) updated.setExpanded(True) items = [] for snip in elements: item = QTreeWidgetItem() item.body = snip.find('body').text item.title = snip.find('title').text item.shortcuts = list(e.text for e in snip.findall('shortcuts/shortcut')) title = item.title or snippets.maketitle( snippets.parse(item.body).text) item.setText(0, title) name = snip.get('id') name = name if name in builtins else None # determine if new, updated or unchanged if not name: name = titles.get(title) item.name = name if not name or name not in allnames: new.addChild(item) items.append(item) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Checked) elif name: if (item.body != snippets.text(name) or title != snippets.title(name) or (item.shortcuts and item.shortcuts != [s.toString() for s in model.shortcuts(name) or ()])): updated.addChild(item) items.append(item) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Checked) else: unchanged.addChild(item) item.setFlags(Qt.ItemIsEnabled) # count: for i in new, updated, unchanged: i.setText(0, i.text(0) + " ({0})".format(i.childCount())) for i in new, updated: if i.childCount(): i.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) i.setCheckState(0, Qt.Checked) def changed(item): if item in (new, updated): for i in range(item.childCount()): c = item.child(i) c.setCheckState(0, item.checkState(0)) tree.itemChanged.connect(changed) importShortcuts = QTreeWidgetItem([_("Import Keyboard Shortcuts")]) if items: tree.addTopLevelItem(importShortcuts) importShortcuts.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) importShortcuts.setCheckState(0, Qt.Checked) dlg.setMessage(_("Choose which snippets you want to import:")) else: dlg.setMessage(_("There are no new or updated snippets in the file.")) unchanged.setExpanded(True) tree.setWhatsThis( _("<p>Here the snippets from {filename} are displayed.</p>\n" "<p>If there are new or updated snippets, you can select or deselect " "them one by one, or all at once, using the checkbox of the group. " "Then click OK to import all the selected snippets.</p>\n" "<p>Existing, unchanged snippets can't be imported.</p>\n").format( filename=os.path.basename(filename))) qutil.saveDialogSize(dlg, "snippettool/import/size", QSize(400, 300)) if not dlg.exec_() or not items: return ac = model.collection() m = model.model() with qutil.busyCursor(): for i in items: if i.checkState(0) == Qt.Checked: index = m.saveSnippet(i.name, i.body, i.title) if i.shortcuts and importShortcuts.checkState(0): shortcuts = list(map(QKeySequence.fromString, i.shortcuts)) ac.setShortcuts(m.name(index), shortcuts) widget.updateColumnSizes()
def abs2rel(cursor): """Converts pitches from absolute to relative.""" selection = cursor.hasSelection() if selection: start = cursor.selectionStart() cursor.setPosition(cursor.selectionEnd()) cursor.setPosition(0, QTextCursor.KeepAnchor) source = tokeniter.Source.selection(cursor, True) else: source = tokeniter.Source.document(cursor, True) pitches = PitchIterator(source) psource = pitches.pitches() if selection: # consume tokens before the selection, following the language t = source.consume(pitches.tokens(), start) if t: psource = itertools.chain((t,), psource) # this class dispatches the tokens. we can't use a generator function # as that doesn't like to be called again while there is already a body # running. class gen(object): def __iter__(self): return self def __next__(self): t = next(psource) while isinstance(t, (ly.lex.Space, ly.lex.Comment)): t = next(psource) if t == '\\relative' and isinstance(t, ly.lex.lilypond.Command): relative() t = next(psource) elif isinstance(t, ly.lex.lilypond.ChordMode): consume() # do not change chords t = next(psource) elif isinstance(t, ly.lex.lilypond.MarkupScore): consume() t = next(psource) return t next = __next__ tsource = gen() def context(): """Consume tokens till the level drops (we exit a construct).""" depth = source.state.depth() for t in tsource: yield t if source.state.depth() < depth: return def consume(): """Consume tokens from context() returning the last token, if any.""" t = None for t in context(): pass return t def relative(): """Consume the whole \relative expression without doing anything. """ # skip pitch argument t = next(tsource) if isinstance(t, Pitch): t = next(tsource) while True: # eat stuff like \new Staff == "bla" \new Voice \notes etc. if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator): t = consume() elif isinstance(t, ly.lex.lilypond.NoteMode): t = next(tsource) else: break if t in ('{', '<<', '<'): consume() # Do it! with qutil.busyCursor(): with cursortools.Editor() as editor: for t in tsource: if t in ('{', '<<'): # Ok, parse current expression. c = source.cursor(t, end=0) # insert the \relative command lastPitch = None chord = None for t in context(): # skip commands with pitches that do not count if isinstance(t, ly.lex.lilypond.PitchCommand): consume() elif isinstance(t, ly.lex.lilypond.ChordStart): # Handle chord chord = [] elif isinstance(t, ly.lex.lilypond.ChordEnd): if chord: lastPitch = chord[0] chord = None elif isinstance(t, Pitch): # Handle pitch if lastPitch is None: lastPitch = Pitch.c1() lastPitch.octave = t.octave if t.note > 3: lastPitch.octave += 1 editor.insertText(c, "\\relative {0} ".format( lastPitch.output(pitches.language))) p = t.copy() t.makeRelative(lastPitch) pitches.write(t, editor) lastPitch = p # remember the first pitch of a chord if chord == []: chord.append(p)
def rel2abs(cursor): """Converts pitches from relative to absolute.""" selection = cursor.hasSelection() if selection: start = cursor.selectionStart() cursor.setPosition(cursor.selectionEnd()) cursor.setPosition(0, QTextCursor.KeepAnchor) source = tokeniter.Source.selection(cursor, True) else: source = tokeniter.Source.document(cursor, True) pitches = PitchIterator(source) psource = pitches.pitches() if selection: # consume tokens before the selection, following the language t = source.consume(pitches.tokens(), start) if t: psource = itertools.chain((t,), psource) # this class dispatches the tokens. we can't use a generator function # as that doesn't like to be called again while there is already a body # running. class gen(object): def __iter__(self): return self def __next__(self): t = next(psource) while isinstance(t, (ly.lex.Space, ly.lex.Comment)): t = next(psource) if t == '\\relative' and isinstance(t, ly.lex.lilypond.Command): relative(t) t = next(psource) elif isinstance(t, ly.lex.lilypond.MarkupScore): consume() t = next(psource) return t next = __next__ tsource = gen() def makeAbsolute(p, lastPitch): """Makes pitch absolute (honoring and removing possible octaveCheck).""" if p.octaveCheck is not None: p.octave = p.octaveCheck p.octaveCheck = None else: p.makeAbsolute(lastPitch) pitches.write(p, editor) def context(): """Consume tokens till the level drops (we exit a construct).""" depth = source.state.depth() for t in tsource: yield t if source.state.depth() < depth: return def consume(): """Consume tokens from context() returning the last token, if any.""" t = None for t in context(): pass return t def relative(t): c = source.cursor(t) lastPitch = None t = next(tsource) if isinstance(t, Pitch): lastPitch = t t = next(tsource) else: lastPitch = Pitch.c1() # remove the \relative <pitch> tokens c.setPosition(source.position(t), c.KeepAnchor) editor.removeSelectedText(c) while True: # eat stuff like \new Staff == "bla" \new Voice \notes etc. if isinstance(source.state.parser(), ly.lex.lilypond.ParseTranslator): t = consume() elif isinstance(t, (ly.lex.lilypond.ChordMode, ly.lex.lilypond.NoteMode)): t = next(tsource) else: break # now convert the relative expression to absolute if t in ('{', '<<'): # Handle full music expression { ... } or << ... >> for t in context(): # skip commands with pitches that do not count if isinstance(t, ly.lex.lilypond.PitchCommand): if t == '\\octaveCheck': c = source.cursor(t) for p in getpitches(context()): # remove the \octaveCheck lastPitch = p c.setPosition((p.octaveCursor or p.noteCursor).selectionEnd(), c.KeepAnchor) editor.removeSelectedText(c) break else: consume() elif isinstance(t, ly.lex.lilypond.ChordStart): # handle chord chord = [lastPitch] for p in getpitches(context()): makeAbsolute(p, chord[-1]) chord.append(p) lastPitch = chord[:2][-1] # same or first elif isinstance(t, Pitch): makeAbsolute(t, lastPitch) lastPitch = t elif isinstance(t, ly.lex.lilypond.ChordStart): # Handle just one chord for p in getpitches(context()): makeAbsolute(p, lastPitch) lastPitch = p elif isinstance(t, Pitch): # Handle just one pitch makeAbsolute(t, lastPitch) # Do it! with qutil.busyCursor(): with cursortools.Editor() as editor: for t in tsource: pass
def rel2abs(cursor, first_pitch_absolute): """Converts pitches from relative to absolute.""" with qutil.busyCursor(): c = lydocument.cursor(cursor, select_all=True) ly.pitch.rel2abs.rel2abs(c, first_pitch_absolute=first_pitch_absolute)
def load(filename, widget): """Loads snippets from a file, displaying them in a list. The user can then choose: - overwrite builtin snippets or not - overwrite own snippets with same title or not - select and view snippets contents. """ try: d = ET.parse(filename) elements = list(d.findall('snippet')) if not elements: raise ValueError(_("No snippets found.")) except Exception as e: QMessageBox.critical(widget, app.caption(_("Error")), _("Can't read from source:\n\n{url}\n\n{error}").format( url=filename, error=e)) return dlg = widgets.dialog.Dialog(widget) dlg.setWindowModality(Qt.WindowModal) dlg.setWindowTitle(app.caption(_("dialog title", "Import Snippets"))) tree = QTreeWidget(headerHidden=True, rootIsDecorated=False) dlg.setMainWidget(tree) userguide.addButton(dlg.buttonBox(), "snippet_import_export") allnames = frozenset(snippets.names()) builtins = frozenset(builtin.builtin_snippets) titles = dict((snippets.title(n), n) for n in allnames if n not in builtins) new = QTreeWidgetItem(tree, [_("New Snippets")]) updated = QTreeWidgetItem(tree, [_("Updated Snippets")]) unchanged = QTreeWidgetItem(tree, [_("Unchanged Snippets")]) new.setFlags(Qt.ItemIsEnabled) updated.setFlags(Qt.ItemIsEnabled) unchanged.setFlags(Qt.ItemIsEnabled) new.setExpanded(True) updated.setExpanded(True) items = [] for snip in elements: item = QTreeWidgetItem() item.body = snip.find('body').text item.title = snip.find('title').text item.shortcuts = list(e.text for e in snip.findall('shortcuts/shortcut')) title = item.title or snippets.maketitle(snippets.parse(item.body).text) item.setText(0, title) name = snip.get('id') name = name if name in builtins else None # determine if new, updated or unchanged if not name: name = titles.get(title) item.name = name if not name or name not in allnames: new.addChild(item) items.append(item) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Checked) elif name: if (item.body != snippets.text(name) or title != snippets.title(name) or (item.shortcuts and item.shortcuts != [s.toString() for s in model.shortcuts(name) or ()])): updated.addChild(item) items.append(item) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) item.setCheckState(0, Qt.Checked) else: unchanged.addChild(item) item.setFlags(Qt.ItemIsEnabled) # count: for i in new, updated, unchanged: i.setText(0, i.text(0) + " ({0})".format(i.childCount())) for i in new, updated: if i.childCount(): i.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) i.setCheckState(0, Qt.Checked) def changed(item): if item in (new, updated): for i in range(item.childCount()): c = item.child(i) c.setCheckState(0, item.checkState(0)) tree.itemChanged.connect(changed) importShortcuts = QTreeWidgetItem([_("Import Keyboard Shortcuts")]) if items: tree.addTopLevelItem(importShortcuts) importShortcuts.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) importShortcuts.setCheckState(0, Qt.Checked) dlg.setMessage(_("Choose which snippets you want to import:")) else: dlg.setMessage(_("There are no new or updated snippets in the file.")) unchanged.setExpanded(True) tree.setWhatsThis(_( "<p>Here the snippets from {filename} are displayed.</p>\n" "<p>If there are new or updated snippets, you can select or deselect " "them one by one, or all at once, using the checkbox of the group. " "Then click OK to import all the selected snippets.</p>\n" "<p>Existing, unchanged snippets can't be imported.</p>\n" ).format(filename=os.path.basename(filename))) qutil.saveDialogSize(dlg, "snippettool/import/size", QSize(400, 300)) if not dlg.exec_() or not items: return ac = model.collection() m = model.model() with qutil.busyCursor(): for i in items: if i.checkState(0) == Qt.Checked: index = m.saveSnippet(i.name, i.body, i.title) if i.shortcuts and importShortcuts.checkState(0): shortcuts = list(map(QKeySequence.fromString, i.shortcuts)) ac.setShortcuts(m.name(index), shortcuts) widget.updateColumnSizes()
def abs2rel(cursor): """Converts pitches from absolute to relative.""" with qutil.busyCursor(): c = lydocument.cursor(cursor, select_all=True) ly.pitch.abs2rel.abs2rel(c)
def rel2abs(cursor): """Converts pitches from relative to absolute.""" with qutil.busyCursor(): c = lydocument.cursor(cursor, select_all=True) ly.pitch.rel2abs.rel2abs(c)