def setup_editor(self): variable_format = QTextCharFormat() variable_format.setFontWeight(QFont.Bold) variable_format.setForeground(Qt.blue) self._highlighter.add_mapping("\\b[A-Z_]+\\b", variable_format) single_line_comment_format = QTextCharFormat() single_line_comment_format.setBackground(QColor("#77ff77")) self._highlighter.add_mapping("#[^\n]*", single_line_comment_format) quotation_format = QTextCharFormat() quotation_format.setBackground(Qt.cyan) quotation_format.setForeground(Qt.blue) self._highlighter.add_mapping("\".*\"", quotation_format) function_format = QTextCharFormat() function_format.setFontItalic(True) function_format.setForeground(Qt.blue) self._highlighter.add_mapping("\\b[a-z0-9_]+\\(.*\\)", function_format) font = QFont() font.setFamily("Courier") font.setFixedPitch(True) font.setPointSize(10) self._editor = QPlainTextEdit() self._editor.setFont(font) self._highlighter.setDocument(self._editor.document())
def highlight(self, addr): fmt = QTextCharFormat() fmt.setBackground(Qt.cyan) fmt.setFont('Courier New') cur = self.box.textCursor() text = self.box.toPlainText() count = 0 for line in text.split('\n'): if len(line) > 0: line_addr = line.split()[0] n = (len(line_addr[2:-1]) * 4) mask = (2**n) - 1 if int(line_addr[:-1], 16) == (addr & mask): break count += 1 block = self.box.document().findBlockByLineNumber(count) cur.setPosition(block.position()) cur.select(QTextCursor.LineUnderCursor) cur.setCharFormat(fmt) self.box.setTextCursor(cur)
def highlight(self, addr, group): self.clear_highlight() # adding new highlights fmt = QTextCharFormat() fmt.setBackground(Qt.cyan) fmt.setFont('Courier New') addr_block = self.addresses.document().findBlockByLineNumber((addr - self.baseAddress) // 16) # gets linenos mem_block = self.mem_display.document().findBlockByLineNumber((addr - self.baseAddress) // 16) chr_block = self.chr_display.document().findBlockByLineNumber((addr - self.baseAddress) // 16) addr_cur = self.addresses.textCursor() # getting cursors mem_cur = self.mem_display.textCursor() chr_cur = self.chr_display.textCursor() char_offset = 0 mem_offset = 0 self.endian_sem.acquire() if self.endian == Endian.big: mem_offset = ((addr % 16) // group) * (2 * group + 1) char_offset = (addr % 16) * 3 elif self.endian == Endian.little: mem_offset = (((16 / group) - 1 )* (2 * group + 1)) - ((addr % 16) // group) * (2 * group + 1) char_offset = (15*3) - ((addr % 16) * 3) self.endian_sem.release() addr_cur.setPosition(addr_block.position()) # getting positions mem_cur.setPosition(mem_block.position() + mem_offset) # gives character offset within 16 byte line chr_cur.setPosition(chr_block.position() + char_offset) # sets position of char chr_text = self.chr_display.toPlainText() if chr_text[chr_cur.position()] == '\\' and chr_cur.position() + 1 < len(chr_text) and chr_text[chr_cur.position() + 1] in ['0', 'n', 't']: chr_cur.setPosition(chr_cur.position() + 2, mode=QTextCursor.KeepAnchor) else: chr_cur.setPosition(chr_cur.position() + 1, mode=QTextCursor.KeepAnchor) addr_cur.select(QTextCursor.LineUnderCursor) # selects whole line mem_cur.select(QTextCursor.WordUnderCursor) # selects just one word addr_cur.setCharFormat(fmt) # setting format mem_cur.setCharFormat(fmt) chr_cur.setCharFormat(fmt) self.addresses.setTextCursor(addr_cur) self.mem_display.setTextCursor(mem_cur) self.chr_display.setTextCursor(chr_cur) self.highlight_sem.acquire() self.is_highlighted = True self.highlight_addr = addr self.highlight_sem.release()
class ParagonScriptHighlighter(QSyntaxHighlighter): def __init__(self, document): super().__init__(document) self.error_line = None self.command_format = QTextCharFormat() self.command_format.setForeground(QtCore.Qt.darkMagenta) self.error_format = QTextCharFormat() self.error_format.setUnderlineColor(QtCore.Qt.darkRed) self.error_format.setUnderlineStyle( QtGui.QTextCharFormat.SpellCheckUnderline) self.error_format.setBackground(QtCore.Qt.red) def highlightBlock(self, text: str): if text.startswith("$") and not text.startswith( "$G") and not text.startswith("$Nu"): self.setFormat(0, len(text), self.command_format) if self.currentBlock().blockNumber() == self.error_line: self.setFormat(0, len(text), self.error_format)
def search_result_reset(self): self.goto(QTextCursor.Start) string_format = QTextCharFormat() string_format.setBackground(QColor('#668B8B')) extras = [] self.search_positions = [] while True: extra = QTextEdit.ExtraSelection() found = self.result_text_edit.find(self.search_line.text()) if not found: break extra.cursor = self.result_text_edit.textCursor() extra.format = string_format self.search_positions.append(extra.cursor.position()) extras.append(extra) self.result_text_edit.setExtraSelections(extras) self.goto(QTextCursor.Start) self.search_result()
def replace_patterns(self, code): # clear format fmt = QTextCharFormat() fmt.setBackground(QColor('#2b2b2b')) cursor = QTextCursor(self.document()) cursor.setPosition(0, QTextCursor.MoveAnchor) cursor.setPosition(len(code) - 1, QTextCursor.KeepAnchor) self.just_changed_text = True cursor.setCharFormat(fmt) # static elements color = QColor(255, 255, 0, 50) for pattern in self.static_elements: positions = [ i for i in range(len(code)) if code.startswith(pattern, i) ] for occ in reversed(positions): fmt.setBackground(color) cursor.setPosition(occ, QTextCursor.MoveAnchor) cursor.setPosition(occ + len(pattern), QTextCursor.KeepAnchor) self.just_changed_text = True cursor.setCharFormat(fmt) # usable components color = QColor(100, 100, 255, 200) for pattern in self.components: positions = [ i for i in range(len(code)) if code.startswith(pattern, i) ] for occ in reversed(positions): fmt.setBackground(color) cursor.setPosition(occ, QTextCursor.MoveAnchor) cursor.setPosition(occ + len(pattern), QTextCursor.KeepAnchor) self.just_changed_text = True cursor.setCharFormat(fmt)
class Highlighter(QSyntaxHighlighter): def __init__(self, parent): super(Highlighter, self).__init__(parent) self.infoFormat = QTextCharFormat() self.infoFormat.setForeground(Qt.white) self.infoFormat.setBackground(Qt.green) self.warningFormat = QTextCharFormat() self.warningFormat.setForeground(Qt.black) # self.warningFormat.setBackground(Qt.yellow) self.warningFormat.setBackground(QColor(MACOSYELLOW[0], MACOSYELLOW[1], MACOSYELLOW[2])) self.errorFormat = QTextCharFormat() self.errorFormat.setForeground(Qt.white) self.errorFormat.setBackground(QColor(MACOSRED[0], MACOSRED[1], MACOSRED[2])) def highlightBlock(self, text): # uncomment this line for Python2 # text = unicode(text) if text.startswith('Info'): self.setFormat(0, len(text), self.infoFormat) elif text.startswith('Warning'): self.setFormat(0, len(text), self.warningFormat) elif text.startswith('Error'): self.setFormat(0, len(text), self.errorFormat)
def highlightBlock(self, text): # Highlight keywords keywords = ["(?<=\\W\\()\\w+(?=[ *&]*\\))","\\w+ (?=.*\\)$)","^ +\\w+ (?=[*&_A-Za-z0-9\\[\\] ]+;)","^\w+","\\bin_addr\\b","\\bssize_t\\b","\\bsocklen_t\\b","\\bsa_family_t\\b","\\b__int32_t\\b","\\b__int8_t\\b","\\b__int16_t\\b","\\b__uint32_t\\b","\\b__uint8_t\\b","\\b__uint16_t\\b","\\bpid_t\\b","\\bcode\\b","\\bLPSTR\\b","\\bSIZE_T\\b","\\bLPVOID\\b","\\bDWORD\\b","\\bclock_t\\b","\\bthis\\b","\\bUINT\\b","\\bHANDLE\\b","\\blonglong\\b","\\bushort\\b","\\bFILE\\b","\\bulong\\b","\\bbyte\\b","\\bfalse\\b","\\btrue\\b","\\buint\\b","\\bsize_t\\b","\\bundefined\\d*\\b","\\bchar\\b", "\\bclass\\b", "\\bconst\\b", "\\bdouble\\b", "\\benum\\b", "\\bexplicit\\b","\\bfriend\\b", "\\binline\\b", "\\bint\\b","\\blong\\b", "\\bnamespace\\b", "\\boperator\\b","\\bprivate\\b", "\\bprotected\\b", "\\bpublic\\b","\\bshort\\b", "\\bsignals\\b", "\\bsigned\\b","\\bslots\\b", "\\bstatic\\b", "\\bstruct\\b","\\btemplate\\b", "\\btypedef\\b", "\\btypename\\b","\\bunion\\b", "\\bunsigned\\b", "\\bvirtual\\b","\\bvoid\\b", "\\bvolatile\\b", "\\bbool\\b"] keyword_format = QTextCharFormat() keyword_format.setForeground(getThemeColor(enums.ThemeColor.KeywordColor)) for keyword in keywords: for match in re.finditer(keyword, text): self.setFormat(match.start(), match.end() - match.start(), keyword_format) # Highlight flow words flow_words = ["\\breturn\\b","\\bif\\b","\\belse\\b","\\bswitch\\b","\\bcase\\b","\\bwhile\\b","\\bfor\\b","\\bdo\\b","\\bgoto\\b"] flow_format = QTextCharFormat() flow_format.setForeground(getThemeColor(enums.ThemeColor.TokenHighlightColor)) for flow in flow_words: for match in re.finditer(flow, text): self.setFormat(match.start(), match.end() - match.start(), flow_format) # Highlight functions function_format = QTextCharFormat() function_format.setForeground(getThemeColor(enums.ThemeColor.CodeSymbolColor)) function_pattern = "\\b\\w+(?=\\()" for match in re.finditer(function_pattern, text): self.setFormat(match.start(), match.end() - match.start(), function_format) # Highlight comments comment_format = QTextCharFormat() comment_format.setForeground(getThemeColor(enums.ThemeColor.CommentColor)) comment_pattern = "\/\/.*$" for match in re.finditer(comment_pattern, text): self.setFormat(match.start(), match.end() - match.start(), comment_format) multi_comment_pattern = "(?s)\\/\\*.*?\\*\\/" for match in re.finditer(multi_comment_pattern, text): self.setFormat(match.start(), match.end() - match.start(), comment_format) # Highlight string constants const_format = QTextCharFormat() const_format.setForeground(getThemeColor(enums.ThemeColor.StringColor)) string_consts = "\"(.*?)\"" for match in re.finditer(string_consts, text): self.setFormat(match.start(), match.end() - match.start(), const_format) # Highlight numeric constants num_const_format = QTextCharFormat() num_const_format.setForeground(getThemeColor(enums.ThemeColor.NumberColor)) num_consts = "\\b\\d+\\b" for match in re.finditer(num_consts, text): self.setFormat(match.start(), match.end() - match.start(), num_const_format) hex_const = "0x[0-9a-f]+\\b" for match in re.finditer(hex_const, text): self.setFormat(match.start(), match.end() - match.start(), num_const_format) # Highlight data data_format = QTextCharFormat() data_format.setForeground(getThemeColor(enums.ThemeColor.DataSymbolColor)) data_consts = "\\b(PTR)?_?DAT_[0-9a-zA-Z]+\\b" for match in re.finditer(data_consts, text): self.setFormat(match.start(), match.end() - match.start(), data_format) # Highlight CPP Class paths cpp_format = QTextCharFormat() cpp_format.setForeground(getThemeColor(enums.ThemeColor.NameSpaceColor)) cpp_path = "\\b\\w*(?=::)" for match in re.finditer(cpp_path, text): self.setFormat(match.start(), match.end() - match.start(), cpp_format) # Params params_format = QTextCharFormat() params_format.setForeground(getThemeColor(enums.ThemeColor.FieldNameColor)) for arg in self.args: params_pattern = "\\b" + arg + "\\b" for match in re.finditer(params_pattern, text): self.setFormat(match.start(), match.end() - match.start(), params_format) # Highlight selection if self.selected: selection_format = QTextCharFormat() #selection_format.setBackground(getThemeColor(enums.ThemeColor.Highlight)) selection_format.setBackground(QColor.fromRgb(121,195,231)) selection_format.setForeground(QColor.fromRgb(42,42,42)) try: selection_pattern = self.selected for match in re.finditer(selection_pattern, text): self.setFormat(match.start(), match.end() - match.start(), selection_format) except: pass
class Editor(QPlainTextEdit): keyPressed = Signal(QEvent) class _NumberArea(QWidget): def __init__(self, editor): super().__init__(editor) self.codeEditor = editor def sizeHint(self): return QSize(self.editor.lineNumberAreaWidth(), 0) def paintEvent(self, event): self.codeEditor.lineNumberAreaPaintEvent(event) def __init__(self, **kwargs): parent = kwargs["parent"] if "parent" in kwargs else None super().__init__(parent) self._addSaveAction = kwargs[ "saveAction"] if "saveAction" in kwargs else False self._addSaveAction = kwargs[ "saveAction"] if "saveAction" in kwargs else False if self._addSaveAction: self.saveACT = QAction("Save") self.saveACT.setShortcut(QKeySequence("Ctrl+S")) self.saveACT.triggered.connect( lambda: self._save_file(self.toPlainText())) self.addAction(self.saveACT) self._saveCB = kwargs[ "saveFunction"] if "saveFunction" in kwargs else None self.setFont(QFont("Courier New", 11)) self.lineNumberArea = Editor._NumberArea(self) self.blockCountChanged.connect(self.updateLineNumberAreaWidth) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateLineNumberAreaWidth(0) self.findHighlightFormat = QTextCharFormat() self.findHighlightFormat.setBackground(QBrush(QColor("red"))) self.searchTxtBx = None def lineNumberAreaWidth(self): digits = 5 max_value = max(1, self.blockCount()) while max_value >= 10: max_value /= 10 digits += 1 space = 3 + self.fontMetrics().width('9') * digits return space def updateLineNumberAreaWidth(self, _): self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) def updateLineNumberArea(self, rect, dy): if dy: self.lineNumberArea.scroll(0, dy) else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) def resizeEvent(self, event): super().resizeEvent(event) cr = self.contentsRect() self.lineNumberArea.setGeometry( QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) def highlightCurrentLine(self): extraSelections = [] if not self.isReadOnly(): selection = QTextEdit.ExtraSelection() lineColor = QColor(229, 248, 255, 255) selection.format.setBackground(lineColor) selection.format.setProperty(QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() extraSelections.append(selection) self.setExtraSelections(extraSelections) def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.lineNumberArea) painter.fillRect(event.rect(), QColor(233, 233, 233, 255)) block = self.firstVisibleBlock() blockNumber = block.blockNumber() top = self.blockBoundingGeometry(block).translated( self.contentOffset()).top() bottom = top + self.blockBoundingRect(block).height() # Just to make sure I use the right font height = self.fontMetrics().height() while block.isValid() and (top <= event.rect().bottom()): if block.isVisible() and (bottom >= event.rect().top()): number = str(blockNumber + 1) fFont = QFont("Courier New", 10) painter.setPen(QColor(130, 130, 130, 255)) painter.setFont(fFont) painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignCenter, number) block = block.next() top = bottom bottom = top + self.blockBoundingRect(block).height() blockNumber += 1 def contextMenuEvent(self, event): menu = self.createStandardContextMenu() if self._addSaveAction: index = 0 if len(menu.actions()) > 6: index = 5 act_beforeACT = menu.actions()[index] menu.insertAction(act_beforeACT, self.saveACT) action = menu.addAction("Find" + "\t" + "Ctrl+F") action.triggered.connect(self.find_key) menu.popup(event.globalPos()) def keyPressEvent(self, event: QKeyEvent): if event.key() == Qt.Key_F and (event.modifiers() & Qt.ControlModifier): self.find_key() if event.key() == Qt.Key_Escape: if self.searchTxtBx is not None: self.searchTxtBx.hide() self.searchTxtBx = None self.clear_format() super(Editor, self).keyPressEvent(event) def find_key(self): if self.searchTxtBx is None: self.searchTxtBx = QLineEdit(self) p = self.geometry().topRight() - self.searchTxtBx.geometry( ).topRight() - QPoint(50, 0) self.searchTxtBx.move(p) self.searchTxtBx.show() self.searchTxtBx.textChanged.connect(self.find_with_pattern) self.searchTxtBx.setFocus() def find_with_pattern(self, pattern): self.setUndoRedoEnabled(False) self.clear_format() if pattern == "": return cursor = self.textCursor() regex = QRegExp(pattern) pos = 0 index = regex.indexIn(self.toPlainText(), pos) while index != -1: cursor.setPosition(index) cursor.movePosition(QTextCursor.EndOfWord, QTextCursor.KeepAnchor, 1) cursor.mergeCharFormat(self.findHighlightFormat) pos = index + regex.matchedLength() index = regex.indexIn(self.toPlainText(), pos) self.setUndoRedoEnabled(True) def clear_format(self): cursor = self.textCursor() cursor.select(QTextCursor.Document) cursor.setCharFormat(QTextCharFormat()) cursor.clearSelection() self.setTextCursor(cursor) def setSaveCB(self, cb): self._saveCB = cb def _save_file(self, text): if self._saveCB is not None: self._saveCB(text)
class DiffTable(QTableWidget): def __init__(self): super().__init__() self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.setColumnCount(4) self.horizontalHeader().setVisible(False) self.verticalHeader().setVisible(False) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setMinimumSectionSize(10) self.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.verticalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) # 컬러 세트 color_sub = QColor() color_sub.setNamedColor('#ffaaaa') color_add = QColor() color_add.setNamedColor('#aaffaa') self.fmt_sub = QTextCharFormat() self.fmt_sub.setBackground(color_sub) self.fmt_add = QTextCharFormat() self.fmt_add.setBackground(color_add) self.a = '' self.b = '' def _make_table(self, a, b): self.setRowCount(0) al, bl = [], [] bn = 1 for ar, br, flag in difflib._mdiff(a.splitlines(keepends=True), b.splitlines(keepends=True), context=2): if flag is None: self.insertRow(self.rowCount()) self.setItem(self.rowCount() - 1, 0, self._table_item()) self.setItem(self.rowCount() - 1, 1, self._table_item('...')) self.setItem(self.rowCount() - 1, 2, self._table_item('...')) self.setItem(self.rowCount() - 1, 3, self._table_item('(생략)', center=False)) else: an, at = ar bn, bt = br if flag: if type(an) is int: al.append([str(an), at]) if type(bn) is int: bl.append([str(bn), bt]) else: self._insert_merged( al, bl) # flag가 True일 때 모아둔 al, bl을 표에 실제 입력 al, bl = [], [] self.insertRow(self.rowCount()) self.setItem(self.rowCount() - 1, 0, self._table_item()) self.setItem(self.rowCount() - 1, 1, self._table_item(str(an))) self.setItem(self.rowCount() - 1, 2, self._table_item(str(bn))) self.setItem( self.rowCount() - 1, 3, self._table_item((lambda x: x[:-1] if x.endswith('\n') else x)(at), center=False)) self._insert_merged(al, bl) if self.rowCount() == 0: self.insertRow(0) self.setItem(0, 3, self._table_item('변경 사항이 없습니다.', False)) elif type(bn) is int and bn < len( b.splitlines()): # 맨 마지막에 생략 표시가 자동으로 안 됨 self.insertRow(self.rowCount()) self.setItem(self.rowCount() - 1, 0, self._table_item()) self.setItem(self.rowCount() - 1, 1, self._table_item('...')) self.setItem(self.rowCount() - 1, 2, self._table_item('...')) self.setItem(self.rowCount() - 1, 3, self._table_item('(생략)', center=False)) @staticmethod def _table_item(text='', center=True): item = QTableWidgetItem(text) font = QFont() if center: # 행번호 item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.NoItemFlags) item.setFont(font) font.setPointSize(7) else: item.setFlags(Qt.NoItemFlags) item.setFont(font) font.setPointSize(9) return item def _insert_merged(self, al, bl): if al: row = self.rowCount() self.insertRow(row) self.setItem(row, 1, self._table_item('\n'.join(map(lambda x: x[0], al)))) self.setItem(row, 2, self._table_item()) self.setItem(row, 3, self._table_item()) editor = NPTextEdit(bg_color='#ffeef0') editor.sig_size.connect(self.item(row, 3).setSizeHint) self._highlight(editor, ''.join(map(lambda x: x[1], al)), self.fmt_sub, self.fmt_sub) self.setCellWidget(row, 3, editor) if not bl: self.setItem(row, 0, self._table_item()) self.setCellWidget(row, 0, QCheckBox(checked=True)) self.cellWidget(row, 3).setReadOnly(True) if bl: row = self.rowCount() self.insertRow(row) self.setItem(row, 1, self._table_item()) self.setItem(row, 2, self._table_item('\n'.join(map(lambda x: x[0], bl)))) self.setItem(row, 3, self._table_item()) editor = NPTextEdit(bg_color='#e6ffed') editor.sig_size.connect(self.item(row, 3).setSizeHint) self._highlight(editor, ''.join(map(lambda x: x[1], bl)), self.fmt_add, self.fmt_add) self.setCellWidget(row, 3, editor) if not al: self.setCellWidget(row, 0, QCheckBox(checked=True)) if al and bl: # 변경 시 radio button 필요 group = QButtonGroup(self) group.setExclusive(True) ar = QRadioButton() br = QRadioButton(checked=True) group.addButton(ar) group.addButton(br) self.setCellWidget(self.rowCount() - 2, 0, ar) self.setCellWidget(self.rowCount() - 1, 0, br) @staticmethod def _get_pos_list(t: str): start, end, n, lst = 0, 0, 0, [] while True: start = t.find('\0', n) end = t.find('\1', n) if start == -1: break else: lst.append((t[start + 1], start, end)) n = end + 1 return lst def _highlight(self, editor: QTextEdit, text: str, fmt: QTextCharFormat, fmt_chg: QTextCharFormat): cursor = QTextCursor(editor.textCursor()) if text.endswith('\n'): text = text[:-1] elif text.endswith('\n\1'): text = text[:-2] + '\1' pos_list = self._get_pos_list(text) text = text.replace('\0+', '').replace('\0-', '').replace('\0^', '').replace('\1', '') editor.setPlainText(text) n = 0 for tag, start, end in pos_list: if not end == start + 2: cursor.setPosition(start - n) cursor.setPosition(end - n - 2, QTextCursor.KeepAnchor) if tag == '^': cursor.mergeCharFormat(fmt_chg) else: cursor.mergeCharFormat(fmt) n += 3 def _retrieve(self): i = 0 n = self.rowCount() lines = [] while i < n: widget_a = self.cellWidget(i, 0) if type(widget_a) is QRadioButton: widget_b = self.cellWidget(i + 1, 0) if type(widget_b) is QRadioButton: b_num = self.item(i + 1, 2).text() if '\n' in b_num: lines.append( (int(b_num[:b_num.find('\n')]), int(b_num[b_num.rfind('\n') + 1:]), 0, self.cellWidget(i + int(widget_b.isChecked()), 3).toPlainText())) else: lines.append( (int(b_num), int(b_num), 0, self.cellWidget(i + int(widget_b.isChecked()), 3).toPlainText())) i += 1 elif type(widget_a) is QCheckBox: text = self.cellWidget(i, 3).toPlainText() if self.item(i, 1).text(): # 삭제 if not widget_a.isChecked(): if n == 1: # 완전 삭제 lines.append((1, None, 1, text)) elif i == n - 1: # 마지막 b_num = self.item(i - 1, 2).text() lines.append((int(b_num) + 1, None, 1, text)) else: b_num = self.item(i + 1, 2).text() lines.append((int(b_num), None, 1, text)) elif self.item(i, 2).text(): # 추가 mode = 0 if widget_a.isChecked() else -1 b_num = self.item(i, 2).text() if '\n' in b_num: lines.append( (int(b_num[:b_num.find('\n')]), int(b_num[b_num.rfind('\n') + 1:]), mode, text)) else: lines.append((int(b_num), int(b_num), mode, text)) i += 1 return lines @staticmethod def _assemble(b, ln_list): bs = b.splitlines(keepends=True) n = 0 for s, e, m, t in ln_list: t = t + '\n' if m == 0: # 일반적인 RadioButton del bs[s - 1 - n:e - n] bs.insert(s - 1 - n, t) n += e - s elif m == 1: bs.insert(s - 1 - n, t) n -= 1 elif m == -1: del bs[s - 1 - n:e - n] n += e - s + 1 return ''.join(bs) # return (lambda x: x[:-1] if x.endswith('\n') else x)(''.join(bs)) def make_diff(self, a: str = None, b: str = None): if a is not None: self.a = a if b is not None: self.b = b self._make_table(self.a, self.b) def refresh_diff(self): self.make_diff(b=self.current_text()) def current_text(self): return self._assemble(self.b, self._retrieve())
class TextView(QPlainTextEdit): def __init__(self, parent: Optional[QWidget]): super().__init__(parent) self.setTabChangesFocus(True) self.setReadOnly(False) self.setCenterOnScroll(True) self.verticalScrollBar().valueChanged.connect(self.highlight_visible) self._search_term = None self._text_format = QTextCharFormat() self._text_format.setFontWeight(QFont.Bold) self._text_format.setForeground(Qt.darkMagenta) self._text_format.setBackground(Qt.yellow) def clear(self): super().clear() def set_text(self, text: Optional[str]): super().setPlainText(text) self.scroll_to_first_location() self.highlight_visible() def set_search_term(self, search_term: Optional[str]): self._search_term = search_term self.unhighlight() self.highlight_visible() def unhighlight(self): cursor = QTextCursor(self.document()) cursor.movePosition(QTextCursor.Start) cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) cursor.setCharFormat(QTextCharFormat()) def scroll_to_first_location(self): cursor = self.document().find(self._search_term) if not cursor.isNull(): self.setTextCursor(cursor) self.ensureCursorVisible() self.setTextCursor(QTextCursor()) def start_cursor(self): return self.cursorForPosition(self.viewport().rect().topLeft()) def end_cursor(self): return self.cursorForPosition(self.viewport().rect().bottomRight()) def highlight_visible(self): # Might not work with RTL text document = self.document() end = self.end_cursor() # Highlighting is a little slow for large documents, so we limit # highlighting to the visible range. # We should be searching in the visible range (start to end), but it # seems like we can't do that. So we search as long as we find anything, # and stop when it is beyond the visible range. This means that we might # be searching more text than we need to. cursor = self.start_cursor() while not (cursor := document.find(self._search_term, cursor)).isNull(): if cursor.position() > end.position(): break cursor.setCharFormat(self._text_format)