tags/python StyXman's glob http://grulicueva.homelinux.net/~mdione/glob//tags/python/ StyXman's glob ikiwiki 2009-11-20T16:42:58Z satyr-0.1-beta1 http://grulicueva.homelinux.net/~mdione/glob//posts/satyr-0.1-beta1/ 2009-11-20T16:42:58Z 2009-11-20T00:38:21Z <p>Today I sat down and tried to refactor <code>satyr</code> once more after dinner. This time I was trying to decouple the functionality related to multi-collection playlists from <code>PlayListModel</code> while moving it to the <code>default</code> skin. The idea was to be able to create another skin which used a <code>QTableView</code>, which in turn would be a trampoline for implementing tag editing and writing. But I started to stall a little, and that normally provokes me to defocus, to dezone. When that happens, I go and read some piled up posts in <code>akregator</code>[1].</p> <p>This time I came around <a href= "http://www.cyrius.com/journal/fossbazaar/lessons-from-freedos">a post by Martin Michlmayr</a> (who I read through <a href= "http://planet.debian.org/">Planet Debian</a>) from 10 days ago which talks about lessons learned about free software projects. Actually the post is just a resume of 4 posts from the FreeDOS founder Jim Hall. At some point he writes «releases are important».</p> <p><strong>Bing!</strong> goes my head[2]. <code>satyr</code> is already 3 months, 12 days or 117 revisions old and I hadn't released it yet, even after I promissed to do so almost a month ago! The problem is that I kept adding features (and squashing bugs in <code>Phonon</code>[3]) and completely forgot about releasing. Just one semi-colon before he also writes «initial users of the software should be recruited as developers»... which users? If one doesn't release, one might never have users to recruit!</p> <p>So instead of the pharaonic refactor I had in mind (an in another branch, blessed be <code>bazaar</code>) I wrote a <code>setup.py</code> script in 15 minutes, massaged a little the files (I had to create a package and modify almost all the files to reflect this), tested a little, and produced a nice triplet of files:</p> <pre> <code>satyr-0.1-beta1.tar.bz2 satyr-0.1-beta1.tar.gz satyr-0.1-beta1.zip </code> </pre> <p>So there you have it, a realease! Ok, it's a 'beta1', but it's out. Go grab it, test it, complain about bugs, tell us you like it, suggest improvements, whatever! And tell your best friend to use it, even if you don't like it! Where to get it? Why, from <a href= "http://mirrors.aixtools.net/sv/satyr/">the project's download's page</a>, of course!</p> <hr /> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <span class="selflink">python</span></p> <hr /> <p>[1] Yes, once upon a time I developed my own feed reader called <code>kReiSSy</code>, but it's implemented in <code>PyKDE3</code> and I don't plan to port it yet, even if somehow is better for me than <code>akregator</code>. A shame, reallly...</p> <p>[2] in the same way that a µ-wave oven goes “bing”, not in an “Eureka!” way...</p> <p>[3] I even fixed the need for the 'file' scheme in the Gstreamer backend.</p> qlistmodel-in-pyqt http://grulicueva.homelinux.net/~mdione/glob//posts/qlistmodel-in-pyqt/ 2009-11-19T00:28:01Z 2009-11-19T00:28:01Z <p>One of the things I had to while developing <code>satyr</code> is building a model for a <code>QListViewer</code>. It should be straighforward from qt's documentation, but I found a couple of things that I would like to put in a post, specially because there doesn't seem to be much models in <code>PyQt4</code> easily found in the web.</p> <p>According to its description, a subclass of <code>QAbstractListModel</code> as this one should mostly implement the <code>data()</code> and <code>rowCount()</code> methods, which is true. This example creates a read-only model, so no need to implement <code>setData()</code>, but given the simplicity of <code>data()</code>, it doesn't seem too difficult to do. I also wanted it to react when more <code>Song</code>s were added on the fly[1].</p> <p>The method <code>data()</code> is the most important one. It is not only used for retrieving the data itself, but also some metadata useful for showing the data, like icons and other stuff. For selecting what the caller wants, it refers a <code>Qt.ItemDataRole</code>. The role for the data itself is <code>Qt.DisplayRole</code>. One of the particularities of this method is that it could be called with any vegetable as input; namely, it can refer to a row that does not exist anymore or for metadata that you don't care about. In those cases you must return an empty <code>QVariant</code>, not <code>None</code>. So, a first implementation is:</p> <pre class="hl"> <span class="hl kwa">def</span> <span class= "hl kwd">data</span> <span class="hl sym">(</span>self<span class= "hl sym">,</span> modelIndex<span class= "hl sym">,</span> role<span class="hl sym">):</span> <span class="hl kwa">if</span> modelIndex<span class= "hl sym">.</span><span class="hl kwd">isValid</span> <span class= "hl sym">()</span> <span class= "hl kwa">and</span> modelIndex<span class= "hl sym">.</span><span class="hl kwd">row</span> <span class= "hl sym">()&lt;</span>self<span class= "hl sym">.</span>count <span class= "hl kwa">and</span> role<span class= "hl sym">==</span>Qt<span class="hl sym">.</span>DisplayRole<span class="hl sym">:</span> <span class= "hl slc"># songForIndex() returns the Song corresponding to the row</span> song<span class="hl sym">=</span> self<span class= "hl sym">.</span><span class= "hl kwd">songForIndex</span> <span class= "hl sym">(</span>modelIndex<span class= "hl sym">.</span><span class="hl kwd">row</span> <span class= "hl sym">())</span> <span class= "hl slc"># formatSong() returns a QString with the data to show</span> data<span class="hl sym">=</span> <span class= "hl kwd">QVariant</span> <span class= "hl sym">(</span>self<span class="hl sym">.</span><span class= "hl kwd">formatSong</span> <span class= "hl sym">(</span>song<span class="hl sym">))</span> <span class="hl kwa">else</span><span class="hl sym">:</span> data<span class="hl sym">=</span> <span class= "hl kwd">QVariant</span> <span class="hl sym">()</span> <span class="hl kwa">return</span> data </pre> <p>This method, together with a <code>rowCount()</code> that simply returns <code>self.count</code>, is enough for showing data that is already there. Notice that the <code>QModelIndex</code> can be not valid, and in this case we only care about its row because we're a list.</p> <p>But then I wanted my <code>QListViewer</code> to show songs progresively as they are loaded/scanned[2] and also as they are found as new. But then a problem arises: the view is like a table of only one column. The width of this colunm at the begining is the same width as the <code>QListView</code> itself. But what happens when the string shown is too big? What happens is that it gets chopped. We must inform the view that some of the rows are bigger. That's where the metadata comes into play.</p> <p>Another possible role is <code>Qt.SizeHintRole</code>. If we return a size instead of an empty <code>QVariant</code>, that size will be used to expand the column as needed, even giving us a scrollbar if it's wider that the view.</p> <p>Now, we're supposed to show the tags for the <code>Song</code> (that's what <code>formatSong()</code> does if possible; if not, it simply returns the filepath), so this width should be calculated based on the length of the string that represents the song[3]. But if we try to read the tags for all the songs as we load the <code>Collection</code>, we end up with too much disk activity before you can show anything to the user, which is unacceptable[4]. So instead we calculate based on the filepath, which is used for <code>Songs</code> with too few tags anyways. Here's the hacky code:</p> <pre class="hl"> <span class="hl sym">...</span> <span class="hl slc"># FIXME: kinda hacky</span> self<span class="hl sym">.</span>fontMetrics<span class= "hl sym">=</span> <span class= "hl kwd">QFontMetrics</span> <span class= "hl sym">(</span>KGlobalSettings<span class= "hl sym">.</span><span class= "hl kwd">generalFont</span> <span class="hl sym">())</span> <span class="hl sym">...</span> <span class="hl kwa">def</span> <span class= "hl kwd">data</span> <span class="hl sym">(</span>self<span class= "hl sym">,</span> modelIndex<span class= "hl sym">,</span> role<span class="hl sym">):</span> <span class="hl kwa">if</span> modelIndex<span class= "hl sym">.</span><span class="hl kwd">isValid</span> <span class= "hl sym">()</span> <span class= "hl kwa">and</span> modelIndex<span class= "hl sym">.</span><span class="hl kwd">row</span> <span class= "hl sym">()&lt;</span>self<span class= "hl sym">.</span>count<span class="hl sym">:</span> song<span class="hl sym">=</span> self<span class= "hl sym">.</span><span class= "hl kwd">songForIndex</span> <span class= "hl sym">(</span>modelIndex<span class= "hl sym">.</span><span class="hl kwd">row</span> <span class= "hl sym">())</span> <span class="hl kwa">if</span> role<span class= "hl sym">==</span>Qt<span class= "hl sym">.</span>DisplayRole<span class="hl sym">:</span> data<span class="hl sym">=</span> <span class= "hl kwd">QVariant</span> <span class= "hl sym">(</span>self<span class="hl sym">.</span><span class= "hl kwd">formatSong</span> <span class= "hl sym">(</span>song<span class="hl sym">))</span> <span class="hl kwa">elif</span> role<span class= "hl sym">==</span>Qt<span class= "hl sym">.</span>SizeHintRole<span class="hl sym">:</span> <span class= "hl slc"># calculate something based on the filepath</span> data<span class="hl sym">=</span> <span class= "hl kwd">QVariant</span> <span class= "hl sym">(</span>self<span class= "hl sym">.</span>fontMetrics<span class= "hl sym">.</span><span class="hl kwd">size</span> <span class= "hl sym">(</span>Qt<span class= "hl sym">.</span>TextSingleLine<span class= "hl sym">,</span> song<span class= "hl sym">.</span>filepath<span class="hl sym">))</span> <span class="hl kwa">else</span><span class= "hl sym">:</span> data<span class="hl sym">=</span> <span class= "hl kwd">QVariant</span> <span class="hl sym">()</span> <span class="hl kwa">else</span><span class="hl sym">:</span> data<span class="hl sym">=</span> <span class= "hl kwd">QVariant</span> <span class="hl sym">()</span> <span class="hl kwa">return</span> data </pre> <p>The last point then is reacting to <code>Song</code>s are added on the fly. This is also easy: you tell the views you're about to insert rows, you insert them, tell the views you finished, and then emit <code>dataChanged()</code>:</p> <pre class="hl"> <span class="hl kwa">def</span> <span class= "hl kwd">addSong</span> <span class= "hl sym">(</span>self<span class="hl sym">):</span> <span class= "hl slc"># lastIndex keeps track of the last index used.</span> row<span class="hl sym">=</span> self<span class= "hl sym">.</span>lastIndex self<span class="hl sym">.</span>lastIndex<span class= "hl sym">+=</span> <span class="hl num">1</span> self<span class="hl sym">.</span><span class= "hl kwd">beginInsertRows</span> <span class= "hl sym">(</span><span class= "hl kwd">QModelIndex</span> <span class= "hl sym">(),</span> row<span class= "hl sym">,</span> row<span class="hl sym">)</span> <span class= "hl slc"># actually the Song has already been added to the Collection[5]</span> <span class="hl slc"># so I don't do anything here,</span> <span class= "hl slc"># but if you keep your rows in this model you should do something here</span> self<span class="hl sym">.</span><span class= "hl kwd">endInsertRows</span> <span class="hl sym">()</span> self<span class="hl sym">.</span>count<span class= "hl sym">+=</span> <span class="hl num">1</span> modelIndex<span class="hl sym">=</span> self<span class= "hl sym">.</span><span class="hl kwd">index</span> <span class= "hl sym">(</span>row<span class="hl sym">,</span> <span class= "hl num">0</span><span class="hl sym">)</span> self<span class="hl sym">.</span>dataChanged<span class= "hl sym">.</span><span class="hl kwd">emit</span> <span class= "hl sym">(</span>modelIndex<span class= "hl sym">,</span> modelIndex<span class="hl sym">)</span> </pre> <p>Later I'll post any peculiarities I find porting all this stuff to a read/write <code>QTableModel</code>.</p> <hr /> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <span class="selflink">python</span></p> <hr /> <p>[1] That's material for another post :)</p> <p>[2] This feature can be said to be a little too much. Actually, I get a flicker when scanning.</p> <p>[3] Of course the next step is to use a table view and make a model for it.</p> <p>[4] Right now the load time for a <code>Collection</code> of ~6.5k songs is quite long as it is.</p> <p>[5] This is a design decision which is not relevant to this example.</p> skinning-satyr http://grulicueva.homelinux.net/~mdione/glob//posts/skinning-satyr/ 2009-11-19T00:28:01Z 2009-11-17T11:15:32Z <p>One of the features I planned for <code>satyr</code> almost since the begining was the possibility to have 'skins'. In this context, a skin would not only implement the look and feel, but also could implement features the weren't available in the shipped classes. I also planned to implement this feature after I had most of the others one already done. But then I was bored this weekend with nothing to do and I decided to set off to at least investigate how to do it. Of course, what happened was that I implemented it almost completely.</p> <p>Up to now, <code>satyr</code>'s user interface was implemented in two files: <code>default.ui</code>, which was compiled with <code>pyuic4</code> into <code>default.py</code>, and some code in <code>satyr.py</code> itself. This of course would not scale, and I always had the idea of moving the behaviour implemented in <code>satyr.py</code> to a file called <code>default.py</code> and load the ui directly from the <code>default.ui</code> file without compiling, getting rid of the need for a compilation at the same time. This also meant that then a skin would consist of a <code>.py</code> file and possibly a <code>.ui</code> file. There are three problems to solve for this: getting the local-to-the-user's skin directory, loading the skin and loading the correspondant <code>.ui</code> file.</p> <p>The first part is simple from the <code>PyKDE4</code> point of view:</p> <pre class="hl"> <span class= "hl slc"># get the app's dir; don't forget the trailing '/'!</span> appDir<span class="hl sym">=</span> KStandardDirs<span class= "hl sym">.</span><span class= "hl kwd">locateLocal</span> <span class= "hl sym">(</span><span class="hl str">'data'</span><span class= "hl sym">,</span> <span class="hl str">'satyr/'</span><span class= "hl sym">)</span> </pre> <p>I'll first explain the other two parts, loading the skin and the <code>.ui</code> file, before returning more deeply to the consequences of this solution.</p> <p>I put all the skins in a <code>skins</code> subdirectory. To make it a proper python module I added an empty <code>__init__.py</code> file. Now, I could simply <code>import skins.&lt;skinName&gt;</code> and possibly instantiate some class in it, but of course one cannot write that. I could resort to <code>eval ('import skins.'+skinName)</code>, but we know that <code>eval()</code> has the most long-standing typo in the history of computer languages, and it's actually called <code>evil()</code>.</p> <p>What we can do is resort to <code>__import__()</code> instead. This little function does approximately what we want. I say approx because it has some surprises in the sleeves of its sleeveless code. I suggest you to go read carefully its documentation. Meanwhile, the magic itself:</p> <pre class="hl"> mod<span class="hl sym">=</span> <span class= "hl kwb">__import__</span> <span class= "hl sym">(</span><span class="hl str">'skins.'</span><span class= "hl sym">+</span>skinName<span class="hl sym">,</span> <span class= "hl kwb">globals</span><span class="hl sym">(),</span> <span class= "hl kwb">locals</span><span class="hl sym">(),</span> <span class= "hl str">'MainWindow'</span><span class="hl sym">)</span> mw<span class="hl sym">=</span> mod<span class= "hl sym">.</span><span class= "hl kwd">MainWindow</span> <span class="hl sym">()</span> </pre> <p>Loading the <code>.ui</code> file in the skin's code is rather simple: just get the skin module's filepath, replace <code>.py</code> with <code>.ui</code>, and load it with <code>PyQt4.uic.loadUiType()</code>[1]. This function returns a generated class for the topmost widget and its Qt base class. This generated class has a <code>setupUi()</code> method that is the one that actually builds de UI[2]. So, we just instantiate the main window's class and call its <code>setupUi()</code> method:</p> <pre class="hl"> <span class="hl kwa">from</span> PyQt4 <span class= "hl kwa">import</span> uic <span class="hl slc"># !!! __file__ might end with .py[co]!</span> uipath<span class="hl sym">=</span> __file__<span class= "hl sym">[:</span>__file__<span class="hl sym">.</span><span class= "hl kwd">rfind</span> <span class="hl sym">(</span><span class= "hl str">'.'</span><span class="hl sym">)]+</span><span class= "hl str">'.ui'</span> <span class="hl slc"># I don't care about the base class</span> <span class="hl sym">(</span>UIMainWindow<span class= "hl sym">,</span> buh<span class="hl sym">)=</span> uic<span class= "hl sym">.</span><span class= "hl kwd">loadUiType</span> <span class="hl sym">(</span>uipath<span class="hl sym">)</span> self<span class="hl sym">.</span>ui<span class= "hl sym">=</span> <span class= "hl kwd">UIMainWindow</span> <span class="hl sym">()</span> self<span class="hl sym">.</span>ui<span class= "hl sym">.</span><span class="hl kwd">setupUi</span> <span class= "hl sym">(</span>self<span class="hl sym">)</span> </pre> <p>Note the comment about the <code>__file__</code> attribute of a module.</p> <p>Now, and back to the first part, finding the local-to-the-user's skin directory is the easiest part. From there, things get a little bit more complicated:</p> <ul> <li>The <code>skins</code> subdirectory might not exist.</li> <li>If you create it, you gotta make sure to also throw in a <code>__init__.py</code> file.</li> <li>Once you've done it, you also need to add the local-to-the-user's app directory to the path. It's easy, just prepend it to <code>sys.path</code>, so it's used before any system-wide directory.</li> <li>The last problem that remains is exactly that: once the <code>__init__.py</code> is there and the user's dir is prepended to <code>sys.path</code>, the user's local skin directory is always used when importing anything from the <code>skins</code> module, so if a skin is not there it is not loadable. All skins distributed with <code>satyr</code> will be inaccesible!</li> </ul> <p>So I'm in a kind of dead alley here. I have a couple of ideas on how to work-around this, but they're at best hacky, and I don't want to implement them until I'm sure that it's inevitable.</p> <hr /> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <span class="selflink">python</span></p> <hr /> <p>[1] Not a very happy name, if you ask me.</p> <p>[2] Very similar to what you get if you compile the <code>.ui</code> with <code>pyuic4</code>.</p> phonon-and-filenames-fixed http://grulicueva.homelinux.net/~mdione/glob//posts/phonon-and-filenames-fixed/ 2009-11-16T09:31:29Z 2009-11-15T13:45:16Z <p>I certainly hope this is the last post in the <code>Phonon</code>-and-badly-encoded/mixed-encodings-filenames saga, but I know is just wishful thinking: as all encoding-related problems they never really dissapear, it's just that you hadn't hit the right wrong stone yet. In any case, I fixed all my later problems wherever they where, and now I can answer this question: how to play files whose filenames are badly encoded and/or have mixed encodings, all this in <code>Phonon</code>?</p> <p>Right now the answer is: you have to provide a properly encoded <code>QUrl</code>. How, you might ask, can I get one of those? Are they selled in the same odly-looking places where you can buy cigarretes, or even marihuana[1]? The answer, luckly, is way more simple.</p> <p>Putting together all the code I've been showing about <code>Python</code>, <code>PyQt4/PyKDE4</code> and <code>Phonon</code> recently, it comes down to this[5]:</p> <pre> <code># path is a str() qba= QByteArray (path) # the exceptions are not needed, # but is cleaner if you print the outcome of this qu= QUrl.fromEncoded (qba.toPercentEncoding ("/ ")) # this is needed by the gstreamer backend[3], # and the xine backend doesn't complain qu.setScheme ('file') </code> </pre> <p>... and that's it. You can now create a <code>MediaSource</code> with this <code>qu</code>.</p> <p>There are a couple of ideas that I want to express as conclusion to all this:</p> <ul> <li>In an ideal world these things should not happen. But this is one of the lesser problems with this non-ideal world, so bear with it.</li> <li>Paths should not be stored in <code>QString</code>s, even if they can (and they do) store this kind of pathnames, because if you try to 'encode' its contents (in the Unicode sense; that is, convert it to an encoding like UTF-8[4]) you get farts or barks at best. Yes, you always have <code>constData()</code> but from <code>QString</code>'s class reference there is no warranty that this will keep being the case[6].</li> <li>In fact, <code>QString</code>'s class reference says at some point: «[one case] where <code>QByteArray</code> is appropriate are when you need to store raw binary data...», and <a href= "http://grulicueva.homelinux.net/~mdione/glob/posts/from-qstring-to-bytes-in-pyqt/"> as I already wrote</a>, «[t]his would be the case for paths; you <strong>need</strong> the bytes».</li> <li><code>QFile</code> and <code>QDir</code> can only be created from <code>QString</code>s. I'm not sure if, given all I wrote, that's right.</li> </ul> <p>The good news is that <code>satyr</code> now can play any file that the backends can whatever their filename-as-string-of-bytes is, I'm a little bit happier about it, I got another contribution to <code>KDE</code> and might even have to close a lot of bugs!</p> <hr /> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <span class="selflink">python</span> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../phonon/">phonon</a></p> <hr /> <p>[1] That question is only legal in Nederlands[2] and very few others cities in the planet.</p> <p>[2] Actually is not <em>legal</em>. See <a href= "http://en.wikipedia.org/wiki/Drug_policy_of_the_Netherlands#Non-enforcement"> this wikipedia article</a>.</p> <p>[3] I might pull up my sleeves again and fix that.</p> <p>[4] You might have already know this, but if you not: you cannot print Unicode, because Unicode is not and encoding. You <em>have</em> to encode it first. Hence, the <code>toLatin1()</code>, <code>toUtf8()</code> and similar <code>QString</code> methods, and also the inverse <code>from*()</code>.</p> <p>[5] Of course the equivalent C++ code also works, with <code>path</code> being a <code>char *</code>.</p> <p>[6] And in the case of <code>PyQt4</code>, that method is not even available. But <a href= "http://grulicueva.homelinux.net/~mdione/glob/posts/from-qstring-to-bytes-in-pyqt/"> I already globed about it</a>.</p> our-man-in-toulon http://grulicueva.homelinux.net/~mdione/glob//posts/our-man-in-toulon/ 2009-11-15T13:45:16Z 2009-11-15T13:45:16Z <p>A couple of days ago <a href= "http://feedproxy.google.com/~r/ElBlogDeMarcelo/~3/kukV8oltwWI/">Marcelo Fernández wrote a simple image viewer in <code>PyGTK</code></a>. It's less than 200 lines long[1], and I thought that it would be nice to compare how the same app would be written in <code>PyKDE4</code>. But then I though that it would not be fair, as <code>KDE</code> is a whole desktop environment and <code>GTK</code> is 'only' a widget library, so I did it in <code>PyQt4</code> instead.</p> <p>To make this even more fair, I hadn't had a good look at the code itself, I only run it to see what it looks like: a window with only the shown image in it, both scrollbars, no menu or statusbar, and no external file, so I assume he builds the ui 'by hand'. He mentions these features:</p> <ul> <li>Pan the image with the mouse.</li> <li>F1 to F5 handle the zoom from 'fit to window', 25%, 50%, 75% and 100%.</li> <li>Zooming with the mouse wheel doesn't work.</li> </ul> <p>Here's my take:</p> <pre class="hl"> <span class="hl slc">#! /usr/bin/python</span> <span class="hl slc"># -*- coding: utf-8 -*-</span> <span class= "hl slc"># OurManInToulon - Example image viewer in PyQt4</span> <span class= "hl slc"># Marcos Dione &lt;mdione@grulic.org.ar&gt; - http://grulicueva.homelinux.net/~mdione/glob/</span> <span class="hl slc"># TODO:</span> <span class="hl slc"># * add licence! (GPLv2 or later)</span> <span class="hl kwa">from</span> PyQt4<span class= "hl sym">.</span>QtGui <span class= "hl kwa">import</span> QApplication<span class= "hl sym">,</span> QMainWindow<span class= "hl sym">,</span> QGraphicsView<span class= "hl sym">,</span> QGraphicsScene <span class="hl kwa">from</span> PyQt4<span class= "hl sym">.</span>QtGui <span class= "hl kwa">import</span> QPixmap<span class= "hl sym">,</span> QGraphicsPixmapItem<span class= "hl sym">,</span> QAction<span class="hl sym">,</span> QKeySequence <span class="hl kwa">import</span> sys <span class="hl kwa">class</span> <span class= "hl kwd">OMITGraphicsView</span> <span class= "hl sym">(</span>QGraphicsView<span class="hl sym">):</span> <span class="hl kwa">def</span> <span class= "hl kwd">__init__</span> <span class= "hl sym">(</span>self<span class= "hl sym">,</span> pixmap<span class= "hl sym">,</span> scene<span class= "hl sym">,</span> parent<span class= "hl sym">, *</span>args<span class="hl sym">):</span> QGraphicsView<span class="hl sym">.</span><span class= "hl kwd">__init__</span> <span class= "hl sym">(</span>self<span class= "hl sym">,</span> scene<span class="hl sym">)</span> self<span class="hl sym">.</span>zoomLevel<span class= "hl sym">=</span> <span class="hl num">1.0</span> self<span class="hl sym">.</span>win<span class= "hl sym">=</span> parent self<span class="hl sym">.</span>img<span class= "hl sym">=</span> pixmap self<span class="hl sym">.</span><span class= "hl kwd">setupActions</span> <span class="hl sym">()</span> <span class="hl kwa">def</span> <span class= "hl kwd">setupActions</span> <span class= "hl sym">(</span>self<span class="hl sym">):</span> <span class="hl slc"># a factory to the right!</span> zoomfit<span class="hl sym">=</span> <span class= "hl kwd">QAction</span> <span class= "hl sym">(</span>self<span class="hl sym">)</span> zoomfit<span class="hl sym">.</span><span class= "hl kwd">setShortcuts</span> <span class= "hl sym">([</span>QKeySequence<span class= "hl sym">.</span><span class= "hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F1'</span><span class="hl sym">)])</span> zoomfit<span class="hl sym">.</span>triggered<span class= "hl sym">.</span><span class="hl kwd">connect</span> <span class= "hl sym">(</span>self<span class= "hl sym">.</span>zoomfit<span class="hl sym">)</span> self<span class="hl sym">.</span><span class= "hl kwd">addAction</span> <span class= "hl sym">(</span>zoomfit<span class="hl sym">)</span> zoom25<span class="hl sym">=</span> <span class= "hl kwd">QAction</span> <span class= "hl sym">(</span>self<span class="hl sym">)</span> zoom25<span class="hl sym">.</span><span class= "hl kwd">setShortcuts</span> <span class= "hl sym">([</span>QKeySequence<span class= "hl sym">.</span><span class= "hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F2'</span><span class="hl sym">)])</span> zoom25<span class="hl sym">.</span>triggered<span class= "hl sym">.</span><span class="hl kwd">connect</span> <span class= "hl sym">(</span>self<span class= "hl sym">.</span>zoom25<span class="hl sym">)</span> self<span class="hl sym">.</span><span class= "hl kwd">addAction</span> <span class= "hl sym">(</span>zoom25<span class="hl sym">)</span> zoom50<span class="hl sym">=</span> <span class= "hl kwd">QAction</span> <span class= "hl sym">(</span>self<span class="hl sym">)</span> zoom50<span class="hl sym">.</span><span class= "hl kwd">setShortcuts</span> <span class= "hl sym">([</span>QKeySequence<span class= "hl sym">.</span><span class= "hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F3'</span><span class="hl sym">)])</span> zoom50<span class="hl sym">.</span>triggered<span class= "hl sym">.</span><span class="hl kwd">connect</span> <span class= "hl sym">(</span>self<span class= "hl sym">.</span>zoom50<span class="hl sym">)</span> self<span class="hl sym">.</span><span class= "hl kwd">addAction</span> <span class= "hl sym">(</span>zoom50<span class="hl sym">)</span> zoom75<span class="hl sym">=</span> <span class= "hl kwd">QAction</span> <span class= "hl sym">(</span>self<span class="hl sym">)</span> zoom75<span class="hl sym">.</span><span class= "hl kwd">setShortcuts</span> <span class= "hl sym">([</span>QKeySequence<span class= "hl sym">.</span><span class= "hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F4'</span><span class="hl sym">)])</span> zoom75<span class="hl sym">.</span>triggered<span class= "hl sym">.</span><span class="hl kwd">connect</span> <span class= "hl sym">(</span>self<span class= "hl sym">.</span>zoom75<span class="hl sym">)</span> self<span class="hl sym">.</span><span class= "hl kwd">addAction</span> <span class= "hl sym">(</span>zoom75<span class="hl sym">)</span> zoom100<span class="hl sym">=</span> <span class= "hl kwd">QAction</span> <span class= "hl sym">(</span>self<span class="hl sym">)</span> zoom100<span class="hl sym">.</span><span class= "hl kwd">setShortcuts</span> <span class= "hl sym">([</span>QKeySequence<span class= "hl sym">.</span><span class= "hl kwd">fromString</span> <span class="hl sym">(</span><span class="hl str">'F5'</span><span class="hl sym">)])</span> zoom100<span class="hl sym">.</span>triggered<span class= "hl sym">.</span><span class="hl kwd">connect</span> <span class= "hl sym">(</span>self<span class= "hl sym">.</span>zoom100<span class="hl sym">)</span> self<span class="hl sym">.</span><span class= "hl kwd">addAction</span> <span class= "hl sym">(</span>zoom100<span class="hl sym">)</span> <span class="hl kwa">def</span> <span class= "hl kwd">zoomfit</span> <span class= "hl sym">(</span>self<span class= "hl sym">, *</span>ignore<span class="hl sym">):</span> winSize<span class="hl sym">=</span> self<span class= "hl sym">.</span><span class="hl kwd">size</span> <span class= "hl sym">()</span> imgSize<span class="hl sym">=</span> self<span class= "hl sym">.</span>img<span class="hl sym">.</span><span class= "hl kwd">size</span> <span class="hl sym">()</span> <span class="hl kwa">print</span> winSize<span class= "hl sym">,</span> imgSize hZoom<span class="hl sym">=</span> <span class= "hl num">1.0</span><span class="hl sym">*</span>winSize<span class= "hl sym">.</span><span class="hl kwd">width</span> <span class= "hl sym">()/</span>imgSize<span class="hl sym">.</span><span class= "hl kwd">width</span> <span class="hl sym">()</span> vZoom<span class="hl sym">=</span> <span class= "hl num">1.0</span><span class="hl sym">*</span>winSize<span class= "hl sym">.</span><span class="hl kwd">height</span> <span class= "hl sym">()/</span>imgSize<span class="hl sym">.</span><span class= "hl kwd">height</span> <span class="hl sym">()</span> zoomLevel<span class="hl sym">=</span> <span class= "hl kwb">min</span> <span class="hl sym">(</span>hZoom<span class= "hl sym">,</span> vZoom<span class="hl sym">)</span> <span class="hl kwa">print</span> zoomLevel self<span class="hl sym">.</span><span class= "hl kwd">zoomTo</span> <span class= "hl sym">(</span>zoomLevel<span class="hl sym">)</span> <span class="hl kwa">def</span> <span class= "hl kwd">zoom25</span> <span class= "hl sym">(</span>self<span class="hl sym">, *</span>ignore<span class="hl sym">):</span> self<span class="hl sym">.</span><span class= "hl kwd">zoomTo</span> <span class="hl sym">(</span><span class= "hl num">0.25</span><span class="hl sym">)</span> <span class="hl kwa">def</span> <span class= "hl kwd">zoom50</span> <span class= "hl sym">(</span>self<span class="hl sym">, *</span>ignore<span class="hl sym">):</span> self<span class="hl sym">.</span><span class= "hl kwd">zoomTo</span> <span class="hl sym">(</span><span class= "hl num">0.5</span><span class="hl sym">)</span> <span class="hl kwa">def</span> <span class= "hl kwd">zoom75</span> <span class= "hl sym">(</span>self<span class="hl sym">, *</span>ignore<span class="hl sym">):</span> self<span class="hl sym">.</span><span class= "hl kwd">zoomTo</span> <span class="hl sym">(</span><span class= "hl num">0.75</span><span class="hl sym">)</span> <span class="hl kwa">def</span> <span class= "hl kwd">zoom100</span> <span class= "hl sym">(</span>self<span class= "hl sym">, *</span>ignore<span class="hl sym">):</span> self<span class="hl sym">.</span><span class= "hl kwd">zoomTo</span> <span class="hl sym">(</span><span class= "hl num">1.0</span><span class="hl sym">)</span> <span class="hl kwa">def</span> <span class= "hl kwd">zoomTo</span> <span class= "hl sym">(</span>self<span class="hl sym">,</span> zoomLevel<span class="hl sym">):</span> scale<span class="hl sym">=</span> zoomLevel<span class= "hl sym">/</span>self<span class="hl sym">.</span>zoomLevel <span class="hl kwa">print</span> <span class= "hl str">"scaling"</span><span class="hl sym">,</span> scale self<span class="hl sym">.</span><span class= "hl kwd">scale</span> <span class= "hl sym">(</span>scale<span class="hl sym">,</span> scale<span class="hl sym">)</span> self<span class="hl sym">.</span>zoomLevel<span class= "hl sym">=</span> zoomLevel <span class="hl kwa">if</span> __name__<span class= "hl sym">==</span><span class= "hl str">'__main__'</span><span class="hl sym">:</span> <span class= "hl slc"># this code is enough for loading an image and show it!</span> app<span class="hl sym">=</span> <span class= "hl kwd">QApplication</span> <span class= "hl sym">(</span>sys<span class="hl sym">.</span>argv<span class= "hl sym">)</span> win<span class="hl sym">=</span> <span class= "hl kwd">QMainWindow</span> <span class="hl sym">()</span> pixmap<span class="hl sym">=</span> <span class= "hl kwd">QPixmap</span> <span class= "hl sym">(</span>sys<span class="hl sym">.</span>argv<span class= "hl sym">[</span><span class="hl num">1</span><span class= "hl sym">])</span> qgpi<span class="hl sym">=</span> <span class= "hl kwd">QGraphicsPixmapItem</span> <span class= "hl sym">(</span>pixmap<span class="hl sym">)</span> scene<span class="hl sym">=</span> <span class= "hl kwd">QGraphicsScene</span> <span class="hl sym">()</span> scene<span class="hl sym">.</span><span class= "hl kwd">addItem</span> <span class= "hl sym">(</span>qgpi<span class="hl sym">)</span> view<span class="hl sym">=</span> <span class= "hl kwd">OMITGraphicsView</span> <span class= "hl sym">(</span>pixmap<span class= "hl sym">,</span> scene<span class= "hl sym">,</span> win<span class="hl sym">)</span> view<span class="hl sym">.</span><span class= "hl kwd">setDragMode</span> <span class= "hl sym">(</span>QGraphicsView<span class= "hl sym">.</span>ScrollHandDrag<span class="hl sym">)</span> view<span class="hl sym">.</span><span class= "hl kwd">show</span><span class="hl sym">()</span> app<span class="hl sym">.</span><span class= "hl kwd">exec_</span> <span class="hl sym">()</span> <span class="hl slc"># up to here!</span> <span class="hl slc"># end</span> </pre> <p>Things to note:</p> <ul> <li>The code for loading, showing the image and pan support is only 13 lines of <code>Python</code> code, including 3 imports. The resulting app is also able to handle vector graphics, but of course I didn't exploit that, I just added a <code>QPixmap</code>/<code>QGraphicsPixmapItem</code> pair.</li> <li>Zooming is implemented via <code>QGraphicsView.scale()</code>, which is accumulative (scaling twice to 0.5 actually scales to 0.25 of the original size), so I have to keep the zoom level all the time. There should be a <code>zoom()</code> interface!</li> <li>The code for calculating the scale level is not very good: scaling between 75% and 50% or 25% produces scales of 0.666 and 0.333, which I think at the end of the day will accumulate a lot of error.</li> <li>For the same reason, <code>zoomToFit()</code> has to do some magic. I also got hit by the integer division of <code>Python</code> (I was getting zoom factors of 0) so I had to add <code>1.0*</code> to the claculations. It's good that this is fixed in <code>Python2.6/3.0</code>.</li> <li>The size reported by the <code>QMainWindow</code> was any vegetable (it said 640x480 when it actually was 960x600), so I used the <code>QGraphicsView</code> instead. WTF?</li> <li>For some strange reason <code>zoomToFit()</code> scales the image a little bigger than it should, so a scrollbar appears in the direction of the constraining dimension.</li> <li>Less that 100 lines! Even if <code>setupActions()</code> could surely be improved.</li> <li>In Marcelo's favor I should mention that he writes docstrings for most of his methods both in english and spanish (yes, of course I read his code after I finished mine). I barely put a couple of comments, but doing the same should add 10 more lines, tops. Also, I don't want to convert this into a who-has-it-smaller contest (the code, I mean :).</li> <li>It took me approx 3 hours, with no previous knowledge of how to do it and no internet connection, so no asking around. I just used the «Qt Reference Documentation», going to the «Gropued Classes» page and to the «Graphics View Classes» from there.</li> <li>It doesn't zoom with the mouse wheel either.</li> <li>The default colors of <code>ikiwiki</code>'s <code>format</code> plugin are at most sucky, but better than nothing.</li> </ul> <hr /> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../omit/">omit</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <span class="selflink">python</span></p> <hr /> <p>[1] Unluckly he didn't declared which license it has, so I'm not sure if I really can do this. I GPL'ed mine.</p> python-magic-filename-encoding http://grulicueva.homelinux.net/~mdione/glob//posts/python-magic-filename-encoding/ 2009-08-29T14:49:06Z 2009-08-29T14:49:06Z <p>There's a problem with <code>Phonon</code>. If you try to play a file in a format that it doesn't support, instead of failing or simply sending the <code>finished()</code> signal it just does nothing. I'll have to look more deeply into this and figure out if it's a bug or feature or how to work-around it.</p> <p>For now I just decided to use <code>python-magic</code> to decide if the file is playable or not. This module uses <code>libmagic</code>, the same library that the <code>file</code> utility uses. But I hit a problem with it. One of its ussages is to call <code>magic.file()</code> with the filename, but if the filename contains a non-ascii character you get something like this:</p> <pre> <code>Traceback (most recent call last): File "satyr.py", line 173, in next self.play () File "satyr.py", line 128, in play mimetype_enc= self.magic.file (self.filename) UnicodeEncodeError: 'ascii' codec can't encode character u'\xed' in position 37: ordinal not in range(128) </code> </pre> <p>I found a workaround. There's another usage, which is to call <code>magic.buffer()</code> with a piece of data to recognize. So I just simply <code>open()</code> the file, <code>read()</code> 4KiB and pass the data to that function.</p> <pre> <code>f= file (self.filename) data= f.read (4096) mimetype_enc= self.magic.buffer (data) </code> </pre> <p>Now it works properly. I will file a bug report to the <code>file</code> package once I resort out my mail setup (I cannot send bugs with <code>reportbug</code>).</p> <p><span class="selflink">python</span> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a></p> from-qstring-to-bytes-in-pyqt http://grulicueva.homelinux.net/~mdione/glob//posts/from-qstring-to-bytes-in-pyqt/ 2009-11-15T13:45:16Z 2009-08-17T18:16:29Z <p><code>Satyr</code> handles paths. There are some problems with paths and (<em>sigh</em>) encondings. Of those, here are two: there's no way to know in which encoding the filenames in a filesystem are enconded (f.i., there's no way to ask the filesystem), and even if that were possible, the filenames might not even be enconded in that enconding. In these (still!) transitioning times, lots and lots and shitloads of filesystems are used in UTF-8 environments, but some filenames are still in old ISO-8859-1 or whatever the system was using before.</p> <p>Then comes <code>QString</code>. I'm taking a path from the command line; this path is the location of the (right now only) <code>Collection</code> for the player. I'm handling the command line using <code>KCmdLineOptions</code>, which returns <code>QStrings</code>. As we all know, <code>QString</code>, just like the <code>unicode</code> type in <code>Python</code>, handles all the data internally as Unicode, which is The Right Thing™. If you really need the internal data, say as bytes, you can always call the <code>constData()</code> method and be happy with it[1]. This would be the case for paths; you <strong>need</strong> the bytes.</p> <p>Then comes <code>PyQt4</code>. For some reason, which maybe I will ask in <a href= "http://www.riverbankcomputing.co.uk/mailman/listinfo/pyqt">the pyqt devel ML</a>[2], <code>constData()</code> is not available. What to do? Well, that's what this post is about. What you're about to read is hacky as it can be, but then it works. I might feel dirty, but I can live with it. As long as I mark it as a utter/über hack and promise to revert it once that's possible...</p> <pre> <code># path is a QString qba= QByteArray () qba.append (path) path= str (qba) # now path is a list of bytes/string. </code> </pre> <p>Even if this part of the bug is fixed, then <code>Phonon.MediaSource</code> or <code>Phonon.MediaObject.play()</code> fails when feeded that same path with this message:</p> <pre> <code>ERROR: backend MediaObject reached ErrorState after 1 . It seems a KioMediaStream will not help here, trying anyway. </code> </pre> <p>or simply refusing to continue. Sure, in my case I should simply ignore the filename and inform the user what's going on, but sometimes you can't be so gentle.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <span class="selflink">python</span> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../phonon/">phonon</a></p> <hr /> <p>[1] blah.</p> <p>[2] but then it doesn't make much sense now since Phil wants to get rid of QString (for several reasons, which might most possibly include this one).</p> qobject-dbus-fixed http://grulicueva.homelinux.net/~mdione/glob//posts/qobject-dbus-fixed/ 2009-08-14T09:56:41Z 2009-08-14T09:56:41Z <p>I seem to have fixed the bug I mentioned in <a href= "http://grulicueva.homelinux.net/~mdione/glob/posts/qobject-dbus/">my last post</a>. This is what I had:</p> <pre> <code>class MetaPlayer (type (QObject), type (dbus.service.Object)): """Dummy metaclass that allows us to inherit from both QObject and d.s.Object""" pass class Player (QObject, dbus.service.Object): __metaclass__= MetaPlayer [...] </code> </pre> <p>Notice that <code>MetaPlayer</code> doesn't have a explicit <code>__init__()</code> method; one would spect that Python would take are of that. Here's the fixing code:</p> <pre> <code>MetaQObject= type (QObject) MetaObject= type (dbus.service.Object) class MetaPlayer (MetaQObject, MetaObject): """Dummy metaclass that allows us to inherit from both QObject and d.s.Object""" def __init__(cls, name, bases, dct): MetaObject.__init__ (cls, name, bases, dct) MetaQObject.__init__ (cls, name, bases, dct) </code> </pre> <p>I really don't understand why I have to be so explicit. Maybe it's because the metaclass for <code>dbus.service.Object</code>, <code>dbus.service.InterfaceType</code>, inherits from the <code>type</code> type[1]; this type is a new style class[2], but doesn't inherits from <code>object</code>. Thus, I think, the inherited <code>__init__()</code> methods are not called automatically.</p> <p>In any case, now I can mix <code>QObject</code> and <code>dbus.service.Object</code>, and it works fine. For instance, this call works:</p> <pre> <code>$ qdbus org.kde.satyr /player quit </code> </pre> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../dbus/">dbus</a> <span class="selflink">python</span> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a></p> <hr /> <p>[1] the <code>type</code> type is of type <code>type</code>! here:</p> <pre> <code>In [1]: type (type) Out[1]: &lt;type 'type'&gt; </code> </pre> <p>[2] its type is not <code>instance</code> but <code>type</code>, as mentioned above.</p> qobject-dbus http://grulicueva.homelinux.net/~mdione/glob//posts/qobject-dbus/ 2009-08-13T21:56:02Z 2009-08-13T21:56:02Z <p>In <a href= "http://grulicueva.homelinux.net/~mdione/glob/posts/pykde-dbus-server/"> my last post</a> I said «The next step is to make my <code>Player</code> class to export its methods via <code>DBus</code> and that's it!». Well, tell you what: is not that easy. If you try to inherit from <code>QObject</code> and <code>dbus.service.Object</code> you get this error:</p> <pre> <code>In [3]: class Klass (QtCore.QObject, dbus.service.Object): pass TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases </code> </pre> <p>This occurs when both ancestors have their own metaclasses. Unluckily <code>Python</code> doesn't resolve it for you. The answer is to create a intermediate metaclass which inherits from both metaclasses (which we can obtain with <code>type()</code>) and make it the metaclass of our class. In code:</p> <pre> <code>class MetaPlayer (type (QObject), type (dbus.service.Object)): """Dummy metaclass that allows us to inherit from both QObject and d.s.Object""" pass class Player (QObject, dbus.service.Object): __metaclass__= MetaPlayer [...] </code> </pre> <p>Is that it now? Can I go and do my code? Unfortunately no. See this:</p> <pre> <code>qdbus org.kde.satyr / /player Cannot introspect object /player at org.kde.satyr: org.freedesktop.DBus.Python.KeyError (Traceback (most recent call last): File "/usr/lib/pymodules/python2.5/dbus/service.py", line 702, in _message_cb retval = candidate_method(self, *args, **keywords) File "/usr/lib/pymodules/python2.5/dbus/service.py", line 759, in Introspect interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__] KeyError: '__main__.Player' ) </code> </pre> <p>This is the class <code>dbus.service.Object</code> complaining something else. It's getting late here and I'm tired, so I'll continue tomorrow.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../dbus/">dbus</a> <span class="selflink">python</span> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a></p> pykde-dbus-server http://grulicueva.homelinux.net/~mdione/glob//posts/pykde-dbus-server/ 2009-08-13T21:56:02Z 2009-08-11T00:29:31Z <p>Continuing with the development of <code>Satyr</code>, which doesn't have any GUI. I thought it would be faster to make a <code>DBus</code> interface that a good GUI, not to mention more interesting.</p> <p>The code snippet of today is this:</p> <pre> <code>class DBusServer (dbus.service.Object): def __init__ (self): bus= dbus.SessionBus () bus_name= dbus.service.BusName ('org.kde.satyr.dbus.test', bus=bus) dbus.service.Object.__init__ (self, bus_name, "/server") @dbus.service.method('org.kde.satyr.dbus.test', in_signature='', out_signature='') def bing (self): print "bing!" @dbus.service.method('org.kde.satyr.dbus.test', in_signature='', out_signature='') def quit (self): app.quit () dbus.mainloop.qt.DBusQtMainLoop (set_as_default=True) dbs= DBusServer () sys.exit (app.exec_ ()) </code> </pre> <p>This simply defines a class which registers itself with the session bus under the name <code>org.kde.satyr.dbus.test</code>, exporting itself under the path <code>/server</code> and then defining a method that goes <strong>bing!</strong> :) and another one that quits the app. Note the decorator for the methods.</p> <p>You might notice the <code>dbus.mainloop.qt.DBusQtMainLoop (set_as_default=True)</code> call. This is needed because both Qt and DBus in asyncronous mode (which is the one we're using and the only one that works under Qt or Gtk, AFAIK) both have their own event loops, and that makes some kind of magic that let both loops coexist without blocking the other. This must be called before connecting to the bus; otherwise, you get this error:</p> <pre> <code>RuntimeError: To make asynchronous calls, receive signals or export objects, D-Bus connections must be attached to a main loop by passing mainloop=... to the constructor or calling dbus.set_default_main_loop(...) </code> </pre> <p>So, let's test the beast. We run the script in one terminal and in the other:</p> <pre> <code>mdione@mustang:~/src/projects/satyr/live$ qdbus | grep satyr org.kde.satyr.py-10154 org.kde.satyr.dbus.test mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test / /server mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server method void org.kde.satyr.dbus.test.bing() method QString org.freedesktop.DBus.Introspectable.Introspect() mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server org.kde.satyr.dbus.test.bing mdione@mustang:~/src/projects/satyr/live$ qdbus org.kde.satyr.dbus.test /server org.kde.satyr.dbus.test.quit </code> </pre> <p>And in the other console:</p> <pre> <code>mdione@mustang:~/src/projects/satyr/live$ python dbus_test.py bing! </code> </pre> <p>It goes bing!... and then finishes. The next step is to make my <code>Player</code> class to export its methods via <code>DBus</code> and that's it!. More info in the <a href= "http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html">Python DBus tutorial</a>.</p> <p><a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../dbus/">dbus</a> <span class="selflink">python</span> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../pykde/">pykde</a> <a href="http://grulicueva.homelinux.net/~mdione/glob//tags/python/../satyr/">satyr</a></p>