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() with cursortools.compress_undo(cursor): for item in ly.rhythm.music_items(c, partial=ly.document.OUTSIDE): c = QTextCursor(cursor.document()) c.setPosition(item.end) 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 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() with cursortools.compress_undo(cursor): for item in ly.rhythm.music_items(c, partial=ly.document.OUTSIDE): c = QTextCursor(cursor.document()) c.setPosition(item.end) 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 _cursor_moved(self): tc = QTextCursor(self.Editor.textCursor()) # copy of cursor self.ColNumber.setText(str(tc.positionInBlock())) tb = tc.block() if tb == self.last_text_block: return # still on same line, nothing more to do # Fill in line-number widget, line #s are origin-1 self.LineNumber.setText(str(tb.blockNumber() + 1)) # Fill in the image name and folio widgets pn = self.page_model.page_index(tc.position()) if pn is not None: # the page model has info on this position self.ImageFilename.setText(self.page_model.filename(pn)) self.Folio.setText(self.page_model.folio_string(pn)) else: # no image data, or cursor is above page 1 self.ImageFilename.setText('') self.Folio.setText('') # Change the current-line "extra selection" to the new current line. # Note: the cursor member of the extra selection may not have a # selection. If it does, the current line highlight disappears. The # cursor "tc" may or may not have a selection; to make sure, we clone # it and remove any selection from it. cltc = QTextCursor(tc) cltc.setPosition(tc.position(), QTextCursor.MoveAnchor) # Set the cloned cursor into the current line extra selection object. self.current_line_sel.cursor = cltc # Re-assign the list of extra selections to force update of display. self.Editor.setExtraSelections(self.extra_sel_list)
def paintEvent(self, event): if not globalSettings.lineNumbersEnabled: return QWidget.paintEvent(self, event) painter = QPainter(self) painter.fillRect(event.rect(), colorValues['lineNumberArea']) cursor = QTextCursor(self.editor.document()) cursor.movePosition(QTextCursor.Start) atEnd = False if globalSettings.relativeLineNumbers: relativeTo = self.editor.textCursor().blockNumber() else: relativeTo = -1 while not atEnd: rect = self.editor.cursorRect(cursor) block = cursor.block() if block.isVisible(): number = str(cursor.blockNumber() - relativeTo).replace( '-', '−') painter.setPen(colorValues['lineNumberAreaText']) painter.drawText(0, rect.top(), self.width() - 2, self.fontMetrics().height(), Qt.AlignRight, number) cursor.movePosition(QTextCursor.EndOfBlock) atEnd = cursor.atEnd() if not atEnd: cursor.movePosition(QTextCursor.NextBlock)
def _cursor_moved(self): tc = QTextCursor(self.Editor.textCursor()) # copy of cursor self.ColNumber.setText( str( tc.positionInBlock() ) ) tb = tc.block() if tb == self.last_text_block : return # still on same line, nothing more to do # Fill in line-number widget, line #s are origin-1 self.LineNumber.setText( str( tb.blockNumber()+1 ) ) # Fill in the image name and folio widgets pn = self.page_model.page_index(tc.position()) if pn is not None : # the page model has info on this position self.ImageFilename.setText(self.page_model.filename(pn)) self.Folio.setText(self.page_model.folio_string(pn)) else: # no image data, or cursor is above page 1 self.ImageFilename.setText('') self.Folio.setText('') # Change the current-line "extra selection" to the new current line. # Note: the cursor member of the extra selection may not have a # selection. If it does, the current line highlight disappears. The # cursor "tc" may or may not have a selection; to make sure, we clone # it and remove any selection from it. cltc = QTextCursor(tc) cltc.setPosition(tc.position(),QTextCursor.MoveAnchor) # Set the cloned cursor into the current line extra selection object. self.current_line_sel.cursor = cltc # Re-assign the list of extra selections to force update of display. self.Editor.setExtraSelections(self.extra_sel_list)
def cmdJoinLines(self, cmd, repeatLineCount=None): if repeatLineCount is not None: self._selectRangeForRepeat(repeatLineCount) start, end = self._selectedLinesRange() count = end - start if not count: # nothing to join return self._saveLastEditLinesCmd(cmd, end - start + 1) cursor = QTextCursor(self._qpart.document().findBlockByNumber(start)) with self._qpart: for _ in range(count): cursor.movePosition(QTextCursor.EndOfBlock) cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) self.moveToFirstNonSpace(cursor, QTextCursor.KeepAnchor) nonEmptyBlock = cursor.block().length() > 1 cursor.removeSelectedText() if nonEmptyBlock: cursor.insertText(' ') self._qpart.setTextCursor(cursor)
def cursors(self): """Cursors for rectangular selection. 1 cursor for every line """ cursors = [] if self._start is not None: startLine, startVisibleCol = self._start currentLine, currentCol = self._qpart.cursorPosition if abs(startLine - currentLine) > self._MAX_SIZE or \ abs(startVisibleCol - currentCol) > self._MAX_SIZE: # Too big rectangular selection freezes the GUI self._qpart.userWarning.emit('Rectangular selection area is too big') self._start = None return [] currentBlockText = self._qpart.textCursor().block().text() currentVisibleCol = self._realToVisibleColumn(currentBlockText, currentCol) for lineNumber in range(min(startLine, currentLine), max(startLine, currentLine) + 1): block = self._qpart.document().findBlockByNumber(lineNumber) cursor = QTextCursor(block) realStartCol = self._visibleToRealColumn(block.text(), startVisibleCol) realCurrentCol = self._visibleToRealColumn(block.text(), currentVisibleCol) if realStartCol is None: realStartCol = block.length() # out of range value if realCurrentCol is None: realCurrentCol = block.length() # out of range value cursor.setPosition(cursor.block().position() + min(realStartCol, block.length() - 1)) cursor.setPosition(cursor.block().position() + min(realCurrentCol, block.length() - 1), QTextCursor.KeepAnchor) cursors.append(cursor) return cursors
def cursor_position(self, position): line, column = position line = min(line, self.line_count() - 1) column = min(column, len(self.line_text(line))) cursor = QTextCursor(self.document().findBlockByNumber(line)) cursor.setPosition(cursor.block().position() + column, QTextCursor.MoveAnchor) self.setTextCursor(cursor)
def set_new_cursor_position(self, cursor: QTextCursor) -> None: """ Set new cursor position in main status bar Args: cursor(QTextCursor): active cursor of code editor in active tab """ self.statusbar_widget.set_line_and_column( cursor.block().blockNumber() + 1, cursor.positionInBlock() + 1)
def cursors(self): """Cursors for rectangular selection. 1 cursor for every line """ cursors = [] if self._start is not None: startLine, startVisibleCol = self._start currentLine, currentCol = self._qpart.cursorPosition if abs(startLine - currentLine) > self._MAX_SIZE or \ abs(startVisibleCol - currentCol) > self._MAX_SIZE: # Too big rectangular selection freezes the GUI self._qpart.userWarning.emit( 'Rectangular selection area is too big') self._start = None return [] currentBlockText = self._qpart.textCursor().block().text() currentVisibleCol = self._realToVisibleColumn( currentBlockText, currentCol) for lineNumber in range(min(startLine, currentLine), max(startLine, currentLine) + 1): block = self._qpart.document().findBlockByNumber(lineNumber) cursor = QTextCursor(block) realStartCol = self._visibleToRealColumn( block.text(), startVisibleCol) realCurrentCol = self._visibleToRealColumn( block.text(), currentVisibleCol) if realStartCol is None: realStartCol = block.length() # out of range value if realCurrentCol is None: realCurrentCol = block.length() # out of range value cursor.setPosition(cursor.block().position() + min(realStartCol, block.length() - 1)) cursor.setPosition( cursor.block().position() + min(realCurrentCol, block.length() - 1), QTextCursor.KeepAnchor) cursors.append(cursor) return cursors
def select_between(tc: QTextCursor, start_char: str, end_char: str, select_inside: bool = True) -> None: pos = tc.positionInBlock() text = tc.block().text() start_pos = text.rfind(start_char, 0, pos) if start_pos == -1: return end_pos = text.find(end_char, pos) if end_pos == -1: return if select_inside: start_pos += len(start_char) else: end_pos += len(end_char) while end_pos < len(text) and text[end_pos].isspace(): end_pos += 1 tc.setPosition(tc.block().position() + start_pos) tc.setPosition(tc.block().position() + end_pos, QTC.KeepAnchor)
def highlightHorizontalLine(self, text, cursor, bf, strt): found = False for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text): prevBlock = self.currentBlock().previous() prevCursor = QTextCursor(prevBlock) prev = prevBlock.text() prevAscii = str(prev.replace(u'\u2029','\n')) if prevAscii.strip(): #print "Its a header" prevCursor.select(QTextCursor.LineUnderCursor) #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) formatRange = QTextLayout.FormatRange() formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] formatRange.length = prevCursor.block().length() formatRange.start = 0 prevCursor.block().layout().setAdditionalFormats([formatRange]) self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR']) for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['eHR'],text): prevBlock = self.currentBlock().previous() prevCursor = QTextCursor(prevBlock) prev = prevBlock.text() prevAscii = str(prev.replace(u'\u2029','\n')) if prevAscii.strip(): #print "Its a header" prevCursor.select(QTextCursor.LineUnderCursor) #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) formatRange = QTextLayout.FormatRange() formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] formatRange.length = prevCursor.block().length() formatRange.start = 0 prevCursor.block().layout().setAdditionalFormats([formatRange]) self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR']) return found
def indent_line(self, line_no, indent_length): block = self._get_block(line_no) cursor = QTextCursor(block) cursor.joinPreviousEditBlock() cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) if indent_length < 0: for i in range(-indent_length): cursor.deleteChar() else: cursor.insertText(" " * indent_length) if indent_length: cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor) line = unicode(cursor.block().text()) if len(line) and line[0] == " ": cursor.movePosition(QTextCursor.NextWord, QTextCursor.MoveAnchor) self.editview.setTextCursor(cursor) cursor.endEditBlock()
def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), colorValues['lineNumberArea']) cursor = QTextCursor(self.document()) cursor.movePosition(QTextCursor.Start) atEnd = False while not atEnd: rect = self.cursorRect(cursor) block = cursor.block() if block.isVisible(): number = str(cursor.blockNumber() + 1) painter.setPen(colorValues['lineNumberAreaText']) painter.drawText(0, rect.top(), self.lineNumberArea.width()-2, self.fontMetrics().height(), Qt.AlignRight, number) cursor.movePosition(QTextCursor.EndOfBlock) atEnd = cursor.atEnd() if not atEnd: cursor.movePosition(QTextCursor.NextBlock)
def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), Qt.cyan) cursor = QTextCursor(self.document()) cursor.movePosition(QTextCursor.Start) atEnd = False while not atEnd: rect = self.cursorRect(cursor) block = cursor.block() if block.isVisible(): number = str(cursor.blockNumber() + 1) painter.setPen(Qt.darkCyan) painter.drawText(0, rect.top(), self.lineNumberArea.width() - 2, self.fontMetrics().height(), Qt.AlignRight, number) cursor.movePosition(QTextCursor.EndOfBlock) atEnd = cursor.atEnd() if not atEnd: cursor.movePosition(QTextCursor.NextBlock)
def _cursor_moved(self): tc = QTextCursor(self.Editor.textCursor()) # copy of cursor self.ColNumber.setText( str( tc.positionInBlock() ) ) tb = tc.block() if tb == self.last_text_block : return # still on same line, nothing more to do # Fill in line-number widget, line #s are origin-1 self.LineNumber.setText( str( tb.blockNumber()+1 ) ) # Fill in the image name and folio widgets pn = self.page_model.page_index(tc.position()) if pn is not None : # the page model has info on this position self.ImageFilename.setText(self.page_model.filename(pn)) self.Folio.setText(self.page_model.folio_string(pn)) else: # no image data, or cursor is above page 1 self.ImageFilename.setText('') self.Folio.setText('') # Change the extra selection to the current line. The cursor needs # to have no selection. Yes, we are here because the cursor moved, # but that doesn't mean no-selection; it might have moved because # of a shift-click extending the selection. #xtc.clearSelection() self.current_line_sel.cursor = tc self.Editor.setExtraSelections(self.extra_sel_list)
def paintEvent(self, event): if not globalSettings.lineNumbersEnabled: return QWidget.paintEvent(self, event) painter = QPainter(self) painter.fillRect(event.rect(), colorValues['lineNumberArea']) cursor = QTextCursor(self.editor.document()) cursor.movePosition(QTextCursor.Start) atEnd = False if globalSettings.relativeLineNumbers: relativeTo = self.editor.textCursor().blockNumber() else: relativeTo = -1 while not atEnd: rect = self.editor.cursorRect(cursor) block = cursor.block() if block.isVisible(): number = str(cursor.blockNumber() - relativeTo).replace('-', '−') painter.setPen(colorValues['lineNumberAreaText']) painter.drawText(0, rect.top(), self.width() - 2, self.fontMetrics().height(), Qt.AlignRight, number) cursor.movePosition(QTextCursor.EndOfBlock) atEnd = cursor.atEnd() if not atEnd: cursor.movePosition(QTextCursor.NextBlock)
class TextSectionEditor(QTextDocument): def __init__(self, sectionId: str, content="", pos=0, selectionStart=0, selectionEnd=0): super().__init__() self.dtb = DTB() self.sectionId = sectionId self.setDefaultStyleSheet(CSS) self.setHtml(content) self.s_start = selectionStart self.s_end = selectionEnd self.cur = QTextCursor(self) self.cur.setPosition(pos) self.result = { "text": "", "cursorPosition": self.pos, "eventAccepted": False } self.pending = False @property def len(self): return self.characterCount() @property def s_len(self): return abs(self.s_end - self.s_start) @property def pos(self): return self.cur.position() def onChange(self): self._update_ddb() self.setResponse(True) return self.result def onLoad(self): item = self.dtb.getDB("Section", self.sectionId) self.setHtml(item["text"]) self.setResponse(True, cur=self.len) return self.result def onMenu(self, style={}, **kwargs): backup = [self.pos, self.s_start, self.s_end] for k, v in style.items(): # un peut répétition mais uniquement sur des if alors ... if k == "fgColor": self._set_fg_color(v) elif k == "underline": # pragma: no branch self._set_underline(style["underline"]) self.cur.setPosition(backup[0]) self.s_start = backup[1] self.s_end = backup[2] if self.pending: # else: self._update_ddb() else: self.setResponse(False) return self.result def onKey(self, event): # on met en premier ceux à qui il faut passer l'event if event["key"] == Qt.Key_Return: self.do_key_return(event) elif event["modifiers"] == Qt.ControlModifier: self.do_control_modifier(event) else: self.setResponse(False) if self.pending: self._update_ddb() return self.result def do_control_modifier(self, event): if event == KeyW.KEY_1: self.do_key_1() elif event == KeyW.KEY_2: self.do_key_2() elif event == KeyW.KEY_3: self.do_key_3() elif event == KeyW.KEY_4: self.do_key_4() elif event["key"] == Qt.Key_U: # pragma: no branch self.do_key_u() def do_key_1(self): self._set_fg_color(BLACK) def do_key_2(self): self._set_fg_color(BLUE) def do_key_3(self): self._set_fg_color(GREEN) def do_key_4(self): self._set_fg_color(RED) def do_key_return(self, event): block = self.findBlock(self.pos) if event["modifiers"] == Qt.ControlModifier: self._appendEmptyBlock() elif event["modifiers"] == Qt.ShiftModifier: self._insertEmptyBlock() elif block.blockFormat().headingLevel(): self._appendEmptyBlock() else: if self._headerAutoFormat(): return else: self.setResponse(False) def do_key_u(self): self._set_underline("toggle") def setResponse(self, accepted, text=None, cur=None): self.result["text"] = text or self.toHtml() self.result["cursorPosition"] = cur or self.cur.position() self.result["eventAccepted"] = accepted def _appendEmptyBlock(self, section="p", pre_move=QTextCursor.EndOfBlock, set_response=True): self.cur.movePosition(pre_move) self.cur.insertBlock(blockFormat[section], blockCharFormat[section]) self.cur.insertFragment(QTextDocumentFragment.fromPlainText("")) self.pending = True if set_response: self.setResponse(True) def _insertEmptyBlock(self, section="p", pre_move=QTextCursor.StartOfBlock, set_response=True): old = self.cur.blockFormat().headingLevel() self._appendEmptyBlock(pre_move=pre_move, set_response=False) self._set_block_style(old) self.cur.movePosition(QTextCursor.PreviousBlock) self._set_block_style(section) self.pending = True if set_response: # pragma: no branch self.setResponse(True) def _headerAutoFormat(self): # on check les expressions régulières suivantes: # #, ##, ###, ####, ##### line = self.cur.block().text() matched = RE_AUTOPARAGRAPH_DEBUT.search(line) matched_at_start = False if not matched: matched = RE_AUTOPARAGRAPH_FIN.search(line) if not matched: return False else: matched_at_start = True # strip les # et applique les styles par défault level = len(matched.groups()[0]) if matched_at_start: text = self.cur.block().text()[level + 1:] else: text = self.cur.block().text()[:-(level + 1)] self.cur.beginEditBlock() self.cur.select(QTextCursor.LineUnderCursor) self.cur.setCharFormat(blockCharFormat[level]) self.cur.insertText(text) self.cur.setBlockFormat(blockFormat[level]) self.cur.endEditBlock() self._appendEmptyBlock() self.pending = True self.setResponse(True) return True @contextmanager def _merge_char_format(self): self._select_word_or_selection() f: QTextCharFormat = QTextCharFormat() yield f self.cur.mergeCharFormat(f) self.pending = True self.setResponse(True, cur=max(self.pos, self.s_start, self.s_end)) def _select_word_or_selection(self): if self.s_start < self.pos: self.cur.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, self.s_len) elif self.s_end > self.pos: self.cur.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, self.s_len) else: self.cur.select(QTextCursor.WordUnderCursor) def _set_block_style(self, level): self.cur.setBlockFormat(blockFormat[level]) self.cur.setBlockCharFormat(blockCharFormat[level]) def _set_fg_color(self, color): with self._merge_char_format() as f: f.setForeground(QBrush(QColor(color))) def _set_underline(self, value): with self._merge_char_format() as f: if value == "toggle": value = not self.cur.charFormat().fontUnderline() f.setFontUnderline(value) def _update_ddb(self): new_body = TextSectionFormatter(self.toHtml()).build_body() self.dtb.setDB("Section", self.sectionId, {"text": new_body}) return new_body
class Log(QTextBrowser): """Widget displaying output from a Job.""" def __init__(self, parent=None): super(Log, self).__init__(parent) self.setOpenLinks(False) self.cursor = QTextCursor(self.document()) self._types = job.ALL self._lasttype = None self._formats = self.logformats() def setMessageTypes(self, types): """Set the types of Job output to display. types is a constant bitmask from job, like job.STDOUT etc. By default job.ALL is used. """ self._types = types def messageTypes(self): """Return the set message types (job.ALL by default).""" return self._types def connectJob(self, job): """Gives us the output from the Job (past and upcoming).""" for msg, type in job.history(): self.write(msg, type) job.output.connect(self.write) def textFormat(self, type): """Returns a QTextFormat() for the given type.""" return self._formats[type] def write(self, message, type): """Writes the given message with the given type to the log. The keepScrolledDown context manager is used to scroll the log further down if it was scrolled down at that moment. If two messages of a different type are written after each other a newline is inserted if otherwise the message would continue on the same line. """ if type & self._types: with self.keepScrolledDown(): changed = type != self._lasttype self._lasttype = type if changed and self.cursor.block().text() and not message.startswith('\n'): self.cursor.insertText('\n') self.writeMessage(message, type) def writeMessage(self, message, type): """Inserts the given message in the text with the textformat belonging to type.""" self.cursor.insertText(message, self.textFormat(type)) @contextlib.contextmanager def keepScrolledDown(self): """Performs a function, ensuring the log stays scrolled down if it was scrolled down on start.""" vbar = self.verticalScrollBar() scrolleddown = vbar.value() == vbar.maximum() try: yield finally: if scrolleddown: vbar.setValue(vbar.maximum()) def logformats(self): """Returns a dictionary with QTextCharFormats for the different types of messages. Besides the STDOUT, STDERR, NEUTRAL, FAILURE and SUCCESS formats there is also a "link" format, that looks basically the same as the output formats, but blueish and underlined, to make parts of the output (e.g. filenames) look clickable. """ textColor = QApplication.palette().color(QPalette.WindowText) successColor = qutil.addcolor(textColor, 0, 128, 0) # more green failureColor = qutil.addcolor(textColor, 128, 0, 0) # more red linkColor = qutil.addcolor(textColor, 0, 0, 128) # more blue stdoutColor = qutil.addcolor(textColor, 64, 64, 0) # more purple s = QSettings() s.beginGroup("log") outputFont = QFont(s.value("fontfamily", "monospace", str)) outputFont.setPointSizeF(s.value("fontsize", 9.0, float)) output = QTextCharFormat() output.setFont(outputFont) # enable zooming the log font size output.setProperty(QTextFormat.FontSizeAdjustment, 0) stdout = QTextCharFormat(output) stdout.setForeground(stdoutColor) stderr = QTextCharFormat(output) link = QTextCharFormat(output) link.setForeground(linkColor) link.setFontUnderline(True) status = QTextCharFormat() status.setFontWeight(QFont.Bold) neutral = QTextCharFormat(status) success = QTextCharFormat(status) success.setForeground(successColor) failure = QTextCharFormat(status) failure.setForeground(failureColor) return { job.STDOUT: stdout, job.STDERR: stderr, job.NEUTRAL: neutral, job.SUCCESS: success, job.FAILURE: failure, 'link': link, }
def display_tooltips_for_cursor(self, cursor: QTextCursor, display_point: QPoint) -> None: """Displays a tooltip at current cursor position if a citation tag or an image tag is under cursor. Hides the tooltip if no tags are under cursor""" def is_inside_tag(tag, cursor_pos) -> bool: """Returns True if given cursor position is inside tag boundaries""" return tag[0] <= cursor_pos < (tag[1] + tag[0]) show_citation_tooltips = self.SettingsManager.get_setting_value( "Editor/Show citation tooltips") show_image_tooltips = self.SettingsManager.get_setting_value( "Editor/Show image tooltips") hide_tooltip = True current_line_text = cursor.block().text() current_line_tags = self.highlighter.get_tags(current_line_text) cursor_pos = cursor.positionInBlock() for tag in current_line_tags: tag_text = current_line_text[tag[0]:tag[0] + tag[1]] if show_citation_tooltips and tag[ 2] == "citation" and is_inside_tag(tag, cursor_pos): # Remove brackets and @ symbol from the tag text citation_identifier = components.extractors.citation_extractor( tag_text)[0] # If the citekey is not in self.document_info["citations"] or doesn't have a citation text yet, show # placeholder if citation_identifier not in self.document_structure[ "citations"]: QToolTip.showText(display_point, "Fetching citation info...", self, QRect(), 5000) hide_tooltip = False elif citation_identifier in self.document_structure[ "citations"] and self.document_structure["citations"][ citation_identifier]["citation"] == "": QToolTip.showText(display_point, "Fetching citation info...", self, QRect(), 5000) hide_tooltip = False # If the citekey is in self.document_info["citations"] and citation show citation info else: QToolTip.showText( display_point, self.document_structure["citations"] [citation_identifier]["citation"], self, QRect(), 5000) hide_tooltip = False elif show_image_tooltips and tag[2] == "image" and is_inside_tag( tag, cursor_pos): # Do nothing if not self.SettingsManager.get_setting_value( "Editor/Show citation tooltips"): return path = tag_text[tag_text.find("(") + 1:tag_text.find(")")] width = self.SettingsManager.get_setting_value( "Editor/Image tooltip width") height = self.SettingsManager.get_setting_value( "Editor/Image tooltip height") QToolTip.showText( display_point, f"<img src='{path}' width='{width}' height='{height}'>", self, QRect(), 5000) hide_tooltip = False # Hide tooltip if no image or citation is under cursor if hide_tooltip: QToolTip.hideText()
def is_empty_line(cursor: QtGui.QTextCursor) -> bool: return cursor.block().text() in {"\N{PARAGRAPH SEPARATOR}", "\n", ""}
class Log(QTextBrowser): """Widget displaying output from a Job.""" def __init__(self, parent=None): super(Log, self).__init__(parent) self.setOpenLinks(False) self.cursor = QTextCursor(self.document()) self._types = job.ALL self._lasttype = None self._formats = self.logformats() def setMessageTypes(self, types): """Set the types of Job output to display. types is a constant bitmask from job, like job.STDOUT etc. By default job.ALL is used. """ self._types = types def messageTypes(self): """Return the set message types (job.ALL by default).""" return self._types def connectJob(self, job): """Gives us the output from the Job (past and upcoming).""" for msg, type in job.history(): self.write(msg, type) job.output.connect(self.write) def textFormat(self, type): """Returns a QTextFormat() for the given type.""" return self._formats[type] def write(self, message, type): """Writes the given message with the given type to the log. The keepScrolledDown context manager is used to scroll the log further down if it was scrolled down at that moment. If two messages of a different type are written after each other a newline is inserted if otherwise the message would continue on the same line. """ if type & self._types: with self.keepScrolledDown(): changed = type != self._lasttype self._lasttype = type if changed and self.cursor.block().text( ) and not message.startswith('\n'): self.cursor.insertText('\n') self.writeMessage(message, type) def writeMessage(self, message, type): """Inserts the given message in the text with the textformat belonging to type.""" self.cursor.insertText(message, self.textFormat(type)) @contextlib.contextmanager def keepScrolledDown(self): """Performs a function, ensuring the log stays scrolled down if it was scrolled down on start.""" vbar = self.verticalScrollBar() scrolleddown = vbar.value() == vbar.maximum() try: yield finally: if scrolleddown: vbar.setValue(vbar.maximum()) def logformats(self): """Returns a dictionary with QTextCharFormats for the different types of messages. Besides the STDOUT, STDERR, NEUTRAL, FAILURE and SUCCESS formats there is also a "link" format, that looks basically the same as the output formats, but blueish and underlined, to make parts of the output (e.g. filenames) look clickable. """ textColor = QApplication.palette().color(QPalette.WindowText) successColor = qutil.addcolor(textColor, 0, 128, 0) # more green failureColor = qutil.addcolor(textColor, 128, 0, 0) # more red linkColor = qutil.addcolor(textColor, 0, 0, 128) # more blue stdoutColor = qutil.addcolor(textColor, 64, 64, 0) # more purple s = QSettings() s.beginGroup("log") outputFont = QFont(s.value("fontfamily", "monospace", str)) outputFont.setPointSizeF(s.value("fontsize", 9.0, float)) output = QTextCharFormat() output.setFont(outputFont) # enable zooming the log font size output.setProperty(QTextFormat.FontSizeAdjustment, 0) stdout = QTextCharFormat(output) stdout.setForeground(stdoutColor) stderr = QTextCharFormat(output) link = QTextCharFormat(output) link.setForeground(linkColor) link.setFontUnderline(True) status = QTextCharFormat() status.setFontWeight(QFont.Bold) neutral = QTextCharFormat(status) success = QTextCharFormat(status) success.setForeground(successColor) failure = QTextCharFormat(status) failure.setForeground(failureColor) return { job.STDOUT: stdout, job.STDERR: stderr, job.NEUTRAL: neutral, job.SUCCESS: success, job.FAILURE: failure, 'link': link, }