Prolog 4/4: GUI
The Plan
I will write a GUI for last week’s RSS reader in order to explore how to write a GUI in a logic based language using Prolog’s XPCE toolkit.
The XPCE toolkit is:
- An IDE for SWI-Prolog.
- A library for writing GUI in Prolog.
- An object layer for Prolog.
What to Expect
When this article is complete:
- You will have:
- A GUI front end for the RSS reader from last week
- You will know:
- How to use SWIPL-Win.
- How to write a GUI in Prolog.
Files Used in this Project
- rssGui.txt: The GUI code. (Change the extension from txt to pl)
- rss.txt: The original RSS reader file, used here as a library. (Change the extension from txt to pl)
- feeds.txt: Sample RSS feed config file.
The Code
Compiling the Code… More or Less
I discovered that compiling GUI code for XPCE is easier said than done. I made several attempts, read a couple of tutorials and got nowhere. If there are any Prolog guru’s reading who wouldd like to set me straight, please leave a comment.
On the other hand, running the code on the is pretty easy:
- Download the files for this project and re-name them as described above.
- Double click on “rssGui.pl”
- In the SWIPL window, type,
rssGui.
At that point, you should have a window with the RSS reader in it.
Loading Libraries And Other Activities
:- consult(rss).
:- pce_autoload(finder, library(find_file)).
:- pce_global(@finder, new(finder)).
To load the rss
library from last week, tell Prolog to consult
it. The two lines afterward load the file finder into memory. This makes it possible to open a file finder to select the rss config file.
Laying Out the GUI, and Adding Hooks
rssGui :-
new(Frame, frame('RSS Reader')),
new(RssFeeds, browser),
send(Frame, append(RssFeeds)),
send(new(NewsList, browser(news)), right, RssFeeds),
send(RssFeeds, select_message, message(@prolog, newsDisplay, RssFeeds, NewsList)),
send(new(Buttons, dialog), below(RssFeeds)),
send(Buttons, append(button(load, message(@prolog, load, RssFeeds)))),
send(Frame, open).
XPCE makes it possible to access objects in Prolog by adding three predicates:
new
: Instantiates a new object.send
: Sends information to the object.get
: Gets information from an object.
To make a new GUI window, first a frame
is created(Line 2) which contains all the GUI elements.
Lines 3 and 4 create a List Box to contain the channel names and adds it to the Frame.
Line 5 adds a List Box to the right side of the channel list box for displaying news titles.
Line 6 adds an action so that when a value is selected in RssFeeds, the newsDisplay
predicate is fired with RssFeeds and NewsList for arguments.
Line 7 adds a container for a button.
Line 8 adds a button to the dialog which has the text “load” and fires load
when clicked.
Line 9 opens the frame.
Read the Config and Load the RSS Feeds
load(Browser) :-
get(@finder, file, exists := @on, FileName),
readConfig(FileName, URLs),
send(Browser, clear),
foreach(member(URL, URLs), readAndListChannels(Browser, URL)).
Line 2 opens up a standard file dialog and unifies the selected file with FileName
, from there the code is much like the display code from last week.
I used a separate predicate to write each of the URLs to avoid potential logical contradictions in the foreach
.
Fetch the Feed and Add it to the RssFeeds Browser
readAndListChannels(Browser, URL) :-
catch((rssFetch(URL, XML),
rssRead(XML, RSS),
foreach(member(channel(Name, News), RSS),
send(Browser, append, create(dict_item, Name, Name, News)))),
_,
readAndListChannels(Browser, URL)).
Again, much of this is similar to the code to display the feeds on the command line. The only real change is in Line 5, where the data from each channel is wrapped into a dictionary item with:
- identifier: Name
- key value: Name
- object: News
As before, this will keep trying until it succeeds or blows the stack.
Display the News Titles in the NewsList Browser
newsDisplay(RssFeeds, NewsList) :-
get(RssFeeds, selection, Channel),
get(Channel, object, News),
get(News, size, SizeVal),
send(NewsList, clear),
foreach(between(1, SizeVal, LineNumber),
sendVector(NewsList, News, LineNumber)).
Line 2 unifies Channel with the currently selected item in RssFeeds, the dictionary item from readAndListChannels
.
Line 3 pulls the object from the dictionary item, a vector.
Line 4 determines the length of the vector.
Line 5 clears the NewsList so we don’t just keep adding to the end.
Line 6-7 iterates over each of the values in the vector and appends them to NewsList with sendVector.
Append a Cell from a Vector to a Browser
sendVector(Browser, Vector, Index) :-
get(Vector, element, Index, Value),
send(Browser, append, Value).
Given a Browser, Vector and Index, sendVector
pulls the Indexth element from the Vector and appends it to the Browser.
Final Summary
Prolog’s logic based semantics make for an interesting programming experience. Up to this point I had trouble imagining how you’d deal with dynamic situations, like user input or a file being read in, in context to a language that is based on logic. Now that I’ve spent a month working with it, it’s an incredibly elegant way to solve problems. That being said, I think I’m going to stick with Python for my day to day scripting needs.
Coming Up
Next week is a fifth Monday, so I’ll be working with a tool, Robot Framework. Robot Framework is a Python based glue language for writing test cases in a clear format which is readable to non-technical users.
In February, I’ll be working with Squeak. Squeak several neat features, it is:
- generally considered the first full object oriented language
- where unit tests frameworks came from (in the form that most of us are used to today)
- contained entirely in its own design environment
- designed to be useful as an education language
- host to Seaside a powerful web framework
Resources
- XPCE’s Main Page
- XPCE UserGuide: An excellent resource, it has a vast number of examples, but is also quite dense.
- Making an executable for XPCE
- FAQ for XPCE, executables