Programming

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.

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.

Learning Graphics View the Slow Way

I wanted to draw a simple network diagram: a few hundred boxes, some labels, and lines joining them. My old version used a custom widget with a large paintEvent(). It worked until I added selection, dragging and zooming. Then every mouse press became a small geometry examination, and every repaint redrew the universe.

Qt 4.2 already had Graphics View, but I postponed learning it because the three principal classes sounded like bureaucracy. This week I finally moved the diagram to QGraphicsScene, QGraphicsView and a handful of QGraphicsItem subclasses. Naturally, my first implementation reproduced most of the mistakes from the custom widget, only with newer nouns.

I created every item at viewport coordinates and moved them whenever the scroll bars changed. That made scrolling jitter, selection rectangles miss their targets, and zooming move the diagram away from the mouse. I then tried compensating with more coordinate conversions. By noon I had code converting a point from the view to the scene, back to the view, and possibly into a neighbouring dimension.

The useful idea is that the scene owns a stable logical coordinate system. Items live in scene coordinates, while the view is merely one camera onto that scene. Scrolling and zooming alter the camera transform; they should not rearrange the objects. Mouse positions arriving at the viewport can be mapped into the scene once, at the boundary, and item-local coordinates can be left to the item.

After removing my scroll-bar corrections, panning became smooth. Scaling the view around an anchor also stopped corrupting the stored positions. More importantly, a second view could display the same scene at a different zoom without duplicating the graph. That one experiment explained the separation better than the class names did.

Painting required another correction. My naive item painted outside its boundingRect() to make room for a shadow. Sometimes the shadow remained on screen after the item moved, producing a trail that looked like a very cheap supernatural effect. The scene uses the bounding rectangle to decide what may need repainting and which items intersect an area. If the paint escapes that promise, the scene has no reason to clean it up.

I expanded the rectangle to include the pen and shadow, then supplied a tighter shape() for hit testing. Those two concepts need not be identical. The bounding rectangle should be cheap and conservative; the shape can describe the clickable outline more accurately. With that split, selecting a line no longer required clicking somewhere in its enormous rectangular aura.

Performance was my next concern. I assumed one item per node, label and connector would necessarily be slower than one giant paint function. At first it was, because each node recomputed text dimensions and connector paths during every paint. Caching those values when the data changed made a larger difference than reducing the item count. The view can already avoid painting items outside the exposed region, something my giant widget did not do intelligently.

There are still choices rather than magic switches. Device-coordinate caching helps items that do not change but are viewed repeatedly; it is less attractive for items constantly transformed. Indexing the scene accelerates lookup in a mostly stable diagram, but updating an index while thousands of objects move can cost more than a simple scan. The correct setting depends on whether the scene behaves like a map or like a box of agitated insects.

The new event handling is also cleaner. Instead of asking every object whether a mouse point belongs to it, I let the scene deliver events to the topmost suitable item. Movable and selectable flags cover ordinary interaction. A custom item only handles a mouse event when its behavior is genuinely special. This removed an entire home-grown selection system, including one bug where hidden nodes could still be dragged.

Qt 4.3 adds useful refinements, but the important lesson is already present: Graphics View is not a faster canvas. It is a retained scene with explicit geometry, coordinate mapping, event delivery and multiple views. It rewards honest descriptions of where an item is and what it paints. Lie about either and it retaliates with artifacts that appear random but are, annoyingly, deserved.

My practical conclusion is to use a plain custom widget for drawings that are truly one object and have simple interaction. Once the drawing contains independent things that move, select, overlap or need more than one view, Graphics View earns its three-class introduction. Start in scene coordinates, keep bounding rectangles accurate, cache computations rather than blindly caching pixels, and let the framework perform the hit testing. It is less code, but only after deleting the code written to defeat it.

Qt Model/View Without the Magic

I added sorting to a log viewer with QSortFilterProxyModel, clicked the third visible row, and opened the wrong log entry. The view had done exactly what I asked. I had used a proxy row as an index into the source list.

A proxy has its own indexes and its own order. Once sorting or filtering is active, row 3 in the view says nothing useful about row 3 in the source. The handoff needs to be explicit:

QModelIndex proxyIndex = view->currentIndex();
QModelIndex sourceIndex = proxy->mapToSource(proxyIndex);
if (sourceIndex.isValid())
    openEntry(sourceIndex.row());

The reverse direction matters too. When the parser reports a source index that should be selected, mapFromSource() finds its current position in the proxy. Calling view->setCurrentIndex() with the source index may look plausible, but the index still belongs to a different model and should not be handed to that view.

My next bug was staler. I changed a severity threshold stored by the proxy, emitted a signal from the dialog, and nothing disappeared. The filter result was cached. After changing the threshold, the proxy had to invalidate its filtering so that filterAcceptsRow() ran again:

void LogProxy::setMinimumLevel(int level)
{
    if (minimumLevel == level)
        return;

    minimumLevel = level;
    invalidateFilter();
}

Resetting the source model also made the screen update, but it was the wrong hammer. It discarded selections and told every attached view that the source structure had vanished when only the proxy’s mapping was stale.

Dynamic sorting has a similar edge. If a source value used by the sort or filter changes, the source must emit dataChanged() for that value. The proxy can then reconsider the affected mapping. Without that notification, asking the proxy to repaint merely gives it another chance to use the same old answer.

Selections and drag-and-drop code are where I now look first for mapping mistakes. Anything crossing the proxy boundary should say which side its index belongs to. That small bit of bookkeeping beats opening the wrong error at two in the morning.

A Model Pattern for Qt 3 List Views

I have a Qt 3 dialog that displays several hundred server records in a QListView. The first implementation stored the actual application state in QListViewItem subclasses. It worked until another window needed the same records and sorting the view began to feel suspiciously like rearranging the database.

Qt 3 does not provide the general item model API one might wish for here. QListView owns a tree of items, and those items naturally contain display text. That does not require making them the authoritative business objects.

My simpler pattern is to keep records in an ordinary domain collection and make each list item an adapter containing only a stable identifier. Display columns are copied from the record when the view is populated or refreshed.

class ServerItem : public QListViewItem
{
public:
    ServerItem(QListView *view, int id)
        : QListViewItem(view), serverId(id) {}

    int id() const { return serverId; }

private:
    int serverId;
};

When selection changes, the controller reads the item’s ID and looks up the record. Editing updates the record first, then refreshes the item text. Deleting the view item never deletes the underlying record unless the user explicitly requested that operation.

This separation fixes several observed problems. Sorting changes presentation order without changing application order. Two views can show the same record differently. Reloading the widget cannot silently discard unsaved state hidden in a child item. Unit-like tests can exercise record operations without constructing widgets or starting an X connection.

Signals and slots keep the pieces reasonably loose. The dialog connects selection and activation signals to slots that translate a QListViewItem * into a record ID. The record-owning object emits a signal after a successful change, and the view refreshes the affected row.

There is a caveat: copying all text into items can be expensive for very large datasets. For hundreds or a few thousand rows it is straightforward and fast enough. If the data is enormous, a custom widget or a specialised table that fetches visible values may be warranted. I would measure before building that machinery.

Stable IDs are important. Storing a pointer into a collection is tempting, but reallocating a vector or reloading records can invalidate it. An integer or other durable key makes the lookup explicit and failures easier to detect.

I do not call this a framework. It is three rules: own data outside widgets, put identities in items, and route changes through one owner. Qt’s item widgets remain useful presentation objects without becoming a tiny, badly documented database. Most dialogs need no grander architecture; they merely need the courage not to store reality in column zero.