Qt

QtConcurrent for the Small Jobs

I had a directory full of images to scale and a progress dialog that froze while doing it. The obvious fix was a worker thread. The less obvious consequence was a new class, a queue, two mutexes and several opportunities to close the dialog at exactly the wrong moment.

Qt 4.4 includes QtConcurrent, which handles this sort of bounded parallel work at a higher level. Instead of managing threads, I can express the operation as a function over a sequence and let mapped() apply it. The result is represented by a QFuture, and QFutureWatcher turns progress and completion into signals suitable for the event-driven parts of the application.

The thread pool is the useful mechanism here. Creating one thread per thumbnail would merely exchange a frozen interface for a small scheduling experiment. QtConcurrent uses a pool and distributes work across it. The application says what can run independently; the library decides which worker executes each item.

There are several forms. run() is convenient for one function call. mapped() transforms each input into an output, while filtered() selects items. The blocking variants are tempting in a command-line program but defeat the point in a GUI thread. I use a watcher and return to the event loop.

This does not make shared state safe. The mapping function must not casually update widgets, append to an unprotected container or rely on execution order. GUI objects remain in their own thread. I pass immutable input into the workers and collect results through the future. If work needs elaborate communication or a long-lived thread with its own event loop, QThread is still the clearer tool.

Cancellation is another qualified promise. A future can request cancellation, but code already running does not vanish. The operation must reach places where cancellation can take effect. For tiny image conversions this is acceptable; for one enormous function it may not be.

I prefer QtConcurrent when the problem really is a collection of independent jobs. It removes thread bookkeeping without hiding that concurrency exists. That is a narrower claim than “threads made easy,” and therefore more believable. My thumbnail dialog now moves smoothly, and the implementation lost enough plumbing that I can almost understand code I wrote last week.

Proxy Models Instead of Copied Lists

I added filtering to a Qt 4.3 table by copying matching records into a second list. It worked until an edit in the filtered table changed the copy but not the original. I fixed that by copying changes backward, after which sorting ensured I copied them to the wrong row.

The obvious approach had created two sources of truth and an index-translation problem. Row seven in the filtered list was not row seven in the source, and after sorting it might not remain row seven in either list. Storing both numbers beside every record only made the confusion durable.

QSortFilterProxyModel exists to represent that transformation without duplicating the data. The view uses indexes from the proxy. The proxy maps them to source indexes when it asks for data or applies an edit. Sorting changes the proxy order, while the source model retains its own structure.

My second mistake was keeping a proxy index for later use. When the filter changed, that index became invalid. A model index is a temporary address into a particular model, not a permanent record identifier. For short-lived view operations, map indexes at the boundary with mapToSource() or mapFromSource(). For lasting references, store an identifier from the data and locate it again, or use persistent indexes only when the model can support their semantics correctly.

Filtering also taught me not to perform expensive parsing in filterAcceptsRow() on every request. I exposed normalized searchable text through a custom role in the source model and let the proxy compare that. Invalidating the filter after relevant data changes is cheaper than reconstructing a duplicate list and less likely to produce stale rows.

The model/view mechanism is initially more formal than copying strings into a table, but the formality identifies ownership. The source owns records, the proxy owns presentation order and inclusion, and the view owns selection and display.

My practical conclusion is to reach for a proxy whenever the same data needs another ordering or subset. Do not copy records merely to make a view convenient. Copies are friendly right up to the first edit, whereupon they become competing historical accounts.

When Rendering Bugs Move

A Qt application of mine began drawing stale text after I scrolled a custom widget. The bug appeared only with desktop compositing enabled, so I immediately blamed the driver. Then I ran it on a different card and saw the same stale text in a slightly different place.

My naive fix was to call update() repeatedly after scrolling. That reduced the frequency without curing it. Repainting more often can hide an invalid region briefly, but it cannot make incorrect geometry correct.

I reduced the widget until it painted only two rectangles and a label. The problem remained when one rectangle moved outside the area returned by the widget’s internal item bounds. My clipping assumptions were wrong: I invalidated the old logical rectangle but painted a pen and text extending beyond it. Compositing changed timing and exposure enough to reveal the mistake more reliably.

After expanding the dirty region to include every painted pixel, both drivers behaved. I then tested without compositing, with XRender compositing and with OpenGL compositing. That matrix is useful because a defect that follows one backend suggests a different layer from a defect that follows the application everywhere.

Adding a plain background behind the moving item made stale areas obvious and gave the reduced test a result anyone could recognize immediately.

Driver debugging still requires exact information. Card family, kernel module, X driver, Mesa version, colour depth and compositor backend can all matter. “Latest driver” is not a version, especially a week later. A minimal program is worth far more than a description of a large application that sometimes looks odd.

The most useful distinction is between wrong commands and wrong execution. If the application issues the wrong clipping, geometry or lifetime operations, changing drivers may only move the symptom. If a tiny correct sequence fails on one driver and succeeds elsewhere, the driver report becomes persuasive.

My practical conclusion is to simplify before assigning blame. Disable layers one at a time, record the exact matrix, and inspect the application’s painted bounds even when hardware acceleration appears implicated. Graphics stacks are complicated enough without accusing the innocent portion first. Unfortunately, every portion has previous convictions.

Trolltech, Nokia and the Toolkit

Nokia’s proposed acquisition of Trolltech has produced the predictable range of reactions, from “Qt has won” to “Qt is doomed” with very little tedious ground in between. I depend on Qt for code and on KDE for my desktop, so I wanted a more useful answer than choosing a banner.

My first attempt was to infer the future from the purchase price and press statements. This yielded several confident theories and no information about what happens to tomorrow’s source release. Corporate language is excellent at sounding precise while promising room to turn a ship around inside every sentence.

The practical questions are narrower. Will Qt continue to be developed as a cross-platform toolkit? Will the free-software editions remain available under their existing terms? Will Trolltech’s engineers keep authority over technical decisions? Will commercial customers still see a supplier interested in desktop systems as well as phones?

Nokia has an obvious reason to value Qt’s embedded work and Qtopia. A large device company can fund engineering, testing and broader deployment that Trolltech could not easily buy alone. That could improve Qt itself, particularly where desktop and embedded needs overlap. It could also pull priorities toward Nokia’s products. Ownership does not abolish incentives; it changes which incentives attend the meetings.

KDE is less exposed than a proprietary customer because the toolkit’s free license and the KDE Free Qt Foundation provide important guarantees. Source availability also means the code cannot simply be made to vanish. But a living toolkit is more than a source archive. Maintainers, release discipline, documentation and sustained cross-platform work matter.

For now, nothing in my build process changes. Qt 4 remains the strongest general application toolkit I have used, and the acquisition is not yet complete. I will judge Nokia by releases, licensing decisions and retained developers rather than by either celebratory or funeral rhetoric.

My conclusion is cautious optimism. More resources could be excellent for Qt, and Nokia appears to understand that a broad developer community is part of what it is buying. If it tries to turn Qt into a narrow internal component, it will have paid handsomely to destroy much of that value. Even very large companies are usually capable of noticing an invoice.

QtDBus and the Silent Reply

I wrote a tiny QtDBus client to ask a desktop service for its current state. The call returned immediately with an empty string, so I spent an hour changing service names and object paths. They were correct.

My naive mistake was treating an asynchronous call as a strangely fast synchronous one. I sent the message and read my result before the reply had travelled back through the bus. Adding a sleep appeared to fix it, which is how bad ideas acquire supporters.

QtDBus offers both blocking calls and asynchronous replies. A blocking call is simple, but doing it from the graphical thread can freeze the interface while another process is slow or absent. An asynchronous call returns a pending reply; completion must be observed later, after the event loop receives it. Errors are replies too, and ignoring them turns a useful complaint into an empty value.

I changed the client to watch the pending call and handle success and error explicitly. The window remains responsive, and stopping the service now produces an intelligible message instead of mysterious silence. I also registered the one custom type crossing the boundary rather than hoping QVariant would develop telepathy.

The bus is attractive because applications can expose small interfaces without linking directly to each other. That boundary is also real: names may be unavailable, processes may exit, and types must have a declared wire representation.

My conclusion is to use asynchronous calls for desktop interactions unless startup absolutely depends on the answer. Never repair an event-driven mistake with a delay. Sleeping makes the race less visible, not less present, rather like closing one’s eyes during a compiler warning.