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:
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.# 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) }
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: