next up previous index
Next: Prolog as the Up: Event Handling Previous: Event Handling

Tk as the Client

    This approach is used for simple interfaces where the GUI is the main program and Prolog is used only as a slave which from time to time provides some data. In this case, Tcl/Tk is the master program (or client), it processes all events and from time to time it asks Prolog to run a particular query. Prolog acts as a server which processes Tk requests. During the Prolog query execution no Tk events are handled and the Prolog execution can be neither stopped, nor interrupted or aborted. Prolog cannot deliver one solution and then backtrack to provide the next one -- if more than one solution is needed, an all-solutions predicate must be used, e.g. findall/3   The Tk events are not explicitly polled. Primitives to be used:

Let us construct a simple example of this interface type. The GUI will call a Prolog predicate data/3. Its first argument will be specified using a radiobutton  , the second argument with a scale   and the third argument is the result which will be displayed in a label. The Prolog file contains the definition of data/3 and a predicate to start the interface:

:- lib(tk).

top :-
    tk([file('data.tcl')]).

data("a", I, X) :-
    X is exp(I)/2.3.
data("b", 0, 0).
data("b", I, X) :-
    X is ln(I) * 1.5.
data("c", I, X) :-
    I < 50,
    X is 1.5*I.
data("c", I, X) :-
    I >= 50,
    X is 2.5*I.

The Tcl file defines the widget and the procedure data_execute which invokes Prolog and displays the result:

# frame for the radiobuttons
frame .frb
# frame for the result
frame .fr
# frame for the buttons
frame .fb

foreach b {a b c} {
    radiobutton .frb.$b -text $b -variable rb -value $b
    pack .frb.$b -side left -anchor w
}

# Initial values for the radiobuttons
set rb a

scale .s -from 0 -to 100 -orient horizontal -length 200

label .fr.l -text "result: "
message .fr.m -background #fff -width 100
pack .fr.l .fr.m -side left

button .fb.e -text "Execute" -command {data_execute $rb .s .fr.m}
button .fb.q -text "Quit" -command exit
pack .fb.e .fb.q -side left -expand 1 -fill x

pack .frb .s .fr .fb -side top -expand 1 -fill x

# Query Prolog. b is the value of the radiobutton variable, sw the scale
# window and mw the reply message window
proc data_execute {b sw mw} {
    global var

    prolog "data $b [$sw get] X"
    $mw configure -text $var(X)
}
Note how the input values are passed to the button command data_execute: the radiobutton variable is passed inside a script, because it is a global variable and thus visible when the script is executed. The scale value is obtained directly from the scale widget.

After calling top/0, the interface appears and allows us to query a Prolog predicate by selecting values using the displayed widgets:

Another, more realistic example, might be a graphical interface to a Prolog database. The database contains facts about countries (from Warren's CHAT-80) program:

country(Country, Region, Latitude, Longitude, Area, Population, Capital, Currency)
We will construct a graphical interface which allows to query any combination of these attributes (we will omit Latitude and Longitude as they are not very intuitive anyway). The interface will consist of six entry   widgets, one for each considered argument, and a listbox   to display the resulting set. The listbox will have a scrollbar   for browsing inside. Here is the source of the file country.tcl which builds the widgets:
# Set up the whole display
proc make_display {} {
    set entries {country region area population capital currency}
    frame .b
    button .b.query -text "Query" -command "query .e [list $entries] .l.l"
    button .b.clear -text "Clear" -command "clear .e [list $entries] .l.l"
    button .b.quit -text "Quit" -command exit
    pack .b.query .b.clear .b.quit -side left -fill x -expand 1
    frame .e
    foreach e $entries {
        make_entry .e $e .b.query
        pack .e.$e -side left
    }
    frame .l
    listbox .l.l -width 80 -height 5 -selectmode single -yscroll ".l.scroll set"
    scrollbar .l.scroll -command ".l.l yview"
    pack .l.scroll -side right -fill y
    pack .l.l -side left -expand 1 -fill both
    pack .b .e .l -side top -expand 1 -fill x
}

# Create one entry widget; when Return is typed, start querying
proc make_entry {w label but} {
    frame $w.$label
    label $w.$label.l -text $label
    entry $w.$label.e -width 15
    bind $w.$label.e <Key-Return> [$but cget -command]
    pack $w.$label.l $w.$label.e -side top -expand 1 -fill x
}

# Query the Prolog database
proc query {w entries lb} {
    global var

    set list {}
    foreach e $entries {
        lappend list [$w.$e.e get]
    }
    prolog "find_country [list $list] C"
    $lb delete 0 end
    foreach c $var(C) {
        $lb insert end $c
    }
}

# Clear the entries and the listbox
proc clear {w entries lb} {
    foreach e $entries {
        $w.$e.e delete 0 end
    }
    $lb delete 0 end
}

# And now start the whole thing
make_display

The corresponding Prolog file country1.pl looks as follows:

:- lib(tk).

top :-
    tk([file('country.tcl')]).

find_country(List, Countries) :-
    query_list(List, Args, Goals),
    Goal =.. [country|Args],
    findall(S, (Goal, Goals, term_string(Goal, S)), Countries).

% Process the entries passed from Tk. Empty strings are ignored,
% entries of the form >Num or <Num are interpreted as argument
% greater or smaller than Num, otherwise strings converted to atoms.
query_list([], [], true).
query_list([""|L], [_|L1], G) :-
    !,
    query_list(L, L1, G).
query_list([S|L], [A|L1], Goals) :-
    (string(S) ->
        (substring(S, ">", 1) ->
            extract_num(S, Num),
            Goals = (A > Num, G)
        ;
        substring(S, "<", 1) ->
            extract_num(S, Num),
            Goals = (A < Num, G)
        ;
            atom_string(A, S),
            G = Goals
        )
    ;
        A = S,
        G = Goals
    ),
    query_list(L, L1, G).

extract_num(S, Num) :-
    string_length(S, N),
    N1 is N - 1,
    substring(S, 2, N1, SN),
    number_string(Num, SN).

country(C, R, A, P, Cap, Cu) :-
    country(C, R, _, _, A, P, Cap, Cu).

% The actual database from CHAT-80
country(afghanistan,indian_subcontinent,33,-65,254861,18290000,kabul,afghani).
country(albania,southern_europe,41,-20,11100,2350000,tirana,lek).
...

When we start the program by calling top/0, the following display appears:

We can now type values into the entries. Text entries can either contain the exact name or they are empty. Numeric entries may either contain the exact value, or strings ">nnn" or ">nnn" which are interpreted as looking for all values grater or smaller, respectively. For instance, when looking for all countries whose area is larger than one million sqm and population less than hundred millions, we can enter the following query:



next up previous index
Next: Prolog as the Up: Event Handling Previous: Event Handling



Micha Meier
Tue Jul 2 09:49:39 MET DST 1996