Monday, April 13, 2015

What I can and can't control

Started today with things I can control, namely, issues that affect my code. I fixed a minor issue where the numbers in the Footnotes table weren't vertically aligned same as the text items. I fixed the issue where the Bookloupe table produced a bunch of console messages when first refreshed. I closed two issues that had been rendered moot by an enhancement to the natsort package. I fixed the issue about the Help viewer not having any "back" facility.

Implementing "Back" on the Help

That was interesting. It brought me back to familiarity with the limitations of QWebPage and QWebHistory. When you first load a web page from a file, its browser history is empty. And it stays empty during any number of link-following clicks, if those clicks are only to anchors within the current page. It just doesn't record local links. Only if you click away and make it load an external URL does it make a history entry.

Well, what does a user do with a Help page, which is quite a complete little manual with a TOC on one side? Clicks around to local links, of course. Rarely exiting the page to another page. So for most of what the user does, there is no history, and self.view.history().canGoBack() returns False.

So I had to implement my own history stack. This turned out to not be difficult. There is a facility for "delegating" all links. When this is done, the user's clicking on a link only creates a signal. The slot for the signal gets a copy of the QUrl which is the target. So I just got the string value, and if it began with "file:" then I assumed it was local and appended it to a list of strings. Then handed it on to the QWebView.load().

When the user hits one of the keys ctl-[, ctl-b, or ctl-left, my keyEvent code asks first if self.view.history().canGoBack(). If so, call self.view.back(); done. Else, is the list of link strings nonempty? If so, pop off the last one, make it a QUrl, and call self.view.load(). If the list is empty, call the subroutine that reloads the help file; that puts the view back to the top of the page.

Bundling for Linux

Then, feeling rather full of myself, I ran cxfreeze to bundle the newly updated app, and zipped that up and put it on my Public Dropbox.

Then I fired up my "test" system which has none of Python or Qt installed. Well, turns out that a stock Ubuntu 14 does have Qt installed. I kind of knew that, because I kind of knew that some of the Ubuntu programs (Software Updater, I think) are Qt-based. Anyway the test is, does this stock system run the bundled app.

Would you like to guess?

No, of course it bloody doesn't. "Cannot mix incompatible Qt library (version 0x50300) with this library (version 0x50401) Aborted (core dumped)"

Sigh.

If you search on that string you will find a whole lot of people running a wide variety of software over the past 3 years who hit that exact message. What it means is, the PyQt5.QtCore library is trying to load libQt5Core.so, and it is getting one, but from the local system instead of from the bundle-folder.

But Dave, you say, I thought that you were all happy with cxfreeze because it was bundling everything, all the dependencies.

Well, it appears I was deceived. It is not bundling the Qt5 dependencies. It is treating them like libc.so, to be obtained locally.

I quickly trained myself on ltrace and ldd, the Linux equivalents of tools I was using on MacOS to figure out what nuitka wasn't bundling. Just as soon as the program calls into PyQt5.QtWidgets to create a QApplication, boom. If I do ldd on PyQt5.QtWidgets, I can see the links to /usr/lib for the libQt* libraries.

Edit: I was mistaken. The desired libraries, e.g. libQt5Core.so.5, are in fact there in the bundle. They are being copied correctly. However, the links revealed by, for example, "ldd PyQt5.QtCore.so" are not pointing at the files in the bundle. They are pointing at /usr/lib. The problem is not what is being copied, it is the link info in the PyQt5.Q* modules.

The link info in the copied libraries, e.g. the links from "ldd libQt5Core.so.5", have been altered to point into the bundle. So it is a matter of pointing the links in the PyQt modules as well. I now have to go and find out how cxfreeze does that, when it does it, and why it doesn't do it in all cases.

No comments: