Thursday, June 25, 2015

A tough bug

So before I ship PPQT for real, I thought I better take one more look at an elusive problem. I had noticed that sometimes, keying ^f in the Edit panel did not bring the Find panel to the front as it is supposed to do. But the behavior seemed to be intermittent.

Recall that PPQT has on the left, a tabset of Edit panels, one per open document. And on the right, a tabset of various functional Panels like the Notes panel, Images panel, and Find panel. Each open document has its own set of function panels. So when you switch to a different document's Edit panel, the right half of the window is repopulated with the function panels for that document.

So what would happen is this. Say there are two documents open and document A is active (its Edit panel is visible). And on the right, the Images panel is displaying a scanned page from A. The user is typing and wants to find something, so keys ^f. What should, and usually does, happen is that the Find panel replaces the Images panel and the cursor focus moves to the Find text field.

What sometimes happened was that instead, the Images panel remained, but a narrow blue keyboard-focus rectangle appeared over it, outlining the position of the Find text field on the (invisible) Find panel.

It took a half-hour of repeated experiments to work out the failing conditions. There had to be at least two documents open. You had to switch between them in a certain order.

Internally, what I supposed was happening was that the Edit panel was trapping the ^f keystroke in its key event handler, and immediately issuing a signal named editKeyEvent, which was connected to a slot in the Find panel code. Using a breakpoint I verified that control got to the Find panel, and that it would call into a main window function to get itself displayed. The main window code is simple,

    def make_tab_visible(self, tabwidg):
        ix = self.panel_tabset.indexOf(tabwidg)
        if ix >= 0 : # widget exists in this tabset
            self.panel_tabset.setCurrentIndex(ix)
            return
        mainwindow_logger.error('Request to show nonexistent widget')

It asks the active tabset if it knows this widget; if it does, please make it active. What was happening was that the Find panel making this call was not the Find panel in the active tabset. It was the Find panel widget of the other document.

Wut?

This sent me off on a long tail-chase on the signal/slot setup. Somehow, I thought, the Edit panel for one book must have connected its ^f keystroke event signal to the wrong Find panel. But that code was all solid.

In the course of investigating the hook-up of the Edit signal to the Find panel, I noticed that there was another way for that signal to be emitted. It might come from the keyPressEvent handler of the edit panel. But it might also come from the Find... action of the Edit menu. Oho!

Months ago I implemented variable Edit menus. Each Edit panel has its own Edit menu, in which the menu Action for Find... (with a ^F keyboard accelerator option) was hooked to call a little function that emitted that Edit panel's signal to that Edit panel's Find panel. The Word panel and Notes panel also have their own Edit menus. This was all to get around Qt problems that plagued version 1, where different panels feuded over the ownership of the single global Edit menu. Now the Words panel has its own Edit menu that does things that relate to Words panel facilities, etc.

When any of these panels get a focusIn Event, they immediately call the main window asking it to put up their own custom Edit menu. When I implemented this, I put in a little gimmick to save some time.

_LAST_KEY = None
def set_up_edit_menu(key, action_list) :
    global _EDIT_MENU, _LAST_KEY
    if key != _LAST_KEY :
        _LAST_KEY = key
        _EDIT_MENU.clear()
        # code to populate the Edit menu from action_list
    _EDIT_MENU.setEnabled(True)

Each caller would pass a simple letter key, and the main window could avoid clearing and populating the menu when, as often happens, focus goes in and out of the same panel over and over.

The problem was, every Edit panel called with a key of 'E'. Ooops! That meant that when the focus moved from one document's Edit panel to another's—without a stop between in some other panel that had an Edit menu—the Edit menu would not be repopulated. It would still have the Actions defined by the first document. And that included a signal to the first document's Find panel when ^f was keyed or when Edit>Find was selected. So the signal would go to the wrong Find panel widget. It would see it wasn't visible, so it would call the main window; the main window would not find that widget in the current tabset; no Find panel would be displayed; but the Find panel would then call for keyboard focus to its Find text field, resulting in a focus rectangle over the top of the Images panel.

The fix was to make the key be something unique to the caller, and Python supplies a unique id via the id() built-in function. For a bonus, the callers no longer have to provide that key as an argument.

def set_up_edit_menu(action_list) :
    global _EDIT_MENU, _LAST_MENU
    if id(action_list) != _LAST_MENU :
        _LAST_MENU = id(action_list)
        _EDIT_MENU.clear()
        # populate the Edit menu from action_list
    _EDIT_MENU.setEnabled(True)

No comments: