Qt

Replacing Signals With Small Interfaces

I ported the non-visual half of a Qt utility to Go and immediately missed signals and slots. They had been carrying progress, log messages, and completion notifications between objects. My first replacement was a channel for every event. It worked and looked like a telephone exchange designed by an enthusiastic octopus.

The simpler answer for synchronous notifications was a small interface:

type Progress interface {
    Step(done, total int)
}

func scan(files []string, progress Progress) os.Error {
    for i, name := range files {
        if err := scanFile(name); err != nil {
            return err
        }
        progress.Step(i+1, len(files))
    }
    return nil
}

The terminal implementation prints a line. A test implementation records calls. A future graphical shell can arrange delivery to its UI thread. The scanner knows only the behavior it needs.

This differs from a Qt signal in an important way: the call is ordinary and synchronous. The scanner waits while Step runs. That makes control flow and failure behavior obvious, but a slow observer slows the operation. If isolation is required, an adapter can implement Progress by sending values to a channel consumed elsewhere.

I prefer putting that asynchronous decision in the adapter rather than in the core interface. A channel everywhere turns every notification into a lifetime and shutdown problem. An interface everywhere can accidentally block. Neither mechanism removes design; they merely make different design visible.

For optional progress I use a no-op implementation rather than scattering nil checks through the scanner. It is one tiny type with one empty method, and it keeps the hot path unsurprising.

Completion remains a return, not another notification. The caller already has a direct control path, and duplicating it creates two accounts of whether work finished.

The Go compiler decides satisfaction structurally. My terminal reporter does not inherit from a framework base or declare that it implements Progress; matching Step is enough. That made extracting this contract from existing code much easier than I expected.

This is written against an early 2011 weekly snapshot, including the os.Error return convention. APIs will move. The lesson I am keeping is narrower: use a direct small interface for required behavior, then add a channel at the boundary where asynchronous delivery is actually needed. Not every callback needs its own postal service.

Interfaces After Qt

Qt taught me to appreciate explicit contracts. It also taught me that an object system can acquire enough machinery to qualify for municipal government.

Go’s interfaces are much smaller. A type satisfies an interface by having the required methods; it does not announce the relationship in its declaration. This took a moment to trust because I am used to inheritance lists serving as signposts.

Here is the complete contract for something that can write bytes in my current snapshot:

type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

A file can satisfy it. So can a network connection or a tiny test buffer. The producer only needs to accept a Writer:

func emit(w Writer, p []byte) os.Error {
    _, err := w.Write(p)
    return err
}

There is no implements Writer marker on those concrete types. The advantage is that I can define an interface around behavior already provided by a package. The package author did not need to predict my abstraction.

The cost is discoverability. Looking at a type alone does not list every interface it happens to satisfy, and a method signature changed by one type is a different method. Compiler diagnostics are doing more of the navigation work than they do in my C++ code.

I tried the idea on a log collector. The original Python version accepted anything with a write method because Python accepts almost anything until it does not. The Go version gets similar flexibility, but verifies the method set when values are connected. That is a useful middle ground.

Interfaces also contain a pair of things internally: a dynamic type and a dynamic value. An interface value can therefore be non-empty even when the pointer stored inside it is nil. I managed to manufacture that surprise within an hour, which must be some kind of efficiency record.

These details reflect a weekly build, not a stable release. Method-set and library conventions may still shift. Even so, structural satisfaction feels important. It encourages narrow contracts at the point of use rather than grand interface families designed before the program has done anything useful.

From Qt Slots to Small Tools

Most of my systems work still begins in C. If the job needs a user interface, it usually grows a Qt shell. If it needs to be finished before lunch, a Python script appears beside it and quietly becomes permanent.

That arrangement works, but every boundary charges rent. The C tool has ownership rules, the Qt side has objects and signals, and the Python helper has a different deployment story. I have spent an unreasonable amount of time explaining to machines where their libraries live.

I have been trying the experimental Go compiler from the weekly snapshots. It is plainly unfinished, and the language may change under this post, but it occupies an interesting spot: compiled programs, garbage collection, a small syntax, and concurrency that does not begin with a page of pthread ceremony.

My first useful experiment was the sort of filter I normally write in Python:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    in := bufio.NewReader(os.Stdin)
    for {
        line, err := in.ReadString('\n')
        if len(line) != 0 {
            fmt.Printf("%d %s", len(line), line)
        }
        if err == os.EOF {
            break
        }
        if err != nil {
            fmt.Fprintf(os.Stderr, "read: %s\n", err.String())
            os.Exit(1)
        }
    }
}

ReadString may return bytes together with os.EOF, so the line is handled before the error. That keeps a final line without a newline. EOF ends input normally; another os.Error gets reported instead of masquerading as a clean finish.

On my snapshot, I build that directly with 6g and 6l. A later snapshot may spell an import or an I/O call differently; that is currently part of the admission price.

The pleasant surprise is not that this is shorter than C. Python already wins that contest while barely getting out of bed. The surprise is that the result is a small native executable and the source still exposes the mechanics I care about. There is no class hierarchy between the input and me.

I do miss deterministic destruction from C++, especially around Qt resources. Garbage collection is not a substitute for closing a file. Go’s defer looks like the intended answer for many such cases, and I plan to abuse it scientifically.

I am not replacing working C or Qt code. That would be migration as a hobby, a particularly expensive hobby. I am moving the little glue programs first and watching where the new language becomes awkward. So far, it feels less like Python made fast and more like C with several recurring arguments removed.

Qt 4.5 Lowers Two Barriers

Two things have repeatedly made Qt harder to suggest: licensing confusion and the lack of an obvious first development environment. Qt 4.5 improved both.

Qt is now available under the LGPL as well as its existing licenses. That gives more applications a straightforward way to use the library while keeping their own licensing choices, provided they comply with the LGPL’s requirements. It is not a declaration that licenses no longer need reading. It merely makes the common case less theatrical.

Qt Creator 1.0 supplies the other missing front door. It understands Qt projects, code navigation, building and debugging without trying to become an operating system disguised as an editor. I opened an existing project and was productive quickly, despite initially searching for several commands where my usual editor keeps them.

Creator is still young, and experienced users with carefully constructed environments may not gain much. I am not abandoning my editor after one pleasant afternoon. For newcomers, however, a focused tool removes a pile of setup from the first experiment.

I strongly prefer this combination: a capable cross-platform toolkit with a less restrictive entry point and an IDE that teaches its normal workflow. Neither guarantees good applications. They simply let people reach the part where application design can go wrong, which is where programmers traditionally demonstrate real creativity.

QtWebKit, One Page, Three Worlds

I needed to show a formatted report inside a Qt application. The report had headings, tables, a few images and links to related objects. My first attempt used a QTextDocument, and it was perfectly respectable until the design acquired a stylesheet and enough layout requirements to qualify as a small web page.

Qt 4.4’s QtWebKit module looked like the easy answer. Put the HTML in a QWebView, connect the link signal and go home. It did work, but the interesting part begins when the page needs to interact with the application. At that point there are three different worlds involved: Qt widgets, WebKit’s page and frame objects, and JavaScript running inside a frame.

The useful layers

QWebView is the widget. It fits into a normal layout and provides the familiar actions such as loading, back, forward and reload. It owns a QWebPage, which holds page-wide behavior: navigation policy, actions, settings and the main frame. The QWebFrame represents a frame’s document and is where HTML, JavaScript and the rendered contents meet.

For a simple report I can call setHtml() on the frame or view, optionally supplying a base URL so relative images and stylesheets resolve properly. That last argument is easy to omit and produces the pleasing result of a beautifully laid-out report with no images. I know this because I conducted the experiment several times.

Loading is asynchronous. A call to load() starts work and returns. Signals report progress and completion. Code that immediately asks for the final document after load() is racing the network and parser, even when the URL points to a local file. This is the same event-loop discipline as the rest of Qt, merely attached to a much more complicated component.

Crossing into JavaScript

QtWebKit can expose a QObject to JavaScript with addToJavaScriptWindowObject(). Slots and properties then become available in the script environment. This is convenient for a report that needs to request an application action. A link can call a narrow bridge object rather than encoding commands into invented URLs and parsing them later.

The narrow part is important. Exposing the main window would give page script an enormous accidental API. I prefer a small object with slots such as showItem(int) or requestPrint(). The bridge validates its arguments and emits signals; application code performs the actual work. The web page gets capabilities, not the keys to the building.

The JavaScript window object can be cleared when the frame’s context changes, so the bridge may need to be added again when notified. Treating injection as a one-time constructor chore works for the first page and mysteriously fails after navigation. Web pages have lifecycles too, because apparently one kind was not enough.

Communication can go in the other direction with evaluateJavaScript(). It is useful for updating a small piece of page state, but assembling large scripts from C++ strings becomes unpleasant immediately. For anything substantial I keep the logic in a JavaScript resource and invoke a defined function with carefully encoded data.

An embedded page should not automatically handle every clicked URL. The application may want internal links to select objects, ordinary HTTP links to open in the user’s browser, and unknown schemes to do nothing. QWebPage provides navigation hooks where that decision belongs.

I initially connected linkClicked() and assumed the job was finished. It was not: navigation behavior depends on the page’s link delegation policy. Once configured, the signal allows the application to decide rather than letting every click replace the report. This is less magical and therefore better.

Content is another policy boundary. For reports generated entirely by the application, exposing a bridge is manageable because the application controls the HTML and script. Doing the same for arbitrary remote pages is a different security proposition. A page that can invoke native slots has left the ordinary browser sandbox through the door I opened for it.

A large tool for a specific job

QtWebKit brings a real browser engine, not merely a richer label. That means capable HTML and CSS rendering, JavaScript, network loading, history and all the state accompanying them. It also means more memory and startup cost than QTextDocument. If the requirement is a few paragraphs with bold text, a web engine is unnecessary furniture.

Printing and exporting deserve testing as well. What looks right in the viewport may paginate badly. External resources may still be loading when the user presses Print. Font choices differ from the surrounding widgets unless the page stylesheet makes them deliberate. Embedding the web does not make it cease being the web.

For complex, interactive documents that already fit the HTML model, I strongly prefer QtWebKit to inventing a private layout engine from labels and painters. It gives designers familiar tools and keeps document structure out of widget code. I qualify that preference heavily: the page should be controlled, the native bridge should be tiny, and navigation should be explicit.

My report ended up simpler than the QTextDocument version, not because QtWebKit itself is simple, but because its complexity matches the problem. The trick is to use the browser as a document component and resist the urge to turn every dialog into a website. We have enough browsers already.