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.editBlock(cursor): try: with util.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 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 env = None if QSettings().value("lilypond_settings/no_translation", False) in (True, "true"): env = dict(os.environ) env["LANGUAGE"] = "C" with util.busyCursor(): try: proc = subprocess.Popen( command, 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 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.setWindowTitle(app.caption(_("dialog title", "Import Snippets"))) tree = QTreeWidget(headerHidden=True, rootIsDecorated=False) dlg.setMainWidget(tree) 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))) util.saveDialogSize(dlg, "snippettool/import/size", QSize(400, 300)) if not dlg.exec_() or not items: return ac = model.collection() m = model.model() with util.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 transpose(cursor, mainwindow): """Transposes pitches.""" language = documentinfo.info(cursor.document()).pitchLanguage() 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_help, validate = validate) if text == None: return transposer = ly.pitch.Transposer(*readpitches(text)) 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 util.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 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 util.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 util.busyCursor(): with cursortools.Editor() as editor: for t in tsource: pass