Posts Tagged ‘QTextEdit’

QTextEdit with line numbers

June 5th, 2010

The Qt QPlainTextEdit class provides a very nice plaintext editor that can be used e.g. for displaying and editing source code. One thing missing in the class though is the ability to display line numbers in the margin of the editor, which (in my opinion) is a very important feature of any code editor. Luckily, after a bit of searching on the web I found this excellent blog entry which includes a code snippet that extends QTextEdit with the ability to display line numbers. For one of my recent projects I used and slightly modified this code, eliminating among other things the QFrame that was used there and using a QPlainTextEdit instead of a QTextEdit as base class of the editor, which makes the processing of the line numbers slightly faster. Here is my modified version of the code:

#Original code (MIT license): http://john.nachtimwald.com/2009/08/15/qtextedit-with-line-numbers/
from PyQt4.QtGui import * 
from PyQt4.QtCore import *
import sys
 
class LineTextWidget(QPlainTextEdit):
 
    def append(self,string):
        self.appendPlainText(string)
 
    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):
            width = QFontMetrics(self.edit.document().defaultFont()).width(str(self.highest_line)) + 10
            if self.width() != width:
                self.setFixedWidth(width)
                self.edit.setViewportMargins(width,0,0,0)
            QWidget.update(self, *args)
 
        def paintEvent(self, event):
            contents_y = 0
            page_bottom = self.edit.viewport().height()
            font_metrics = QFontMetrics(self.edit.document().defaultFont())
            current_block = self.edit.document().findBlock(self.edit.textCursor().position())
 
            painter = QPainter(self)
 
            # Iterate over all text blocks in the document.
            block = self.edit.firstVisibleBlock()
            viewport_offset = self.edit.contentOffset()
            line_count = block.blockNumber()
            painter.setFont(self.edit.document().defaultFont())
            while block.isValid():
                line_count += 1
 
                # The top left position of the block in the document
                position = self.edit.blockBoundingGeometry(block).topLeft()+viewport_offset
                # 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()) + font_metrics.ascent()+font_metrics.descent()-1, 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):
        QPlainTextEdit.__init__(self, *args)
 
        self.number_bar = self.NumberBar(self)
        self.number_bar.setTextEdit(self)
 
        self.viewport().installEventFilter(self)
 
    def resizeEvent(self,e):
        self.number_bar.setFixedHeight(self.height())
        QPlainTextEdit.resizeEvent(self,e)
 
    def setDefaultFont(self,font):
      self.document().setDefaultFont(font)
 
    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 is self.viewport():
            self.number_bar.update()
            return False
        return QPlainTextEdit.eventFilter(object, event)
 
#To test the class.
if __name__ == '__main__':
  qApp = QApplication(sys.argv)
  qApp.connect(qApp, SIGNAL('lastWindowClosed()'), qApp,
                    SLOT('quit()'))
 
  MyWindow = QMainWindow()
 
  MyEdit = LineTextWidget()
 
  MyWindow.setCentralWidget(MyEdit)
 
  MyWindow.show()
 
  qApp.exec_()