I started porting a small KDE utility today and made the predictable mistake: I opened the main window source first. Ten minutes later I had changed several class names and learned almost nothing about whether the program still worked.

The useful work began when I backed up and found the seams. This program has a parser, a document object, a job that reads files, and a window that displays results. Only the last part truly cares about widgets. I built the parser and document as a small Qt 4 library before touching the screen.

The immediate payoff was a command-line test:

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    if (argc != 2) {
        qCritical() << "usage:" << argv[0] << "file";
        return 1;
    }

    Document document;
    QString error;
    if (!document.load(QString::fromLocal8Bit(argv[1]), &error)) {
        qCritical() << error;
        return 1;
    }

    qDebug() << document.entries().count();
    return 0;
}

That exposed two assumptions hidden by the old interface. One parser path depended on a widget-created codec, and file loading reported errors by opening a message box. Neither belongs in the core. Loading now returns a status and an error string; the caller decides how to present it.

This matters because KDE 4 is architecture and porting work at the moment, not a desktop I would hand to an unsuspecting relative. APIs and libraries are being separated so applications can use services without importing a sack of unrelated GUI machinery. A compiling window is pleasant evidence, but it is not proof of a sound port.

Porting from the non-GUI core outward gives me executable checkpoints and makes ownership clearer. Old applications sometimes mix policy, storage, and widgets so thoroughly that extracting a core costs more than a direct compile fix. For a tiny or soon-to-be-replaced tool, the ugly route may be honest.

One more practical trick: make every compatibility warning visible during the port. I am using verbose builds and keeping a short list of intentionally deferred warnings. If the list grows without explanation, it is not a list; it is a compost heap.

I also run the command-line test from a clean build tree, not only from the source directory. That catches accidental dependence on local files before the GUI can hide it.

Tomorrow I will return to the main window. It should now have less to do, which is my favourite feature in a window class.