Showing posts with label Mac OS X. Show all posts
Showing posts with label Mac OS X. Show all posts

Thursday, April 17, 2014

What's Happening?

The last couple of days have been a long digression into the world of QTest and QEvents. It was motivated by my desire to make an automated unit test particularly of the context menu I've added to the editview.

The motivation for a context menu, like so many changed features of PPQT2, is that there can be multiple books open at once. In V1, there was only one book and accordingly only one:

  • Primary spelling dictionary
  • File of "scannos" (common OCR errors like "arid")
  • Choice of whether or not to highlight spelling errors
  • Choice of whether or not to highlight scannos

But now these choices have to be individualized per book. In V1, there could be a File menu action, "Choose scanno file..." but in V2, if that action was in the File menu, there'd have to be a convention about which of the (possibly several) open books those scannos should apply to. The one with the keyboard focus? Suppose the keyboard focus is in the Help panel? Similarly for the V1 View > Choose Dictionary menu item. And for the View > Highlight Scannos/Spelling toggles. All these choices need to be clearly associated to a single book. Hence, a context menu in the editor panel with four actions. When you have to right-click on the editor in order to choose a scanno file, you know where those scannos will be applied.

The little context menu is in and working, at least to casual testing. But I'm trying to do this shit right; and that means, an automated unit test. And that meant, I presumed, using QTest's mouse actions to simulate a control-click on the edit widget.

So I wrote up a test case that went in part like this:

ev = the_book.editv.Editor # ref to QPlainTextEdit widget
ev_w = ev.width()
ev_h = ev.height()
ev_m = QPoint(ev_w/2,ev_h/2) # the middle
QTest.mouseClick(ev, Qt.LeftButton, Qt.CtlModifier, ev_m, 1000)

...and, it didn't work. Nothing. I tried all sorts of mouse actions using both QTest's methods (mouseClick, mouseDblClick, mousePress, mouseRelease) and actually composing my own QMouseEvent objects and pushing them in with QApplication.postEvent(). Fast forward through about six hours of fiddle-faddling over three days. Sometimes I could get a double-click to work and sometimes not. Mouse presses or clicks with any button and modifier—nada. zip.

Now as it happens, I have an "event filter" on the editor. This is because I want to handle certain keystrokes, as described previously. But the edit widget is created by code generated from Qt Designer. That means it can only be a standard QPlainTextEditor. The normal way to intercept keystrokes is to subclass a widget and override its keyPressEvent() method. Don't think there's a way to get Qt Designer to plug in a custom subclass of a widget type.

However there's a way that you can install an "event filter" on any widget. That directs all events for that widget through the filter function. If it handles the event it returns True; if not, it returns False and the event is presented to the widget in the normal way. So editview puts a filter on events to the edit widget, picks off just the keyEvents it wants, and passes the rest on.

So I took advantage of this to just print out all the events passing through the edit widget so I could find out just what the heck mouse events it was getting when I clicked to bring up the context menu.

Surprise! It doesn't get any!

The Qt docs would have one believe that as a mouse moves over and clicks or drags on a widget, there's a constant flow of QMouseEvent objects to it. Nope. Not on my Macbook, anyway.

There are Enter and Leave events as the mouse pointer comes into and out of the frame of the widget. These aren't mouse events as such. There are lots of other sorts of events like Paint and Tooltip. But there are almost no mouse events posted. What does appear while the mouse is active, on every click and streaming during any drag, is QInputMethodQuery events. During a mouse click or drag, when I'd expect a stream of QMouseEvent postings, all that comes is a stream of QInputMethodQuery.

This peculiar class has only one property, a query with a not very helpful list of values. Of these possible "queries" only one is being sent in my system, the query IMEnabled meaning "The widget accepts input method input". The receiver is supposed to set something from a set of even less-interesting values in the event. Of course, my event filter doesn't see what is being set; it only sees the event on its way in.

Something nefarious is going on here. Perhaps it is only in the Mac OS; perhaps it only affects QTextEdit and derivatives (QTest mouse actions directed to other widgets seem to work). But for the editor, on my macbook, the whole mouse event architecture is effectively being ignored, replaced by something only minimally documented and not amenable to code introspection for unit-testing.

There are also a few InputMethodQueries issued just before any keyPressEvent and I am deeply suspicious that this is related to the inconsistent handling of the Mac keyboard I noted earlier.

That aside, the net from all this investigation is to realize that I don't really need to simulate the mouse at all. All I need to do is to fabricate a QContextMenuEvent with a given position in the middle of the editor. Post that; then use getChildAt that same position to get a reference to the context menu, and then I can send it keystrokes using QTest.

To be tried tomorrow.

Friday, April 11, 2014

Why Can't Huns Spell?

Lordy but I hate the kind of work described below. It's really stressful. (I know, kvetch, kvetch, kvetch.)

I'm to the point where I want to test the ability to mark words that fail spellcheck. To do that, I need the ability to check spelling, duh!

PPQT version 1 went through stages of spell-checking, each representing many hours of effort. First, trying to send words over a pipe to Aspell running as a subprocess. Then I wrote my own all-Python spell-checker to use the Myspell/OpenOffice dictionary format. That was a useful learning exercise. I learned:

  • All about the format and content of the .dic/.aff dictionary files.
  • That German is a damned hard language to spell-check.
  • That there are a lot of subtleties to the spell-check algorithms.

In the end German defeated my code. I just couldn't get it to handle multiple affixes properly. In the nick of time I found this Python wrapper for Hunspell. Like a lot of FOSS, it was created by someone who needed it a few years ago, and that person has apparently moved on and left it dangling unmaintained. But it can be made to work, with effort—for Python 2.x. And it was suuu-weet once I got it going, blazing fast and reliable. I made it work for Mac OS and for Linux, but blew many hours failing to make it work for my Windows distribution. Eventually I went on ELance and paid a dude $150 to make the Hunspell wrapper work on Windows. Money well spent.

But PPQT2 is built on Python 3.3 (well, probably 3.6 by the time it's done) and the Hunspell wrapper doesn't work for that. However, another user posted a diff file that, he claimed, made it work with Python 3. So I spent some hours today getting it compiled and installed.

It should be a one-liner, python setup.py install, but of course that don't work because there are things in the setup.py script that assume Linux, and a prior release of Hunspell. So you tweak that a while. Reviewing my notes from last fall, you run python setup.py build and it fails, then you manually run a compile command that works to actually create the module, then python setup.py install will install it. The compile command that worked for 2.7 (contributed to that wiki by another user, bless her heart) was:

gcc -fno-strict-aliasing -fno-common -g -fwrapv\
 -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch x86_64\
 -pipe -D_LINUX -I/usr/include/hunspell\
 -I/usr/include/python2.7 -lpython -lhunspell-1.2\
 -shared hunspell.c -o build/hunspell.so

But that doesn't work after the diff was applied for Python 3. It coughed up an unresolved symbol _PyModule_Create2 for no apparent reason. So, what's a search engine for if not to find obscure error messages? And Da Google turned up many people with this problem dating back to 2010. A stackoverflow response, although not directly responsive, pointed to lack of inclusion of the python3.3 library, and that was it. Here's the command that actually compiles and links hunspell for Python 3:

P=/Library/Frameworks/Python.framework/Versions/Current
gcc -fno-strict-aliasing -fno-common -g -fwrapv\
 -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch x86_64\
 -pipe -D_LINUX -I/usr/local/include/hunspell\
 -I$P/include/python3.3m\
 -L$P/lib -lpython3.3 -lhunspell-1.3 -shared\
 hunspell.c -o build/hunspell.so

So I now have a working Hunspell that I can start playing with, on Mac OS at least. Such a relief! OK, Ghu alone knows what it will take to make it work on other platforms, but that's months away. For now, my Huns can spell.

Friday, April 4, 2014

Further on the Mac Option Key

The Qt Forum post I made about the Option-key problem, after 22 hours, has been viewed 32 times but drawn no responses. I also posted a respectful query on the pyqt list this morning (after obsessing about the issue some of the night).

I also spent a couple more hours delving deeply into the QCoreApplication, QGuiApplication, and QApplication docs, hoping to find some kind of magic switch to change the behavior of the key interface. I speculate that Qt5 has better Cocoa integration and as a result is getting the logical key from a higher-level interface than before.

Supposing it can't be fixed or circumvented, what I will have to do is: In constants.py where the key values and key sets are determined, check the platform and use Qt.MetaModifier instead of Qt.AltModifier when defining keys for Mac. This substitutes the actual Control shift for the Option shift.

That would be the only module with a platform dependency. Others just use the names of keys and key-sets defined in constants.py. For the user, I will have to have separate documentation about bookmarks, for Mac and non-Mac. For non-Mac, it'll remain "Press control and alt with a number 1-9 to set that bookmark." For mac it will be "Press the Control key and the Command key together with a number 1-9..." And the beautiful consistency ("where you see 'alt' think 'option'" at the front and never mention it again) is gone.

Another issue is the use of ctl-alt-M and ctl-alt-P in the Notes panel, to insert the current line or image number. Possibly I can just change the key definition in constants to whatever the mac keyboard generates for option-M and option-U (pi and mu, it seems). Or keep the directions consistent, and completely wipe out any use of Option-keys in Mac.


Also today I tested and committed the zoom keys, which work a treat. The unit test module buzzes up 10 points and down 15, looks great.

Thursday, April 3, 2014

A Bump in the Road

Today I thought I'd add in the special keystrokes to the editview. There are three groups of them: a set that interact with the Find dialog (^f, ^g, etc), and these I'm deferring until I actually work on the Find panel; a bookmark set, (ctl-1 to 9 to jump to a bookmark, ctl-alt-1 to 9 to set one); and ctl-plus/minus to zoom. All of these were implemented and working in version 1, using the keyPressEvent() method to trap the keys.

So I messed around and tidied up the constants that define the various groups of keys as sets, so the keyPressEvent can very quickly determine if a key is one it handles, or not: if the_key in zoom_set and so on.

With the brush cleared, I copied over the keyPressEvent code from V1 and recoded it (smarter and tighter) for V2 and ran a test, and oops something is not working.

Specifically, it is no longer possible to set bookmark 2 by pressing ctl-alt-2. On a mac, that's command-option-2, which Qt delivers as the Qt.ALT_MODIFIER plus Qt.CTL_MODIFIER and the key of Qt.KEY_2.

Or rather, it used to do that. I fired up PPQT version 1 just to make sure. Yup, could set a bookmark using cmd-opt-2. But not in the new version. Put in debug printout. The key event delivered the same modifier values, ctl+alt, but the key value was... 0x2122, the ™ key? And cmd-alt-3 gave me Qt.KEY_STERLING, 0xA3. And cmd-alt-1 is a dead key.

Pondering ensued. OK, these are the glyphs that you see, if you open the Mac Keyboard viewer widget and depress the Option key. So under Qt5, the keyboard event processor is delivering the OS's logical key, but under Qt4 in the same machine at the same time it delivers the physical key.

Oh dear.

I spent several hours searching stackoverflow and the qt-project forums and bug database but nothing seemed relevant. I posted a query in the Qt forum. But I have little hope. It looks very much as if I'll have to change they key choices for bookmarks, and make them platform-dependent. In Windows and Linux they can continue to be ctl[-alt]-1 to 9, but in Mac OS this will change. The only reliable special key modifiers are control (Command) and meta (the Control key!).

In V1 it was great that I could document just once at the top of the docs, that in Mac, "ctl means cmd" and "alt means option". And that was consistent throughout. Now it won't be because the Option key is effectively dead for my purposes. I'll have to tell the mac user, "when I say control I mean command, but when I say alt, I mean control." Won't that be nice? Plus, I'll have to have code that looks at the platform and redefines the key sets for Mac at startup. Very disappointing.