Project 1-2: A GUI for the Reader

This tutorial con tinues creating a RSS feed reader in Director. It is intended to demonstrate an 'object orientated' approach to creating applications in Director.

Summary of Part 1

In part 1, three basic scripts were created:

  • SubscriptionMgr for managing a list of subscriptions (URLS) and creating a FeedObject for each subscription
  • RSS-Feed for creating 'Feed Objects' which have methods for getting information about a feed, list of article titles and descriptions for specific articles.
  • RSS-Parser for parsing the RSS XML into Director-friendly proplists.

These scripts, along with a couple of generic scripts from the Lingoworkshop Xlib, can be download in this movie.

Creating a Prototype GUI Framework

Before doing anything too fancy with the GUI, we are going to build the following bare-bones prototype.

Using the source movie from Part 1, I'm going to add two new castlibs: a 'Media' cast lib (for static media) and 'GUI-Framework' (for behaviours). Now I am going to add a simple quickdraw sprite to channel 1, startting at frame 1 and ending at sprite 20. On to this sprite, I am going to add a behaviour called 'Gui-interface'. This is the 'main' script that creates the SubscriptionMgr and connects FeedObjs with the various GUI widgets.

Screen capture of score and cast

This GUI-interface script starts off fairly simply:



-- behaviour "Gui-interface"



property SubscriptionMgr



on beginSprite me

  

  SubscriptionMgr = script("SubscriptionMgr").new()

  Widgets = [:]

  

end

So when the sprite begins, it creates a new instance of the SubscriptionMgr script and stores this for later use. It also creates an empty property list of widgets. This will get populated when the widgets come into existence.

There will be three widgets in the bare-bones version that this GUI-interface is going to interact with:

  • a listBox for showing all the subscriptions
  • a listBox for showing a list of headlines for the selected feed
  • a text box for displaying the selected item of the current feed

To keep things simple, I'm going to use a very simple 'listBox' behaviour that works with #text members. So add two text members to the score, one for the subscriptions and one for the titles. Then add the following behaviour to them



-- behaviour "Listbox-Simple"



property spriteObjRef

property memberObjRef

property CurrentList



property id

property Callback



on beginSprite (me)

  

  spriteObjRef = sprite(me.spriteNum)

  memberObjRef = spriteObjRef.member

  sendAllSprites(#RegisterListBox, me, ID)

  memberObjRef.color =  color( 0, 0, 0 )

end





on SetList me, aList

  

  if aList.ilk <> #List then aList = []

  memberObjRef.color =  color( 0, 0, 0 )

  memberObjRef.text = implode(return, aList)

  

end



on mouseDown (me)

  

  whichLine = spriteObjRef.pointToLine(the mouseLoc)

  if whichLine > 0 then 

    memberObjRef.color =  color( 0, 0, 0 )

    memberObjRef.line[whichLine].color =  color( 0, 0, 119 )

    sendAllSprites(Callback, whichLine)

  end if

  

end



on GetPropertyDescriptionList (me)

  

  pdList = [:]

  pdList[#ID] = [#Comment: "ID", #Format: #Symbol, #Default: #GenericWidget]

  pdList[#Callback] = [#Comment: "Callback", #Format: #Symbol, #Default: #ListSelect]

  return PDList

end

What this behaviour does is send a specified message to all sprites when a line is clicked, passing the line as a parameter. When it is created, it also sends a 'registerWidget' message to all sprites. Drag this behaviour on to the sprite used for the "SubscriptionList' and in the GetPropertyDescriptionList dialog, specify the ID as #SubscriptionList and the callback as #SelectFeedAt. Then drag this behaviour on to the sprite used for the 'TitleList' and in the GetPropertyDescriptionList dialog, specify the ID as #TitleList and the callback as #SelectTitleAt.

So, now we will modify the GUI-interface to listen for the Register message and handle the callbacks we have specified:



-- behaviour "Gui-interface"



property SubscriptionMgr

property SelectedFeed

property MonitorStatus

property Widgets



on beginSprite me

  

  SubscriptionMgr = script("SubscriptionMgr").new()

  Widgets = [:]

  

end



-- Messages from the GUI Widgets



-- The ListBox widgets



on RegisterListBox (me, sender, id)

  case (id) of

    #SubscriptionList:

      subscriptions = SubscriptionMgr.GetSubscriptions()

      sender.SetList(subscriptions)

      Widgets[#SubscriptionList] = sender

    #TitleList:

      sender.SetList([])

      Widgets[#TitleList] = sender

  end case

end





on SelectFeedAt (me, pos)

  feedObj = SubscriptionMgr.OpenSubscription(pos)

  if feedObj.ilk <> #instance then

    -- there was an error creating the feed

    put "ERROR creatring feed object: " & feedObj

  else

    SelectedFeed = feedObj

    -- start tracking the status of the SelectedFeed

    MonitorStatus = 1

    -- remove contents from titlesList

    Widgets[#TitleList].SetList([])

  end if

end



on SelectTitleAt (me, pos)

  if SelectedFeed.ilk <> #instance then

    -- there was an error creating the feed

    put "ERROR access feed object: (no feed instantiated)"

  else

    description = SelectedFeed.getItemAt(pos)

    put "DISPLAY ITEM " & description

  end if

end



on enterframe (me)

  -- are we monitoring the status of a feed?

  if MonitorStatus then 

    feedStatus = SelectedFeed.GetStatus()

    if feedStatus = "Done" then

      MonitorStatus = 0

      titlesList=SelectedFeed.GetTitleList()

      call(#SetList, Widgets[#TitleList], titlesList)

    end if

  end if

end

In the RegisterListBox method of this script, we store a reference to the widget. We use this reference to send messages to the widget later on. We also use this opportunity to display the default lists in the widgets.

The SelectFeedAt method tries to create a FeedObj for the specified feed. It does this by calling the OpenSubscription method of the SubscriptionMgr. If it was able to get an object, it then sets a flag to monitor the progress and removes anything currently being displayed in the titles listBox or the article display.

TheSelectTitleAtmethod simply retrieves the item specifed from the currently selected FeedObj, and then tells the ArticleDisplay object to display the details of that item.

Because we need to wait for the FeedObj to download and parse the XML, we cannot populate the title list as soon as a feed is selected. Therefore, we check on enterframe whether the FeedObj status is "done" (meaning that the feed has been downloaded and is ready). If the feedObj is ready, we get the title list from the feedObj and tell the titleList widget to display this list.

The wdget for displaying the item details will attempt to use Director's sketchy HTML properties of #Text members.



-- behaviour "Item Display"



property spriteObjRef

property CurrentList



property id

property Callback



on beginSprite (me)

  

  spriteObjRef = sprite(me.spriteNum)

  sendAllSprites(#RegisterArticleDisplay, me, VOID)

  

end



on Display (me, what)



 if what.ilk <> #PropList then 

 	out = string(what)

 	spriteObjRef.member.text = out



 else



-- construct the html

out = "Article"

out = out & ""

out = out & ""

out = out & "

"&what[#title]&"

" if stringP(what[#description]) then txt = what[#description] if txt contains "" & what[#description] & "

" end if end if if string(what[#link]) <> "" then out = out & "Read More" end if out = out & "
" spriteObjRef.member.html = out end if if string(what[#link]) <> "" then out = out & "Read More" end if out = out & "" spriteObjRef.member.html = out end if end on hyperlinkClicked(me, data, range) gotoNetPage(data,"_blank") end

Like the listbox widgets, it sends a registration message - although in this case the message is #RegisterArticleDisplay. This widget doesn't send any messages back ot the GUI. Next, we update the GUI-interface to listen for the register message. We also modify the #SelectTitleAt method so that is uses this widget to display the selected item (rather than putting it to the message window)



-- behaviour "Gui-interface"



property SubscriptionMgr

property SelectedFeed

property MonitorStatus

property Widgets



on beginSprite me

  

  SubscriptionMgr = script("SubscriptionMgr").new()

  Widgets = [:]

  

end



-- Messages from the GUI Widgets



-- Item Display



on RegisterArticleDisplay (me, sender, id)

  Widgets[#ArticleDisplay] = sender

  sender.Display("")

end





-- The ListBox showing all the feeds



on RegisterListBox (me, sender, id)

  case (id) of

    #SubscriptionList:

      subscriptions = SubscriptionMgr.GetSubscriptions()

      sender.SetList(subscriptions)

      Widgets[#SubscriptionList] = sender

    #TitleList:

      sender.SetList([])

      Widgets[#TitleList] = sender

  end case

end





on SelectFeedAt (me, pos)

  feedObj = SubscriptionMgr.OpenSubscription(pos)

  if feedObj.ilk <> #instance then

    -- there was an error creating the feed

    put "Error creatring feed object: " & feedObj

  else

    SelectedFeed = feedObj

    -- start tracking the status of the SelectedFeed

    MonitorStatus = 1

    -- remove headlines and article

    Widgets[#TitleList].SetList([])

    Widgets[#ArticleDisplay].Display( "" )

  end if

end



on SelectTitleAt (me, pos)

  if SelectedFeed.ilk <> #instance then

    -- there was an error creating the feed

    put "Error access feed object: (no feed instantiated)"

  else

    description = SelectedFeed.getItemAt(pos)

    Widgets[#ArticleDisplay].Display( description )

  end if

end



on enterframe (me)

  -- are we monitoring the status of a feed?

  if MonitorStatus then 

    feedStatus = SelectedFeed.GetStatus()

    if feedStatus = "Done" then

      MonitorStatus = 0

      titlesList=SelectedFeed.GetTitleList()

      call(#SetList, Widgets[#TitleList], titlesList)

    end if

    member("Status").text = feedStatus

  end if

end



The last visual item I am going to add is a simple text member (called "status") which will display the status of the current feedobject. In the enterframe method of the GUI-Interface, if we are monitoring a feedObj, then the text of this member is updated.

Score and cast

Summary of Part 2

So far, we have created a basic RSS Reader that we can interact with via a crude GUI. The next step, before we start creating a final GUI, is to create a 'daemon' that will automatically download feeds and tell us which ones contain unread messages.

Downloads

Source movie containing the scripts discussed availablein the next page.

First published 17/03/2006