Blog

Qt 3.3 Signals Without Magic

I had a small Qt 3.3 utility that refreshed a list after reading a directory. It worked until I renamed a slot, at which point clicking the button did precisely nothing. No crash, no compiler error, just a very calm refusal to cooperate.

My naive fix was to add qDebug() calls everywhere except the place that mattered. The button emitted its signal and the refresh function worked when called directly. After too much staring, I finally checked the return value from connect() and ran the program with Qt warnings visible.

bool connected = connect(refreshButton, SIGNAL(clicked()),
                         this, SLOT(reloadDirectory()));
Q_ASSERT(connected);

The slot declaration and the string in SLOT() no longer agreed. Qt’s meta-object compiler records the signal and slot information that ordinary C++ does not provide. The connection is therefore checked at runtime, not by the compiler. Once I remembered that mechanism, the silence stopped being mysterious.

The generated meta-object code also explains why a class using signals or slots needs the Q_OBJECT macro and must pass through moc. Forgetting that step can produce linker errors rather than a friendly explanation. With the normal Qt build tools, the extra generation is automatic; with a handmade makefile, it is another dependency that must be stated correctly.

I tested disconnection and destruction as well. Qt removes connections involving a destroyed object, which is safer than retaining callbacks into dead C++ instances. That convenience still depends on the receiver actually being a QObject with a well-defined lifetime.

I now keep signals and slots in the appropriate class section, inspect the generated warnings, and assert important connections during development. I also avoid clever overloads unless they buy something substantial. The old string syntax is useful, but it can turn a typo into a tiny mime performance.

Small executable tests help here too: create the sender and receiver, emit once, and verify the resulting state before involving the whole window.

Qt 3.3 remains pleasant C++ because it removes a great deal of event plumbing. It does not remove the need to understand that plumbing. Framework convenience is best treated as compressed machinery: use it freely, but know where to open the panel when it stops.

KDE 3.3 Feels Settled

I upgraded my main workstation to KDE 3.3 because I wanted the newer applications, not because I was looking for excitement. Desktop upgrades have supplied quite enough excitement already.

My first attempt was the usual impatient one: replace the packages, keep every old setting, and hope. The desktop started, but a couple of panels behaved oddly and Konqueror inherited some settings that no longer made much sense. I briefly blamed KDE. Then I tried a fresh user account and discovered that my carefully accumulated configuration was the real antique.

What impressed me was how little else needed attention. Session restoration brought back the applications I expected. The file manager, terminal, editor, and control modules felt like parts of one desktop rather than strangers sharing a theme. Even mundane details such as keyboard shortcuts were consistent enough that I stopped thinking about them.

That is what maturity looks like to me. It is not a spectacular new dialog. It is being able to spend the afternoon in C++ rather than repairing the environment used to write it.

I copied only the settings I understood into the fresh account and left the archaeological layers behind. KDE 3.3 may not make for dramatic dinner conversation, but a desktop that gets out of the way is doing its job very well.

Watching Latency on Linux 2.6

I have been comparing two Linux 2.6 configurations for a workstation: one with kernel preemption enabled and one without it. Looking only at total build time made them appear nearly identical, while using the machine made them feel different. The missing measurement was latency.

Throughput asks how much work finishes in an interval. Latency asks how long a particular piece of work waits. A kernel build is a reasonable throughput load, but its completion time does not reveal that an audio process waited long enough to miss a deadline or that keyboard input paused for a fraction of a second.

My crude but useful test runs the same build while playing audio, copying a large file, and interacting with KDE. I record build time, note audio underruns reported by the application, and repeat enough times to distinguish a pattern from one lucky run.

time make -j2 bzImage

The O(1) scheduler helps when many tasks are runnable. Its priority arrays and per-CPU run queues keep selection costs predictable, while interactive heuristics favour tasks that sleep and wake frequently. But the scheduler cannot run a task until the current execution context can be switched.

That is where kernel preemption matters. With it enabled, ordinary kernel code can be preempted when no lock or other critical condition forbids it. An interactive task awakened during a long system call may run sooner. Interrupt handlers and protected sections remain non-preemptible, so a poor driver can still dominate worst-case delay.

On my machine, enabling preemption does not materially improve the build time. It does reduce occasional input stalls and audio trouble under mixed I/O load. That is the result I expected: better responsiveness, not newly manufactured processor cycles.

Thread behaviour can alter the picture too. NPTL mutexes stay in userspace when uncontended and use futex operations when a thread must sleep or wake. A heavily contended threaded program may therefore create a very different scheduling load from one with mostly private work. Counting threads alone says little; runnable and blocked states matter.

I also test without excessive parallelism. Starting far more compile jobs than processors can turn the test into a contest between memory pressure, disk traffic, and scheduling. That may resemble a real workload, but it is a poor way to isolate one mechanism.

The caveat is that my observations are not hard real-time guarantees. General-purpose Linux still has sources of unbounded or hardware-dependent delay, and an average hides the worst pause. For desktop tuning, however, repeated underrun counts and interaction under a controlled load tell me more than one impressive jobs-per-minute number.

I am keeping preemption enabled on the workstation and evaluating it separately on servers. A server handling many requests may care about response-time distribution, but a batch machine may prefer the smallest scheduling overhead. There is no universally fast switch. First decide whether the itch is throughput or latency; only then does the benchmark know what question to answer.

Small KDE 3.2 Habits That Save Time

After several months with KDE 3.2, the features saving me time aren’t the ones that make attractive release screenshots. They’re small habits around Konqueror, the Run Command dialog, and reusable KDE components.

The first is using location protocols directly. Konqueror is not limited to local paths and HTTP URLs. Typing an sftp:// location gives me a remote file view through the normal interface. I can keep local and remote views open and copy between them without teaching every application a separate transfer procedure.

I still use command-line scp for scripted or large transfers. A graphical view is convenient, not evidence that errors no longer occur. If a copy matters, I verify its size or checksum at the destination.

The second habit is splitting Konqueror’s view when comparing directories. One window with two linked views is easier to reason about than two overlapping windows. Because the file view is a component, the shell can arrange it without becoming a separate file manager for every layout.

The third is assigning keyboard shortcuts only after noticing repeated actions. KDE permits a heroic quantity of customisation, but configuring twenty speculative shortcuts creates a keyboard dialect I will forget by Friday. I add one shortcut when an action has annoyed me several times, then use it until it becomes natural.

Profiles are useful for the same reason. A Konqueror profile can restore a practical arrangement of views and locations. I keep one for file work and another for browsing rather than forcing one toolbar and sidebar arrangement to serve both jobs.

Finally, I use KDE’s Run Command dialog—the minicli utility—for applications I already know by name. Navigating a menu helps while discovering software; pressing Alt-F2 and typing the program name is faster afterward. Command history makes repeated invocations painless.

None of this is revolutionary. That is the point. KDE’s strength is the common infrastructure beneath applications: URL handling, parts, shortcuts, and session behaviour. Once I use those facilities consistently, individual programs require fewer special habits.

There is a danger in turning desktop configuration into a hobby that prevents desktop work. I avoid changes that save two seconds once and require an evening of maintenance. The useful tweaks survive upgrades, use standard KDE facilities, and are easy to explain six months later.

KDE 3.2 gives me plenty of choices. The trick isn’t exercising all of them. It’s finding the few that remove recurring friction, then leaving the control centre alone long enough to get something done.

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.