HOWTO: KConfigXT with PyKDE4

If you read around the KDE Techbase, or if you develop KDE applications, you may have heard about KDE’s KConfigXT. This is an extension of KDE’s KConfig, and can be used to generate nice configure dialogs with multiple pages with minimal effort, also taking care of saving and applying settings. In short, something really neat! But there are problems when using it with interpreted language bindings (such as PyKDE, which is the one I use):

  • KConfigXT requires an XML file and an INI-like file to be compiled by kconfig_compiler in order to produce C++ files
  • There is no such a tool (at least to my knowledge) that does the same job for bindings

So what to do? Either give up on the niceness of KConfigXT, or work around the issue. I chose the latter.

Bypassing the kconfig_compiler limitation

What kconfig_compiler does is basically generate the KConfigSkeleton sublcass needed for KConfigXT. So, we can create our own subclass by hand: a little more work is involved but nothing too hard. The following snippets, taken from a GPL application I’m working on, will demonstrate how to do it.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyKDE4.kdeui import *

#UI stuff - see later
from ui_generalpage import Ui_GeneralPage

class Preferences(KConfigSkeleton):

    """Class to handle preferences."""

    def __init__(self, *args):
        super(Preferences, self).__init__(self, *args)
        self.setCurrentGroup("General")
        self._danbooru_boards_list = QStringList()
        self._danbooru_boards = self.addItemStringList("danbooruUrls",
                                                       self._danbooru_boards_list,
                                                       QStringList())

Here we set up a KConfigSKeleton subclass. The first thing we set up after that is the config group we want to use: we can have several, for example “General”, “Services”, and so on. Of course an empty group makes nothing, so we populate it, in this case with a QStringList configuration item (see the API docs (KCoreConfigSkeleton, but KConfigSkeleton is a subclass of that) for more types you can use). You’ll notice that we assign an empty QStringList to a variable, which is then used in the addItemStringList call later. That’s because KConfigSkeleton (in C++) wants a pointer to the type of content, and since in Python we don’t have pointers, we pass a reference (and by binding it to our class instance, we make sure it won’t be garbage-collected).

The addItemStringList (and other addItem*) methods use three parameters: a string with the name of the option (remember this!), the reference to the item, and a default value (in this case an empty QStringList).. Once we’ve set the options, we call readConfig() in the initializer to make sure the values are read from the config (or default values created).

I set all the attributes as “hidden” to prevent direct manipulation. To access them, I set up properties (following the example of minirok, another PyKDE4 application).

    @property
    def boards_list(self):
        return self._danbooru_boards.value()

As you can see, the value of the configuration items is accessed via the value() function. Once tihs is done, we’re done with regards to the KConfigSkeleton part.

KConfigDialog

Of course now we want to create the config dialog. We do so by subclassing KConfigDialog. First, though, we must create the page contents. We do so in Qt Designer. We first create a widget, and then we place our configuration widgets on it. It is essential that the widget that will store our configuration details have the name kcfg_CONFIGOPTION, where CONFIGOPTION is the name you specified as a string in the KConfigSkeleton initializer. Once done, we save the ui file, compile with pykdeuic4, we set it in the imports of our preferences file and we’re good to go.

The following is an example of a general configuration page widget (the UI_* is the pykdeuic4 generated file):

class GeneralPage(QWidget, Ui_GeneralPage):

    def __init__(self, parent=None, preferences=None):
        super(GeneralPage, self).__init__(parent)
        self.setupUi(self)

        self.kcfg_danbooruUrls.insertStringList(preferences.boards_list)

As you can see, we pass the preferences instance in the initializer, so we can populate the widgets (which are indeed named kcfg_danbooruUrls, just like the config option I set earlier) . Of course you can connect signals and whatnot to slots in case you want to do something with your widgets. I didn’t have the need, so no need to.

And once we have this set up, we can finally create the KConfigDialog!

class PreferencesDialog(KConfigDialog):

    def __init__(self, parent=None, name=None, preferences=None):
        super(PreferencesDialog, self).__init__(parent, name, preferences)

        self.setButtons(KDialog.ButtonCode(KDialog.Ok |KDialog.Apply |
                                            KDialog.Cancel))

        self.general_page = GeneralPage(self, preferences)
        self.general_page_item = self.addPage(self.general_page, 'General')

In the initializer, we pass the preferences instance, the name (used later to determine if a page is already open, since KConfigDialog is non-modal), and of course, the parent widget. We then set the buttons (using bitwise ORs) we want to show. Finally, we instantiate our page widget and add it to the dialog, specifying a name that will appear on the side. Using the setIcon method you can also set an icon for your page. In case you want to perform checks and other things, reimplement slotButtonClicked in your subclass with (self, button) as parameters. But dont’ forget to call the original KConfigDialog.slotButtonClicked(button) in that case.

Wrapping it up: calling KConfigDialog

Finally, how to call your dialog? First of all, you need to instantiate your preferences object, for example in your main window application code:

self.preferences=preferences.Preferences()

Then, in your code, you do something like this:

if KConfigDialog.showDialog("Preferences dialog"):
    return
else:
    dialog = preferences.PreferencesDialog(self, "Preferences dialog",
                                           self.preferences)
    dialog.show()

The first if ensures that if there is already one dialog open, it won’t open another. If that’s not the case, we instantiate the dialog, passing it the name (which must be the same as the if above), the parent, and the preferences instance.

Voila’. In the end, you’ll get something like this (slightly different):

Image of the example KConfigDialog

I know my own UI sucks here, but it’s something I’m still experimenting…

For this tutorial, thanks go to Pino “pinotree” Toscano, who pointed me to the “minirok” project, which makes use of KConfigXT, and Adeodato Simò, the author of minirok.

3 thoughts on “HOWTO: KConfigXT with PyKDE4”

  1. “There is no such a tool (at least to my knowledge) that does the same job for bindings”

    There is actually a Ruby kconfig_compiler in kdebindings/ruby/korundum/tools/rbkconfig_compiler

    You might be able to adapt that to generate python. I think that would be easier than starting with the one that generates C++. I agree that the KConfigXT stuff is a bit hard to use with bindings though in the way it uses references to types – they are really hard to work with, and a nightmare to coordinate with garbage collection.

    I think this an area of the KDE api where it would have been a really good idea to involve language bindings guys at the start. In fact, as far as I can see using references to primitve types instead of just sticking to QVariant based get/set properties is more trouble than it is worth even in C++. The generated Ruby code uses the QVariant properties to change the values, but you still need a C++ reference to hold the actual data.

  2. @Will Stephenson: Would it be worth to get a SVN account just for this? I don’t mind committing it there, just asking.

    @Richard Dale: Thanks for the pointer, I’ll see if I can take at least a look.

Comments are closed.