Prolog 4/4: GUI

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:

  1. Download the files for this project and re-name them as described above.
  2. Double click on “rssGui.pl”
  3. 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:

  1. identifier: Name
  2. key value: Name
  3. 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

Scala 4/4: GUI

Writing GUI Code in Scala

This is the last article in a series, to read the whole series go here.

The Plan

For the last week of Scala I’ve put together a GUI for week 3’s RSS aggregator using the Swing library. Scala’s Swing library is a fairly thin wrapper around Java’s Swing library. The primary difference between them is that Scala’s library takes advantage of its more powerful type system.

What to Expect

When this article is complete:

  • You will have:
    • A front end for the RSS agregator I wrote in week 3.
  • You will know:
    • How to write a basic Swing application in Scala.
Screenshot:

RSS GUI

Files Used in this Project

The Code

Libraries

import RssReader._
import swing._

You can use the libraries that you’ve written in the same way you call any other Java class library. Remember that you must compile RssReader.scala before you try to compile RssGui.scala.

A Top Level Object

object RssGui extends SimpleSwingApplication {

The SimpleSwingApplication incorporates all of the basics that you need for a standard windowed application in order to: create, draw and destroy the window when it’s closed.

Container for RSS Data

  var rssFeeds = List(("None Loaded", Seq("")))

This will hold the data for the RSS feeds. I’ve chosen to initialize it with "None Loaded" and an empty string because it gives the GUI something to display until a config file is selected and loaded, and it let’s Scala know what the rssFeeds‘s type is.

File Chooser

  var configFileChooser = new FileChooser(new java.io.File("./"))
  var configFileName = new String

The configFileChooser is a FileChooser that will open, defaulting to the current working directory. configFileName will hold the value selected, the details for how this will work are in the “Defining Behaviors” section.

Layout

For the most part the code here stands for itself, which is wonderful. Once you understand the basics for creation and layout for one component, they’re all the same. As a result I’ve be described each new idea in detail when I encountered it and let the rest alone.

Channel Panel Construction
Channel Select
  val channelLabel = new Label {
    text = "Select a channel:"
  }

  val channelSelect = new ComboBox(rssFeeds(0)._1) { }

  val channelSelectPanel = new BoxPanel(Orientation.Vertical) {
    contents += channelLabel
    contents += channelSelect
    border = Swing.EmptyBorder(0, 0, 0, 30)
  }

Lines 1-3: Create a new label with “Select a channel:” for its value.
Line 5: Create a ComboBox that is initialized to the first tuple value in the first list value: “None Loaded”
Line 7: Create a new panel that is filled top to bottom.
Line 8-9: Add the Label and ComboBox to the panel.
Line 10: Surround the panel in an empty border with 30 pixels on the right side (to separate it from the RssDisplay).

Rss Display
  val rssDisplay = new ListView(rssFeeds(0)._2)

This creates a nearly empty ListView. It has one line with an empty string. Until it’s populated with something more complex, it won’t display at all.

Combining channelPanel
  val channelPanel = new BoxPanel(Orientation.Horizontal) {
    contents += channelSelectPanel
    contents += rssDisplay
    border = Swing.EmptyBorder(0, 0, 30, 0)
  }

This creates a new panel with Orientation.Horizontal so each element will be added left to right. The channelSelectPanel and rssDisplay are added to complete the channelPanel.

Button Panel Construction
  val setConfigFileButton = new Button {
    text = "Set Config File"
  }

  val loadRssButton = new Button {
    text = "Load Feeds"
  }

  val buttonPanel = new BoxPanel(Orientation.Horizontal) {
    contents += setConfigFileButton
    contents += loadRssButton
  }

Create the two buttons and lays them out in a panel.

Top Window
  val topWindow = new BoxPanel(Orientation.Vertical) {
    contents += channelPanel
    contents += buttonPanel
    border = Swing.EmptyBorder(10, 10, 10, 10)
  }

Add the two top level panels together and put a border around them to make it neat.

Defining Behaviors

The Last Bit of Layout

  def top = new MainFrame {
    title = "Rss Reader"

    // Overall construction
    contents = topWindow

This creates the overall window and assigns topWindow as its contents. The title “Rss Reader” will appear in the title bar of the window.

Listening

    listenTo(channelSelect.selection, setConfigFileButton, loadRssButton)

Each of the GUI elements that you want to be able to have act as controllers must be listed in listenTo. I’ve chosen to add them all at once, you can also add them one at a time as in:

   listenTo(channelSelect.selection)
   listenTo(setConfigFileButton)
   listenTo(loadRssButton)

To determine when a value has been selected we are listening to channelSelect.selection instead of channelSelect.

Reacting

For each action performed on one of the listenTo‘ed elements it will run through each of the listed reactions to see if any of them are appropriate for the action/object combination.

    reactions += {
      case swing.event.SelectionChanged(`channelSelect`) =>
        rssDisplay.listData = rssFeeds(channelSelect.selection.index)._2
        pack
      case swing.event.ButtonClicked(`setConfigFileButton`) =>
        if(configFileChooser.showOpenDialog(channelSelectPanel) ==
            FileChooser.Result.Approve) {
          configFileName = configFileChooser.selectedFile.getAbsolutePath
        }
      case swing.event.ButtonClicked(`loadRssButton`) =>
        if(configFileName != "") {
          rssFeeds = combineFeeds(getRssFeeds(configFileName))
          channelSelect.peer.setModel(ComboBox.newConstantModel(rssFeeds.map(_._1)))
          rssDisplay.listData = rssFeeds(0)._2
          pack
        }
    }
  }
}

For readability I’ve left the entire set intact above, I’ll look at them for each case here.

A New Channel is Selected
      case swing.event.SelectionChanged(\`channelSelect\`) =>
        rssDisplay.listData = rssFeeds(channelSelect.selection.index)._2
        pack

Line 1: The case looks for the event, SelectionChanged and to which element that event is applied, channelSelect, the backquotes above are important.
Line 2: When the selection changes, set the display to contain the story titles from the rssFeeds value with the same index as the value selected.
Line 3: Once the values have been changed, packs the window so everything fits.

The Config File Button is Clicked
      case swing.event.ButtonClicked(\`setConfigFileButton\`) =>
        if(configFileChooser.showOpenDialog(channelSelectPanel) ==
            FileChooser.Result.Approve) {
          configFileName = configFileChooser.selectedFile.getAbsolutePath
        }

Line 2-3: Opens the configFileChooser and checks to make sure that a value has come back.
Line 4: Assigns the absolute path to the selected file to configFileName.

The Load Rss Button is Clicked
      case swing.event.ButtonClicked(\`loadRssButton\`) =>
        if(configFileName != "") {
          rssFeeds = combineFeeds(getRssFeeds(configFileName))
          channelSelect.peer.setModel(ComboBox.newConstantModel(rssFeeds.map(_._1)))
          rssDisplay.listData = rssFeeds(0)._2
          pack
        }

Line 2: Verifies that some value has been assigned to configFileName.
Line 3: Uses combineFeeds from RssReader to read in the RSS values.
Line 4: Set’s the channelSelect ComboBox to contain the names of each of the RSS feeds.
Line 5. Set’s the rssDisplay to contain the titles from teh first feed.

Final Summary

Scala has been an extraordinarily interesting language. The XML package alone makes it worth adding to your toolbox and, as can be seen by how clean the GUI design code is, it’s not a one trick pony. The type system, which sadly I haven’t been able to go into nearly enough detail here, has many of the nice features you’ll find in Haskell. And of course it’s great to be able to make use of all of Java’s libraries for free.

I do have a couple of small complaints:

  1. The error messages are sometimes a bit obscure, but this is a common flaw in all but the most mature open source projects.
  2. When code compiles, each anonymous function gets its own class. For anyone coding in a functional style, this quickly leads to a deeply cluttered directory.

These complaints are minor though, and the first can certainly be fixed. Overall it’s been a pleasure to work with and I look forward to using it for real tasks in the near future.

Coming Up

Next week I’ll be starting on Prolog. Prolog is a declarative language that is centered on formal logic.

Resources

Python 4/4: GUI Libraries

This is the last article in a series, to read the entire series go here.

Writing a GUI for our RSS Aggregator

The Plan

I’ll be finishing up the Python set with an overview of the Tkinter GUI library. There are numerous other GUI libraries available for Python, the best known of which is wxPython. I chose Tkinter because it comes built into Python and the primary goal here is to, as much as possible, look at Python as it comes out of the box.

What to Expect

When this article is complete:

  • You will have:
    • A basic GUI for last week’s RSS reader.
  • You will know:
    • How to write a GUI in Python’s Tkinter.
    • How to use lambda expressions to create functions on the fly.

The final GUI will look like this:

RSS Gui

Files Used in this Project

  • RssReader.txt: A library for reading RSS information, that I wrote in the previous project. Rename it to “RssReader.py” after downloading.
  • RssReaderGui.txt: The sourcecode for this project. Rename it to “RssReaderGui.py” after downloading.
  • feeds.txt: A sample input file.

The Code

Libraries

from RssReader import generateRsses
from Tkinter import *
from tkFileDialog import askopenfilename
  • Tkinter: Python’s built in GUI library.
  • tkFileDialog: A standard dialog for selecting files.

Global Variables

currentFeeds = {}
rssDisplay = None
chooseChannel = None
currentChannel = None
currentFeeds = None

I’ve seen two ways to organizing the code for a simple one window GUI in Python. One is to wrap it all in a single large class. The other is to write a group of functions that share a set of global variables. In this case a single class would have the same problems as globals along with the overhead of adding self. to every method call and class variable.

The variables that are set to None above are place holders for global variables that will be assigned later. They don’t have to be assigned here, but it’s useful to know what your globals are at the front end.

Toplevel Window Creation

def rssWindow():
  root = Tk()
  root.title("Rss Reader")
  root.geometry("750x500")
  root = channelFrame(root)
  root = buttonFrame(root)
  return root

Although you can define your functions in arbitrary order, I’ve written these in a, more or less, heirarchical order. I’ll walk through what I did here line by line:

  1. At the top level we create a Tk object.
  2. Give the window a title
  3. and an initial size.
  4. The frame that contains the drop down menu and list of articles.
  5. The frame that contains the select and load buttons.

The Upper Window with the Dropdown and Listbox Layout

RSS Gui

def channelFrame(parent):
  channelFrame = Frame(parent)
  channelSelectFrame = Frame(channelFrame)
  channelSelectFrame = channelLabel(channelSelectFrame)
  channelSelectFrame = channelSelect(channelSelectFrame)
  channelSelectFrame.pack(side=LEFT)
  channelFrame = rssDisplay(channelFrame)
  channelFrame.pack(side=TOP, expand=YES, fill=BOTH)
  return parent 

Here we create the window that contains textual information from the RSS feeds. To accomplish this, I create frames which contain either other frames or GUI elements. Once the elements are created and organized by frame they are in, then the window behavior is set with .pack

def rssDisplay(parent):
  global rssDisplay
  rssDisplay = Listbox(parent)
  rssDisplay.pack(side=RIGHT, expand=YES, fill=BOTH)
  return parent

def channelLabel(parent):
  label = Label(parent, text="Select a channel:")
  label.pack(side=TOP)
  return parent

def channelSelect(parent):
  global chooseChannel, currentChannel
  currentChannel = StringVar(parent)
  channelList = ["None"]
  currentChannel.set(channelList[0])
  chooseChannel = OptionMenu(parent, currentChannel, *channelList)
  chooseChannel.pack(side=BOTTOM)
  return parent

Here the individual components are generated, the behaviors of each are described later. On line 2 I used the global keyword. You only need to use this keyword if you plan on changing the global variable, not if you’re going to access it.

The Lower Window with the ‘Set Config File’ and ‘Load Feeds’ Buttons

RSS Gui

def buttonFrame(parent):
  buttonFrame = Frame(parent)
  buttonFrame = setConfigFileButton(buttonFrame)
  buttonFrame = loadRssButton(buttonFrame)
  buttonFrame.pack(side=RIGHT, expand=YES, fill=X)
  return parent

def setConfigFileButton(parent):
  button = Button(parent, command=setConfigFile)
  button["text"] = "Set Config File"
  button.pack(side=LEFT)
  return parent

def loadRssButton(parent):
  button = Button(parent, command=loadRss)
  button["text"] = "Load Feeds"
  button.pack(side=RIGHT)
  return parent

Here I create a top level layout for each of the buttons then describe the specific behavior of each one. We describe the buttons behavior by passing the function to be called via the command argument.

Commands called by various elements

Lambda, functions are first class objects

def loadRss():
  global chooseChannel, currentChannel
  chooseChannel["menu"].delete(0, END)
  channelList = currentFeeds.keys()
  for channelName in channelList:
    chooseChannel["menu"].add_command(label=channelName, 
        command=lambda (temp = channelName): selectChannel(temp))
  selectChannel(channelList[0])

The loadRss function clears the values from the drop down menu then adds each of the current channels to it. It also uses a Lambda function to set the behavior of the drop down box when a given channel is selected.

Lambda functions, as used in line 7 of the above code are extraordinarily useful. It comes in handy, as in the above case, when you need to create a function for which some of the internal values are not known until runtime. Here the Lambda function’s purpose is to assign the default value, channelName to the selectChannel function and assigns it as chooseChannel‘s command.

In general Lambda functions come in the form lambda arg, arg: arg + arg. For people who aren’t used to functional it’s important to remember that there’s only ever one line in a lambda function and the value of that line is always returned without any need to use the return keyword.

def selectChannel(channelName):
  global chooseChannel, rssDisplay
  chooseChannel.setvar(chooseChannel.cget("textvariable"), value = channelName)
  rssDisplay.delete(0, END)
  for feed in currentFeeds[channelName]:
    rssDisplay.insert(END, feed)

When one of the channels is selected from the drop down, this function fires and populates the display window with the names of the current articles.

def setConfigFile():
  global currentFeeds
  currentFeedFile = askopenfilename(filetypes=[("allfiles", "*"), 
                                                ("textfiles","*.txt")])
  currentFeeds = combineFeeds(currentFeedFile)
  loadRss()

Here the program opens a file dialog with askopenfilename which will return the flie selected. It then combines all of the feeds with the same channel name and loads them into the select box to display them with loadRss.

def combineFeeds(fileName):
  feeds = {}
  for feed in generateRsses(fileName):
    for channelName in feed.keys():
      if feeds.has_key(channelName):
        feeds[channelName] += feed[channelName]
      else:
        feeds[channelName] = feed[channelName]
  return feeds

combineFeeds reads the RSS sources from the given filename, downloads the feeds using the library from the last article then combines any channels that happen to have the same name.

if __name__ == "__main__":
  rssWindow().mainloop()

To actually create the window and make it useful, the program calls rssWindow(), described at top of the file, then runs the mainloop() method on it.

Final Summary

I’ve thoroughly enjoyed touring through Python, my observations are that Python has: * A gentle learning curve. * Many libraries with good documentation. * An easy to read syntax. * Flexible semantics.

This has been an interesting run, next week I’ll be taking a look at Markdown, a lightweight markup language which allows you to create nicely typset documents from the comfort of your text editor. Starting in December I’ll be taking a look through Scala a functional/object oriented language that runs in the Java VM.

Resources