<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kde4 on Roberto Selbach</title><link>https://rselbach.com/tags/kde4/</link><description>Recent content in Kde4 on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 23 Mar 2007 18:58:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/kde4/index.xml" rel="self" type="application/rss+xml"/><item><title>The Desktop Is Still a Workbench</title><link>https://rselbach.com/the-desktop-is-still-a-workbench/</link><pubDate>Fri, 23 Mar 2007 18:58:00 +0000</pubDate><guid>https://rselbach.com/the-desktop-is-still-a-workbench/</guid><description>&lt;p&gt;I ran today&amp;rsquo;s KDE 4 session long enough to open a ported application, play a sound through Phonon, and watch a Plasma applet redraw. Then the panel vanished after a configuration change.&lt;/p&gt;
&lt;p&gt;That sequence is a useful status report. The architecture is becoming tangible, but this is still a workbench, not a finished desktop. Ported applications exercise Qt 4 and the new libraries. Plasma prototypes test containment and data ideas. Solid and Phonon test boundaries around hardware and multimedia. Integration exposes where those boundaries disagree.&lt;/p&gt;
&lt;p&gt;My current smoke test is intentionally small:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmake ..
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;make
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ctest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;startkde
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last command belongs on a test account and display. Configuration formats and services are moving, and preserving my everyday session is more valuable than proving bravery.&lt;/p&gt;
&lt;p&gt;I prefer testing one complete path, such as device arrival through Solid to an applet update, over collecting screenshots of disconnected pieces. The caveat is that integration failures can obscure which component broke, so each layer still needs its own focused test.&lt;/p&gt;
&lt;p&gt;Today&amp;rsquo;s vanished panel turned out to be a containment lifecycle mistake, not a rendering failure. That distinction is the work now: finding ownership, sequencing, and API problems while change is still affordable.&lt;/p&gt;
&lt;p&gt;It is exciting work. It is also unfinished work. Both statements fit comfortably in the same build, though not always in the same process.&lt;/p&gt;</description></item><item><title>CMake Build Trees Are Disposable</title><link>https://rselbach.com/cmake-build-trees-are-disposable/</link><pubDate>Tue, 09 Jan 2007 19:31:00 +0000</pubDate><guid>https://rselbach.com/cmake-build-trees-are-disposable/</guid><description>&lt;p&gt;My KDE 4 build kept compiling code for a library I had just disabled. I checked the option twice, added a loud message to &lt;code&gt;CMakeLists.txt&lt;/code&gt;, and still got the old generated header. The compiler was not being stubborn. I was looking at leftovers.&lt;/p&gt;
&lt;p&gt;The first clue was &lt;code&gt;CMakeCache.txt&lt;/code&gt;: the build directory had been configured against another source checkout. The second was a generated &lt;code&gt;config.h&lt;/code&gt; whose timestamp did not move when I reran CMake. An earlier configure had put a copy in the source tree, and that directory appeared first in the include path.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make clean&lt;/code&gt; did not help. It removed compiled output known to the generated makefiles; it did not remove the cache, old makefiles, or a header generated by an obsolete rule. That distinction cost me most of the afternoon.&lt;/p&gt;
&lt;p&gt;The useful diagnosis was simple. I configured a brand-new build directory from the same checkout and compared the generated command line and &lt;code&gt;config.h&lt;/code&gt;. The fresh tree had the option I expected. At that point there was no reason to repair the old tree one cache entry at a time, so I discarded it and configured again.&lt;/p&gt;
&lt;p&gt;I also removed the generated header from the source tree and made its destination unambiguous:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cmake" data-lang="cmake"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;configure_file(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;CMAKE_CURRENT_SOURCE_DIR&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/config.h.cmake&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;CMAKE_CURRENT_BINARY_DIR&lt;span style="color:#f92672"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/config.h&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the binary include directory supplies &lt;code&gt;config.h&lt;/code&gt;, and a fresh checkout cannot accidentally inherit one. If the clean build disagrees with the old build, I check the old cache&amp;rsquo;s source path, generated-file timestamps, and include order before blaming CMake detection.&lt;/p&gt;
&lt;p&gt;Build trees are cheap. An hour spent negotiating with stale generated CMake files is not.&lt;/p&gt;</description></item><item><title>Network State Is Not a Boolean</title><link>https://rselbach.com/network-state-is-not-a-boolean/</link><pubDate>Tue, 12 Dec 2006 18:44:00 +0000</pubDate><guid>https://rselbach.com/network-state-is-not-a-boolean/</guid><description>&lt;p&gt;My test applet announced &amp;ldquo;offline&amp;rdquo; while NetworkManager was still associating with an access point. A second later it announced &amp;ldquo;online.&amp;rdquo; Technically energetic, socially useless.&lt;/p&gt;
&lt;p&gt;I had reduced state to a boolean. The mechanism has transitions: unavailable, disconnected, preparing, configuring, connected, and failure are meaningfully different to a user and to an application deciding whether to retry.&lt;/p&gt;
&lt;p&gt;The applet now maps backend states into three presentation states:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Connectivity&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Offline,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Connecting,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Online
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It preserves the detailed state in the adapter for diagnostics, but the paint code does not understand every NetworkManager value. Solid provides the abstraction boundary; the widget receives only what it can honestly display.&lt;/p&gt;
&lt;p&gt;I prefer reacting to state-change notifications rather than polling every second. Polling wastes work and can still miss a short transition. The caveat is startup: the client must query initial state before relying on later notifications, or it may wait forever for a change that already happened.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Online&amp;rdquo; also means only that the connection mechanism considers a network active. It does not guarantee a working name server, reachable destination, or paid hotel login. The applet should report connection state, not certify the Internet.&lt;/p&gt;
&lt;p&gt;Three states are less satisfyingly binary. They are also less wrong, an increasingly fashionable property in software.&lt;/p&gt;</description></item><item><title>D-Bus 1.0 and Boring Interfaces</title><link>https://rselbach.com/dbus-1-0-and-boring-interfaces/</link><pubDate>Tue, 14 Nov 2006 20:09:00 +0000</pubDate><guid>https://rselbach.com/dbus-1-0-and-boring-interfaces/</guid><description>&lt;p&gt;D-Bus 1.0 arrived last week, so I replaced a small experimental IPC call today and discovered that my &amp;ldquo;simple&amp;rdquo; interface exposed an internal class name, an internal enum, and optimism about call order.&lt;/p&gt;
&lt;p&gt;The wire has four names worth keeping straight: a bus name identifies the service, an object path identifies an object, an interface groups methods and signals, and a member names the operation. Treating all four as one application string works until the service grows a second object.&lt;/p&gt;
&lt;p&gt;For a thumbnail service I settled on deliberately plain values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service: org.kde.Thumbnailer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;path: /Thumbnailer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;interface: org.kde.Thumbnailer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;method: Queue(string url, int width, int height)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;signal: Ready(string url, string file)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The exact names may change; the useful constraint is that callers do not need my C++ headers or object layout. Strings and integers cross the process boundary predictably. Errors should cross as named errors, not magic negative dimensions.&lt;/p&gt;
&lt;p&gt;A method call can be synchronous, but that does not make blocking the GUI wise. Thumbnail generation may involve disk access or a broken file. The caller should queue work and react to completion, while also handling service disappearance. Processes are allowed to crash independently; that is among their principal features.&lt;/p&gt;
&lt;p&gt;I prefer small, boring bus interfaces with operations phrased in domain terms. They are easier to inspect and less likely to preserve an accidental implementation forever. The caveat is round-trip cost and versioning. Turning every getter into a remote call creates slow, chatty code, and changing a published signature later can break clients I do not control.&lt;/p&gt;
&lt;p&gt;Introspection is useful during development, but readable metadata does not replace a written contract. Callers still need to know whether requests are idempotent, which errors are expected, and how long returned object paths remain valid.&lt;/p&gt;
&lt;p&gt;For KDE 4 porting, D-Bus 1.0 gives us a stable point to build against, not permission to export every object. I am writing the interface first, testing it from a separate process, and only then attaching the current implementation. If the test needs a private header, the boundary has failed.&lt;/p&gt;
&lt;p&gt;Today&amp;rsquo;s service returns one error instead of hanging when the source file vanishes. This is less exciting than transparent desktop integration, but substantially more useful when transparent desktop integration loses a file.&lt;/p&gt;</description></item><item><title>Plasma Data Before Decoration</title><link>https://rselbach.com/plasma-data-before-decoration/</link><pubDate>Fri, 13 Oct 2006 22:18:00 +0000</pubDate><guid>https://rselbach.com/plasma-data-before-decoration/</guid><description>&lt;p&gt;I changed the weather prototype&amp;rsquo;s frame today and accidentally made it fetch the forecast again. The border and the network request had become neighbours in one class, then naturally began borrowing each other&amp;rsquo;s tools.&lt;/p&gt;
&lt;p&gt;The experiment now separates a data source from the visualisation. The source produces named values and announces updates. The applet chooses how to present them. Fake data is enough to exercise the boundary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QVariantMap WeatherSource&lt;span style="color:#f92672"&gt;::&lt;/span&gt;snapshot() &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QVariantMap data;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data.insert(&lt;span style="color:#e6db74"&gt;&amp;#34;condition&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;cloudy&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data.insert(&lt;span style="color:#e6db74"&gt;&amp;#34;temperature&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data.insert(&lt;span style="color:#e6db74"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Porto Alegre&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; data;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The applet receives a complete snapshot instead of reading half-updated fields one by one. This matters if the source later performs work asynchronously: a temperature from the new report paired with yesterday&amp;rsquo;s condition is internally consistent only in avant-garde meteorology.&lt;/p&gt;
&lt;p&gt;The visual item invalidates its content when data changes. Theme or frame changes invalidate geometry and decoration, but do not restart the source. Keeping those paths separate avoids needless work and gives the containment freedom to alter presentation.&lt;/p&gt;
&lt;p&gt;For this little applet, pullable snapshots plus one update notification beat a long series of property-specific signals. A new field can appear without adding another connection, and a newly created applet can request current state immediately. Copying a large map for frequent updates would be wasteful, though, and weakly typed keys invite spelling mistakes. This fits small desktop data, not every stream.&lt;/p&gt;
&lt;p&gt;Error data travels in the same snapshot rather than through a modal dialog. The applet can then show stale values with a warning, hide them, or offer a retry according to the space and purpose of that visualisation.&lt;/p&gt;
&lt;p&gt;There are unresolved questions. Sources need lifetimes, update intervals, error states, and perhaps sharing between several applets. A clock should not require sixty private timer objects merely because sixty visualisations exist. On the other hand, premature sharing can couple applets through one source&amp;rsquo;s policy.&lt;/p&gt;
&lt;p&gt;This remains a Plasma prototype, not evidence of a completed KDE 4 shell. The useful result is that I can replace the rectangle with text, or the text with a graph, without changing how forecast data arrives.&lt;/p&gt;
&lt;p&gt;The forecast itself still says cloudy. At least this time it did not download new clouds when I changed the border colour.&lt;/p&gt;</description></item><item><title>Solid Lines Around Hardware</title><link>https://rselbach.com/solid-lines-around-hardware/</link><pubDate>Tue, 29 Aug 2006 20:41:00 +0000</pubDate><guid>https://rselbach.com/solid-lines-around-hardware/</guid><description>&lt;p&gt;My network widget broke today because a property changed underneath it. More precisely, the widget knew enough about the hardware backend to parse a backend-specific property, and not enough to notice when that property was absent. This is the sort of knowledge that makes software feel powerful right up to the first different machine.&lt;/p&gt;
&lt;p&gt;Solid is the KDE 4 attempt to draw a stable line between applications and hardware discovery mechanisms. An application should ask for devices and capabilities in KDE terms. A backend can obtain those facts from HAL, NetworkManager, or another platform facility. The abstraction is not meant to make all hardware identical; it is meant to stop every application from inventing a private translation layer.&lt;/p&gt;
&lt;p&gt;The useful mental model is a device plus interfaces. A device has an identity and general properties. Interfaces describe capabilities such as storage access, battery status, or networking. Code can first ask whether a capability exists, then use the corresponding interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Solid&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Device device(udi);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (device.isDeviceInterface(Solid&lt;span style="color:#f92672"&gt;::&lt;/span&gt;DeviceInterface&lt;span style="color:#f92672"&gt;::&lt;/span&gt;StorageVolume)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Solid&lt;span style="color:#f92672"&gt;::&lt;/span&gt;StorageVolume &lt;span style="color:#f92672"&gt;*&lt;/span&gt;volume &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; device.as&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Solid&lt;span style="color:#f92672"&gt;::&lt;/span&gt;StorageVolume&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qDebug() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; volume&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;label() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; volume&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;size();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pointer still needs checking because devices disappear and backends can fail. A USB disk is not contractually obliged to remain attached while my slot admires it.&lt;/p&gt;
&lt;p&gt;Identity is harder than it first appears. A display name is for humans and can change. A device path may reflect discovery order. A HAL UDI is useful within that backend but should not leak into an application&amp;rsquo;s persistent file format if the application claims backend independence. For a short-lived object map, the current unique identifier is fine. For durable preferences, I need a stable property appropriate to the device class, and sometimes no truly stable identifier exists.&lt;/p&gt;
&lt;p&gt;Hotplug events introduce ordering questions. My first implementation reacted to a &amp;ldquo;device added&amp;rdquo; signal by immediately requesting every interface. Some properties were not ready yet, and parent devices could appear after children. The safer path is to tolerate incomplete devices, query only required capabilities, and update when relevant properties change. Applications must not infer a complete topology from the first signal they see.&lt;/p&gt;
&lt;p&gt;Networking demonstrates why policy should remain separate. NetworkManager can report devices, available networks, activation state, and changes. Solid can present that information consistently to KDE code. It should not mean that a text editor decides which wireless network to activate. A desktop networking component may own that policy; most applications only need to know whether connectivity is currently available, perhaps with enough uncertainty to avoid promising too much.&lt;/p&gt;
&lt;p&gt;Storage has a similar distinction. Discovering a volume is not the same as mounting it, and mounting it is not the same as opening a file manager. Those are three mechanisms with three possible policy owners. When a camera appears, a photo application may offer an import action. Automatically performing one because a hardware signal arrived would be efficient in the same way as a trapdoor.&lt;/p&gt;
&lt;p&gt;For the widget, I replaced direct HAL property access with a tiny internal state derived from Solid interfaces:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NetworkState&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QString name;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; available;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; active;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The widget receives this state and paints it. It no longer knows whether the information came from NetworkManager or a test backend. The adapter listens for device and property changes, rebuilds the affected state, and emits one application-level update. This also keeps backend callbacks away from paint code.&lt;/p&gt;
&lt;p&gt;I prefer capability queries over a deep inheritance tree of device types. Real hardware combines roles inconveniently, and capability composition lets one object expose several useful interfaces. The caveat is discoverability: callers must know which combinations make sense, and an abstraction can become a bag of optional pointers if its contracts are weak.&lt;/p&gt;
&lt;p&gt;I also prefer asynchronous operations for actions such as mounting or network activation. Hardware can wait for media, authentication, or a helper process. Blocking the GUI thread while reality decides what to do is poor manners. Completion needs an error path that preserves enough backend detail for diagnosis without requiring the application to parse backend internals.&lt;/p&gt;
&lt;p&gt;None of this makes Solid finished. We still need to learn which properties are genuinely portable, how device lifetimes should be represented, and how much backend-specific information may escape without defeating the purpose. HAL itself is evolving, and NetworkManager behaviour differs across the machines I can test. A clean interface can expose uncertainty; it cannot abolish it.&lt;/p&gt;
&lt;p&gt;My practical rule after today&amp;rsquo;s failure is simple: application code depends on capabilities, UI code depends on application state, and raw backend properties stop at the adapter boundary. Exceptions may be necessary for specialised tools, but they should look like exceptions rather than quietly becoming architecture.&lt;/p&gt;
&lt;p&gt;The widget now survives the missing property. It also reports my wired interface as &amp;ldquo;network cable,&amp;rdquo; which is accurate, stable, and not likely to win a naming award.&lt;/p&gt;</description></item><item><title>Phonon Is Not a Codec Collection</title><link>https://rselbach.com/phonon-is-not-a-codec-collection/</link><pubDate>Fri, 07 Jul 2006 17:39:00 +0000</pubDate><guid>https://rselbach.com/phonon-is-not-a-codec-collection/</guid><description>&lt;p&gt;A test player handled one Ogg file and failed on another machine before lunch. My first reaction was to ask which Phonon codec was missing. That question contains the bug in my mental model.&lt;/p&gt;
&lt;p&gt;Phonon is intended as a multimedia API above actual playback engines. The application builds a graph of media objects, audio outputs, and paths. A backend translates that graph to whatever engine is available. Codec support therefore depends on the backend and its installed components, not on Phonon carrying a suitcase of decoders.&lt;/p&gt;
&lt;p&gt;The small playback case looks roughly like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;MediaObject &lt;span style="color:#f92672"&gt;*&lt;/span&gt;media &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;MediaObject(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;AudioOutput &lt;span style="color:#f92672"&gt;*&lt;/span&gt;output &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;AudioOutput(Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;MusicCategory, &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;createPath(media, output);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;media&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;setCurrentSource(Phonon&lt;span style="color:#f92672"&gt;::&lt;/span&gt;MediaSource(fileName));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;media&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;play();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The category matters. It describes the purpose of the sound so system policy can distinguish music from notifications or communication. It is not merely a friendly enum to satisfy the compiler.&lt;/p&gt;
&lt;p&gt;Errors remain asynchronous. Calling &lt;code&gt;play()&lt;/code&gt; starts a process; it does not certify that bytes will reach speakers. The application must listen for state changes and present the backend&amp;rsquo;s error without pretending every failure is a missing file.&lt;/p&gt;
&lt;p&gt;I prefer depending on a narrow playback abstraction instead of teaching each KDE application the details of several multimedia engines. That should make device selection and policy consistent. The caveat is the least-common-denominator problem: specialised editors and unusual pipelines may need capabilities the abstraction cannot sensibly expose.&lt;/p&gt;
&lt;p&gt;I am also avoiding backend detection in the application. Branching on a backend name would make today&amp;rsquo;s workaround tomorrow&amp;rsquo;s required behaviour. If a capability is necessary, the API should expose that capability or the application should report that it cannot perform the operation.&lt;/p&gt;
&lt;p&gt;For porting, I am keeping the boundary obvious. The document remembers a URL and playback position; a small controller owns Phonon objects; widgets issue commands and display state. This makes backend surprises testable without turning the main window into an audio engine wearing buttons.&lt;/p&gt;
&lt;p&gt;The architecture is still settling, and backend behaviour will not be perfectly identical. Today&amp;rsquo;s useful certainty is modest: &amp;ldquo;works through one engine here&amp;rdquo; is not the same claim as &amp;ldquo;Phonon supports this format everywhere.&amp;rdquo; Computers remain attentive readers of fine print.&lt;/p&gt;</description></item><item><title>ThreadWeaver and the Size of a Job</title><link>https://rselbach.com/threadweaver-and-the-size-of-a-job/</link><pubDate>Wed, 14 Jun 2006 18:54:00 +0000</pubDate><guid>https://rselbach.com/threadweaver-and-the-size-of-a-job/</guid><description>&lt;p&gt;I fed 12,000 thumbnail jobs into a ThreadWeaver experiment today. The queue accepted them with admirable composure. The application then spent more time managing tiny jobs than producing thumbnails.&lt;/p&gt;
&lt;p&gt;The mechanism is straightforward: jobs enter a queue, worker threads take runnable jobs, and completion is reported back. This is much better than creating one thread per thumbnail. Threads have stacks, scheduling cost, and an unfortunate tendency to outlive the code that launched them.&lt;/p&gt;
&lt;p&gt;My mistake was choosing a job that was too small. Each item performed a little path work, checked a cache, and often returned without decoding anything. Queue locking and signal delivery became a noticeable fraction of total work.&lt;/p&gt;
&lt;p&gt;Batching nearby items helped:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ThumbnailBatch&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ThreadWeaver&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Job
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ThumbnailBatch(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QStringList &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;paths)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; m_paths(paths)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; run()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foreach (&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;path, m_paths)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; createThumbnail(path);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;private&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QStringList m_paths;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The batch is large enough to amortise scheduling but small enough that several workers remain busy. There is no universal number. Disk latency, decoding cost, and cache hit rate all change the useful grain size, so I am measuring rather than engraving 32 on a tablet.&lt;/p&gt;
&lt;p&gt;Cancellation also becomes a design issue. Removing queued jobs is easy; stopping one already decoding is cooperative. Long operations need checkpoints, and the job must own or safely reference everything it touches. A window closing while a worker holds its raw pointer is not cancellation. It is delayed excitement.&lt;/p&gt;
&lt;p&gt;I prefer a shared job queue such as ThreadWeaver for independent background work because it bounds concurrency and centralises scheduling. The caveat is that it does not make unsafe code safe. Shared caches still need locking, GUI objects still belong to the GUI thread, and dependencies between jobs still need explicit representation.&lt;/p&gt;
&lt;p&gt;Priority needs restraint as well. Marking every visible thumbnail urgent merely recreates a first-in queue with more bookkeeping. I reserve priority for work whose delay is observable now, and still ensure ordinary jobs eventually run.&lt;/p&gt;
&lt;p&gt;The current prototype sends immutable input into a job and returns a completed image through a queued signal. That copy may cost something, but the ownership story fits in one sentence. For now, clarity beats shaving a copy whose cost I have not measured.&lt;/p&gt;</description></item><item><title>A Panel Made of Rectangles</title><link>https://rselbach.com/a-panel-made-of-rectangles/</link><pubDate>Wed, 03 May 2006 23:11:00 +0000</pubDate><guid>https://rselbach.com/a-panel-made-of-rectangles/</guid><description>&lt;p&gt;I spent the evening moving three coloured rectangles around a blank window. This sounds less impressive than &amp;ldquo;working on the KDE 4 desktop,&amp;rdquo; which is precisely why it is a more accurate description.&lt;/p&gt;
&lt;p&gt;The prototype is testing a Plasma idea: applets live in a containment, and the containment decides how available space is assigned. The applet should express useful size hints rather than assume that the panel is horizontal, 32 pixels high, and forever attached to the bottom edge.&lt;/p&gt;
&lt;p&gt;My first attempt stored absolute geometry. Rotating the panel exposed the mistake immediately. The second stores constraints and lets the containment lay things out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QSize Applet&lt;span style="color:#f92672"&gt;::&lt;/span&gt;minimumSize() &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;QSize&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QSize Applet&lt;span style="color:#f92672"&gt;::&lt;/span&gt;preferredSize() &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;QSize&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;96&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The prototype then reruns layout when its own size or form changes. Nothing here proves the final API. It proves that geometry policy has to live somewhere other than each applet&amp;rsquo;s collection of special cases.&lt;/p&gt;
&lt;p&gt;Painting is the second experiment. Keeping content separate from the surrounding frame means a theme can alter margins and background without every applet learning the new decoration. That also means the layout must account for frame insets before assigning content space. I forgot this and produced a clock whose final digit lived just outside civilisation.&lt;/p&gt;
&lt;p&gt;I prefer small visual prototypes with deliberately fake data at this stage. They make resizing, invalidation, and composition bugs obvious before a real calendar or device monitor introduces its own failures. The caveat is that rectangles do not exercise accessibility, input, localisation, or expensive content. A prototype can validate one mechanism while quietly avoiding four others.&lt;/p&gt;
&lt;p&gt;I am saving each experiment as a tiny runnable case. When the graphics or layout API changes, these cases answer whether the concept still works without requiring a complete desktop session to survive long enough for the test.&lt;/p&gt;
&lt;p&gt;There is no finished Plasma desktop hiding behind these rectangles. There are unresolved questions about applet lifecycle, configuration, persistence, and rendering. The useful result tonight is narrower: containment-driven layout survives changing orientation better than hard-coded applet geometry.&lt;/p&gt;
&lt;p&gt;Tomorrow I may replace one rectangle with a clock. This will increase the prototype&amp;rsquo;s accuracy by one time zone and its bug count by an unknown but probably larger number.&lt;/p&gt;</description></item><item><title>Converting One CMake Target at a Time</title><link>https://rselbach.com/converting-one-cmake-target-at-a-time/</link><pubDate>Sat, 15 Apr 2006 16:22:00 +0000</pubDate><guid>https://rselbach.com/converting-one-cmake-target-at-a-time/</guid><description>&lt;p&gt;Today&amp;rsquo;s CMake conversion failed at the final link with an undefined reference from a file I had not changed. Naturally, I first blamed the linker. Linkers enjoy this arrangement because they never have to attend the meeting.&lt;/p&gt;
&lt;p&gt;The actual cause was dependency order. The old build inherited libraries from a parent directory, so this target had never declared what it used. Moving it exposed the omission.&lt;/p&gt;
&lt;p&gt;I started recording requirements next to the target:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cmake" data-lang="cmake"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set(&lt;span style="color:#e6db74"&gt;indexer_SRCS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;indexer.cpp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;document.cpp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;tokenizer.cpp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kde4_add_executable(&lt;span style="color:#e6db74"&gt;indexer&lt;/span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;indexer_SRCS&lt;span style="color:#f92672"&gt;}&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target_link_libraries(&lt;span style="color:#e6db74"&gt;indexer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;KDE4_KDECORE_LIBS&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;searchcore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I configured from an empty build directory. Reusing yesterday&amp;rsquo;s cache can conceal a missing check or preserve a path that no clean machine has. During conversion, this sequence is worth the extra minute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rm -rf build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmake -DCMAKE_BUILD_TYPE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;debugfull ..
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;make VERBOSE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I am not recommending casual &lt;code&gt;rm -rf&lt;/code&gt;; I am recommending a disposable directory whose purpose is printed on the tin. Source remains elsewhere.&lt;/p&gt;
&lt;p&gt;Generated files need similar care. If a header is produced during the build, its output belongs in the binary tree and the target must depend on the generation step. Writing generated headers into the source tree makes an apparently clean checkout depend on debris from an earlier build. It also makes parallel builds unusually creative.&lt;/p&gt;
&lt;p&gt;I prefer explicit source lists over collecting every &lt;code&gt;*.cpp&lt;/code&gt; automatically. Adding a file should change the build description, which gives review and version control a chance to notice it. The caveat is maintenance: source lists can drift if developers forget them, and globbing can be reasonable for generated collections. For hand-written application code, explicit still wins for me.&lt;/p&gt;
&lt;p&gt;I am applying the same rule to include directories. A target should receive only paths it needs; a global include path can let one directory compile by accident against another directory&amp;rsquo;s private header.&lt;/p&gt;
&lt;p&gt;The best conversion checkpoint is not &amp;ldquo;CMake completed.&amp;rdquo; It is a clean configure, complete build, and one small executable run from the binary tree. Configuration only proves that CMake understood my instructions, not that my instructions were wise.&lt;/p&gt;
&lt;p&gt;One target now builds without inherited flags. Nine remain. This is slower to describe than a bulk conversion and faster to debug than one.&lt;/p&gt;</description></item><item><title>HAL Is a Source, Not a Policy</title><link>https://rselbach.com/hal-is-a-source-not-a-policy/</link><pubDate>Tue, 28 Mar 2006 19:48:00 +0000</pubDate><guid>https://rselbach.com/hal-is-a-source-not-a-policy/</guid><description>&lt;p&gt;I plugged in a USB disk this afternoon and my test application offered to open it twice. Unplugging it removed only one entry. This is an efficient way to make hardware discovery look supernatural.&lt;/p&gt;
&lt;p&gt;The bug was mundane: startup enumeration and hotplug notification both created an object, while neither path checked whether the same device identifier was already present. HAL supplies facts about devices and changes, but it does not decide how my application should represent them.&lt;/p&gt;
&lt;p&gt;I now funnel both paths through one function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; DeviceList&lt;span style="color:#f92672"&gt;::&lt;/span&gt;addDevice(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QString &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;udi)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (devices.contains(udi))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Device device &lt;span style="color:#f92672"&gt;=&lt;/span&gt; backend&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;device(udi);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;device.isValid())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; devices.insert(udi, device);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; emit &lt;span style="color:#a6e22e"&gt;deviceAdded&lt;/span&gt;(udi);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Removal uses the same identifier and emits only after the internal state is consistent. That ordering lets slots query the list without observing a ghost.&lt;/p&gt;
&lt;p&gt;The larger KDE 4 question is where HAL ends and a hardware abstraction such as Solid begins. I do not want every application parsing HAL properties or knowing which backend discovered a battery. Applications should ask stable questions: is this a storage volume, can it be mounted, is this network interface active? Solid can translate those questions to the platform mechanism.&lt;/p&gt;
&lt;p&gt;I prefer keeping the backend thin and putting application policy above Solid. A media player may automatically inspect an audio disc; a file manager may offer actions; a backup tool may ignore both and look only for a labelled volume. The same hardware event should not imply the same decision.&lt;/p&gt;
&lt;p&gt;For testing, the backend now accepts a recorded sequence of add and remove events. Replaying &amp;ldquo;add, add, remove&amp;rdquo; verifies the duplicate guard without requiring me to develop exceptional timing with a USB plug. It also makes event ordering assumptions visible in a small test rather than in a user&amp;rsquo;s device list.&lt;/p&gt;
&lt;p&gt;The caveat is that abstraction can hide information we have not yet learned to model. During early porting I am logging the raw UDI and selected properties beside the abstract device type. When a device is classified incorrectly, that evidence is valuable. I would remove such noise from a normal release, assuming we eventually have one that does not greet a single disk as twins.&lt;/p&gt;</description></item><item><title>Model/View Without the Fog</title><link>https://rselbach.com/model-view-without-the-fog/</link><pubDate>Thu, 09 Mar 2006 22:06:00 +0000</pubDate><guid>https://rselbach.com/model-view-without-the-fog/</guid><description>&lt;p&gt;I triggered a crash today by deleting the third row of a list. The list had only two visible rows, which was the first clue that my idea of the data and the view&amp;rsquo;s idea had stopped speaking to each other.&lt;/p&gt;
&lt;p&gt;The old code filled a widget item by item and kept pointers to those items in the document. Qt 4&amp;rsquo;s model/view classes ask for a cleaner bargain: the model owns the shape and meaning of the data, while views ask questions through &lt;code&gt;QModelIndex&lt;/code&gt;. That index is a coordinate plus model identity, not the object itself.&lt;/p&gt;
&lt;p&gt;For a flat list, the smallest useful model is not large:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ContactModel&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; QAbstractListModel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ContactModel(QObject &lt;span style="color:#f92672"&gt;*&lt;/span&gt;parent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; QAbstractListModel(parent)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rowCount&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QModelIndex &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;parent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; QModelIndex()) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; parent.isValid() &lt;span style="color:#f92672"&gt;?&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; contacts.count();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QVariant &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QModelIndex &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;index, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; role) &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;index.isValid() &lt;span style="color:#f92672"&gt;||&lt;/span&gt; index.model() &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index.row() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; index.row() &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; contacts.count() &lt;span style="color:#f92672"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index.column() &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; QVariant();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (role &lt;span style="color:#f92672"&gt;==&lt;/span&gt; Qt&lt;span style="color:#f92672"&gt;::&lt;/span&gt;DisplayRole)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; contacts.at(index.row()).name;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; QVariant();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;private&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QList&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Contact&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; contacts;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The parent check is important even for a list. A valid parent asks for children beneath an item; this model has none. Returning the full count there can make a view believe it has discovered a tree of infinite enthusiasm.&lt;/p&gt;
&lt;p&gt;The more interesting part is changing data. The view cannot infer that a row appeared because a &lt;code&gt;QList&lt;/code&gt; grew. The model must announce structural changes around the actual mutation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ContactModel&lt;span style="color:#f92672"&gt;::&lt;/span&gt;append(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; Contact &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;contact)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; row &lt;span style="color:#f92672"&gt;=&lt;/span&gt; contacts.count();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; beginInsertRows(QModelIndex(), row, row);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; contacts.append(contact);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; endInsertRows();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Those calls let attached views adjust selections, editors, and cached geometry coherently. For an ordinary value change, I modify the value and emit &lt;code&gt;dataChanged(index, index)&lt;/code&gt; instead. Resetting the whole model after every edit appears easier, but it discards selection and makes modest updates expensive.&lt;/p&gt;
&lt;p&gt;My crash came from removal code that changed the collection first and emitted signals afterwards. During that gap, a view requested an index using the old shape against the new data. Correct order is not decorative:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ContactModel&lt;span style="color:#f92672"&gt;::&lt;/span&gt;remove(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; row)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (row &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; row &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; contacts.count())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; beginRemoveRows(QModelIndex(), row, row);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; contacts.removeAt(row);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; endRemoveRows();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Selections add another wrinkle. A normal &lt;code&gt;QModelIndex&lt;/code&gt; is intended for immediate use. If I must retain an index while rows can move, I use &lt;code&gt;QPersistentModelIndex&lt;/code&gt;, and even then I ask whether storing a domain identifier would be clearer. Persistent indexes survive model notifications, not arbitrary lies told by the model.&lt;/p&gt;
&lt;p&gt;Sorting and filtering belong in a proxy when possible. &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; sits between source and view, translating indexes in both directions. That means code receiving a selected proxy index must map it before addressing source data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QModelIndex sourceIndex &lt;span style="color:#f92672"&gt;=&lt;/span&gt; proxy&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;mapToSource(view&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;currentIndex());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Contact contact &lt;span style="color:#f92672"&gt;=&lt;/span&gt; model&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;contactAt(sourceIndex.row());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Forgetting this works beautifully until sorting changes the order. These are the bugs that wait politely through a demonstration and introduce themselves to the first user.&lt;/p&gt;
&lt;p&gt;I prefer models that expose domain data through roles and operations, with widgets knowing as little as possible about storage. One model can then drive a list, a combo box, and perhaps a custom view without three synchronisation routines. The caveat is that model/view has real ceremony. A fixed list of five labels does not need a heroic subclass; &lt;code&gt;QStringListModel&lt;/code&gt; may be enough.&lt;/p&gt;
&lt;p&gt;Editable models need one more honest contract. &lt;code&gt;flags()&lt;/code&gt; must advertise editable indexes, and &lt;code&gt;setData()&lt;/code&gt; must validate the role and value before changing storage. Returning &lt;code&gt;true&lt;/code&gt; means the edit happened, not merely that the method received a pleasant visit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; ContactModel&lt;span style="color:#f92672"&gt;::&lt;/span&gt;setData(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QModelIndex &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;index,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QVariant &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;value, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; role)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (role &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; Qt&lt;span style="color:#f92672"&gt;::&lt;/span&gt;EditRole &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;index.isValid() &lt;span style="color:#f92672"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index.model() &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; index.row() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index.row() &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; contacts.count() &lt;span style="color:#f92672"&gt;||&lt;/span&gt; index.column() &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; contacts[index.row()].name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; value.toString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; emit &lt;span style="color:#a6e22e"&gt;dataChanged&lt;/span&gt;(index, index);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The model and bounds checks matter here too. An index from another model is not permission to address this model&amp;rsquo;s storage, even when its row happens to fit. The delegate asks the model to commit; it does not gain ownership of the underlying record.&lt;/p&gt;
&lt;p&gt;There is also a testing advantage. I can exercise &lt;code&gt;rowCount&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, insertion, and removal with &lt;code&gt;QCoreApplication&lt;/code&gt;, no screen required. My current test records the emitted row ranges and checks the resulting names. It is not glamorous, but neither is debugging a stale selection at midnight.&lt;/p&gt;
&lt;p&gt;Qt 4&amp;rsquo;s mechanism got less foggy when I stopped treating the model as a collection adapter. The view trusts its signals and invariants. Once I stopped lying to it, the disappearing third row quit crashing, though it remains philosophically ambitious.&lt;/p&gt;</description></item><item><title>Porting the Seam, Not the Screen</title><link>https://rselbach.com/porting-the-seam-not-the-screen/</link><pubDate>Fri, 17 Feb 2006 18:35:00 +0000</pubDate><guid>https://rselbach.com/porting-the-seam-not-the-screen/</guid><description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The immediate payoff was a command-line test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; argc, &lt;span style="color:#66d9ef"&gt;char&lt;/span&gt; &lt;span style="color:#f92672"&gt;**&lt;/span&gt;argv)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QCoreApplication app(argc, argv);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (argc &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qCritical() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;usage:&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; argv[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;] &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Document document;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QString error;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;document.load(QString&lt;span style="color:#f92672"&gt;::&lt;/span&gt;fromLocal8Bit(argv[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]), &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;error)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qCritical() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; error;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qDebug() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; document.entries().count();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Tomorrow I will return to the main window. It should now have less to do, which is my favourite feature in a window class.&lt;/p&gt;</description></item><item><title>CMake Before Coffee</title><link>https://rselbach.com/cmake-before-coffee/</link><pubDate>Tue, 31 Jan 2006 08:42:00 +0000</pubDate><guid>https://rselbach.com/cmake-before-coffee/</guid><description>&lt;p&gt;This morning I changed one header and watched the old build machinery reconsider what appeared to be western civilisation. That was enough encouragement to try the same small library with CMake.&lt;/p&gt;
&lt;p&gt;The useful bit was not clever syntax. It was stating the target directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cmake" data-lang="cmake"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set(&lt;span style="color:#e6db74"&gt;parser_SRCS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;parser.cpp&lt;/span&gt; &lt;span style="color:#e6db74"&gt;token.cpp&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kde4_add_library(&lt;span style="color:#e6db74"&gt;parser&lt;/span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;parser_SRCS&lt;span style="color:#f92672"&gt;}&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target_link_libraries(&lt;span style="color:#e6db74"&gt;parser&lt;/span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;QT_QTCORE_LIBRARY&lt;span style="color:#f92672"&gt;}&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;install(&lt;span style="color:#e6db74"&gt;TARGETS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;parser&lt;/span&gt; &lt;span style="color:#e6db74"&gt;DESTINATION&lt;/span&gt; &lt;span style="color:#f92672"&gt;${&lt;/span&gt;LIB_INSTALL_DIR&lt;span style="color:#f92672"&gt;}&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I configured outside the source tree:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmake ..
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;make VERBOSE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That last command matters while converting. The generated command line tells me which include path, definition, or library I forgot. Guessing from a linker error is possible, but so is repairing a watch with a spoon.&lt;/p&gt;
&lt;p&gt;I prefer one explicit target with its own sources and libraries over directory-wide flags. It makes dependencies visible and avoids accidentally linking every library against everything else. The caveat is that our CMake helper macros are still moving, so a tidy file today may need adjustment next week.&lt;/p&gt;
&lt;p&gt;My practical rule is to convert one buildable directory at a time, compile it, then continue. A giant mechanical conversion produces a giant pile of errors with no useful ordering. Small green steps are less heroic and considerably faster.&lt;/p&gt;</description></item><item><title>The Timer That Froze the Window</title><link>https://rselbach.com/the-timer-that-froze-the-window/</link><pubDate>Thu, 12 Jan 2006 21:14:00 +0000</pubDate><guid>https://rselbach.com/the-timer-that-froze-the-window/</guid><description>&lt;p&gt;I found today&amp;rsquo;s problem by dragging a window. The repaint stopped, the title bar sulked, and a progress label remained at 12 percent while the application did several seconds of perfectly respectable work.&lt;/p&gt;
&lt;p&gt;The work was running from a &lt;code&gt;QTimer&lt;/code&gt; slot. I had assumed that using a timer made it asynchronous. It does not. A timer merely arranges for a callback to be delivered by the event loop; the callback still runs on that loop&amp;rsquo;s thread. If the slot computes for five seconds, paint events wait five seconds. So do mouse events and, more importantly, the user&amp;rsquo;s patience.&lt;/p&gt;
&lt;p&gt;The quick diagnostic was wonderfully primitive:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Scanner&lt;span style="color:#f92672"&gt;::&lt;/span&gt;scanNext()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qDebug() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;enter scanNext&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scanEverything();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; qDebug() &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;leave scanNext&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pause sat between those messages. Splitting the operation into bounded pieces fixed the immediate problem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Scanner&lt;span style="color:#f92672"&gt;::&lt;/span&gt;scanNext()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; hasMore(); &lt;span style="color:#f92672"&gt;++&lt;/span&gt;i)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scanOne();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (hasMore())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QTimer&lt;span style="color:#f92672"&gt;::&lt;/span&gt;singleShot(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;, SLOT(scanNext()));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A zero-duration timer does not promise instant execution. It puts more work back into event processing, allowing pending paints and input to run between batches. The batch size is deliberately boring: small enough to keep the interface alive, large enough not to drown in scheduling overhead.&lt;/p&gt;
&lt;p&gt;I prefer this incremental approach when the job naturally divides into independent records. It keeps all GUI objects on their owning thread and makes cancellation straightforward. The caveat is that a single expensive record still blocks everything. In that case the computation belongs on a worker thread, with results sent back rather than widgets touched directly.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;QCoreApplication::processEvents()&lt;/code&gt; inside the loop is tempting, but it permits other callbacks to enter while the current operation is unfinished. That can expose partially updated state or let the user start the same action twice. Returning to the event loop between explicit batches gives me a cleaner boundary: each batch leaves the object consistent before yielding.&lt;/p&gt;
&lt;p&gt;That boundary also gives cancellation a predictable checkpoint between records, rather than during an arbitrary mutation.&lt;/p&gt;
&lt;p&gt;Qt 4&amp;rsquo;s event loop is not magic concurrency dust. This is disappointing only until one remembers that magic concurrency dust would probably deadlock before breakfast.&lt;/p&gt;</description></item></channel></rss>