* QTextEdit With Line Numbers

Posted on August 15th, 2009 by John. Filed under programming.


Here is a Qt4 widget written in Python that allows for line numbers next to a QTextEdit. Similar to what is seen in a number of text editors such as gedit and kate.

from PyQt4.Qt import QFrame, QWidget, QTextEdit, QHBoxLayout, QPainter
 
class LineTextWidget(QFrame):
 
    class NumberBar(QWidget):
 
        def __init__(self, *args):
            QWidget.__init__(self, *args)
            self.edit = None
            # This is used to update the width of the control.
            # It is the highest line that is currently visibile.
            self.highest_line = 0
 
        def setTextEdit(self, edit):
            self.edit = edit
 
        def update(self, *args):
            '''
            Updates the number bar to display the current set of numbers.
            Also, adjusts the width of the number bar if necessary.
            '''
            # The + 4 is used to compensate for the current line being bold.
            width = self.fontMetrics().width(str(self.highest_line)) + 4
            if self.width() != width:
                self.setFixedWidth(width)
            QWidget.update(self, *args)
 
        def paintEvent(self, event):
            contents_y = self.edit.verticalScrollBar().value()
            page_bottom = contents_y + self.edit.viewport().height()
            font_metrics = self.fontMetrics()
            current_block = self.edit.document().findBlock(self.edit.textCursor().position())
 
            painter = QPainter(self)
 
            line_count = 0
            # Iterate over all text blocks in the document.
            block = self.edit.document().begin()
            while block.isValid():
                line_count += 1
 
                # The top left position of the block in the document
                position = self.edit.document().documentLayout().blockBoundingRect(block).topLeft()
 
                # Check if the position of the block is out side of the visible
                # area.
                if position.y() > page_bottom:
                    break
 
                # We want the line number for the selected line to be bold.
                bold = False
                if block == current_block:
                    bold = True
                    font = painter.font()
                    font.setBold(True)
                    painter.setFont(font)
 
                # Draw the line number right justified at the y position of the
                # line. 3 is a magic padding number. drawText(x, y, text).
                painter.drawText(self.width() - font_metrics.width(str(line_count)) - 3, round(position.y()) - contents_y + font_metrics.ascent(), str(line_count))
 
                # Remove the bold style if it was set previously.
                if bold:
                    font = painter.font()
                    font.setBold(False)
                    painter.setFont(font)
 
                block = block.next()
 
            self.highest_line = line_count
            painter.end()
 
            QWidget.paintEvent(self, event)
 
 
    def __init__(self, *args):
        QFrame.__init__(self, *args)
 
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
 
        self.edit = QTextEdit()
        self.edit.setFrameStyle(QFrame.NoFrame)
        self.edit.setAcceptRichText(False)
 
        self.number_bar = self.NumberBar()
        self.number_bar.setTextEdit(self.edit)
 
        hbox = QHBoxLayout(self)
        hbox.setSpacing(0)
        hbox.setMargin(0)
        hbox.addWidget(self.number_bar)
        hbox.addWidget(self.edit)
 
        self.edit.installEventFilter(self)
        self.edit.viewport().installEventFilter(self)
 
    def eventFilter(self, object, event):
        # Update the line numbers for all events on the text edit and the viewport.
        # This is easier than connecting all necessary singals.
        if object in (self.edit, self.edit.viewport()):
            self.number_bar.update()
            return False
        return QFrame.eventFilter(object, event)
 
    def getTextEdit(self):
        return self.edit

Tags: , ,



6 Responses to “QTextEdit With Line Numbers”

  1. John Says:

    All code posted unless otherwise specified is licensed under the MIT License. This is compatible with your GPL3 project, just add the header at that link to the file. However, if you really don’t want MIT code in your project I’m okay with relicensing it under the GPL3 for use in your project. Again, just add the GPL3 header with attribution to me. Also, have a look at my Better QPlainTextEdit With Line Numbers because it fixes a few short comings of this one. If you have any questions about it and extending it feel free to ask.

  2. Lee Harr Says:

    This TextEdit line numbering code is useful for me. There is no license mentioned. May I use it in a GPL3 project I am working on? Do you require attribution? Thanks!

  3. Lee Harr Says:

    Thanks. I tried the Better Line Numbers, but I am still running PyQt 4.4 and there is some code there that will not run (eg TextEdit.cursorPositionChanged). This one is doing exactly what I need. I appreciate your generous licensing.

  4. Paul Van Camp Says:

    Very impressive. It really must have taken some digging on your part figure out how to get the numbers to line up correctly.

    I thank you and the artists at our studio who will be using this thank you.

    PVC

  5. Andreas Says:

    Very nice code, exactly what I was looking for!

    I slightly modified the code to fit my needs, eliminating the QFrame and using a QPlainTextEdit instead of a QTextEdit as base class of the editor. I published the modified code on my blog: http://www.japh.de/blog/qtextedit-with-line-numbers/

  6. John Says:

    Andreas,

    Take a look at http://john.nachtimwald.com/2009/08/19/better-qplaintextedit-with-line-numbers/ . It uses a QPlainTextEdit instead of the QTextEdit and adds a few things like highlighting of the current line. It still uses the QFrame… I do like your changes to eliminate the use of the QFrame.

Trackback URI | Comments RSS

Leave a Reply