Code Project: create an animated RSS reader with Clutter

Code

In a previous tutorial we had a look at the basics of Clutter as we used it to build a network speed monitor. This time we'll be looking at some of the very powerful animation techniques used in Clutter, how to group objects, and a little more about text actors. We will be doing this in the guise of implementing a feed reader. There isn't enough space for us to implement a complete multi-stream reader and explore the animations, but we will be covering enough ground to get you started on building such a beast, including fetching the data from the feed and applying it to the Clutter objects.

For those of you who haven't been tempted by one of these magnificent Python tutorials before, we usually try to do as much as possible in the interactive mode of Python first. It is a kinder, gentler environment than the normal mode in which programs are run, as you can type things in and experiment. The code listings in these cases include the Python prompt >>> at the beginning of the line when you have something to type in, and without it when the environment is giving you some feedback, just as it appears on screen.

(NB: don't miss our collection of free Python tutorials, and you can also try your hand at our Clutter beginners tutorial for C programmers if you're feeling adventurous!)

Download the code for this project
(includes various extra features just for kicks)

Getting fed

The very first thing we should look at is how to get the data from our feed. We have come across the most excellent Feedparser library for Python before, and we will be using it again this time in order to retrieve the amazingly interesting data for our app.

Before we do that though, we need to have a URL for a feed. You can choose any you like. The best way to find out a particular feed address is to go to the relevant page in a web browser and look for the RSS syndication icon. More often than not, this icon is linked to the feed address, so you can just copy the link location with your browser or read it off the status bar. You could use the TuxRadar feed, for example, at www.tuxradar.com/rss. For our example we are going to use the BBC news feed, for two reasons. The first is that it supplies an image reference, which will be nice for our experiments with Clutter textures, and the second is that it gets updated with news stories pretty much constantly, which makes it good for testing.

See that little orange icon? Click that and you'll get RSS. Easy!

See that little orange icon? Click that and you'll get RSS. Easy!

RSS and other feeds

There are plenty of different elements stuffed into an average RSS feed. Apart from the feed image, we're only using ones that are guaranteed to be present in any feed that you might come across. If you want to know more about what things might be there, you should take a look at the different standards documents. Yes, to make things more confusing there were different versions of RSS developed at different times by largely different groups with very different ideas about how things should go. Using the Feedparser module smooths out a lot of the nonsense.

The Harvard website has a really useful tutorial on constructing and RSS feed, which, cunningly for our purposes, works as an equally good tutorial for ripping one apart too: http://cyber.law.harvard.edu/rss/rss.html.

The BBC news feed is at http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/world/rss.xml.

>>> import feedparser
>>>f= feedparser.parse('http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/world/rss.xml') 
>>>f
{'feed': {'lastbuilddate': u'Wed, 30  Dec  2010 19:11:25 GMT', 'subtitle': u'Get the latest BBC World News:
international news, features and analysis from Africa, Americas, South Asia, Asia-Pacific, Europe and the
Middle East.', 'language ...

Actually, causing Python to display the variable we assigned to the feed just spews out the whole content of the container (we have truncated it in the output shown). To get to the bits you are interested in, you need to use the keys. All of the actual feed items are stored in a big list referenced by 'entries', and contain the information for the item - summary, timestamp, link URL and so on.

>>> f.entries[0].title
u'Nokia expands claim against Apple'
>>> f.entries[0].link
u'http://news.bbc.co.uk/1/hi/technology/8434132.stm'
>>> f.entries[0].updated_parsed 
time.struct_time(tm_year=2009, tm_mon=12, tm_mday=29, tm_hour=18, tm_min=0, tm_sec=46, tm_wday=2,
tm_yday=364, tm_isdst=0) 
>>> f.entries[0].updated 
u'Wed, 30 Dec 2009 18:00:46 +0000' 
>>> f.entries[0].summary
u'Nokia ramps up its legal fight against Apple, claiming that almost all of its products infringe
its patents.'

As you can see, we can get pretty much everything we need to build a parser out of these elements. There is one optional thing that we might want though - an image. The feed specs allow for an image to be supplied as a channel 'ident' for the feed, which is passed along as a URL to the image and some text describing it. This should be in the body of the feed, in an element called image, so we can reference it with (in this case), bbc.feed.image.href.

There are many ways we could use this image, but one of the simplest is to just download it locally - then we can do what we like with it. A great way to do this in Python is to use the splendid urllib module for Python. Among its ever-sharp tools is the urlretrieve method, which will download the content of a URL and save it to the system's temporary storage (usually /tmp), before handing back a filename and a bunch of HTTP info. The saved file should get cleaned up when the temporary storage is hosed down, or we can delete it when we quit if we want to be nice. Here it is:

>>> import urllib
>>> img, data =urllib.urlretrieve(f.feed.image.href)
>>> img
'/tmp/tmpTsCyDc.gif' 

We'll do something useful with this file in just a moment.

Cluttering up

With that little excursion into the realm of feeds out of the way, we can get down to the serious business of getting all cluttery. Now, in the last tutorial we introduced the basic Clutter elements of the stage, actors and timelines. If you missed it, well, obviously you should get a back issue. Or, because you don't want to miss another one - why not subscribe? Subscribers get to look at the PDFs of past issues online. Just a thought. Anyhow, for now, here is the 'Previously on Lost' short recap. A Clutter window is called a stage. This is where the action happens, and by default in PyClutter is implemented in a standard GTK window. The elements that appear on (in) the stage are called actors, and can be anything from text to simple shapes or pixmap textures.

The last one is what interests us this time. We will be using text actors again, but Clutter allows us to import images directly from files to use. Those of you with memories longer than a Channel 4 ad break will no doubt remember that we have such a file lurking around. Just to prove that it works, we should set up a stage and all the usual Clutter, erm, clutter, and then import our new pixmap.

>>> import clutter
>>> black=clutter.color(0,0,0,255)
>>> white=clutter.color(255,255,255,255)
>>> stage= clutter.Stage()
>>> stage.set_size(400,60)
>>> stage.set_color(white)
>>> stage.show_all()
>>> ident =clutter.texture_new_from_file(img) 
>>> stage.add(ident)

This code just sets up a simple stage, sets the background colour to white and then adds the pixmap image. You can see from the code that unlike many graphic toolkits, we can add the actor to the stage after the stage is already on display. You should see something like, if not exactly the same as, the image on the first page of this tutorial.

Foresight is a wonderful thing, which is why the pixmap fits so nicely on the stage. As we didn't set a position for it, it just comes in at (0,0), which is the top-left of the stage.

Getting animated

Before we finish our masterful feed reader, now would be a useful interlude during which to mess around with some animation. Last time, we animated some text objects through the use of the timeline - a powerful part of the Clutter magic that gives us easy interrupts that we can use to animate all sorts of items. However, since the 1.0 release of Clutter there is an even more powerful way to animate objects. The Clutter module now provides new methods for animating actors, and the most powerful of these is one that we are going to experiment with next.

A Clutter actor has an animate method. The arguments it takes are an animation mode, followed by a duration (in milliseconds) and then the properties and values to be animated. Let's break that down a bit. The animation mode can be defined, but Clutter has already built in several types that can be referenced through the Clutter module. These act as the tweening mechanisms for all the values you list.

Here is a selection:

  • CLUTTER_LINEAR
  • CLUTTER_EASE_IN_QUAD
  • CLUTTER_EASE_OUT_QUAD
  • CLUTTER_EASE_IN_OUT_QUAD
  • CLUTTER_EASE_IN_CUBIC
  • CLUTTER_EASE_OUT_EXPO
  • CLUTTER_EASE_IN_OUT_EXPO
  • CLUTTER_EASE_IN_OUT_CIRC

...and there are many more. These models compute the in-between phases of properties at any frame in the animation. The linear mode is a linear progression, while the others are variations producing different effects.

So, say we had an object at position 0,0 and we animated it over 2,000 milliseconds to a position 100,0 animating only the x position. With a linear animation mode it would be at position 50,0 after one second. The animation takes the current values as the start point, and the supplied values as the end point. Any property that is not supplied is not animated. The supplied properties have to come in a pair with the end value following, and any property can be animated. It's easier with a few examples (I hope you still have your stage open):

>>> ident.set_anchor_point(60,30)
>>> ident.set_position(60,30)
>>> ident.animate(clutter.LINEAR,2000,'rotation-angle-y',720)
<clutter.Animation object at 0xa3f861c (ClutterAnimation at 0xa4bb050)> 
>>> ident.animate(clutter.LINEAR,2000,'rotation-angle-y',720)
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at 0xa4bb0c8)> 

The second time we ran the rotation, nothing happened. That is because the property value was the same - the value you supply isn't the amount to animate by, it is the position the object should end up. The second time we ran the animation, the object was already in that position, so it was animated - it just didn't go anywhere. You can of course, animate more than one property at a time. Try this:

>>> ident.animate(clutter.LINEAR,2000,'x',100, 'rotation-angle-y', 360 )
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at 0xa4bb2c8)> 
>>> ident.animate(clutter.EASE_IN_SINE,2000,'x',0, 'rotation-angle-y', 0 )
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at 0xa4bb3ae)>

This time the little logo did a dance and then returned to its original position.

Just the facts

Well, spinning logos are all well and good, but what we really want to see is the text for our headline and the summary of the story sitting there in the space next to it. This will leave us with three actors on the screen.

While Clutter can handle animating different actors concurrently, it becomes a bit of a pain for us to have to handle separate animations for the whole group of actors. The key word there, if you hadn't picked up on it, is 'group'. Clutter now supports containers, including a group container (the others are rather like GTK stacking containers, and we will no doubt use them some other time). First of all we'll create a new group, then define the elements to go in it and add them all. To save messiness we will import the ident image again as a new object.

>>> group1= clutter.Group()
>>> ident1=clutter.texture_new_from_file(img) 
>>> head1=clutter.Text()
>>> head1.set_position(130, 5)
>>> head1.set_color(blue)
>>> head1.set_text(f.entries[0].title)
>>> body1=clutter.Text()
>>> body1.set_max_length(75)
>>> body1.set_position(130, 22)
>>> body1.set_size(250, 100)
>>> body1.set_line_wrap(True)
>>> body1.set_text(f.entries[0].summary)
>>> group1.add(ident1, head1, body1)
>>> group1.show_all()
>>> stage.add(group1)

In this code we have created a new ident image and two text objects - one to represent the title of the news item (head1), and one for the summary (body1). We have set the text for them from the first entry we found in the feed and set them up in a good position next to the ident image. Here we have seemed to set absolute positions for the head1 and the body1 text objects, but these positions will actually remain relative to the group. When we initiate the group, it becomes the parent object to the actors, rather than the stage as before. So the positions of the objects are relative to the group position (which starts off by default at 0,0).

You should try to remember this. For example:

>>> head1.get_position()
(130,5)
>>> group1.set_position(10,10)
>>> head1.get_position()
(130,5)
>>> group1.set_position(0,0)

You saw all the objects move along the screen when we changed the group position, but don't fall into the trap of thinking that the child objects think they have moved. They haven't - they are still in the same place, but their world has moved... The great thing about this is, of course, that we can also animate a group - we need only supply one transformation, because all the properties of the individual actors are relative to the group.

>>> group.animate(clutter.LINEAR,2000,"x",200,"y,"30")
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at 0xa4bb2c8)> 
>>> group.animate(clutter.LINEAR,2000,"x",0,"y,"0")
<clutter.Animation object at 0xa3f85f4 (ClutterAnimation at 0xa4bb4a8)> 

This time all the elements moved smoothly as though they were one, which (in terms of the group) they are. We can still adjust them individually - change the text or move them, but any transformations are relative to the group, not the stage.

So, now we have a group, we can add another, then implement two animation modes to make the transition between the different items from the news feed:

>>> group2= clutter.Group()
>>> ident2=clutter.texture_new_from_file(img) 
>>> head2=clutter.Text()
>>> head2.set_position(130, 5)
>>> head2.set_color(blue)
>>> head2.set_text(f.entries[1].title)
>>> body2=clutter.Text()
>>> body2.set_max_length(75)
>>> body2.set_position(130, 22)
>>> body2.set_size(250, 100)
>>> body2.set_line_wrap(True)
>>> body2.set_text(f.entries[1].summary)
>>> group2.add(ident2, head2, body2)
>>> group2.hide()
>>> stage.add(group2)
>>> group1.animate(clutter.EASE_OUT_EXPO,4000,'x',800,' y',0,'rotation-angle-y',180) 
<clutter.Animation object at 0x92ca61c (ClutterAnimation at 0x935a2a0)> 
>>> group2.animate(clutter.EASE_OUT_EXPO,1,'x',400,'y',100,'rotation-angle-y',720) 
<clutter.Animation object at 0x92ca66c (ClutterAnimation at 0x935a118)> 
>>> group2.show() 
>>> group2.animate(clutter.EASE_OUT_EXPO,3000,'x',0,'y',0,'rotation-angle-y',0) 
<clutter.Animation object at 0x92ca16c (ClutterAnimation at 0x8f46028)> 

Here we have set up a new group and hidden it before adding to the stage. Then, for convenience's sake, we animate it out of the way and reveal it. As it is out of the stage area, it can't be seen and doesn't interfere with the first group. When we reveal it, it is still offstage, but the new animation then puts it back in the proper position, and it seems to glide through the ether to come to a perfect stop. Hurrah! We are now expert animators to rival the likes of Disney. Maybe.

This screenshot doesn't really do the finished app justice - run it yourself to see the animation in action!

This screenshot doesn't really do the finished app justice - run it yourself to see the animation in action!

Doc Holiday

Clutter is really great. The only thing that sucks at the moment is the documentation for the Python module. Although the classes and methods are largely identical to the C implementation of Clutter (naturally), there are a few subtle differences, and sometimes the semantics of doing something in Python can get you in a muddle. Python's introspection tools are useful for this, particularly the dir() function, which you can call on any object, even a module. Try it on Clutter to see a list of the static types and methods available: dir(clutter), or on a method: dir(clutter.Text).

Moving on

There is a more fleshed-out version of our feed reader on the DVD, but it could still do with some extensions. There is very little in the way of error checking for instance (what if the feed is empty or the web connection is lost?) and it only handles a single feed rather than merging together several feeds. The comments in the source code on the disc should give you some ideas about how it can be extended.

Next time we'll be looking at more advanced methods of animation, and stringing animations together. We will also extend the basic building blocks of clutter by showing how to incorporate elements of the Cairo graphics library.

First published in Linux Format

First published in Linux Format magazine

You should follow us on Identi.ca or Twitter


Your comments

What does the last animate

What does the last animate do in the code you supplied? I changed the first one to:
x=self.group.animate(clutter.LINEAR,1000,'x',-1000,'y',0,'rotation-angle-y',270)

and the second to:
self.group.set_position(1000,0)
x=self.group.animate(clutter.LINEAR,1000,'x',0,'y',0,'rotation-angle-y',0)

Yet nothing seems any different?

Sorry, I meant the second

Sorry, I meant the second one doesn't do anything

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar