Advanced Scripting: Events and Slots

In everything described in the earlier chapter on scripting, script code is always executed in response to an event occuring. This is true, even if the code is stored in a script module, because it must be invoked from the code associated with an event. This is fine in principal, but has a major shortcoming.

Suppose that we want to provide the user with a combo-box control that can be used to quickly find records. The control should contain one entry for each record that has been fetched from the server database, and each entry should be derived from some combination of values - for instance, in a form showing client information, the control might show name and department. Whenever the user updates a record, the control should be updated to match, for example when the user deletes a record, or changes a client's name. Also, as the user moves amongst the records, the control should change to show the correct entry for the current record.

This can be implemented using a KBChoice control, plus some script code associated with some of the events of the form block. Specifically, code needs to be associated with:

The problem is that, although the code is all logically associated with the control, most of it is actually written into the form block; and, even worse, it is split up between several block events. So, if you decide to remove the control, you have to go and clean up the code associated with the block events; also, copying and pasting the control into another form will not work at all, since the block event code is not copied. And, lastly, you may have other code associated with the block events, so there is a real opportunity for confusion.

The Event/Slot mechanism provides a way around these problems, and allows you to associate all the script code with the control; in the above example, using this mechanism, no code has to be installed into the block events. The basic mechanism is to provide a way of telling Rekall that code associated with one object should be executed when an event occurs in some other object ( If you are familiar with Qt's signal/slot mechanism, this will sound familiar; an event in Rekall is equivalent to a signal in Qt. Indeed, internally Rekall uses this as part of the implementation of its event/slot mechanism. But if you are not familiar, don't worry! ) ; so, for instance, the occurance of the block On Current event would trigger the execution of the approriate code in the selection control.

An object in Rekall can have one or more slots, each of which contains script code; and a slot can be connected to one or more events in some other object. When the event occurs, the slot's code is executed. The slot code is defined much as for an event, except that where the code in a event is defined in a function called eventFunc, for a slot the function should be called slotFunc. This function always takes the following arguments:

As an example, here is the slot function for a button which we want to disable whenever the user is executing a query. This slot would be connected to the On Action event of the block which contains the button. The first argument is the button itself, and the second the block. If this code is only connected to the On Action event then the third argument will always be onaction. The fourth argument gathers up all remaining arguments as a list ( This is a pythonism. You definitely need to use this if the slot is to be connected to two or more events that have a different number of arguments. ) ; the first item in the list is the specific action. By the way, this button may be the one that causes the query execution, that is, it might be the one that does block.doAction(RekallMain.actQuery) as part of its On Click event. By making the code below part of a slot and connecting it to the block's On Action event, it works however the query is started.

def slotFunc (button, block, event, *args) :
    if args[0] == RekallMain.actQuery :
        button.setEnabled (0)
        return
    if args[0] == RekallMain.actExecute :
        button.setEnabled (1)
        return
    if args[0] == RekallMain.actCancel :
        button.setEnabled (1)
        return
      

To access the slots of an object in a form or report (or in a component), open the properties dialog for the object in the normal way, then double-click the Slots property. This brings up a list of all slots associated with the object, from which you can add new slots, edit existing ones, and delete unwanted ones.

The earlier screenshot showed a slot immediately after the Edit button had been clicked, bringing up the slot dialog. At the top is the slot name; Rekall does not actually use this at present, and it can be anything you like. Since a slot can be connected to more than one event, the next control is a combobox which shows all current connections. Each connection has a name, again Rekall does not use it and it can be anything you like.

The next three controls are disabled unless a link is being edited and show, in order, the name given to the link, the object to which the connection is made, and the particular event. The next screenshot shows the same dialog, but after clicking the Edit button in that dialog to edit the blockAction link (the fact that the slot and the link have the same name is not significant).

The object to which the slot is connected is identified by a path in just the same way as used in the object.getNamedCtrl() script method, ie., it is the path from this object to the target object. In this example, the object path is .., which means the object's parent, which is the block that the button is embedded in. If we thought that the button might be embedded in a container, itself embedded in the block, then a more general path would be getBlock() ( Using getBlock() works even if the button is inside a container which is inside another container, which is in turn inside a block - or any such nesting. But note that getBlock() must be written exactly that way, is is not implemented as a function call, so if you (say) add some spaces, it won't work! ) .

A convenient way to set the object path and event controls is to use the helper button to the right. This shows a small dialog, illustrated below, which shows the structure of the form or report. The object whose slot you are editing is shown in red. You can then navigate to the object to which you wish to connect; the combobox at the bottom then shows the events for that object, and you can select the required one.