def actionTriggered(self, name): # convert arpeggio_normal to arpeggioNormal, etc. name = _arpeggioTypes[name] cursor = self.mainwindow().textCursor() # which arpeggio type is last used? lastused = '\\arpeggioNormal' types = set(_arpeggioTypes.values()) block = cursor.block() while block.isValid(): s = types.intersection(tokeniter.tokens(block)) if s: lastused = s.pop() break block = block.previous() # where to insert source = tokeniter.Source.fromCursor(cursor, True, -1) with cursortools.editBlock(cursor): for p in music.music_items(source, tokens=source.tokens): c = source.cursor(p[-1], start=len(p[-1])) c.insertText('\\arpeggio') if name != lastused: cursortools.stripIndent(c) import indent indent.insertText(c, name + '\n') # just pick the first place return
def changeIndent(cursor, direction): """Changes the indent in the desired direction (-1 for left and +1 for right). Returns True if the indent operation was applied. The cursor may contain a selection. """ # get some variables from the document indent_vars = indentVariables(cursor.document()) blocks = list(cursortools.blocks(cursor)) block = blocks[0] pos = cursor.selectionStart() - block.position() token = tokeniter.tokens(block)[0] if tokeniter.tokens(block) else None if cursor.hasSelection() or pos == 0 or (token and isinstance(token, ly.lex.Space) and token.end >= pos): # decrease the indent state = tokeniter.state(block) current_indent = getIndent(block) new_indent = current_indent + direction * indent_vars['indent-width'] if state.mode() in ('lilypond', 'scheme'): computed_indent = computeIndent(block) if cmp(computed_indent, new_indent) == direction: new_indent = computed_indent diff = new_indent - current_indent with cursortools.editBlock(cursor): for block in blocks: setIndent(block, getIndent(block) + diff) return True
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 slotAccepted(self): """Makes the score and puts it in the editor.""" from . import build builder = build.Builder(self) cursor = self.parent().currentView().textCursor() with cursortools.editBlock(cursor): cursortools.insertText(cursor, builder.text()) indent.reIndent(cursor)
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(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.editBlock(cursor): cursor.insertText('\\' + name) if metainfo.info(cursor.document()).autoindent: indent.insertText(insert, text) else: insert.insertText(text)
def reformat(cursor): """Reformats the selection or the whole document, adjusting the whitespace.""" def newlinebefore(t): editor.insertText(tokeniter.cursor(block, t, end=0), '\n') def newlineafter(t): editor.insertText(tokeniter.cursor(block, t, start=len(t)), '\n') indent_vars = indent.indentVariables(cursor.document()) with cursortools.editBlock(cursor): indent.reIndent(cursor) with cursortools.Editor() as editor: for block in get_blocks(cursor): denters = [] tokens = tokeniter.tokens(block) nonspace_index = -1 for i, t in enumerate(tokens): if isinstance(t, ly.lex.Indent) and t in ('{', '<<'): denters.append(i) elif isinstance(t, ly.lex.Dedent) and t in ('}', '>>'): if denters: denters.pop() elif nonspace_index != -1: newlinebefore(t) elif not isinstance(t, ly.lex.Space): nonspace_index = i for i in denters: if i < nonspace_index: newlineafter(tokens[i]) # TODO: wrap long lines indent.reIndent(cursor) with cursortools.Editor() as editor: for block in get_blocks(cursor): tokens = tokeniter.tokens(block) if (len(tokens) == 2 and isinstance(tokens[0], ly.lex.Space) and isinstance(tokens[1], ( ly.lex.lilypond.LineComment, ly.lex.scheme.LineComment)) and len(tokens[1]) > 2 and len(set(tokens[1][:3])) == 1): # move commented lines with more than 2 comment characters # to column 0 editor.removeSelectedText(tokeniter.cursor(block, tokens[0])) else: # remove trialing whitespace for t in tokens[::-1]: if isinstance(t, ly.lex.Space): editor.removeSelectedText(tokeniter.cursor(block, t)) else: break
def actionTriggered(self, name): name = name[8:] direction = ['_', '', '^'][self.direction() + 1] isSpanner = name not in dynamic_marks if isSpanner: dynamic = dynamic_spanners[name] else: dynamic = '\\' + name cursor = self.mainwindow().textCursor() if not cursor.hasSelection(): # dynamic right before the cursor? left = tokeniter.partition(cursor).left if not left or not isinstance(left[-1], ly.lex.lilypond.Dynamic): # no, find the first pitch source = tokeniter.Source.fromCursor(cursor, True, -1) for p in music.music_items(source): cursor = source.cursor(p[-1], start=len(p[-1])) break cursor.insertText(direction + dynamic) self.mainwindow().currentView().setTextCursor(cursor) else: source = tokeniter.Source.selection(cursor, True) cursors = [source.cursor(p[-1], start=len(p[-1])) for p in music.music_items(source)] if not cursors: return c1, c2 = cursors[0], cursors[-1] # are there dynamics at the cursor? then skip them d1 = dynamics(c1) if d1: c1 = tokeniter.cursor(c1.block(), d1[-1], start=len(d1[-1])) with cursortools.editBlock(cursor): if len(cursors) > 1: # dynamics after the end cursor? d2 = dynamics(c2) if isSpanner and not d2: # don't terminate the spanner if there's a dynamic there c2.insertText('\\!') elif set(d1).intersection(dynamic_spanners.values()): # write the dynamic at the end if there's a spanner at start # remove ending \! if there terminator = tokeniter.find("\\!", d2) if terminator: c2 = tokeniter.cursor(c2.block(), terminator) if direction in d1: c2.insertText(dynamic) else: c2.insertText(direction + dynamic) return c1.insertText(direction + dynamic)
def actionTriggered(self, name): d = ['_', '', '^'][self.direction()+1] if name == "spanner_slur": spanner = d + '(', ')' elif name == "spanner_phrasingslur": spanner = d + '\\(', '\\)' elif name == "spanner_beam16": spanner = d + '[', ']' elif name == "spanner_trill": spanner = '\\startTrillSpan', '\\stopTrillSpan' cursor = self.mainwindow().textCursor() with cursortools.editBlock(cursor): for s, c in zip(spanner, spanner_positions(cursor)): c.insertText(s)
def insertText(cursor, text): """Inserts text and indents it if there are newlines in it.""" if '\n' not in text: cursor.insertText(text) return line = cursor.document().findBlock(cursor.selectionStart()).blockNumber() with cursortools.editBlock(cursor): cursor.insertText(text) block = cursor.document().findBlockByNumber(line) last = cursor.block() tokeniter.update(block) # tokenize inserted lines while last != block: block = block.next() if setIndent(block, computeIndent(block)): tokeniter.update(block)
def reIndent(cursor): """Re-indents the selected region or the whole document.""" if cursor.hasSelection(): blocks = cursortools.blocks(cursor) else: blocks = cursortools.allBlocks(cursor.document()) with cursortools.editBlock(cursor): for block in blocks: tokeniter.update(block) if tokeniter.state(block).mode() in ('lilypond', 'scheme'): indent = computeIndent(block) else: indent = getIndent(block) if setIndent(block, indent): tokeniter.update(block) # force token update if changed
def actionTriggered(self, name): if self.tool().shorthands.isChecked() and name in shorthands: text = '_-^'[self.direction()+1] + shorthands[name] else: text = ('_', '', '^')[self.direction()+1] + '\\' + name cursor = self.mainwindow().textCursor() selection = cursor.hasSelection() cursors = articulation_positions(cursor) if cursors: with cursortools.editBlock(cursor): for c in cursors: c.insertText(text) if not selection: self.mainwindow().currentView().setTextCursor(c) elif not selection: cursor.insertText(text)
def keyPressEvent(self, ev): if homekey.handle(self, ev): return super(View, self).keyPressEvent(ev) if metainfo.info(self.document()).autoindent: # run the indenter on Return or when the user entered a dedent token. import indent cursor = self.textCursor() if ev.text() == '\r' or (ev.text() in ('}', '#', '>') and indent.indentable(cursor)): with cursortools.editBlock(cursor, True): indent.autoIndentBlock(cursor.block()) # keep the cursor at the indent position on vertical move cursor = self.textCursor() pos = cursor.position() cursor.setPosition(cursor.block().position()) # move horizontal cursor.setPosition(pos) # move back to position self.setTextCursor(cursor)
def hyphenate(self): """Hyphenates selected Lyrics text.""" view = self.mainwindow().currentView() cursor = view.textCursor() found = [] # find text to hyphenate if cursor.hasSelection(): source = tokeniter.Source.selection(cursor) else: source = tokeniter.Source.document(cursor) for token in source: if isinstance(token, ly.lex.lilypond.LyricText): # a word found for m in _word_re.finditer(token): found.append((source.cursor(token, m.start(), m.end()), m.group())) if not found and cursor.hasSelection(): # no tokens were found, then tokenize the text again as if in lyricmode start = cursor.selectionStart() state = ly.lex.State(ly.lex.lilypond.ParseLyricMode) for token in state.tokens(cursor.selection().toPlainText()): if isinstance(token, ly.lex.lilypond.LyricText): # a word found for m in _word_re.finditer(token): cur = QTextCursor(cursor) cur.setPosition(start + token.pos + m.start()) cur.setPosition(start + token.pos + m.end(), QTextCursor.KeepAnchor) found.append((cur, m.group())) if not found and cursor.hasSelection(): # still not succeeded, then try flat text for m in _word_re.finditer(cursor.selection().toPlainText()): cur = QTextCursor(cursor) cur.setPosition(start + m.start()) cur.setPosition(start + m.end(), QTextCursor.KeepAnchor) found.append((cur, m.group())) if found: import hyphendialog h = hyphendialog.HyphenDialog(self.mainwindow()).hyphenator() if h: with cursortools.editBlock(cursor): for cur, word in found: hyph_word = h.inserted(word, ' -- ') if word != hyph_word: cur.insertText(hyph_word)
def insert(name, view): """Insert named snippet into the view.""" text, variables = snippets.get(name) cursor = view.textCursor() selection = variables.get('selection', '') if 'yes' in selection and not cursor.hasSelection(): return if 'strip' in selection: cursortools.strip(cursor) pos = cursor.selectionStart() line = cursor.document().findBlock(pos).blockNumber() with cursortools.editBlock(cursor): # insert the snippet, might return a new cursor if 'python' in variables: new = insert_python(text, cursor, name, view) else: new = insert_snippet(text, cursor, variables) # QTextBlocks the snippet starts and ends block = cursor.document().findBlockByNumber(line) last = cursor.block() # re-indent if not explicitly suppressed by a 'indent: no' variable if last != block and 'no' not in variables.get('indent', ''): tokeniter.update(block) # tokenize inserted lines while True: block = block.next() if indent.setIndent(block, indent.computeIndent(block)): tokeniter.update(block) if block == last: break if not new and 'keep' in selection: end = cursor.position() cursor.setPosition(pos) cursor.setPosition(end, QTextCursor.KeepAnchor) view.setTextCursor(new or cursor)
def rhythm_remove(cursor): with cursortools.editBlock(cursor): for c in cursors(cursor, ly.lex.lilypond.Duration): c.removeSelectedText()
def rhythm_remove_scaling(cursor): with cursortools.editBlock(cursor): for c in cursors(cursor, ly.lex.lilypond.Scaling): c.removeSelectedText()