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 c = lydocument.cursor(cursor) c.end = None source = lydocument.Source(c, True, ly.document.OUTSIDE, True) for p in ly.rhythm.music_tokens(source): cursor = source.cursor(p[-1], start=len(p[-1])) break cursor.insertText(direction + dynamic) self.mainwindow().currentView().setTextCursor(cursor) else: c = lydocument.cursor(cursor) source = lydocument.Source(c, True, tokens_with_position=True) cursors = [ source.cursor(p[-1], start=len(p[-1])) for p in ly.rhythm.music_tokens(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.compress_undo(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 articulation_positions(cursor): """Returns a list of positions where an articulation can be added. Every position is given as a QTextCursor instance. If the cursor has a selection, all positions in the selection are returned. """ c = lydocument.cursor(cursor) if not cursor.hasSelection(): # just select until the end of the current line c.select_end_of_block() rests = True partial = ly.document.OUTSIDE else: rests = False partial = ly.document.INSIDE source = lydocument.Source(c, True, partial, True) positions = [] for p in ly.rhythm.music_tokens(source): if not rests and isinstance(p[0], ly.lex.lilypond.Rest): continue positions.append(source.cursor(p[-1], start=len(p[-1]))) if not cursor.hasSelection(): break # leave if first found, that's enough return positions
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 c = lydocument.cursor(cursor) c.select_end_of_block() source = lydocument.Source(c, True, ly.document.OUTSIDE, True) with cursortools.compress_undo(cursor): for p in ly.rhythm.music_tokens(source): c = source.cursor(p[-1], start=len(p[-1])) c.insertText('\\arpeggio') if name != lastused: cursortools.strip_indent(c) indent = c.block().text()[:c.position()-c.block().position()] c.insertText(name + '\n' + indent) # just pick the first place return
def positions(cursor): """Return a list of QTextCursors describing the grob the cursor points at. When the cursor point at e.g. a slur, the returned cursors describe both ends of the slur. The returned list may contain zero to two cursors. """ c = lydocument.cursor(cursor) c.end = None source = lydocument.Source(c, True) for token in source.tokens: break else: return [] cur = source.cursor(token, end=0) cursors = [cur] # some heuristic to find the relevant range(s) the linked grob represents if isinstance(token, ly.lex.lilypond.Direction): # a _, - or ^ is found; find the next token for token in source: if not isinstance(token, (ly.lex.Space, ly.lex.Comment)): break end = token.end + source.block.position() if token == '\\markup': # find the end of the markup expression depth = source.state.depth() for token in source: if source.state.depth() < depth: end = token.end + source.block.position() break elif token == '"': # find the end of the string for token in source: if isinstance(token, ly.lex.StringEnd): end = token.end + source.block.position() break elif isinstance(token, ly.lex.MatchStart): # find the end of slur, beam. ligature, phrasing slur, etc. name = token.matchname nest = 1 for token in source: if isinstance(token, ly.lex.MatchEnd) and token.matchname == name: nest -= 1 if nest == 0: cursors.append(source.cursor(token)) break elif isinstance(token, ly.lex.MatchStart) and token.matchname == name: nest += 1 cur.setPosition(end, QTextCursor.KeepAnchor) return cursors
def actionTriggered(self, name): d = ['_', '', '^'][self.direction()+1] single = '' if name == "grace_grace": inner = '' outer = '\\grace { ', ' }' single = '\\grace ' elif name == "grace_beam": inner = d + '[', ']' outer = '\\grace { ', ' }' elif name == "grace_accia": inner = '' outer = '\\acciaccatura { ', ' }' single = '\\acciaccatura ' elif name == "grace_appog": inner = '' outer = '\\appoggiatura { ', ' }' single = '\\appoggiatura ' elif name == "grace_slash": inner = d + '[', ']' outer = '\\slashedGrace { ', ' }' elif name == "grace_after": inner = d + '{ ' outer = '\\afterGrace ', ' }' cursor = self.mainwindow().textCursor() with cursortools.compress_undo(cursor): if inner: for i, ci in zip(inner, spanner_positions(cursor)): ci.insertText(i) if cursor.hasSelection(): ins = self.mainwindow().textCursor() ins.setPosition(cursor.selectionStart()) ins.insertText(outer[0]) ins.setPosition(cursor.selectionEnd()) ins.insertText(outer[1]) else: if single: cursor.insertText(single) else: c = lydocument.cursor(cursor) c.end = None source = lydocument.Source(c, True, ly.document.OUTSIDE, True) music_list = list(ly.rhythm.music_tokens(source)) try: m = music_list[2][0] after = source.cursor(m, 1) except IndexError: after = self.mainwindow().textCursor() after.movePosition(cursor.EndOfLine) after.insertText(outer[1]) cursor.insertText(outer[0])
def actionTriggered(self, name): cursor = self.mainwindow().textCursor() style = _glissandoStyles[name] c = lydocument.cursor(cursor) c.select_end_of_block() source = lydocument.Source(c, True, ly.document.OUTSIDE, True) for p in ly.rhythm.music_tokens(source): c = source.cursor(p[-1], start=len(p[-1])) if style: text = "-\\tweak #'style #'{0} \\glissando".format(style) else: text = '\\glissando' c.insertText(text) return
def hyphenate(self): """Hyphenates selected Lyrics text.""" view = self.mainwindow().currentView() cursor = view.textCursor() found = [] c = lydocument.cursor(cursor, select_all=True) # find text to hyphenate source = lydocument.Source(c) for token in source: if isinstance(token, ly.lex.lilypond.LyricText): # a word found pos = source.position(token) for m in _word_re.finditer(token): found.append((pos + m.start(), pos + 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 pos = start + token.pos for m in _word_re.finditer(token): found.append( (pos + m.start(), pos + m.end(), m.group())) if not found and cursor.hasSelection(): # still not succeeded, then try flat text for m in _word_re.finditer(cursor.selection().toPlainText()): found.append((start + m.start(), start + m.end(), m.group())) if found: import hyphendialog h = hyphendialog.HyphenDialog(self.mainwindow()).hyphenator() if h: with c.document as d: for start, end, word in found: hyph_word = h.inserted(word, ' -- ') if word != hyph_word: d[start:end] = hyph_word
def spanner_positions(cursor): """Return a list with 0 to 2 QTextCursor instances. At the first cursor a starting spanner item can be inserted, at the second an ending item. """ c = lydocument.cursor(cursor) if cursor.hasSelection(): partial = ly.document.INSIDE else: # just select until the end of the current line c.select_end_of_block() partial = ly.document.OUTSIDE source = lydocument.Source(c, True, partial, True) positions = [source.cursor(p[-1], start=len(p[-1])) for p in ly.rhythm.music_tokens(source)] if cursor.hasSelection(): del positions[1:-1] else: del positions[2:] return positions