%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/dialogs/ |
Current File : //lib/calibre/calibre/gui2/dialogs/template_dialog_code_widget.py |
''' Created on 26 Mar 2021 @author: Charles Haley Based on classes in calibre.gui2.tweak_book.editor License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net> ''' from qt.core import ( QFont, QPainter, QPalette, QPlainTextEdit, QRect, Qt, QTextEdit, QTextFormat, QTextCursor ) from calibre.gui2.tweak_book.editor.text import LineNumbers from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color class LineNumberArea(LineNumbers): def mouseDoubleClickEvent(self, event): super().mousePressEvent(event) self.parent().line_area_doubleclick_event(event) class CodeEditor(QPlainTextEdit): def __init__(self, parent): QPlainTextEdit.__init__(self, parent) # Use the default theme from the book editor theme = get_theme(None) self.line_number_palette = pal = QPalette() pal.setColor(QPalette.ColorRole.Base, theme_color(theme, 'LineNr', 'bg')) pal.setColor(QPalette.ColorRole.Text, theme_color(theme, 'LineNr', 'fg')) pal.setColor(QPalette.ColorRole.BrightText, theme_color(theme, 'LineNrC', 'fg')) self.line_number_area = LineNumberArea(self) self.blockCountChanged.connect(self.update_line_number_area_width) self.updateRequest.connect(self.update_line_number_area) self.cursorPositionChanged.connect(self.highlight_cursor_line) self.update_line_number_area_width(0) self.highlight_cursor_line() self.clicked_line_numbers = set() def highlight_cursor_line(self): sel = QTextEdit.ExtraSelection() # Don't highlight if no text so that the placeholder text shows if not (self.blockCount() == 1 and len(self.toPlainText().strip()) == 0): sel.format.setBackground(self.palette().alternateBase()) sel.format.setProperty(QTextFormat.Property.FullWidthSelection, True) sel.cursor = self.textCursor() sel.cursor.clearSelection() self.setExtraSelections([sel,]) def update_line_number_area_width(self, block_count=0): self.gutter_width = self.line_number_area_width() self.setViewportMargins(self.gutter_width, 0, 0, 0) def line_number_area_width(self): # get largest width of digits w = self.fontMetrics() self.number_width = max(map(lambda x:w.width(str(x)), range(10))) digits = 1 limit = max(1, self.blockCount()) while limit >= 10: limit /= 10 digits += 1 return self.number_width * (digits+1) def update_line_number_area(self, rect, dy): if dy: self.line_number_area.scroll(0, dy) else: self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height()) if rect.contains(self.viewport().rect()): self.update_line_number_area_width() def resizeEvent(self, ev): QPlainTextEdit.resizeEvent(self, ev) cr = self.contentsRect() self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height())) def line_area_doubleclick_event(self, event): # remember that the result of the divide will be zero-based line = event.y()//self.fontMetrics().height() + 1 + self.firstVisibleBlock().blockNumber() if line in self.clicked_line_numbers: self.clicked_line_numbers.discard(line) else: self.clicked_line_numbers.add(line) self.update(self.line_number_area.geometry()) def number_of_lines(self): return self.blockCount() def set_clicked_line_numbers(self, new_set): self.clicked_line_numbers = new_set self.update(self.line_number_area.geometry()) def paint_line_numbers(self, ev): painter = QPainter(self.line_number_area) painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.ColorRole.Base)) block = self.firstVisibleBlock() num = block.blockNumber() top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) current = self.textCursor().block().blockNumber() painter.setPen(self.line_number_palette.color(QPalette.ColorRole.Text)) while block.isValid() and top <= ev.rect().bottom(): if block.isVisible() and bottom >= ev.rect().top(): set_bold = False set_italic = False if current == num: set_bold = True if num+1 in self.clicked_line_numbers: set_italic = True painter.save() if set_bold or set_italic: f = QFont(self.font()) if set_bold: f.setBold(set_bold) painter.setPen(self.line_number_palette.color(QPalette.ColorRole.BrightText)) f.setItalic(set_italic) painter.setFont(f) else: painter.setFont(self.font()) painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(), Qt.AlignmentFlag.AlignRight, str(num + 1)) painter.restore() block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) num += 1 def keyPressEvent(self, ev): if ev.key() == Qt.Key.Key_Insert: self.setOverwriteMode(self.overwriteMode() ^ True) ev.accept() return key = ev.key() if key == Qt.Key_Tab or key == Qt.Key_Backtab: ''' Handle indenting usingTab and Shift Tab. This is remarkably difficult because of the way Qt represents the edit buffer. Selections represent the start and end as character positions in the buffer. To convert a position into a line number we must get the block number containing that position. You so this by setting a cursor to that position. To get the text of a line we must convert the line number (the block number) to a block and then fetch the text from that. To change text we must create a cursor that selects all the text on the line. Because cursors use document positions, not block numbers or blocks, we must convert line numbers to blocks then get the position of the first character of the block. We then "extend" the selection to the end by computing the end position: the start + the length of the text on the line. We then uses that cursor to "insert" the new text, which magically replaces the selected text. ''' # Get the position of the start and end of the selection. cursor = self.textCursor() start_position = cursor.selectionStart() end_position = cursor.selectionEnd() # Now convert positions into block (line) numbers cursor.setPosition(start_position) start_block = cursor.block().blockNumber() cursor.setPosition(end_position) end_block = cursor.block().blockNumber() def select_block(block_number, curs): # Note the side effect: 'curs' is changed to select the line blk = self.document().findBlockByNumber(block_number) txt = blk.text() pos = blk.position() curs.setPosition(pos) curs.setPosition(pos+len(txt), QTextCursor.KeepAnchor) return txt # Check if there is a selection. If not then only Shift-Tab is valid if start_position == end_position: if key == Qt.Key_Backtab: txt = select_block(start_block, cursor) if txt.startswith('\t'): # This works because of the side effect in select_block() cursor.insertText(txt[1:]) cursor.setPosition(start_position-1) self.setTextCursor(cursor) ev.accept() else: QPlainTextEdit.keyPressEvent(self, ev) return # There is a selection so both Tab and Shift-Tab do indenting operations for bn in range(start_block, end_block+1): txt = select_block(bn, cursor) if key == Qt.Key_Backtab: if txt.startswith('\t'): cursor.insertText(txt[1:]) if bn == start_block: start_position -= 1 end_position -= 1 else: cursor.insertText('\t' + txt) if bn == start_block: start_position += 1 end_position += 1 # Restore the selection, adjusted for the added or deleted tabs cursor.setPosition(start_position) cursor.setPosition(end_position, QTextCursor.KeepAnchor) self.setTextCursor(cursor) ev.accept() return QPlainTextEdit.keyPressEvent(self, ev)