Code Project: create a Qt RSS reader

Code

We're going to build a complete application that wouldn't take too much additional work to qualify for re-distribution as a bona fide open source application. It's an RSS reader which allows you to add your own feeds, lists the stories on that feed and then lets you read those stories within a browser window attached to the main application.

If you already tried our previous two Qt code projects - Create an ffmpeg front end and Create a media player - and are looking for more Qt fun, read on...

RSS is an XML text file formatted in a specific way. It contains a brief description of each story on the website. The best thing about it is that it's usually updated when a new story is published. Using an RSS reader, or an RSS-compatible browser like Firefox, users can subscribe to an RSS feed from a website, and the reader will check for updates at regular intervals and list any new stories for the user to browse through. And that's exactly what our application is going to do.

It's also going to introduce a few major Qt technologies, including a way to handle streams of XML data, how to dynamically populate a treeview widget, using the WebKit widget and tie all of this together into a dynamically expanding application window that will automatically update to display web pages. This makes the RSS reader the perfect place to start more ambitious projects, even if you abandon the RSS-handling code immediately, the expandable GUI we build for this application is still going to be useful.

Which is why, when you first launch Qt Creator and create a new project, you need to select three separate modules for use in our application. Click on the Qt4 GUI Application template in the wizard, give it a name, and enable the following three modules: QtNetwork, QtWebkit and QtXML. These modules will bundle the three new areas we'll be covering in this tutorial, and adding them from the wizard saves you adding them manually to your project's '.pro' file.

Design the GUI

As with our other Qt programming tutorials, after you get Creator launched with a new project, the place to start is with the GUI design. Just click on the '.ui' file to open the Designer view. This time we're going to take a slightly more open approach to design. The main window is going to be split into two panels. On the left, we'll add the RSS message list and give the user the ability to add their own feeds. The right half of the window will be taken up by the web browser, and we'll use the WebKit widget for this.

But the clever thing about Qt is that we can make either panel expandable or hideable according to whether the user wants to use the internal browser, or perhaps resort to the one they're most used to. If the user doesn't want to see the web view, for example, they just need to drag the central separator to the right and it disappears. This gives our application a great deal of flexibility and doesn't force the web view on people who want to read the news in their own browser.

This particular feature is thanks to something called the Dock Widget in Qt. It offers a great deal of flexibility when arranging your application into sections, and can allow the user to drag and drop different parts of the window around. Drag two Dock Widgets from the Containers list in the Creator page onto the blank application canvas. You may also want to remove the superfluous menu, toolbar and status panel widgets from the Object view as we won't be using them in this application. We've added two dock widgets because we're going to use these to hold widgets on either side of our application, and because they're dock widgets, the user will be able to drag the separator between them to resize each half of the application.

But before adding any more widgets, we need to make sure that we enable only a limited set of features for each dock widget. We don't want the user to have full access to the more wayward abilities of Qt's dock widgets, as ably demonstrated by KDevelop. Select each dock widget in the Object list, and in the properties window below, make sure the selected 'Features' field is set to 'NoDockWidgetFeatures'. This will stop the user from dragging widgets out of the window, or closing them completely. You may want to enable this feature for the browser panel, but we'll leave that decision up to you.

The great thing about dock widgets is that the user can update the proportional difference between the two sides while the application is running.

The great thing about dock widgets is that the user can update the proportional difference between the two sides while the application is running.

Widget Panels

Before adding the other widgets, select both the dock widgets and click on the 'Lay Out Horizontally' button. Follow this with a click on the 'Lay Out in a Grid' button. This will have the effect of stretching both dock widgets across the application window with a separator between them in the middle. When the user resizes the main window, both widgets will stay in their relative positions.

Despite the grid being locked, we can still add widgets to either dock widgets in the normal way, and we're going to start with the left side. If you edit a GUI while the grid is locked, Designer will highlight the position where each widget is going to be inserted using a blue cursor, much like editing with a word processor. You need to drag three widgets into the left panel - a line edit widget and a push button, which we've aligned horizontally at the top of the window, and a tree view that sits beneath. The line edit widget will be where the user enters the URL of the RSS feed, the button will submit the feed to our parser, and the tree view will list each entry for our RSS feed.

We added a default URL to the line edit widget. Just double click on the widget and type something like 'http://www.tuxradar.com/rss', and change the push button text to read 'Add Feed'. Double click on the tree view, and add two further columns and give them names like 'Feed', 'Date' and 'URL'. These columns will contain information from each news story, but only the 'Feed' and 'Date' columns are going to be visible. This is because we want to use the URL column internally, and not show it to the user. It's going to store the URL of the story so that when the user clicks on it, we can send the URL to WebKit.

If we didn't use this trick, we'd have to implement a full-blown MVC solution for our application, and that would take more than four pages to describe in itself. MVC (Model/View/Controller) is a way of separating the data, our URL in this instance, from the view that displays it, while at the same time keeping the two items linked. That latter part is handled by a controller. When you use any of its container classes, Qt is using MVC in the background, and the methods that it uses for adding and removing items are really convenience functions that handle the MVC in the background. We're going to take advantage of this with our tree view, and use simply hide the URL column and use the data within our application, although we can only perform this trick from the source code.

Finally, drag the WebKit widget into the right side panel. This is a self-container browser window, and we don't need to add anything else to make it functional. Just make sure all your widgets are arranged appropriately, and that you've used a few spacers and the 'Lay Out in the Grid' mode on both panels to lock the layout to the scalable window.

Into the left side of the dock widget, we add the treeView, the URL for the XML feeds and the button that will grab the data from the internet.

Into the left side of the dock widget, we add the treeView, the URL for the XML feeds and the button that will grab the data from the internet.

Connections

Now that our layout is finalised, the next step is to add the slots/signal connections that will start to fill in the functionality of our application. Switch to the Signals/Slots editor by either pressing F4 or by clicking on its button in the toolbar. Drag a signal from the 'Add Feed' push button to the outline of the application window, and when the 'Configure Connection' window appears, click on the 'Edit' button on the right hand panel.

We need to add two slots. The first one that will add the feed to the tree view while the other will update the web view when we select a news story in the feed listing. We called the first 'fetch()', while the second one you need to add is called 'itemActivated(QTreeWidgetItem*)'. This is the first time we've come across parameters being passed through the signals/slots mechanism, and there are some strict rules on getting them to work from within designer. The most important is that for a signal to be connected to a slot while passing a parameter like this, both must support exactly the same type. In this instance, it's the QTreeWidgetItem type.

After creating both slots, and connecting 'clicked()' to 'fetch()', drag a new connection from the tree view to the window background. You'll see that many functions include the QItemTreeTree parameter as a single argument. This is the type for each item within the tree view, and passing it in this way will enable us to easily grab the URL for the currently selected news story and use this to update the web browser. Just connect the 'itemActivated(QTreeWidgetItem*)' on the left with the new slot of the same name we've just created for our own application.

This is a view of the SIGNAL and SLOT connections we make from Creator and use for various functions within the application.

This is a view of the SIGNAL and SLOT connections we make from Creator and use for various functions within the application.

Code

We've now built the framework, and it's now time to add the code. As with our other projects, we'll start with 'mainwindow.h' as this is where we'll need to add the new slots we've just created in the GUI. We're also going to add a new slot that we're going to use in the program logic, and this is going to tell our application that the web data has finished being read from the internet.

     void fetch();
     void itemActivated(QTreeWidgetItem * item);
     void readData(const QHttpResponseHeader &);

We now need to add some private members to our project. We're going to use these to manage the data flow and create the data structure for parsing both the XML data and the HTML data we grab from site's RSS feed.

    void parseXml();

    QString currentTag;
    QString linkString;
    QString titleString;
    QString dateString;

    QTreeWidgetItem *feed;

    int connectionId;
    QHttp http;
    QXmlStreamReader xml;

This is all we need to add to the header file. The rest of our coding it going to be constrained to the 'mainwindow.cpp' file, starting with the initialisation function at the top of that file. Firstly, we need to add a connection line before 'setupUi' that will automatically run our 'readyRead' method when we know that HTTP has been correctly parsed by Qt's HTTP grabber. Secondly, we want to hide two of the columns from the treeWidget, as we're only using these to store data and they're not supposed to be visible by the user. We can change the GUI in this way once it's been created by 'setupUi'. Here's what the code looks like:

    connect(&http, SIGNAL(readyRead(const QHttpResponseHeader &)), this,
            SLOT(readData(const QHttpResponseHeader &)));

    ui->setupUi(this);

    ui->treeWidget->setColumnHidden(1, true);
    ui->treeWidget->setColumnHidden(2, true);

We're now going to write the fetch function. This is what's triggered when we type the URL of an RSS feed into the application and click on the 'Add Feed' button.

void MainWindow::fetch()
{
        xml.clear();

        QUrl url(ui->lineEdit->text());

        http.setHost(url.host());
        connectionId = http.get(url.path());
}

This is relatively straightforward. First we clear the stream reading object that hold the xml date, and then transform the text in the lineEdit widget we use to hold the URL into a QUrl, which is Qt's preferred method of accessing online resources. We then use this resource to set the location for QHttp, which is a class that implements the HTTP protocol. We need to use this to grab the XML data, and this function is started in the next line using the 'get' function and our converted URL. When 'http' successfully opens the HTTP location, it will emit the 'readyRead' signal we connected to our own 'readData' function earlier. And it's this function we now need to add:

 void MainWindow::readData(const QHttpResponseHeader &resp)
 {
     if (resp.statusCode() != 200)
         http.abort();
     else {
         xml.addData(http.readAll());
         parseXml();
     }

 }

All this function is going is checking to see whether the URL has been found, if not it aborts and our application goes no further. But if the remote location is then legitimate, we first fill our xml container with data, xml.addData(http.readAll()), before sending that data to our 'parseXML' function. This is the tricky part of the application, as it's going to traverse the XML tree grabbed from the internet and place the chunks of data we need into the treeView. For that reason, it's a rather long chunk of code.

 void MainWindow::parseXml()
 {

     while (!xml.atEnd()) {
         xml.readNext();
         if (xml.isStartElement()) {

             if (xml.name() == "item"){

                 if (titleString!=""){
                    feed = new QTreeWidgetItem;
                    feed->setText(0, titleString);
                    feed->setText(2, linkString);
                    ui->treeWidget->addTopLevelItem(feed);

                 }

                 linkString.clear();
                 titleString.clear();
                 dateString.clear();
             }

             currentTag = xml.name().toString();
         } else if (xml.isEndElement()) {
              if (xml.name() == "item") {

                 QTreeWidgetItem *item = new QTreeWidgetItem(feed);
                 item->setText(0, titleString);
                 item->setText(1, dateString);
                 item->setText(2, linkString);
                 ui->treeWidget->addTopLevelItem(item);

                 titleString.clear();
                 linkString.clear();
                 dateString.clear();
             }

         } else if (xml.isCharacters() && !xml.isWhitespace()) {
             if (currentTag == "title")
                 titleString += xml.text().toString();
             else if (currentTag == "link")
                 linkString += xml.text().toString();
             else if (currentTag == "pubDate")
                 dateString += xml.text().toString();
         }
     }
     if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) {
         qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString();
         http.abort();
     }
 }

That chunk of code looks pretty intimidating but that's mainly because it contains several nested 'if' statements that are dealing with the different types of XML elements we're going to encounter in the RSS feed.

We start with reading each element in turn, and then checking to see whether that element is either a new item on the tree, the end of an element, or whether it contains the actual data of the element.

When the code detects the start of a new element in the XML feed, it sets 'currentTag' to the type of data contained within that element. We're only interested in the 'title', 'link' and 'pubDate' fields, and when the Xml stream moves on to the characters section of the file, the text for each field is moved into 'titleString', 'linkString' and 'dateString' according to how the currentTag type. When the end of an element is detected, we know that each if these strings have been populated and can copy the data into the 'treeView' object in our GUI. That's what the 'item->setText' lines are doing, and these are added to the top item in the treeview created to hold each RSS feed in the 'StartElement' section.

If you have difficulty getting your head around this function, it might be a good idea to run through the code using Creator's excellent debugger. If you set a breakpoint in this function, when the execution of the application reaches this section of code you'll be able to step through each line using the Debug menu and watch the value of the parameters you're interested in.

Finally, the last function we need to add deals with the user clicking on one of the RSS news items, loading the web page the item points to into our WebKit widget. This function is executed with the 'itemActivated' SIGNAL/SLOT connection we made in the GUI designer. We probe the URL from the tree widget item passed by the signal and send this data to the webView widget, casting it to a QUrl as we do so. Fortunately, all this functionality requires only two lines of code:

 void MainWindow::itemActivated(QTreeWidgetItem * item)
 {
     ui->webView->load(QUrl(item->text(2)));
     ui->webView->show();

 }
Try using the debugger to decipher some of the more complicated functions within our project.

Try using the debugger to decipher some of the more complicated functions within our project.

Running the application

And that's all the hard work out of the way. All that's left to do is save the project, build and run. Click on the 'Add Feed' button to add the default RSS feed we created in the GUI, and you should see that the tree view on the left populates with all the latest stories from TuxRadar.com. Click on any of these stories and the web viewer on the right will load the corresponding page.

But the best thing about this application is the way you can scale the two panels. Between the tree view and the web page, you should be able to find three small vertical dots. These can be dragged to the left or right to change the proportion of RSS feeds or the proportion of the web page being shown. Move this bar totally to the right and the web view closes completely. This is a great idea if you just want to see a list of feeds.

There are plenty of easy additions this application needs. We'd start with a refresh button that can be automated. This could add new feeds every hour or so, or manually refresh the list if you click on it. The application also desperately needs the ability to save its settings. This is a fairly major undertaking, and could be the subject for an entire tutorial in itself - why not give it a try yourself?

The finished app: Watch out Akregator, our RSS application is cross-platform and uses WebKit to render the web pages.

The finished app: Watch out Akregator, our RSS application is cross-platform and uses WebKit to render the web pages.

First published in Linux Format

Brought to you by the nice folks at Linux Format magazine

You should follow us on Identi.ca or Twitter


Your comments

Keep up the good work

These tutorials are fantastic. I tried out Qt a few months ago with zero guidance and couldn't quite figure out some of the finer details of the toolkit by myself. These tutorials have been exactly what I needed to figure out the parts I was screwing up on. Keep'em coming. :-)

That is and awesome project

I will try this out over the weekend. And see if I can make a hour lecture on this and show it off at my university. Thanks TuxRadar.

Bravo

This is what i was looking for, a complete Qt tut.. Tnx!!

Awesome

This is an awesome tutorial, I had been trying to do this in gtk but it didn't work very well.

RE: Missing includes...

Just came to post the same thing :)

During project creation, one should also select 'Network' and 'XML'. Or add them to the project file using 'QT += network', etc...

The layout was a pain in the rear. I got the dock working OK but the web view was being awkward sitting in the centralWidget, which was 'locked'. You have to apply the layout to the main window rather than the centralWidget control.

Also, itemActivated() doesn't respond unless I double-click... but I think that's down to the environment rather than Qt (e.g. KDE has single click?). Double click the parent node in an item for an interesting quirk the reader may want to fix.

Great article!

($14,965,286 kneip)

RE: Missing includes...

You're quite right about the omissions in the header file, sorry about that! The main problem was that we forgot to upload and link to the project's source code, which we had planned to use to fill in the blanks.

Our mainwindow.h has the following includes:

#include <QTreeWidget>
#include <QXmlStreamReader>
#include <QtNetwork>
#include <QDebug>
#include <QList>

We've now put a link to the source code at the top of this page. You can simply build the project from this when Qt is installed by untaring the file and typing 'qmake' followed by 'make'. Let us know if you have any more problems.

Errr...

- The tutorial talks about 2 Dock Widgets, but there is only one, then the layout's explanation is very confusing.
- The tutorial talks about a Tree View but it's a Tree Widget.

Thx for the tutorial.

typo

- When you say QItemTreeTree you should say QTreeWidgetItem

.user files can not get shared!

Hi TuxRadar!

Great work on the tutorial, but please do not distribute .user files with the sources. The information in that file can not get shared and it will screw up the project for your readers.

If you use the tar ball, please remove the .user file before opening the project in Qt Creator.

Best Regards,
Tobias

Having trouble with the tutorial

When I select the two dock widgets, the layout buttons are grayed out, so I cannot choose to Lay Out Horizontally or Lay Out In A Grid. Am I doing something wrong?

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