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 getTransposer(document, mainwindow): """Show a dialog and return the desired transposer. Returns None if the dialog was cancelled. """ language = documentinfo.info(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: return ly.pitch.Transposer(*readpitches(text))
def save(mainwindow): title = inputdialog.getText(mainwindow, _("Save as Template"), _("Please enter a template name:"), regexp=r"\w(.*\w)?") if not title: return # 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': headerline += ' template-run;' text = headerline + '\n' + text # save the new snippet model.model().saveSnippet(None, text, title)
def cut_assign(cursor): """Cuts selected text and assigns it to a LilyPond variable.""" # ask the variable name name = inputdialog.getText(None, _("Cut and Assign"), _( "Please enter the name for the variable to assign the selected " "text to:"), regexp="[A-Za-z]+") if not name: return cursortools.strip_selection(cursor) # determine state at cursor block = cursortools.block(cursor) state = tokeniter.state(block) for t in tokeniter.partition(cursor).left: state.follow(t) mode = "" for p in state.parsers(): if isinstance(p, ly.lex.lilypond.ParseInputMode): if isinstance(p, ly.lex.lilypond.ParseLyricMode): mode = " \\lyricmode" elif isinstance(p, ly.lex.lilypond.ParseChordMode): mode = " \\chordmode" elif isinstance(p, ly.lex.lilypond.ParseFigureMode): mode = " \\figuremode" elif isinstance(p, ly.lex.lilypond.ParseDrumMode): mode = " \\drummode" break # find insertion place: found = False while block.previous().isValid(): block = block.previous() state = tokeniter.state(block) if isinstance(state.parser(), ly.lex.lilypond.ParseGlobal): found = True break tokens = tokeniter.tokens(block) for t in tokens: if isinstance(t, ly.lex.lilypond.Name): found = True break elif not isinstance(t, (ly.lex.Space, ly.lex.Comment)): break if found: break insert = QTextCursor(block) text = cursor.selection().toPlainText() space = '\n' if '\n' in text else ' ' text = ''.join((name, ' =', mode, ' {', space, text, space, '}\n\n')) with cursortools.compress_undo(cursor): cursor.insertText('\\' + name) pos = insert.selectionStart() insert.insertText(text) if metainfo.info(cursor.document()).auto_indent: insert.setPosition(pos, QTextCursor.KeepAnchor) with cursortools.compress_undo(insert, True): indent.re_indent(insert)
def rhythm_apply(cursor, mainwindow): durs = inputdialog.getText(mainwindow, _("Apply Rhythm"), _("Enter a rhythm:"), complete=sorted(_history), regexp=r'([0-9./* ]|\\breve|\\longa|\\maxima)+', help="rhythm", icon=icons.get('tools-rhythm')) durations = durs.split() if durations: _history.add(durs.strip()) ly.rhythm.rhythm_overwrite(lydocument.cursor(cursor), durations)
def rhythm_apply(cursor, mainwindow): durs = inputdialog.getText(mainwindow, _("Apply Rhythm"), _("Enter a rhythm:"), complete = sorted(_history), regexp = r'([0-9./* ]|\\breve|\\longa|\\maxima)+', help = "rhythm", icon = icons.get('tools-rhythm')) if not durs: return # user cancelled dialog durations = durs.split() if durations: _history.add(durs.strip()) ly.rhythm.rhythm_overwrite(lydocument.cursor(cursor), durations)
def rhythm_apply(cursor, mainwindow): durs = inputdialog.getText(mainwindow, _("Apply Rhythm"), _("Enter a rhythm:"), complete = sorted(_history), regexp = r'([0-9./* ]|\\breve|\\longa|\\maxima)+', help = rhythm_help, icon = icons.get('tools-rhythm')) if durs and durs.split(): _history.add(durs.strip()) duration_source = remove_dups(itertools.cycle(durs.split())) with cursortools.Editor() as e: for c, d in duration_cursor_items(cursor): e.insertText(c, next(duration_source))
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 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': headerline += ' template-run;' text = headerline + '\n' + text # save the new snippet model.model().saveSnippet(name, text, title)
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 getModalTransposer(document, mainwindow): """Show a dialog and return the desired modal transposer. Returns None if the dialog was cancelled. """ language = documentinfo.info(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 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.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 = transpose_help, validate = validate) if text: words = text.split() return ly.pitch.ModalTransposer(int(words[0]), ly.pitch.ModalTransposer.getKeyIndex(words[1]))
def cut_assign(cursor): """Cuts selected text and assigns it to a LilyPond variable.""" # ask the variable name name = inputdialog.getText( None, _("Cut and Assign"), _("Please enter the name for the variable to assign the selected " "text to:"), regexp="[A-Za-z]+") if not name: return cursortools.strip_selection(cursor) # determine state at cursor block = cursortools.block(cursor) state = tokeniter.state(block) for t in tokeniter.partition(cursor).left: state.follow(t) mode = "" for p in state.parsers(): if isinstance(p, ly.lex.lilypond.ParseInputMode): if isinstance(p, ly.lex.lilypond.ParseLyricMode): mode = " \\lyricmode" elif isinstance(p, ly.lex.lilypond.ParseChordMode): mode = " \\chordmode" elif isinstance(p, ly.lex.lilypond.ParseFigureMode): mode = " \\figuremode" elif isinstance(p, ly.lex.lilypond.ParseDrumMode): mode = " \\drummode" break # find insertion place: found = False while block.previous().isValid(): block = block.previous() state = tokeniter.state(block) if isinstance(state.parser(), ly.lex.lilypond.ParseGlobal): found = True break tokens = tokeniter.tokens(block) for t in tokens: if isinstance(t, ly.lex.lilypond.Name): found = True break elif not isinstance(t, (ly.lex.Space, ly.lex.Comment)): break if found: break insert = QTextCursor(block) text = cursor.selection().toPlainText() space = '\n' if '\n' in text else ' ' text = ''.join((name, ' =', mode, ' {', space, text, space, '}\n\n')) with cursortools.compress_undo(cursor): cursor.insertText('\\' + name) pos = insert.selectionStart() insert.insertText(text) if metainfo.info(cursor.document()).auto_indent: insert.setPosition(pos, QTextCursor.KeepAnchor) with cursortools.compress_undo(insert, True): indent.re_indent(insert)
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 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))