Kommander consists of two main parts: the Executor and the Editor. The Editor provides a development environment that makes creating graphical user interfaces easy. Visual programming is the paradigm and Kommander makes it possible to create applications using the mouse more than the keyboard. You can drag widgets such as push buttons, labels, and checkboxes from tool palettes and place them in the edited dialog box. You can establish connections between objects with the mouse using a mechanism known as signals and slots, so in many cases there is very little need to write actual code. Kommander provides only a few simple language constructs by itself and resorts to using other scripting languages to perform much of the real work. This means among other things that you can equip existing shell scripts with graphical user interfaces easily using Kommander.
Kommander is a relatively new tool and under intense development, so features are added and bugs get fixed often. If you want to try it, download the latest version, since it may be well ahead of the version shipped even with the latest KDE release. If you want to use the version that came with your KDE distribution, you will have to install the kdewebdev package, since that's where Kommander can be found. This article was written for Kommander 1.1development1 and newer. Thus, it will not work with the version that comes with KDE 3.3.2 (some parts might work if you skip others). If you have problems with running the newest Kommander development version (there were many reports of people being unable to get 1.1develoment2 to work), try an older one. Version 1.1development1 is guaranteed to work with this tutorial. All releases, old and new, can be downloaded from this page.
At this time documentation for the project is somewhat sparse, although that is to change soon. There are examples located in the kommander/examples/ subdirectory of the source tree, and kommander/working/ subdirectory contains notes on adding internationalization support to your Kommander apps. A number of useful utilities for Kommander as well as programs written using Kommander can be found on kde-apps.org. There is also a mailing list for Kommander users, where you can ask questions and report bugs.
|Click to enlarge|
In this tutorial, we'll create a simple application for changing your KDE wallpaper. We'll build the application step by step, to introduce basic concepts of Kommander first and gradually move to more advanced ones. We will also show how Kommander uses DCOP to communicate and integrate with other KDE applications.
Let's begin creating our application now. Start Kommander Editor, located in the Development section of your K menu (or run
kmdr-editor from the command line if you can't find the program in your K menu). From the File menu, choose New..., select the Dialog icon, and click OK. A new empty window (referred to as a form in Kommander) will be created in the workspace. Note that in some older versions of Kommander, if you choose anything other than Dialog, you will be unable to edit some of the window's properties.
In the toolbar, there are icons with widgets you can add to your dialog. Click on the Exec Button icon, then click anywhere in the form to add a button. After you select the button by clicking on it, you can resize it and move it around in the window. You can also resize the dialog box.
In your workspace there is a window called the Property editor that allows you to change properties of your widgets. Click on any empty place in the form to select the main window of your app, and in the property editor find the property named "caption". Edit it so it reads "Set wallpaper"; this text will appear as the dialog box's caption. In a similar manner, you can select the button you added to the form and change its "text" property to "&View". The ampersand means that the next letter is to be underlined and used as a keyboard shortcut, so that pressing Alt+V in the dialog box will produce the same result as clicking the button. You can also simply double-click the button to edit its title. Now add another widget, namely a Close Button, and set that button's text to "&Cancel".
One very important property that each widget has is the "name" property, which is the name used to reference a widget in the program. Give all widgets descriptive names, so they can be easily distinguished, except perhaps for labels whose text is never changed, since there's no need to reference such static objects in program code. Set the two buttons' names to viewButton and cancelButton, respectively.
Now it's time to make the buttons perform some actions when clicked. Both the Exec Button and Close Button objects execute a script when clicked; a Close Button also closes the dialog box afterwards. Thus, we don't need to do anything more to make the Cancel button behave as expected. To edit code for the View button, right-click it and select "Edit Kommander Text" from the context menu.
|Click to enlarge|
You now should see a dialog box for editing code. Use the combo box in the upper left corner for selecting the widget whose code you want to edit. In the upper right corner is a combo box labeled "Text for:", which lists so-called slots for the selected widget. We'll explain how slots and signals work later on; for now it should suffice that code associated with the default slot is the script executed when the button is clicked. The bottom part of the dialog box displays controls for inserting function calls. We'll make use of them later in the course of this tutorial.
You use the central part of the dialog box to edit code. As we mentioned before, Kommander can use any scripting language for adding actions to its widgets, but the default scripting language is sh or bash on Linux systems. Thus, the code you enter in the code window can be anything you would normally put in a shell script.
First, let's just try to execute a program. In the code editor for viewButton's default slot, enter
Close the code editor and run the Kommander script by selecting "Run dialog" from the Run menu or by using the Ctrl-R keyboard shortcut. You will be asked to save the script if you haven't done so yet. Your dialog should now appear and you can check that clicking the Cancel button actually closes the dialog box, while clicking View executes kuickshow. The View button will be disabled until kuickshow exits; this behavior can be changed by modifying the button's blockGUI property.
You can download the complete script to this point here.
Getting data from widgets
Now it's time to add a few new widgets and to create some interactions between them. Have a look at the screenshot of the completed application to roughly position your controls, but do not spend too much time tweaking their positions; once all widgets are there, we will use layouts to automatically position widgets in the optimal way.
Now add a File Selector widget and a text label to describe it. The file selector consists of a text field and a button that displays an "Open File" dialog box when clicked. We will use it for choosing the desktop's background image. Give the File Selector the name fileSelector. Change its selectionCaption property to something like "Choose background image" (this text will be displayed in the file dialog's title bar) and selectionFilter to "*.jpg *.png|Images (*.jpg *.png)" (this is the pattern for file names which are to be shown by default in the file dialog, separated from its textual description with a pipe character). As for the label, set its text to "Image file:" and resize it as necessary to display the whole text.
We have several widgets now, so now let's make them exchange some data. The first thing we'll do is to make the View button open the selected file using kuickshow. Open the Kommander Text editor for the viewButton and change the script to:
You can now run the dialog and check that once you open a file using the file selector, clicking on the View button opens that very file in kuickshow. What exactly happens here? The at-sign (@) is used to mark Kommander's special functions. When a script is executed, Kommander first parses it and executes all functions starting with @. Many functions return a value, which is then substituted for the original @function text. The resulting processed text is then executed as a regular shell script.
The fact that executing the script actually happens in two steps is not important now, but at some point you'll see that it becomes significant, with conditional statements and in some other more complicated situations.
In our particular example, we call the function @fileSelector.text, whose return value is the text entered in the fileSelector widget. Suppose you select the file background.jpg. When you click the View button, Kommander replaces this function call with its return value and the resulting shell script:
is executed. The quotes are there so that sh can avoid confusion if the filename contains spaces.
|Click to enlarge|
Kommander offers many functions to choose from for each widget, but you don't have to remember them all. The lower part of Kommander Text editor's window offers a lot of help here. When you click the Function... button, you open a new dialog box where you can browse the available functions. These are divided into several groups, which you can select using the Group combo box.
DCOP is the group that contains functions used for communication between your program's different widgets. After choosing a widget using the Widget combo box, you will see all the functions that you can call for that widget. Select a function and a brief description will appear to the right. If there are any required parameters, you can enter them into appropriate fields and press the button with an arrow pointing down in order for the corresponding function call to be inserted into the edit box at the bottom. On closing the dialog box with OK, Kommander will insert this code into your program. Other groups are: Array, used for array functions (we'll see an example of using them later); File, for file operations (these are more often performed with shell commands, though); String, for dealing with character strings; and Kommander, for generic Kommander language constructs such as loops and functions that don't fit into any other category.
Later in this tutorial we'll have complete code fragments for you to copy into the example program. In practice, when developing your own applications, you would probably want to use the function browser instead to find the necessary functions easily.
The complete script at this stage can be downloaded here.
Signals and slots
Now we will employ the mechanism known as "Signals and Slots" to add even more interaction between elements of the GUI. First, add the elements that are going to be involved. These are: a text label called "information" with the text "Information: " and a pixmap label called "preview". We are going to make these two elements display a short textual information block about the current image and a graphical preview of it. Again, position these elements only roughly in the window. Set the pixmap label's scaledContents property to "true" in order for it to scale the image it will display to the widget's size. In order to be able to see where the widget is, even if there is no image displayed inside, you can change frameShape to groupBoxPanel (or any other value you find visually appealing).
Signals and slots are an effective mechanism for widgets to talk to each other. A widget emits a signal when it wants other elements of the GUI to know that some event took place. By default, most signals are ignored, but we can tell widgets to listen for a particular signal and perform an action when it is emitted. This action is called a slot and the process described above is known as connecting a signal to a slot.
Some signals don't carry any information about the event that caused them to be emitted other than that an event of this particular type took place. Others contain additional data, which can be passed to the slot as parameters (a slot is really a special kind of function). Since the signal's parameters don't have names, a signal with some set of parameters can only be connected to a slot which either takes exactly the same number of parameters of the same types in the same order, or to one which has less parameters, but whose parameters are of the same type in the same order as the beginning parameters of the signal.
For example, a button emits the clicked() signal when it is clicked. As you see, the parameter list in parentheses is empty, so this signal can only be connected to a slot without any parameters. We could, for example, connect it to the pixmap's clear() slot. This would cause our button to clear the contents of the pixmap when clicked.
In contrast, a checkbox emits the toggled(bool) signal when its check state changes. This signal has one parameter of type bool, which can take one of two values: true or false. Since it has one parameter, we can connect it to a slot with 0 parameters and also to a slot with one parameter of the same type. A useful example would be connecting it to some widget's setEnabled(bool) slot. This way that widget would only be enabled if the checkbox were on and disabled if it were off. To get the opposite behavior, we could use the setDisabled(bool) slot.
As we'll see, Kommander eases the task of making signal-slot connections so you don't have to know what signals and slots each particular widget offers. Widgets usually have many slots associated with them and Kommander offers you a list of what's available and compatible with the signal you choose. Some of these are built-in and always execute predefined actions, while others can be user-defined and execute user code that is entered using the Kommander Text editor. Note that you can have many signals connected to the same slot and also one signal connected to many slots. This allows many actions to be taken when a single event triggers them (however, note that the order in which these multiple actions are executed is random). The beauty of signals and slots is that usually we can achieve quite a lot by using only the predefined slots -- thus adding functionality to our dialog box without writing a single line of code.
|Click to enlarge|
To create connections between signals and slots, we use the Connector tool. First we will make the preview pixmap label display a preview of the image whose name is entered in the file selector. After clicking on the Connector icon, press the mouse button with the cursor over the file selector widget and drag the mouse over to the pixmap label. You should see frames around the two widgets and a line connecting them. Let the mouse button go and a dialog box appears. On the left is a list of signals the file selector widget emits, and on the right, a list of slots of the pixmap label that can be connected to the signal selected in the left pane. Notice how the list on the right changes to show only slots with compatible parameter lists when you select different signals on the left.
To achieve the desired result, select the widgetTextChanged(const QString &) signal and then click on the setWidgetText(const QString &) slot. A new connection will appear in the connection list at the bottom. Click OK to close the window. If you ever need to remove a connection or otherwise modify it, you can find an option to open the editor in each widget's context menu.
Now, connect the file selector's widgetTextChanged(const QString &) signal to one more slot, namely the information text label's populate() slot. This is a slot that all widgets have and whose meaning you can define. As the name implies, it is usually used to populate a widget -- that is, to fill it with data.
Now, to change the widget's action taken when the population slot is executed, fire up the Kommander Text editor for our information text label and from the combo box labeled "Text for:" choose population. The code you enter here is a little different from what you entered for the View button's default slot, since it is treated as the text to be displayed in the widget rather than a shell script. So, in order to make the label display the text "Hello, world!" when the population slot is called, you would just enter
in the code edit box. If you were populating a list box or a combo box, you would just enter all the different items, one per line. However, we want something more than just a constant string of text. You can use the command line program
file to display some basic information about the data stored in a disk file. Enter this code for the population slot:
Information: @exec(file -b "@fileSelector.text")
As you probably guessed, this sets the label's text to the word Information followed by the result of calling
file on the file currently entered in the fileSelector when populate() is called. You can now close the code editor and test the program. It should display a preview of the image and some information about it. That's quite impressive, given that we have written exactly two lines of code so far.
The complete script at this stage can be downloaded here.
Now we'll make use of Kommander's array features to extend the GUI a little. Let's add a combo box to allow users to view the selected image not only with kuickshow but also with several other applications.
Add a combo box called viewingApplication and a text label to accompany it. Since this combo box is going to contain a static list of programs, we can simply edit it by right-clicking on it and selecting Edit... from the context menu, or by double-clicking on it. Add three program names: Kuickshow, KView, and The GIMP.
Kommander's arrays are so-called associative arrays -- they map text strings onto other strings. In our example, we will use an array to map item indexes in the combo box into program names as they need to be entered at the console (for example in order to run "The GIMP" we have to type "gimp" at the console). We use numbers as keys because it's convenient here, but keys could be arbitrary strings as well and need not be numeric.
Kommander's array function names start with @Array. First, we will add our mappings to an array called "programs". We want this array to be filled in just after our application starts, when the form's initialization slot is executed. To edit it, right-click any empty place on the form, select "Edit Kommander Text", and select the initialization slot. Enter the following code:
@Array.setValue(programs,0,kuickshow) @Array.setValue(programs,1,kview) @Array.setValue(programs,2,gimp)
Now our "programs" array maps "0" onto "kuickshow", and so on. The last change we must make in order for the application choice to work is to edit the View button's default slot again and replace the previous script with this one:
As you see, we replaced the constant "kuickshow" text with a call to Kommander's @viewingApplication.currentItem function, which returns the viewingApplication combo box's selected item number (counting from 0). @Array.value reads the mapping of that number (which is really treated as a string) from our programs array. Our application now uses the program selected in a combo box for viewing images.
The complete script at this stage can be downloaded here.
If programming in Kommander seems intriguing so far, come back Friday to learn how to make your program communicate with KDE.
See an updated version of this article at the author's Web site.