Monday, September 1, 2014

Sometimes things just work...

In PPQT V1 I emulated a great feature of Guiguts: the ability to limit find/replace to a range of text. For the savvy user this is very useful. Say you are formatting a big table, or the particular range of text like the index of a book. You'd like to do a multiple replace operation within that block of text. A typical example for a table of contents or an index would be to change all \b(\d+)\b page numbers into links to #Page_\1 anchors. You don't want to change every number in the book that way, only the ones in the block of text you are working on.

So you select a block of text and through some feature of the UI, tell the program that this is the range for find/replace operations. Then you can safely do a global replace and know that it will only affect that span.

I forget what the UI for this is in Guiguts. It was something arbitrary and tricky. PPQT V1 was no less arbitrary and tricky. You clicked the "In Selection" checkbox of the Find panel and then clicked either the First or Last search button. At that point the current selection got set as the range.

You had to trust that this was the case, however. Neither V1 nor its model Guiguts showed any visible indication that a search range had been set. You just had to hope you'd set it to cover all, but only, the text you wanted to work on.

For V2, I want to make the setting of the search range more intuitive and also give a visible sign of it. After the great success using the Extra Selections feature of the editor for the current line, I realized that this was the way to put a visible background color under the chosen find range. So what I did this morning was to make this happen:

What you see is a pale blue background showing the extent of a limited find/replace range. Superimposed on it is the pale yellow current-line highlight, and therein lies a tale.

First, the UI. For V2, the way you say "I want a restricted search range" is to click the "In Selection" checkbox making it checked. When that checkbox goes from unchecked to checked, I look at the current selection and if it is "large enough" (a rather arbitrary 100 characters or 4 lines) that selection is made the find-range. And it turns light blue and stays that way until you click "In Selection" off again. So much more obvious than the V1 rule, which I don't even want to think about, it's so dumb.

Displaying the blueness required changes in two other modules. First, all the colors are localized in colors.py, so I had to add get/set_find_range_brush methods to that, and to save and restore the find-range color choice from settings.

Second, the visible display of the document is up to editview.py, which was already handling the current-line mechanism. So for both practicality and MVC purity, that's where display of the range should be. So I generalized that code to always have not one, but two "extra" selections.

Recall that an extra selection is basically a tuple of a QTextCharFormat and a QTextCursor. Anytime any extra selection is altered, you have to call setExtraSelections with a list of them. Previously the list had only one element, the current-line selection. But it was easy to change that so the list always included two selections, one for current line and one for find-range.

For the current line, the related cursor gets updated whenever the edit cursor moves. But for the find-range selection, the related cursor has normally no selection, and thus has no visible effect. The findview code calls into the editview code to set_find_range(cursor) and that puts a text selection in the find-range cursor. And a clear_find_range() call takes it out again.

With that in place I could add the code to the half-done Find panel to use it, and test it and it worked! Well, except for one thing. When the cursor moved through the blue area, the yellow highlight of the current line disappeared. The blue background had priority over the yellow background. Awwww. But wait! The editview was initializing its list of extra selections so:

self.extra_sel_list = [self.current_line_sel, self.range_sel]

Just maybe... I reversed the order of the list,

self.extra_sel_list = [self.range_sel, self.current_line_sel]

and the current line brush now took priority over the blue range brush!

This doesn't seem to be documented but it's good to know: the "extra selection" list is prioritized left to right. Or maybe it goes back-most to front-most in visual depth?

Whatever. Find still has no code for finding anything, but all its widgets work and the In Selection highlight is purty.

No comments: