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)
Exemple #3
0
    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()
Exemple #4
0
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)
Exemple #5
0
    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()
Exemple #6
0
    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)
Exemple #7
0
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)
Exemple #8
0
 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
Exemple #9
0
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)
Exemple #10
0
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())
Exemple #11
0
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)