Monday, August 18, 2014

Finding a Hitch Right Away

Today I mostly spent tidying up editview, and making its unit test and Sikuli tests work. They were back-level. When I wrote them there was no mainwindow or book, so they faked the features of those modules, but they faked them in ways other than I eventually implemented. So I had to strip out the fakery and put in real modules. But then stuff worked.

I spent quite a bit of time on a support method of editview called center_this(), which is given a QTextCursor with a selection of some unknown length (usually short, but I didn't want to limit it), and makes sure that the whole selection is visible, or if the selection is actually taller than the edit's viewport, its top and as much more as possible is visible. This is different from the existing ensureCursorVisible() method of QTextEdit, which only ensures that the first line of a selection is on the screen someplace.

It turned out to be complicated mostly because of the different units involved. You can get the location of a bit of text by the following means,

    def _top_pixel_of_pos(self, pos):
        tc = QTextCursor(self.document)
        tc.setPosition(pos)
        return self.Editor.cursorRect(tc).y()

Apply that to the cursor's selectionStart() and selectionEnd() positions, and add to the latter the height of a line from QTextEdit.fontMetrics().lineSpacing(), and you have the top and bottom of a selection in "viewport coordinates". You get the viewport size, and then can work out by how many pixels you need to scroll up or down to put the selection in the middle.

Now what? Well, the only reliable scrolling method is to modify the value of the vertical scroll bar. (QTextEdit inherits a scroll() method but when I tried to use it some very strange things happened. QTextEdit also inherits from QAbstractScrollArea, which gives it a scrollContentsBy() method that is explictly not to be used "programmatically".)

But the scrollbar operates in arbitrary units, so you have to get your move distance in pixels as a ratio of the pixel-height of the document, and multiply to get a fraction of the scroll bar's maximum value... bleagh. It works, finally.

That's not what I wanted to write about.

For the Find module I really want to provide the user with full PCRE-compatible regexes, and I discussed this in an early post here, where I concluded I was forced to use Python regexes, not Qt5's new PCRE-compatible QRegularExpression support.

Only problem with that is, it requires getting Python-code access to the document contents, which means calling QPlainTextEdit.plainText(). That copies the entire document contents into a Python3 string value. If I have to do that a lot, there could be considerable performance impact. Plus there's the issue of replacing a matched string. To do that, I'd have to

  • Note the position and length of the matched string
  • perform the regex replace in the Python string
  • copy the replacement string
  • form a QTextCursor to select the matched string by position and length in the editor's world
  • use its insertText() method to replace the selection with the replacement string.

Doable, but not pretty. What would be nice would be, if the find() method of the Qt5 version of QTextDocument had been updated to accept a QRegularExpression. I spent a lot of time in V1 working around the restrictions of the document's find() method, but if I could use it, I could avoid trying to keep two massive texts in sync.

So I just tried passing a QRegularExpression to QTextDocument.find() and it was rejected, "arguments did not match any overloaded call". Oh sigh.

Just in case I'm missing something, I posted in the Qt general forum. But I don't have a lot of hope for this.

No comments: