Saturday, August 16, 2014

OK, I am embarrassed (again)

I try not to make a blithering idiot of myself in public too often, but in this matter of highlighting the current line, I certainly have. Proudly presenting code snippets showing an entirely wrong way of doing it... I blush.

OK the right way to highlight some line is to present it to the editor (QTextEdit or QPlainTextEdit) as an "extra selection." An extra selection is a ... what? It isn't a class on its own. It's an object that you acquire as follows:

        self.current_line_sel = QTextEdit.ExtraSelection()

You may have many of these; you tell the editor about them by passing a list of them to its setExtraSelections() method. In my case, a list of one item, a selection marking the current line.

An extra selection object is basically a tuple comprising a QTextCursor and a QTextCharFormat. These are assignable properties, there are no get-set methods for them. So here's the whole initialization.

        self.current_line_fmt = QTextCharFormat()
        self.current_line_fmt.setProperty(QTextFormat.FullWidthSelection, True)
        self.current_line_fmt.setBackground(colors.get_current_line_brush())
        self.current_line_sel = QTextEdit.ExtraSelection()
        self.current_line_sel.format = QTextCharFormat(self.current_line_fmt)

The bit about setting the FullWidthSelection property of the QTextCharFormat is key; it ensures that the new background color will be painted the width of the editor's viewport regardless of the length of the current line. I wouldn't have know that without seeing it in the Code Editor example.

When the cursor-move signal arrives, it is only necessary to update the position of the cursor in the selection, and to re-assign the list of selections. Here is the abbreviated cursor-move code now.

    def _cursor_moved(self):
        tc = QTextCursor(self.Editor.textCursor()) # copy of cursor
...several lines snipped...
        # Change the extra selection to the current line.
        tc.clearSelection()
        self.current_line_sel.cursor = tc
        self.Editor.setExtraSelections([self.current_line_sel])

It is necessary to clear the selection from the cursor before using it. Without that step, my current-line highlight disappears as soon as a non-empty selection is made. Double-click a word and it is highlighted, and the current-line highlight goes out.

It is also necessary to re-assign the list of extra selections every time. It is not enough to update the existing selection's cursor. You have to make the editor aware of the change. Which kind of makes sense.

Anyway that's all there was to it. The code is 50% less than the method I displayed in three previous blog posts. And it has no effect on the undo/redo stack or the document's modified status. So it's all good now, except for my ego.

No comments: