guitone Mac OS X package

Newer versions of Qt come with the nice and handy tool `macdeployqt` – I think it first appeared offically in 4.6. What this basically does is that it looks for linked Qt libraries and plugins in your binary file and copies the needed ones over to your application bundle, so your application is ready for distribution.

So far so good, this worked nicely with the [Carbon binaries](http://qt.nokia.com/downloads/qt-for-open-source-cpp-development-on-mac-os-x) supplied by Nokia, but it stopped working properly with the Cocoa builds. While the latter are advertised as “…for Mac OS 10.5 – 10.6” and really let you build and run your application on 10.5 (yes, I still haven’t followed the crowd and updated), the `macdeployqt` does not. It depends on newer versions of `strip` and `install_name_tool` which only come with newer versions of Xcode, i.e. >= 3.2. And guess what… yes, Xcode 3.2 is of course not available on 10.6.

(Nokia has actually an older, already closed [bug report](http://bugreports.qt.nokia.com/browse/QTBUG-9318) on the issue, but I doubt they will fix the issue, they do not invest much anyways on non-mobile platforms these days and 10.7 is soon out as well, making 10.5 look even more deprecated…)

So at first I thought “lets try build a Cocoa distribution” for guitone, because I believed this could bring some speed and other improvements and I accepted that I had to kick 10.4 support out of the way for this to achieve, but this bug is actually a show stopper for me.

I’ll probably have to give away Mac packaging for guitone in the mid-term future anyways. I do not plan to upgrade to 10.7 nor to a complete new Mac (I don’t like the direction Apple is heading, but this is a different story) and Qt 4.8 will definitely no longer ship with Carbon support.

Qt with Cocoa

If you build your Qt app on Mac OS X the first time with the Cocoa version of Qt and you receive weird UI lockups / freezes while your CPU and memory is eaten step by step, ensure you replaced any existing

QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.4
QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.4u.sdk

with

QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.5
QMAKE_MAC_SDK = /Developer/SDKs/MacOSX10.5.sdk

in your project file, because on Mac OS X 10.4 Qt Cocoa is officially unsupported. So if you need OSX 10.4 compatibility, stick with the Carbon version of Qt (4.7 will be the last version which comes with that), otherwise raise the bar to 10.5 or later and use the Cocoa build.

Makefile-based InnoSetup automation with QMake

Over the last couple of weeks I did several major improvements to the QMake-based build setup guitone uses: The project file comes now with one target to create a tarball, one to create a Mac OS X disk image containing all the needed Qt libraries and one target to install the application, which can be configured to use all the options you know from autotool-based projects (like PREFIX, BINDIR or DESTDIR, to name a few).

But yes, there was one task which was yet missing there – one to automatically create a Win32 installer. The steps to produce that had been so far:

  1. enter the to-be-packaged version in the InnoSetup script file
  2. convert the supplied text files from Unix to DOS line endings, while giving them a .txt extension
  3. call the InnoSetup compiler on the script file and create the executable

Especially the first and second action looked hard to automate, given the fact that Windows does not come with a rich set of tools to process text streams – and requiring a Cygwin installation just for using sed seemed awkward to me. Obviously other people had similar problems before and somebody proposed to emulate sed with a VBScript which would be executed by the Windows Scripting Host (WSH). Wow, cool thing – if I’d just remember my broken Visual Basic knowledge. But didn’t Microsoft have this Javascript Look-a-Like, JScript? Shouldn’t this be executable as well?

Apparently it was and I sat down to hack an improved JScript sed version:

var patterns = new Array();
var replacements = new Array();
var argcount = 0;

for (var i=0; icscript and to combine everything for a proper QMake target. Here we go:

DOCFILES="NEWS README README.driver COPYING"
...
win32 {
    isEmpty(QTDIR):QTDIR           = "c:\Qt\4.6.2"
    isEmpty(MINGWDIR):MINGWDIR     = "c:\MinGW"
    isEmpty(OPENSSLDIR):OPENSSLDIR = "c:\OpenSSL"
    isEmpty(ISCC):ISCC = "c:\Program Files\Inno Setup 5\ISCC.exe"
    
    win32setup.depends  = make_first
    win32setup.target   = win32setup
    win32setup.commands = \
        cscript //NoLogo res\win32\sed.js \
            s/@@VERSION@@/$${VERSION}/ \
            s/@@QTDIR@@/$${QTDIR}/ \
            s/@@MINGWDIR@@/$${MINGWDIR}/ \
            s/@@OPENSSLDIR@@/$${OPENSSLDIR}/ \
            < res\win32\guitone.iss.in > res\win32\guitone.iss && \
        ( for %%f in ($$DOCFILES) do \
            cscript //NoLogo res\win32\sed.js \
                s/\n\$$/\r\n/ \
                < %%f > %%f.txt ) && \
        \"$$ISCC\" res\win32\guitone.iss && \
        ( for %%f IN ($$DOCFILES) do del %%f.txt )
    
    QMAKE_EXTRA_TARGETS += win32setup
}

So if you know enough Javascript you can probably emulate whatever tool you’re missing on Win32 without having to depend on any external dependency. Very cool!

Quitting a Qt application from the Mac OS X dock

So for some weird reason my application was not closable from the Mac OS X dock – it quit properly when you selected “Quit” from the application menu or hit the Cmd-Q shortcut – but the dock menu’s quit action was completely ignored.

While debugging the issue I hooked into `QApplication::notify(QObject *, QEvent *)` and noted that a `QCloseEvent` was send to my application… wait, what? A `QCloseEvent`? Yes, I also thought that these kinds of events would only be send to widgets, namely, top level widgets, but I got one for my application, and Qt plainly ignored it. So I went further ahead and tried to quit the application directly when the event occured, similar to this

bool MyApp::event(QEvent * ev)
{
    bool eaten = false;
    switch (ev->type())
    {
        // other stuff ...
#ifdef Q_WS_MAC:
        case QEvent::Close:
        {
            quit();
            eaten = true;
            break;
        }
#endif
        default:
            eaten = QCoreApplication::event(ev);
            break;
    }
    return eaten;
}

but this didn’t work out so nicely: while the application quitted correctly, the `quit()` slot was apparently called twice in a row. My guess is that Qt ignores the `quitOnLastWindowClosed` property – which I’ve set to false – and since I close all windows in the quit slot it executes quit again, or something else, so I decided to just close all open windows when the `QCloseEvent` arrived – without calling `quit()` directly, and tada, my application quitted and the slot was also only called once. (This was a mistake on my side because I missed a break.)

guitone 1.0rc3 released

I’m proud to announce the third release candidate of guitone 1.0.
This release fixes some critical bugs and also adds support for
drag’n’drop renames in the workspace amongst other minor improvements.

The complete list of changes is as always listed in NEWS. A Mac OS X
binary is also already available, a package for Windows will follow
tomorrow.

I’m still looking for translators – if you’re interested, please drop me
a note.

Qt itemviews trouble

Whoever digged a little deeper into Qt’s item views and models in the past knows that they’re quite tricky beasts sometimes. Two problems where driving me nuts recently:

1. How can I auto-expand the root node of a `QTreeView` whenever the view is resetted?
2. How can I restore the expansion / root state of a `QTreeView` when a model filter cleared all the items out before, effectively resetting the view as in 1.?

After a lot of struggling and lots of debugging I think I found a solution for both problems, which are slightly connected.

To get the auto-expansion feature, I connected the `layoutChanged()` signal of the model to a timer in my view, whose `timeout()` signal then again called the following slot:

void MyTreeView::delayedLayoutChanged()
{
    QModelIndex index = model()->index(0, 0, QModelIndex());
    expand(index); // or alternatively: setRootIndex(index);
}

This simply takes the first element of the model and expands it. Why the trick with the timer? Well, apparently the `layoutChanged()` signal is already processed internally by `QAbstractItemView` or `QTreeView` and its implementation resets the view state completly – which is of course problematic if it gets executed _after_ our own slot. A single-shot timer with an interval of 0 ms is enough here to make Qt use the next event loop cycle and do the right thing for us.

Now, things get more complex if you have a `QSortFilterProxyModel` attached to your tree view – imagine one of these fancy find-as-you-type inputs. If a user enters a search word which clears out all items from your current view and then resets the input again to see the original view, the view eventually gets a `layoutChanged()` signal as well, but because of the clearing you may have lost the expansion level in which the user was before!

`QPersistentModelIndex` is here for the rescue. All you have to do is remember the model index you previously used to expand your view and use that instead of the static one if it is still valid:

QPersistentModelIndex MyTreeView::lastExpansion;
void MyTreeView::expandSomething(const QModelIndex & index)
{
    lastExpansion = static_cast(model())
        ->mapToSource(index);
    expand(index);
}
void MyTreeView::delayedLayoutChanged()
{
    QModelIndex index;
    // is the item still part of the source model? 
    // (important for dynamic source models)
    if (lastExpansion.isValid())
    {
        index = static_cast(model())
              ->mapFromSource(lastExpansion);
    }
    else
    {
        index = model()->index(0, 0, QModelIndex());
    }
    expand(index); // or alternatively: setRootIndex(index);
}

While `QPersistentModelIndex` and `QModelIndex` are two distinct classes, the Trolls made them easily interchangable without special casting magic.

Note that you always need to save the source index and never the proxy index which might be invalidated if it is removed from the view. Things get of course even more complex if you have not one, but two proxy models which filter your view…

guitone 1.0rc1 released

I’m proud to announce the immediate release of guitone-1.0rc1. This is the first release in a series of smaller releases which aims at the stabilization of the guitone codebase. Many (if not most) of the features one would consider needed for a “1.0” release have been implemented, a couple of outstanding bugs (noticable FS#39, FS#41 and
FS#42) have to get fixed beforehand though before that happens. Please test and report bugs if possible.

Outstanding news of this release:

  • Synchronization with other monotone nodes is no possible
  • Workspace action implementation almost feature-complete (add, drop, revert, rename, ignore, unignore and update finally work)
  • guitone can now create new monotone databases and setup new projects
  • Much improved key management with the ability to change the passphrase of keys, filter the key output and also drop keys
  • Many more bugfixes and smaller improvements as well as stabilization of the codebase

For a complete list of changes please check the NEWS file. I’ll try and prepare a Windows binary in the next couple of days, but probably won’t get to that before Wednesday, so if you want to help out, drop me
a note. A binary for Mac OS X should arrive shortly as well.

SSL Verification with Qt and a custom CA certificate

So I wanted to make my application updater for guitone SSL-aware the other day. The server setup was an easy job: Add the new domain (guitone.thomaskeller.biz) to cacert.org, create a new certificate request with the new SubjectAltName (and all the other, existing alternative names – a procedure where this script becomes handy), upload to CAcert, sign it there, download and install the new cert on my server, setup a SSL vhost for the domain – done!

Now, on Qt’s side of things using SSL is rather easy as well, the only thing you have to do is give the setHost method another parameter:

QHttp * con = new QHttp();
con->setHost("some.host.com", QHttp::ConnectionModeHttps);
con->get("/index.html");
// connect to QHttp's done() signal and read the response

This should actually work for all legit SSL setups if Qt (or, to be more precise, the underlying openssl setup) knows about the root certificate with which your server certificate has been signed. Unfortunately, CAcert’s root certificate is not installed in most cases, so you basically have two options:

  1. Connect to QHttp’s sslErrors(...) signal to the QHttp::ignoreSslErrors() slot. This, of course, pretty much defeats the whole purpose of an SSL connection, because the user is not warned on any SSL error, so also legit errors (certificate expired or malicious) are just ignored. (*)
  2. Make the root certificate of CAcert known to the local setup, so the verification process can proceed properly.

I decided to do the latter thing. This is how the code should now look like:

QHttp * con = new QHttp();
QFile certFile("path/to/root.crt");
Q_ASSERT(certFile.open(QIODevice::ReadOnly));
QSslCertificate cert(&certFile, QSsl::Pem);
// this replaces the internal QTcpSocket QHttp uses; unfortunately
// we cannot reuse that one because Qt does not provide an accessor
// for it
QSslSocket * sslSocket = new QSslSocket(this);
sslSocket->addCaCertificate(cert);
httpConnection->setSocket(sslSocket);
con->setHost("some.host.com", QHttp::ConnectionModeHttps);
con->get("/index.html");
// connect to QHttp's done() signal and read the response

Particularily interesting to note here is that the QIODevice (in my case the QFile instance) has to be opened explicitely before it is given to QSslCertificate. I did not do this previously, Qt neither gave me a warning nor an error, but simply refused to verify my server certificate, just because it didn’t load the root certificate properly.

(*) One could, of course, check the exact triggered SSL error from QSslError::error(), in our case this could be f.e. QSslError::UnableToGetLocalIssuerCertificate, but this is rather hacky and could certainly be abused by a man in the middle as well.

Qt Creator

Wow, I absolutely did not see this coming – finally the Trolls^WNokians offer a lean and nice cross-platform IDE for Qt which incorporates all other Qt tools and a gdb frontend! Formerly dubbed “Project Greenhouse” the baby just got a new name and fancy logo: Qt Creator. There is a pre-release version available for download, licensed under a special preview license. The final product should be dual-licensed though, like the rest of the Qt tools are.

Oh what a happy day for Qt users (ever wanted to look at the value of a QString in gdb…?) and what a sad one for all the other free Qt IDEs out there, like edyuk or QDevelop. Especially edyuk looked very promising since it provided a lot of features and a good user interface.

Qt Framework introduction

I did a small workshop on Qt today in my company, mainly to introduce the framework to my fellow developers. I think I did a good job, because I’ve seen the glow in their eyes while presenting the graphics view demos and the 2d paint engine amongst many other things.

Anyways, if you’re interested in Qt as well and want to get a short (German) introduction, snag the workshop slides from the Stuff page.