Wicd .deb packages

Hello world,

it's been a long time since I last posted something. Today, I want to let the world know about the availability of Debian packages for *`wicd <https://launchpad.net/wicd>`__*.

Since the release cycle of wicd is somewhat slow, I decided to provide automatically-built Debian packages for the latest revision of the upstream repository.

The automatic build process is possible thanks to Jenkins and the great jenkins-debian-glue, by Michael Prokop.

Here's the APT snippet, for those who dare:

# For the "upstream" version of wicd
deb http://jupiter.hanskalabs.net/debian/ wicd main

The repository is signed with a separate GnuPG key, which I signed with my main keys. You can download it or retrieve it from a keyserver, then you should add it to your APT keyring:

$ gpg --keyserver pgp.mit.edu --recv-keys 4D00F190
# gpg --export 4D00F190 | apt-key add -

Obviously these builds are not guaranteed to work :) but, since I myself use these autobuilt packages, I usually try to fix problems as soon as I notice them.

If you find bugs, or have proposals to improve wicd, please report them on launchpad!

Mappa dei bus urbani di Mazara!

Oggi, giusto per sfizio, ho creato una mappa dei trasporti su bus di Mazara.

/data/transit-map-mazara/transit.png

Spero piaccia e sia utile! :)

Editable QComboBox with QAbstractTableModel

Dear Lazyweb,

I'm working on a personal PyQt4 project for my parents' office, and I need to autocomplete an editable QComboBox.

I can't use a standard Qt model (I need to store python objects in it), so I subclassed QAbstractTableModel.

Here it is, it's a fairly generic subclass of QAbstractTableModel, with variable number of columns:

class QGenericTableModel(QAbstractTableModel):
    def __init__ (self, columns, parent=None, *args):
        super(QGenericTableModel, self).__init__(parent, *args)
        self.columns = columns
        self.headers = {}
        self.table = []

    def rowCount(self, parent=QModelIndex()):
        return len(self.table)

    def columnCount(self, parent):
        return self.columns

    def row(self, row):
        return self.table[row]

    def setData(self, index, value, role):
        if index.isValid() and role == Qt.EditRole:
            row = index.row()

            t = self.table[row]
            if index.column() >= self.columns:
                return False
            if isinstance(value, QVariant):
                t[index.column()] = value.toString().simplified()
            else:
                t[index.column()] = value
            self.emit(SIGNAL('dataChanged'), index, index)
            return True
        return False

    def data(self, index, role = Qt.DisplayRole):
        if not index.isValid():
            return QVariant()

        if (index.row() >= len(self.table)) or (index.row() < 0):
            return QVariant()

        if role == Qt.DisplayRole:
            return QVariant(self.table[index.row()][index.column()])

        return QVariant()

    def insertRow(self, row, parent=QModelIndex()):
        self.insertRows(row, 1, parent)

    def insertRows(self, row, count, parent=QModelIndex()):
        self.beginInsertRows(parent, row, row+count-1)
        for i in xrange(count):
            self.table.insert(row, ['',]*self.columns)
        self.endInsertRows()
        return True

    def removeRow(self, row, parent=QModelIndex()):
        self.removeRows(row, 1, parent)

    def removeRows(self, row, count, parent=QModelIndex()):
        self.beginRemoveRows(parent, row, row+count-1)
        for i in reversed(xrange(count)):
            self.table.pop(row+i)
        self.endRemoveRows()
        return True

    def flags(self, index):
        if not index.isValid():
            return Qt.ItemIsEnabled

        return super(QGenericTableModel, self).flags(index) | Qt.ItemIsEditable

    def headerData(self, column, orientation, role = Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()

        if orientation == Qt.Horizontal:
            if column >= self.columns:
                return QVariant()
            return self.headers[column]

        return QVariant()

    def setHeaderData(self, column, orientation, value, role = Qt.EditRole):
        self.headers[column] = value

It might not be perfect, but it works, and it's all that matters at the moment (since it's my first serious PyQt4 project). Oh, well, it worked until I tried to use it with an editable QComboBox.

And here's the app code (for the full working code, insert the above class right after the imports):

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *

app = QApplication(sys.argv)

model = QStandardItemModel()
for i, word in enumerate(['saluton', 'gxis la revido', 'hello', 'goodbye']):
    item = QStandardItem(word)
    model.setItem(i, 0, item)

#model = QGenericTableModel(1)
#for i, word in enumerate(['saluton', 'gxis la revido', 'hello', 'goodbye']):
#    model.insertRow(i)
#    index = model.index(i, 0)
#    model.setData(index, word, Qt.EditRole)

combo = QComboBox()
filterModel = QSortFilterProxyModel(combo)
completer = QCompleter(combo)

filterModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
filterModel.setSourceModel(model)
filterModel.setFilterKeyColumn(0)

completer.setModel(filterModel)
completer.setCompletionColumn(0)
completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)

combo.setEditable(True)
combo.setCompleter(completer)
combo.setModel(model)
combo.setModelColumn(0)

if combo.isEditable():
    app.connect(combo.lineEdit(), SIGNAL('textEdited(QString)'), filterModel.setFilterFixedString)

combo.setModel(model)
combo.setModelColumn(0)

combo.show()

sys.exit(app.exec_())

Now, try running it. It works well when I use a QStandardItemModel.

Then, try decommenting the part where I use my subclassed model: it just doesn't work: clicking on any item makes the QComboBox not change its currentIndex, and this only happens if the combobox is editable.

I suspect I'm forgetting to override some function in my model, but I don't know what exactly to override, and Google didn't help me. This is also backed up by the fact that the exact same behaviour shows up when, instead of a QComboBox, I try to autocomplete on a QLineEdit.

So, dear readers: can anyone explain what is happening? Thanks in advance!

UPDATE: it turned out that the culprit was the following bit inside data():

if role == Qt.DisplayRole:
    return QVariant(self.table[index.row()][index.column()])

Adding also Qt.EditRole to the list of possible choices fixed the bug. YAY!

Getting network devices with Python and udev

*UPDATE* I wrote a new article based on Ben Hutching's response.

Just a quick post, mainly as a reminder for when I'll try to implement this in WICD:

#!/usr/bin/python

import gudev

client = gudev.Client(['rfkill', 'net'])
for dev in client.query_by_subsystem('net'):
    if dev.get_sysfs_attr_as_int("type") != 1:
        continue

    driver = dev.get_driver()
    if not driver:
        parent = dev.get_parent()
        if parent:
            driver = parent.get_driver()

     print type, dev.get_name(), driver, dev.get_sysfs_path()

This will print all network devices with ethernet-encapsulated packets (that's what sysfs type "1" is). Here's the output on my system:

eth0 e1000e /sys/devices/pci0000:00/0000:00:19.0/net/eth0
wlan0 b43 /sys/devices/pci0000:00/0000:00:1c.1/0000:10:00.0/ssb0:0/net/wlan0

I'm still missing how to reliably detect if a device is a "wired" or a "wireless" one. I suspect that checking the existence of /phy80211 would be enough, but I can't really tell, and seems like I'm not able to find an exhaustive sysfs reference manual.

Magento: Italian provinces

Ok, so you have Magento, and you're located in Italy. And you'd like your customers to select a province from a dropdown select box.

Unfortunately, Magento doesn't include Italian provinces in their default database: that's why I wrote a simple script to take care of that.

The script is in Python, and uses SQLAlchemy. You'll also need to download the list of provinces. You should run it like this:

$ ./load_provinces.py provinces

Be sure to run it only *once*, otherwise you'll get duplicated provinces in the database.

There is some commented code you should decomment if you want to test the script before actually touching the Magento database.

Also, you need to change the "user", "psw" and "dbname" parameters, and might need to change the "host" and "port" too.

Now, I need to understand how to require address input at the registration page. ARGH.

How to: database-less local map rendering with Mapnik and OSM

Here's a quick howto for local map rendering, using python, mapnik and data from OpenStreetMap.

This tutorial shows how to create a stylesheet for mapnik from scratch, if you're looking for how to tweak OpenStreetMap's mapnik rendering, I plan to write a tutorial on PostGIS-driven rendering at a later time.

*This post is not for those who want to make a local slippy map, nor those who want to render different areas of big dumps (think of big national dumps).*

We'll render some features of a residential area of a city with buildings mapped. We'll get a small dump of the area for this, using the XAPI.

First, you need to install mapnik. On Debian-like systems, it should be as easy as:

# apt-get install python-mapnik

Then, you need some python code to use the mapnik library. Let's call it render.py:

#!/usr/bin/python
import mapnik

# Configuration
style = 'style.xml'
output = 'output.svg'
width = 1280
height = 800
bbox = '12.58664,37.65759,12.5945,37.66325'

#Don't touch below!
bbox = bbox.split(',')
ll = mapnik.Coord(float(bbox[0]), float(bbox[1]))
tr = mapnik.Coord(float(bbox[2]), float(bbox[3]))

mymap = mapnik.Map(width, height)
mapnik.load_map(mymap, style)

map_bbox = mapnik.Envelope(ll, tr)
mymap.zoom_to_box(map_bbox)
mapnik.render_to_file(mymap, output)

For the purposes of this post, we're going to create a 1280x800 map -- adjust that to suit your needs -- and we specified a bounding box corresponding to that area. That's composed as west,south,east,north coordinates. Be sure to get them right :-).

Now we need to write a Mapnik stylesheet, we'll call it style.xml. That's a XML document, you can find the documentation at the official website. The skeleton of a stylesheet is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Map>
<Map bgcolor="lightblue" srs="+proj=latlong +datum=WGS84">
    <Style name="...">
    ...
    </Style>
    <Layer name="...">
        <Datasource>
        ...
        </Datasource>
    </Layer>
</Map>

The stylesheet has an arbitrary number of `Style <http://trac.mapnik.org/wiki/XMLConfigReference#Style>`__ elements and an arbitrary number of `Layer <http://trac.mapnik.org/wiki/XMLConfigReference#Layer>`__ elements. The former class of elements define the appearance of the features, while the latter defines where to get data from. Each layer has a `Datasource <http://trac.mapnik.org/wiki/XMLConfigReference#Datasource>`__ child: here's where you say where to get your data from. We're going to use the osm input plugin, which lets you parse the dump directly. This has some drawbacks:

  • not scalable for big dumps
  • for each bbox you want to render, the full dump has to be re-parsed

But works great for small dumps.

In this tutorial, we want to pretty render a residential area, so we first need to render the road network. This will mainly be made of highway=residential roads.

Each style can have one or more Rule element. These define what elements that particular style should apply to. Each rule can have different rendering settings.

The given dump has different roads: residential, unclassified, tertiary and secondary. Let's add a rule for each. Here's the first of them:

<Rule>
    <Filter>[highway] = 'residential'</Filter>
    <LineSymbolizer>
        <CssParameter name="stroke">#ffffff</CssParameter>
        <CssParameter name="stroke-width">5</CssParameter>
        <CssParameter name="stroke-linejoin">round</CssParameter>
        <CssParameter name="stroke-linecap">round</CssParameter>
    </LineSymbolizer>
    <TextSymbolizer name="name" face_name="DejaVu Sans Book" size="8" fill="#000" halo_radius="1" spacing="300" placement="line" />
</Rule>

It is quite straightforward: you define what elements the rule should apply to, and then add some symbolizer. For a detailed description of each symbolizer, please read the documentation.

The example dump also has a railway and some steps. To render them, we use the stroke-dasharray property:

<Rule>
    <Filter>[highway] = 'steps'</Filter>
    <LineSymbolizer>
        <CssParameter name="stroke">#ff0000</CssParameter>
        <CssParameter name="stroke-width">5.0</CssParameter>
        <CssParameter name="stroke-dasharray">2,1</CssParameter>
    </LineSymbolizer>
</Rule>
<Rule>
    <Filter>[railway] = 'rail'</Filter>
    <LineSymbolizer>
        <CssParameter name="stroke">#222222</CssParameter>
        <CssParameter name="stroke-width">3</CssParameter>
        <CssParameter name="stroke-linejoin">round</CssParameter>
    </LineSymbolizer>
    <LineSymbolizer>
        <CssParameter name="stroke">white</CssParameter>
        <CssParameter name="stroke-width">1</CssParameter>
        <CssParameter name="stroke-linejoin">round</CssParameter>
        <CssParameter name="stroke-dasharray">10,20</CssParameter>
    </LineSymbolizer>
</Rule>

Also here, it is quite simple. The values given to stroke-dasharray determine the size of the dashes, and they can be alternated. So, for the steps we have 2px of #ff0000 and 1px of trasparency, while for the rail we have 10px of white and 20px of #222222.

What if we want to add some markers to the map? Easy, just use another symbolizer, called `PointSymbolizer <http://trac.mapnik.org/wiki/PointSymbolizer>`__:

<Rule>
    <Filter>[highway] = 'stop'</Filter>
    <PointSymbolizer width='10' height='22' file='stop.png' type='png' allow_overlap='true' opacity='1.0' />
</Rule>

Here we mark stop signals with a pre-rendered stop.png (only png and tiff are currently supported, and svg support is only in the development version).

What to do next? Well, we can make this map really nice. We'll render buildings with a pseudo-3D effect, which, however, does not reflect real heights -- but it's rather nice to see. How to achieve this? BuildingSymbolizer is the answer.

<Rule>
    <Filter>[building] &amp;lt;&amp;gt; ''</Filter>
    <BuildingSymbolizer>
        <CssParameter name="fill">#00ff00</CssParameter>
        <CssParameter name="fill-opacity">0.4</CssParameter>
        <CssParameter name="height">0.00007</CssParameter>
    </BuildingSymbolizer>
</Rule>

Here we just draw building=*. Regarding the height, you need to play a bit -- it took a while before I found a good value of it.

One thing missing in this stylesheet, is the rendering of coastlines. Since we don't have a coastline here, I'm skipping it, but the easy way is: use another rule. This is really just a trick; OpenStreetMap renders the coastline in a different way respect to other features, that's why I'm using this trick.

I'm not posting the whole stylesheet, but you can download it. You can also get the script, the OSM dump and the stop.png image.

Now, you should be able to just do:

$ ./render.py

and you will have your map in output.svg :-)

Here's what you would get if you use the files provided above (click to see the original SVG version):

/data/dbless-local-mapnik-osm/output.png

Low-memory madness

When your console looks like:

# free -m -bash: fork: Cannot allocate memory

Something's definitely wrong.

Bike-sharing a Mazara

*Sottotitolo: perché non funzionerà*.

Il Comune di Mazara del Vallo ha acquistato 90 biciclette e 9 ciclo-posteggi ("rastrelliere''), da affidare in gestione a 9 aziende operanti sul territorio mazarese. Fin qui, tutto bene.

Il problema nasce nel momento in cui il Comune detta le regole del servizio. Ogni azienda avrà in concessione 10 biciclette ed 1 ciclo-posteggio. Ogni bicicletta viene concessa al costo di 3,00€ al mese (il ciclo-posteggio è in comodato d'uso gratuito); e potranno essere noleggiate ad un prezzo massimo di 0,30€/ora.

Facciamo due conti.

Per ammortizzare la spesa della concessione (30€/mese), quindi senza alcun guadagno, ci vorrebbero 100 ore di noleggio mensile. Che vuol dire 3 ore circa al giorno. Con questo però ci paghi solo la concessione.

Supponiamo allora che noleggi per un'ora al giorno tutte e 10 le biciclette: sono 3€ giornalieri, 90€/mese. Ci paghi la concessione, e con i restanti 60€ ci fai ben poco; non ci puoi campare, né pagarci le bollette, né pagarci qualcuno.

Giochiamo un po' con i numeri allora, e vediamo il guadagno netto mensile per ore di noleggio giornaliere:

Ore

Guadagno €

20

150

40

330

60

510

Solo con 60 ore giornaliere di noleggio (6 ore per ognuna delle 10 biciclette!) si arriva ad un guadagno ragionevole, che però basta a malapena per pagarci le bollette e l'eventuale affitto.

E se ad attuare questo servizio fosse un'azienda già costituita, e ci fosse bisogno di una persona appositamente preposta? Non la si può pagare meno di 500€/mese. Quindi i numeri di cui sopra sarebbero destinati a salire.

Chiedendomi se i miei fossero calcoli sbagliati, mi sono guardato in giro, e...

  • ATAC Roma fa pagare 0,50€ ogni mezz'ora per il servizio di bike-sharing;
  • A Milano si paga un abbonamento giornaliero di 2,50€, con i primi 30 minuti gratuiti, dopo 0,50€ ogni 30 minuti (o frazione) fino a 2 ore, superate le 2 ore, 2€ ogni ora o frazione (ci sono altri abbonamenti che alla lunga risultano più economici, settimanale e annuale, ma non c'interessano).

Questi sono prezzi credo abbordabili per il cittadino (soprattutto quelli di Roma!), e sostenibili per un'azienda interessata. Con i prezzi proposti dal Comune, invece, bisognerebbe avere un flusso altissimo e continuo di turisti, e un'alta percentuale di questi noleggiare le bici. Cosa che, purtroppo, Mazara non ha al momento.

È sicuramente un'iniziativa lodevole, che però, mi spiace dire, è stata progettata malissimo.

Bash-Completion 1.3 released

After almost 8 months from the previous release, the Bash Completion Team is proud to announce the release of bash-completion 1.3!

Nothing really innovative in this release. Just "boring" new completions, and bugfixes :) For some stats, this release features 184 completions, the previous (1.2) had 168. For more numbers, please read previous posts. New completions are obviously welcome!

Apart from the usual team members (Ville Skyttä, Freddy Vulto, Guillaume Rousse and me), we've had contributions from (in no particular order): Anton Khirnov, Paul Walmsley, Miklos Vajna, Andrej Gelenberg, Stephen Gildea and Andrey G. Grozin. Thank you people for helping us!

The exciting things will arrive with 2.0. So stay tuned, and enjoy the new bash-completion!

-- David

Welcome Michael Gilbert!

I just approved his request for joining the DKMS team. Welcome on board!

Contents © 2013 David Paleino - Powered by Nikola