示例#1
0
 def __init__(self, right=False, parent=None, show_open_in_editor=False):
     PlainTextEdit.__init__(self, parent)
     self.setFrameStyle(0)
     self.show_open_in_editor = show_open_in_editor
     self.side_margin = 0
     self.setContextMenuPolicy(Qt.CustomContextMenu)
     self.customContextMenuRequested.connect(self.show_context_menu)
     self.setFocusPolicy(Qt.NoFocus)
     self.right = right
     self.setReadOnly(True)
     self.setLineWrapMode(self.WidgetWidth)
     font = self.font()
     ff = tprefs['editor_font_family']
     if ff is None:
         ff = default_font_family()
     font.setFamily(ff)
     font.setPointSize(tprefs['editor_font_size'])
     self.setFont(font)
     self.calculate_metrics()
     self.setTabStopWidth(tprefs['editor_tab_stop_width'] * self.space_width)
     font = self.heading_font = QFont(self.font())
     font.setPointSize(int(tprefs['editor_font_size'] * 1.5))
     font.setBold(True)
     theme = get_theme(tprefs['editor_theme'])
     pal = self.palette()
     pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
     pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
     pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
     pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
     pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
     self.setPalette(pal)
     self.viewport().setCursor(Qt.ArrowCursor)
     self.line_number_area = LineNumbers(self)
     self.blockCountChanged[int].connect(self.update_line_number_area_width)
     self.updateRequest.connect(self.update_line_number_area)
     self.line_number_palette = pal = QPalette()
     pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
     pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
     pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
     self.line_number_map = LineNumberMap()
     self.search_header_pos = 0
     self.changes, self.headers, self.images = [], [], OrderedDict()
     self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff), self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
     self.diff_backgrounds = {
         'replace' : theme_color(theme, 'DiffReplace', 'bg'),
         'insert'  : theme_color(theme, 'DiffInsert', 'bg'),
         'delete'  : theme_color(theme, 'DiffDelete', 'bg'),
         'replacereplace': theme_color(theme, 'DiffReplaceReplace', 'bg'),
         'boundary': QBrush(theme_color(theme, 'Normal', 'fg'), Qt.Dense7Pattern),
     }
     self.diff_foregrounds = {
         'replace' : theme_color(theme, 'DiffReplace', 'fg'),
         'insert'  : theme_color(theme, 'DiffInsert', 'fg'),
         'delete'  : theme_color(theme, 'DiffDelete', 'fg'),
         'boundary': QColor(0, 0, 0, 0),
     }
     for x in ('replacereplace', 'insert', 'delete'):
         f = QTextCharFormat()
         f.setBackground(self.diff_backgrounds[x])
         setattr(self, '%s_format' % x, f)
示例#2
0
文件: view.py 项目: MarioJC/calibre
 def __init__(self, right=False, parent=None, show_open_in_editor=False):
     PlainTextEdit.__init__(self, parent)
     self.setFrameStyle(0)
     self.show_open_in_editor = show_open_in_editor
     self.side_margin = 0
     self.setContextMenuPolicy(Qt.CustomContextMenu)
     self.customContextMenuRequested.connect(self.show_context_menu)
     self.setFocusPolicy(Qt.NoFocus)
     self.right = right
     self.setReadOnly(True)
     self.setLineWrapMode(self.WidgetWidth)
     font = self.font()
     ff = tprefs['editor_font_family']
     if ff is None:
         ff = default_font_family()
     font.setFamily(ff)
     font.setPointSize(tprefs['editor_font_size'])
     self.setFont(font)
     self.calculate_metrics()
     self.setTabStopWidth(tprefs['editor_tab_stop_width'] * self.space_width)
     font = self.heading_font = QFont(self.font())
     font.setPointSize(int(tprefs['editor_font_size'] * 1.5))
     font.setBold(True)
     theme = get_theme(tprefs['editor_theme'])
     pal = self.palette()
     pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
     pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
     pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
     pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
     pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
     self.setPalette(pal)
     self.viewport().setCursor(Qt.ArrowCursor)
     self.line_number_area = LineNumbers(self)
     self.blockCountChanged[int].connect(self.update_line_number_area_width)
     self.updateRequest.connect(self.update_line_number_area)
     self.line_number_palette = pal = QPalette()
     pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
     pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
     pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
     self.line_number_map = LineNumberMap()
     self.search_header_pos = 0
     self.changes, self.headers, self.images = [], [], OrderedDict()
     self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff), self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
     self.diff_backgrounds = {
         'replace' : theme_color(theme, 'DiffReplace', 'bg'),
         'insert'  : theme_color(theme, 'DiffInsert', 'bg'),
         'delete'  : theme_color(theme, 'DiffDelete', 'bg'),
         'replacereplace': theme_color(theme, 'DiffReplaceReplace', 'bg'),
         'boundary': QBrush(theme_color(theme, 'Normal', 'fg'), Qt.Dense7Pattern),
     }
     self.diff_foregrounds = {
         'replace' : theme_color(theme, 'DiffReplace', 'fg'),
         'insert'  : theme_color(theme, 'DiffInsert', 'fg'),
         'delete'  : theme_color(theme, 'DiffDelete', 'fg'),
         'boundary': QColor(0, 0, 0, 0),
     }
     for x in ('replacereplace', 'insert', 'delete'):
         f = QTextCharFormat()
         f.setBackground(self.diff_backgrounds[x])
         setattr(self, '%s_format' % x, f)
示例#3
0
class TextBrowser(PlainTextEdit):  # {{{

    resized = pyqtSignal()
    wheel_event = pyqtSignal(object)
    next_change = pyqtSignal(object)
    scrolled = pyqtSignal()
    line_activated = pyqtSignal(object, object, object)

    def __init__(self, right=False, parent=None, show_open_in_editor=False):
        PlainTextEdit.__init__(self, parent)
        self.setFrameStyle(0)
        self.show_open_in_editor = show_open_in_editor
        self.side_margin = 0
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_context_menu)
        self.setFocusPolicy(Qt.NoFocus)
        self.right = right
        self.setReadOnly(True)
        self.setLineWrapMode(self.WidgetWidth)
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.setFont(font)
        self.calculate_metrics()
        self.setTabStopWidth(tprefs['editor_tab_stop_width'] * self.space_width)
        font = self.heading_font = QFont(self.font())
        font.setPointSize(int(tprefs['editor_font_size'] * 1.5))
        font.setBold(True)
        theme = get_theme(tprefs['editor_theme'])
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.viewport().setCursor(Qt.ArrowCursor)
        self.line_number_area = LineNumbers(self)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        self.line_number_map = LineNumberMap()
        self.search_header_pos = 0
        self.changes, self.headers, self.images = [], [], OrderedDict()
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff), self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.diff_backgrounds = {
            'replace' : theme_color(theme, 'DiffReplace', 'bg'),
            'insert'  : theme_color(theme, 'DiffInsert', 'bg'),
            'delete'  : theme_color(theme, 'DiffDelete', 'bg'),
            'replacereplace': theme_color(theme, 'DiffReplaceReplace', 'bg'),
            'boundary': QBrush(theme_color(theme, 'Normal', 'fg'), Qt.Dense7Pattern),
        }
        self.diff_foregrounds = {
            'replace' : theme_color(theme, 'DiffReplace', 'fg'),
            'insert'  : theme_color(theme, 'DiffInsert', 'fg'),
            'delete'  : theme_color(theme, 'DiffDelete', 'fg'),
            'boundary': QColor(0, 0, 0, 0),
        }
        for x in ('replacereplace', 'insert', 'delete'):
            f = QTextCharFormat()
            f.setBackground(self.diff_backgrounds[x])
            setattr(self, '%s_format' % x, f)

    def calculate_metrics(self):
        w = self.fontMetrics()
        self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
        self.space_width = w.width(' ')

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        i = unicode(self.textCursor().selectedText()).rstrip('\0')
        if i:
            a(QIcon(I('edit-copy.png')), _('Copy to clipboard'), self.copy).setShortcut(QKeySequence.Copy)

        if len(self.changes) > 0:
            a(QIcon(I('arrow-up.png')), _('Previous change'), partial(self.next_change.emit, -1))
            a(QIcon(I('arrow-down.png')), _('Next change'), partial(self.next_change.emit, 1))

        if self.show_open_in_editor:
            b = self.cursorForPosition(pos).block()
            if b.isValid():
                a(QIcon(I('tweak.png')), _('Open file in the editor'), partial(self.generate_sync_request, b.blockNumber()))

        if len(m.actions()) > 0:
            m.exec_(self.mapToGlobal(pos))

    def mouseDoubleClickEvent(self, ev):
        if ev.button() == 1:
            b = self.cursorForPosition(ev.pos()).block()
            if b.isValid():
                self.generate_sync_request(b.blockNumber())
        return PlainTextEdit.mouseDoubleClickEvent(self, ev)

    def generate_sync_request(self, block_number):
        if not self.headers:
            return
        try:
            lnum = int(self.line_number_map.get(block_number, ''))
        except:
            lnum = 1
        for i, (num, text) in enumerate(self.headers):
            if num > block_number:
                name = text if i == 0 else self.headers[i - 1][1]
                break
        else:
            name = self.headers[-1][1]
        self.line_activated.emit(name, lnum, bool(self.right))

    def search(self, query, reverse=False):
        ''' Search for query, also searching the headers. Matches in headers
        are not highlighted as managing the highlight is too much of a pain.'''
        if not query.strip():
            return
        c = self.textCursor()
        lnum = c.block().blockNumber()
        cpos = c.positionInBlock()
        headers = dict(self.headers)
        if lnum in headers:
            cpos = self.search_header_pos
        lines = unicode(self.toPlainText()).splitlines()
        for hn, text in self.headers:
            lines[hn] = text
        prefix, postfix = lines[lnum][:cpos], lines[lnum][cpos:]
        before, after = enumerate(lines[0:lnum]), ((lnum+1+i, x) for i, x in enumerate(lines[lnum+1:]))
        if reverse:
            sl = chain([(lnum, prefix)], reversed(tuple(before)), reversed(tuple(after)), [(lnum, postfix)])
        else:
            sl = chain([(lnum, postfix)], after, before, [(lnum, prefix)])
        flags = regex.REVERSE if reverse else 0
        pat = regex.compile(regex.escape(query, special_only=True), flags=regex.UNICODE|regex.IGNORECASE|flags)
        for num, text in sl:
            try:
                m = next(pat.finditer(text))
            except StopIteration:
                continue
            start, end = m.span()
            length = end - start
            if text is postfix:
                start += cpos
            c = QTextCursor(self.document().findBlockByNumber(num))
            c.setPosition(c.position() + start)
            if num in headers:
                self.search_header_pos = start + length
            else:
                c.setPosition(c.position() + length, c.KeepAnchor)
                self.search_header_pos = 0
            if reverse:
                pos, anchor = c.position(), c.anchor()
                c.setPosition(pos), c.setPosition(anchor, c.KeepAnchor)
            self.setTextCursor(c)
            self.centerCursor()
            self.scrolled.emit()
            break
        else:
            info_dialog(self, _('No matches found'), _(
                'No matches found for query: %s' % query), show=True)

    def clear(self):
        PlainTextEdit.clear(self)
        self.line_number_map.clear()
        del self.changes[:]
        del self.headers[:]
        self.images.clear()
        self.search_header_pos = 0
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)

    def update_line_number_area_width(self, block_count=0):
        self.side_margin = self.line_number_area_width()
        if self.right:
            self.setViewportMargins(0, 0, self.side_margin, 0)
        else:
            self.setViewportMargins(self.side_margin, 0, 0, 0)

    def available_width(self):
        return self.width() - self.side_margin

    def line_number_area_width(self):
        return 9 + (self.line_number_map.max_width * self.number_width)

    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):
        PlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        if self.right:
            self.line_number_area.setGeometry(QRect(cr.right() - self.line_number_area_width(), cr.top(), cr.right(), cr.height()))
        else:
            self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))
        self.resized.emit()

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        painter.setPen(self.line_number_palette.color(QPalette.Text))
        change_starts = {x[0] for x in self.changes}

        while block.isValid() and top <= ev.rect().bottom():
            r = ev.rect()
            if block.isVisible() and bottom >= r.top():
                text = unicode(self.line_number_map.get(num, ''))
                is_start = text != '-' and num in change_starts
                if is_start:
                    painter.save()
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                if text == '-':
                    painter.drawLine(r.left() + 2, (top + bottom)//2, r.right() - 2, (top + bottom)//2)
                else:
                    if self.right:
                        painter.drawText(r.left() + 3, top, r.right(), self.fontMetrics().height(),
                                Qt.AlignLeft, text)
                    else:
                        painter.drawText(r.left() + 2, top, r.right() - 5, self.fontMetrics().height(),
                                Qt.AlignRight, text)
                if is_start:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1

    def paintEvent(self, event):
        w = self.viewport().rect().width()
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
        floor = event.rect().bottom()
        ceiling = event.rect().top()
        fv = self.firstVisibleBlock().blockNumber()
        origin = self.contentOffset()
        doc = self.document()
        lines = []

        for num, text in self.headers:
            top, bot = num, num + 3
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            painter.setFont(self.heading_font)
            br = painter.drawText(3, y_top, w, y_bot - y_top - 5, Qt.TextSingleLine, text)
            painter.setPen(QPen(self.palette().text(), 2))
            painter.drawLine(0, br.bottom()+3, w, br.bottom()+3)

        for top, bot, kind in self.changes:
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            if y_top != y_bot:
                painter.fillRect(0,  y_top, w, y_bot - y_top, self.diff_backgrounds[kind])
            lines.append((y_top, y_bot, kind))
            if top in self.images:
                img, maxw = self.images[top][:2]
                if bot > top + 1 and not img.isNull():
                    y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top+1)).translated(origin).y() + 3
                    y_bot -= 3
                    scaled, imgw, imgh = fit_image(int(img.width()/img.devicePixelRatio()), int(img.height()/img.devicePixelRatio()), w - 3, y_bot - y_top)
                    painter.drawPixmap(QRect(3, y_top, imgw, imgh), img)

        painter.end()
        PlainTextEdit.paintEvent(self, event)
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        for top, bottom, kind in sorted(lines, key=lambda t_b_k:{'replace':0}.get(t_b_k[2], 1)):
            painter.setPen(QPen(self.diff_foregrounds[kind], 1))
            painter.drawLine(0, top, w, top)
            painter.drawLine(0, bottom - 1, w, bottom - 1)

    def wheelEvent(self, ev):
        if ev.angleDelta().x() == 0:
            self.wheel_event.emit(ev)
        else:
            return PlainTextEdit.wheelEvent(self, ev)
示例#4
0
class TextBrowser(PlainTextEdit):  # {{{

    resized = pyqtSignal()
    wheel_event = pyqtSignal(object)
    next_change = pyqtSignal(object)
    scrolled = pyqtSignal()
    line_activated = pyqtSignal(object, object, object)

    def __init__(self, right=False, parent=None, show_open_in_editor=False):
        PlainTextEdit.__init__(self, parent)
        self.setFrameStyle(0)
        self.show_open_in_editor = show_open_in_editor
        self.side_margin = 0
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_context_menu)
        self.setFocusPolicy(Qt.NoFocus)
        self.right = right
        self.setReadOnly(True)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
        self.space_width = w.width(' ')
        self.setLineWrapMode(self.WidgetWidth)
        self.setTabStopWidth(tprefs['editor_tab_stop_width'] * self.space_width)
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.setFont(font)
        font = self.heading_font = QFont(self.font())
        font.setPointSize(int(tprefs['editor_font_size'] * 1.5))
        font.setBold(True)
        theme = get_theme(tprefs['editor_theme'])
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.viewport().setCursor(Qt.ArrowCursor)
        self.line_number_area = LineNumbers(self)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        self.line_number_map = LineNumberMap()
        self.search_header_pos = 0
        self.changes, self.headers, self.images = [], [], OrderedDict()
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff), self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.diff_backgrounds = {
            'replace' : theme_color(theme, 'DiffReplace', 'bg'),
            'insert'  : theme_color(theme, 'DiffInsert', 'bg'),
            'delete'  : theme_color(theme, 'DiffDelete', 'bg'),
            'replacereplace': theme_color(theme, 'DiffReplaceReplace', 'bg'),
            'boundary': QBrush(theme_color(theme, 'Normal', 'fg'), Qt.Dense7Pattern),
        }
        self.diff_foregrounds = {
            'replace' : theme_color(theme, 'DiffReplace', 'fg'),
            'insert'  : theme_color(theme, 'DiffInsert', 'fg'),
            'delete'  : theme_color(theme, 'DiffDelete', 'fg'),
            'boundary': QColor(0, 0, 0, 0),
        }
        for x in ('replacereplace', 'insert', 'delete'):
            f = QTextCharFormat()
            f.setBackground(self.diff_backgrounds[x])
            setattr(self, '%s_format' % x, f)

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        i = unicode(self.textCursor().selectedText()).rstrip('\0')
        if i:
            a(QIcon(I('edit-copy.png')), _('Copy to clipboard'), self.copy).setShortcut(QKeySequence.Copy)

        if len(self.changes) > 0:
            a(QIcon(I('arrow-up.png')), _('Previous change'), partial(self.next_change.emit, -1))
            a(QIcon(I('arrow-down.png')), _('Next change'), partial(self.next_change.emit, 1))

        if self.show_open_in_editor:
            b = self.cursorForPosition(pos).block()
            if b.isValid():
                a(QIcon(I('tweak.png')), _('Open file in the editor'), partial(self.generate_sync_request, b.blockNumber()))

        if len(m.actions()) > 0:
            m.exec_(self.mapToGlobal(pos))

    def mouseDoubleClickEvent(self, ev):
        if ev.button() == 1:
            b = self.cursorForPosition(ev.pos()).block()
            if b.isValid():
                self.generate_sync_request(b.blockNumber())
        return PlainTextEdit.mouseDoubleClickEvent(self, ev)

    def generate_sync_request(self, block_number):
        if not self.headers:
            return
        try:
            lnum = int(self.line_number_map.get(block_number, ''))
        except:
            lnum = 1
        for i, (num, text) in enumerate(self.headers):
            if num > block_number:
                name = text if i == 0 else self.headers[i - 1][1]
                break
        else:
            name = self.headers[-1][1]
        self.line_activated.emit(name, lnum, bool(self.right))

    def search(self, query, reverse=False):
        ''' Search for query, also searching the headers. Matches in headers
        are not highlighted as managing the highlight is too much of a pain.'''
        if not query.strip():
            return
        c = self.textCursor()
        lnum = c.block().blockNumber()
        cpos = c.positionInBlock()
        headers = dict(self.headers)
        if lnum in headers:
            cpos = self.search_header_pos
        lines = unicode(self.toPlainText()).splitlines()
        for hn, text in self.headers:
            lines[hn] = text
        prefix, postfix = lines[lnum][:cpos], lines[lnum][cpos:]
        before, after = enumerate(lines[0:lnum]), ((lnum+1+i, x) for i, x in enumerate(lines[lnum+1:]))
        if reverse:
            sl = chain([(lnum, prefix)], reversed(tuple(before)), reversed(tuple(after)), [(lnum, postfix)])
        else:
            sl = chain([(lnum, postfix)], after, before, [(lnum, prefix)])
        flags = regex.REVERSE if reverse else 0
        pat = regex.compile(regex.escape(query, special_only=True), flags=regex.UNICODE|regex.IGNORECASE|flags)
        for num, text in sl:
            try:
                m = next(pat.finditer(text))
            except StopIteration:
                continue
            start, end = m.span()
            length = end - start
            if text is postfix:
                start += cpos
            c = QTextCursor(self.document().findBlockByNumber(num))
            c.setPosition(c.position() + start)
            if num in headers:
                self.search_header_pos = start + length
            else:
                c.setPosition(c.position() + length, c.KeepAnchor)
                self.search_header_pos = 0
            if reverse:
                pos, anchor = c.position(), c.anchor()
                c.setPosition(pos), c.setPosition(anchor, c.KeepAnchor)
            self.setTextCursor(c)
            self.centerCursor()
            self.scrolled.emit()
            break
        else:
            info_dialog(self, _('No matches found'), _(
                'No matches found for query: %s' % query), show=True)

    def clear(self):
        PlainTextEdit.clear(self)
        self.line_number_map.clear()
        del self.changes[:]
        del self.headers[:]
        self.images.clear()
        self.search_header_pos = 0
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)

    def update_line_number_area_width(self, block_count=0):
        self.side_margin = self.line_number_area_width()
        if self.right:
            self.setViewportMargins(0, 0, self.side_margin, 0)
        else:
            self.setViewportMargins(self.side_margin, 0, 0, 0)

    def available_width(self):
        return self.width() - self.side_margin

    def line_number_area_width(self):
        return 9 + (self.line_number_map.max_width * self.number_width)

    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):
        PlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        if self.right:
            self.line_number_area.setGeometry(QRect(cr.right() - self.line_number_area_width(), cr.top(), cr.right(), cr.height()))
        else:
            self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))
        self.resized.emit()

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        painter.setPen(self.line_number_palette.color(QPalette.Text))
        change_starts = {x[0] for x in self.changes}

        while block.isValid() and top <= ev.rect().bottom():
            r = ev.rect()
            if block.isVisible() and bottom >= r.top():
                text = unicode(self.line_number_map.get(num, ''))
                is_start = text != '-' and num in change_starts
                if is_start:
                    painter.save()
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                if text == '-':
                    painter.drawLine(r.left() + 2, (top + bottom)//2, r.right() - 2, (top + bottom)//2)
                else:
                    if self.right:
                        painter.drawText(r.left() + 3, top, r.right(), self.fontMetrics().height(),
                                Qt.AlignLeft, text)
                    else:
                        painter.drawText(r.left() + 2, top, r.right() - 5, self.fontMetrics().height(),
                                Qt.AlignRight, text)
                if is_start:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1

    def paintEvent(self, event):
        w = self.viewport().rect().width()
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        floor = event.rect().bottom()
        ceiling = event.rect().top()
        fv = self.firstVisibleBlock().blockNumber()
        origin = self.contentOffset()
        doc = self.document()
        lines = []

        for num, text in self.headers:
            top, bot = num, num + 3
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            painter.setFont(self.heading_font)
            br = painter.drawText(3, y_top, w, y_bot - y_top - 5, Qt.TextSingleLine, text)
            painter.setPen(QPen(self.palette().text(), 2))
            painter.drawLine(0, br.bottom()+3, w, br.bottom()+3)

        for top, bot, kind in self.changes:
            if bot < fv:
                continue
            y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top)).translated(origin).y()
            y_bot = self.blockBoundingGeometry(doc.findBlockByNumber(bot)).translated(origin).y()
            if max(y_top, y_bot) < ceiling:
                continue
            if min(y_top, y_bot) > floor:
                break
            if y_top != y_bot:
                painter.fillRect(0,  y_top, w, y_bot - y_top, self.diff_backgrounds[kind])
            lines.append((y_top, y_bot, kind))
            if top in self.images:
                img, maxw = self.images[top][:2]
                if bot > top + 1 and not img.isNull():
                    y_top = self.blockBoundingGeometry(doc.findBlockByNumber(top+1)).translated(origin).y() + 3
                    y_bot -= 3
                    scaled, imgw, imgh = fit_image(img.width(), img.height(), w - 3, y_bot - y_top)
                    painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
                    painter.drawPixmap(QRect(3, y_top, imgw, imgh), img)

        painter.end()
        PlainTextEdit.paintEvent(self, event)
        painter = QPainter(self.viewport())
        painter.setClipRect(event.rect())
        for top, bottom, kind in sorted(lines, key=lambda (t, b, k):{'replace':0}.get(k, 1)):
            painter.setPen(QPen(self.diff_foregrounds[kind], 1))
            painter.drawLine(0, top, w, top)
            painter.drawLine(0, bottom - 1, w, bottom - 1)

    def wheelEvent(self, ev):
        if ev.angleDelta().x() == 0:
            self.wheel_event.emit(ev)
        else:
            return PlainTextEdit.wheelEvent(self, ev)