<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Qt4 on Roberto Selbach</title><link>https://rselbach.com/tags/qt4/</link><description>Recent content in Qt4 on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 02 Mar 2007 20:16:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/qt4/index.xml" rel="self" type="application/rss+xml"/><item><title>One Event Loop, One Owner</title><link>https://rselbach.com/one-event-loop-one-owner/</link><pubDate>Fri, 02 Mar 2007 20:16:00 +0000</pubDate><guid>https://rselbach.com/one-event-loop-one-owner/</guid><description>&lt;p&gt;A worker emitted a progress signal today, the label updated twice, and then the application hung while closing. The visible update tempted me to declare the cross-thread code correct. The shutdown was less charitable.&lt;/p&gt;
&lt;p&gt;Qt gives each &lt;code&gt;QObject&lt;/code&gt; thread affinity. Queued events for that object are processed by the event loop in its owning thread. A signal connected across threads normally becomes a queued call, so the receiver&amp;rsquo;s slot runs where the receiver lives. This permits a worker to report results without directly touching GUI objects.&lt;/p&gt;
&lt;p&gt;The safe shape is simple:&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;connect(worker, SIGNAL(progress(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dialog, SLOT(setProgress(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Qt&lt;span style="color:#f92672"&gt;::&lt;/span&gt;QueuedConnection);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The explicit connection type documents the boundary. The worker emits an integer; the dialog owns the label and updates it on the GUI thread.&lt;/p&gt;
&lt;p&gt;My hang came from destruction. The dialog waited for the worker while handling its close event, and the worker was waiting for a queued reply from the dialog&amp;rsquo;s event loop. The GUI event loop could not deliver the reply because it was blocked waiting. Both sides were admirably patient.&lt;/p&gt;
&lt;p&gt;I removed the synchronous callback. Closing now requests cancellation, disables the interface, and lets normal event processing continue until the worker announces completion. Only then is it destroyed. Data passed in signals is copied as needed, so custom value types must be registered if they cross a queued connection.&lt;/p&gt;
&lt;p&gt;I prefer message-like queued signals between worker and GUI code. They make ownership visible and avoid shared widget state. The caveat is that queued delivery is asynchronous: the sender cannot assume the slot has run, event loops must remain active, and ordering guarantees do not replace a proper state machine.&lt;/p&gt;
&lt;p&gt;Nested event loops are another temptation. Opening a modal operation or manually calling event processing can keep paints moving, but it also permits unrelated events to re-enter code whose invariants are half changed. I use that technique only with a very clear boundary, never as a general cure for long work.&lt;/p&gt;
&lt;p&gt;The practical test now closes the window at several stages of the job. Starting and finishing are easy; cancellation and teardown reveal who actually owns what. If a design works only while the user behaves, the user is not the unreliable component.&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>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>