Home · All Classes · Main Classes · Deprecated

Creating your first MeeGo Touch application

This document explains, step-by-step, how to create a simple MeeGo Touch application. It is recommended that you have a basic knowledge of Qt framework, such as QObject and its signals and slots mechanism. On the other hand, even though MeeGo Touch framework is built on top of Qt's Graphics Framework (QGraphicsView, QGraphicsScene, QGraphicsItem, and so on) no prior knowledge of it is needed.

The complete source code of the finished "Music Catalogue" example application can be found in the following location:

libmeegotouch/examples/tutorial_music_catalogue

Adding an application class

Start by creating a new directory for the tutorial application and its main.cpp file.

The first object that you need to add is MApplication. It handles the main event loop and initialises some internal structures needed by the framework. Therefore, main.cpp should be:

#include <MApplication>

int main(int argc, char **argv)
{
    MApplication app(argc, argv);

    return app.exec();
}

The following section describes how to build and run the application.

Creating a project file and building the application

As MeeGo Touch UI framework is built on top of Qt, Qt's building system, qmake, is used.

1. To generate a project file with qmake, issue the following command from within the tutorial application directory.

$ qmake -project

This generates a project file (ending with .pro) in the current directory. Its contents should be:

######################################################################
# Automatically generated by qmake (2.01a) Tue Apr 20 14:00:01 2010
######################################################################

TEMPLATE = app
TARGET =.
DEPENDPATH += .
INCLUDEPATH += .

# Input
SOURCES += main.cpp

The project file is suitable for building plain Qt applications, but it does not work for MeeGo Touch applications yet.

2. To include the MeeGo Touch libraries, headers and compilation options in the build, add the following line to the project file:

CONFIG += meegotouch

You have now created a proper project file for MeeGo Touch.

3. Generate the actual build scripts (Makefile) from the project file:

$ qmake

4. Build the application:

$ make

For more information on qmake, see the qmake documentation.

Creating an application window

If you run your application now, nothing appears on the screen. The next step is to add the required code to create the main application window so that something is displayed.

In MeeGo Touch, the main window is provided by the MApplicationWindow class. Add the class and display it. The code of the minimal example application should now be:

#include <MApplication>
#include <MApplicationWindow>

int main(int argc, char **argv)
{
    MApplication app(argc, argv);
    MApplicationWindow window;

    window.show();

    return app.exec();
}

If you build and run it now, the applications should display a new window which contains bars and navigation controls at the top. Otherwise the window is relatively empty. The actual content of the application appears in the empty space. In MeeGo Touch, application content is organised into pages. For more information, see the following section.

Creating an application page

An application page covers the entire screen and includes the bars and controls as described in the previous section. It provides an unlimited area where a central widget is spread out. The page content is located inside the central widget. If the central widget is bigger than the screen, its entire content can still be accessed by panning the page.

The application page class is called MApplicationPage. Create an application page and assign a label (MLabel class) with the text "Hello World!" as its central widget to display some content on the page. As with all scene windows, you need to call its appear() method to display it on the window. To give the page a title, call its setTitle() method.

The main.cpp should now be:

#include <MApplication>
#include <MApplicationWindow>
#include <MApplicationPage>
#include <MLabel>

int main(int argc, char **argv)
{
    MApplication app(argc, argv);
    MApplicationWindow window;
    MApplicationPage page;

    page.setTitle("My First Page");
    page.setCentralWidget(new MLabel("Hello World!"));
    page.appear(&window);

    window.show();

    return app.exec();
}

Now the title of your page should be displayed in the navigation bar and the "Hello World!" text below it. You can also try to pan the page to see what happens. Since the text fits within the screen boundaries, the page bounces back to its original position after each panning gesture.

hello_world_ready.jpg

"Hello World!" application

This is the minimal code to get a "Hello World!" MeeGo Touch application working.

Further reading

For more information, see Scene and scene windows.

Navigating between pages

A MeeGo Touch application can have several pages. If an action is performed on one page, it may trigger the appearance of a new page. Since only one page can be displayed at any given time, this causes the current page to disappear.

When navigating between pages, you normally switch from a main page to child/sub pages, in a hierarchical fashion. This is known as the drill-down navigation pattern.

To illustrate this navigation pattern, this example shows how to modify the "Hello World!" application to emulate a simple Music Catalogue application.

music_catalogue_navigation.jpg

Navigation pattern of a "Music Catalogue" application

The previous figure illustrates that there are three different pages (from left to right): Main page, artist page, and album page.

The application starts by showing an application window which displays the main page. The main.cpp is:

#include <MApplication>
#include <MApplicationWindow>

// The definition of our data classes
#include "data.h"

#include "mainpage.h"

void fillOutData(QList<Artist *> &artistList);

int main(int argc, char **argv)
{
    MApplication app(argc, argv);
    MApplicationWindow window;
    MainPage *mainPage;

    // That's the data that will be displayed by our application.
    // For the sake of keeping the example as simple as possible we use
    // a very simplistic data structure.
    QList<Artist *> artistsList;
    fillOutData(artistsList);

    mainPage = new MainPage(artistsList);

    mainPage->appear(&window);
    window.show();

    return app.exec();
}

void fillOutData(QList<Artist *> &artistList)
{
    ...
}

It is not necessary to delete the mainPage later, since the appear() call transfers the ownership of the page to the underlying scene.

For information on the fillOutData() that adds hardcoded content, see here.

The data classes in data.h are:

// data.h
#ifndef DATA_H
#define DATA_H

#include <QString>
#include <QStringList>

class Album {
public:
    QString title;
    QString artist;
    QString coverArtFilename;
    QStringList songs;
};

class Artist {
public:
    virtual ~Artist() { qDeleteAll(albums); }

    QString name;
    QList<Album *> albums;
};

#endif

MainPage

The MainPage is a specialised version of MApplicationPage. It takes as a parameter in its constructor the list of artists and displays them as a list of buttons. When you tap an artist's button, a new page appears with their information.

music_mainpage.jpg

Main page of the "Music Catalogue" application

// Filename: mainpage.cpp
#include "mainpage.h"

#include <MButton>
#include <MButtonGroup>
#include <MLabel>
#include <MSceneManager>
#include <QGraphicsLinearLayout>

#include "artistpage.h"

MainPage::MainPage(QList<Artist *> artistsList, QGraphicsItem *parent)
    : MApplicationPage(parent), artistsList(artistsList)
{
    // OBS: in a real application you shouldn't hardcode strings used
    // in the GUI like that but use the translation system to fetch
    // the correct, localised, string instead.
    setTitle("Music Catalogue");
}

MainPage::~MainPage()
{
    qDeleteAll(artistsList);
}

// This is a virtual method that gets called only once by the framework,
// right before your page begins to appear on the scene.
void MainPage::createContent()
{
    // We want to organize our items in a single column. A vertical
    // layout enables us to easily achieve this arrangement.
    // Layout classes take care of setting the correct geometry (size and position)
    // of the items added to it.
    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    centralWidget()->setLayout(layout);

    // Items in a vertical layout are arranged from top to bottom.
    // Thus the label "Artists:" will come first, therefore serving
    // the title of our list.
    layout->addItem(new MLabel("Artists:"));

    MButtonGroup *buttonGroup = new MButtonGroup(this);

    Artist *artist;
    MButton *artistButton;
    for (int i = 0; i < artistsList.count(); i++) {
        artist = artistsList[i];

        artistButton = new MButton;
        artistButton->setText(artist->name);

        layout->addItem(artistButton);
        buttonGroup->addButton(artistButton, i);
    }

    // A MButtonGroup is used because it provides a signal that is emitted
    // whenever any of the buttons added to it gets clicked.
    // MButtonGroup groups the buttons logically only. Their graphical
    // positioning is still handled by the layout.
    connect(buttonGroup, SIGNAL(buttonClicked(int)),
            this, SLOT(displayArtist(int)));
}

void MainPage::displayArtist(int artistIndex)
{
    Artist *artist = artistsList[artistIndex];

    ArtistPage *artistPage = new ArtistPage(artist);

    // When the back button is pressed, the page gets dismissed.
    // By setting MSceneWindow::DestroyWhenDismissed we don't have to
    // keep tabs on this page since it will be automatically destroyed
    // after the dismissal.
    artistPage->appear(scene(), MSceneWindow::DestroyWhenDismissed);
}
// Filename: mainpage.h
#ifndef MAINPAGE_H
#define MAINPAGE_H

#include <MApplicationPage>

#include "data.h"

class MainPage : public MApplicationPage {
    Q_OBJECT
public:
    MainPage(QList<Artist *> artistsList, QGraphicsItem *parent = 0);
    virtual ~MainPage();

protected:
    // From MApplicationPage
    virtual void createContent();

private slots:
    void displayArtist(int artistIndex);

private:
    QList<Artist *> artistsList;
};

#endif

ArtistPage

The ArtistPage is very similar to MainPage. It takes an Artist object and displays the artist's albums as buttons. When you tap an album's button, a new page showing the album appears.

// Filename: artistpage.cpp
#include "artistpage.h"

#include <MButton>
#include <MButtonGroup>
#include <MLabel>
#include <MSceneManager>
#include <QGraphicsLinearLayout>

#include "albumpage.h"

ArtistPage::ArtistPage(const Artist *artist, QGraphicsItem *parent)
    : MApplicationPage(parent), artist(artist)
{
    setTitle(artist->name);
}

void ArtistPage::createContent()
{
    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    centralWidget()->setLayout(layout);

    layout->addItem(new MLabel("Albums:"));

    MButtonGroup *buttonGroup = new MButtonGroup(this);

    Album *album;
    MButton *albumButton;
    for (int i = 0; i < artist->albums.count(); i++) {
        album = artist->albums[i];

        albumButton = new MButton;
        albumButton->setText(album->title);

        layout->addItem(albumButton);
        buttonGroup->addButton(albumButton, i);
    }

    connect(buttonGroup, SIGNAL(buttonClicked(int)),
            this, SLOT(displayAlbum(int)));
}

void ArtistPage::displayAlbum(int albumIndex)
{
    Album *album = artist->albums[albumIndex];

    AlbumPage *albumPage = new AlbumPage(album);
    albumPage->appear(scene(), MSceneWindow::DestroyWhenDismissed);
}
// Filename: artistpage.h
#ifndef ARTISTPAGE_H
#define ARTISTPAGE_H

#include <MApplicationPage>

#include "data.h"

class ArtistPage : public MApplicationPage {
    Q_OBJECT
public:
    ArtistPage(const Artist *artist, QGraphicsItem *parent = 0);

protected:
    virtual void createContent();

private slots:
    void displayAlbum(int albumIndex);

private:
    const Artist *artist;
};

#endif

AlbumPage

AlbumPage is the last and innermost page, from the navigation point of view. It takes an Album object and displays its cover art, artist and song list.

music_albumpage.jpg

Album page of the "Music Catalogue" application

// Filename: albumpage.cpp
#include "albumpage.h"

#include <MLabel>
#include <MBasicListItem>
#include <MImageWidget>
#include <QGraphicsLinearLayout>

AlbumPage::AlbumPage(const Album *album, QGraphicsItem *parent)
    : MApplicationPage(parent), album(album)
{
    setTitle(album->title);
}

void AlbumPage::createContent()
{
    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    centralWidget()->setLayout(layout);

    MImageWidget *albumCover = new MImageWidget(new QImage(album->coverArtFilename));
    layout->addItem(albumCover);

    QString byArtist = QString("By: %1").arg(album->artist);
    layout->addItem(new MLabel(byArtist));

    layout->addItem(new MLabel("Songs:"));

    MLabel *songLabel;
    for (int i = 0; i < album->songs.count(); i++) {
        songLabel = new MLabel(album->songs.at(i));
        layout->addItem(songLabel);
    }
}
// Filename: albumpage.h
#ifndef ALBUMPAGE_H
#define ALBUMPAGE_H

#include <MApplicationPage>

#include "data.h"

class AlbumPage : public MApplicationPage {
    Q_OBJECT
public:
    AlbumPage(const Album *album, QGraphicsItem *parent = 0);

protected:
    virtual void createContent();

private:
    const Album *album;
};

#endif

Compiling

To compile the Music Catalogue application:

1. Update your project file with all the new sources and headers:

HEADERS += albumpage.h data.h mainpage.h artistpage.h
SOURCES += main.cpp mainpage.cpp artistpage.cpp albumpage.cpp

2. Regenerate the build script:

$ qmake

3. Build the application again:

$ make

Further reading

For the lists of the example application, MButtons in a vertical QGraphicsLayout were used. This approach is straightforward but if there are lists with hundreds or even thousands of items, it causes performance issues, to say the least. For those cases, use MList. Its usage is not as simple as in the example, but it gracefully handles lists of any size. To try out MList, read the MList documentation and refactor the "Music Catalogue" example to use MList.

For more information on drill-down and other navigation patterns, see Navigation patterns for applications.

Handling portrait and landscape orientations

In MeeGo Touch, the GUI can be rotated to four different angles on the screen (0, 90, 180 and 270 degrees clockwise). Angles 0 and 180 are in landscape mode (the width is greater than the height), 90 and 270 are in portrait mode (the height is greater than the width).

When changing from a landscape to a portrait orientation angle and the opposite, the available space for the GUI can change significantly (for example, a broad page can change into a very narrow one). Thus, a page may need different layouts for portrait and landscape, so that it looks nice and is usable in any orientation. To easily achieve that, MeeGo Touch provides the class MLayout.

Unlike a regular QGraphicsLayout where you add items directly to it, in MLayout items are added to layout policies and the policies are set to specific orientations of an MLayout (which refer to the landscape layout policy and the portrait layout policy of an MLayout).

The following example illustrates how to modify the AlbumPage of a Music Catalogue application to use a vertical layout when it is in a portrait orientation angle and horizontal layout when it is in a landscape orientation angle. See the following figure:

music_albumpage_layout_orientations.jpg

Different layouts for the AlbumPage according to the screen orientation

To achieve this, modify the implementation of AlbumPage::createContent(). Instead of having all elements (cover art, artist name and song list) in a single layout, there are two separate sub-layouts. One layout holds the cover art and the artist name, and the other one holds the song list. In a landscape orientation the two sub-layouts are arranged side by side, whereas in portrait orientation they are on top of each other.

#include <MLayout>
#include <MLinearLayoutPolicy>

...

void AlbumPage::createContent()
{
    MLayout *layout = new MLayout;
    centralWidget()->setLayout(layout);

    // Build a vertical layout that holds the cover art and the "By: Artist" label.
    QGraphicsLinearLayout *coverAndArtistLayout = new QGraphicsLinearLayout(Qt::Vertical);

    MImageWidget *albumCover = new MImageWidget(new QImage(album->coverArtFilename));
    coverAndArtistLayout->addItem(albumCover);

    QString byArtist = QString("By: %1").arg(album->artist);
    coverAndArtistLayout->addItem(new MLabel(byArtist));

    // Build a vertical layout that will hold the list of songs.
    QGraphicsLinearLayout *songsLayout = new QGraphicsLinearLayout(Qt::Vertical);
    songsLayout->addItem(new MLabel("Songs:"));
    MLabel *songLabel;
    for (int i = 0; i < album->songs.count(); i++) {
        songLabel = new MLabel(album->songs.at(i));
        songsLayout->addItem(songLabel);
    }

    // When in landscape orientation, have the cover and the songs list
    // side-by-side.
    MLinearLayoutPolicy *landscapePolicy = new MLinearLayoutPolicy(layout, Qt::Horizontal);
    landscapePolicy->addItem(coverAndArtistLayout);
    landscapePolicy->addItem(songsLayout);
    layout->setLandscapePolicy(landscapePolicy);

    // When in portrait orientation, have the cover and the songs list
    // on top of each other.
    MLinearLayoutPolicy *portraitPolicy = new MLinearLayoutPolicy(layout, Qt::Vertical);
    portraitPolicy->addItem(coverAndArtistLayout);
    portraitPolicy->addItem(songsLayout);
    layout->setPortraitPolicy(portraitPolicy);
}

Further reading

MeeGo Touch also provides methods to query and set the GUI orientation as well as signals that are emitted whenever the orientation changes. For more information, see Screen rotation.

Creating animations

Properties of graphical items can be easily animated using QPropertyAnimation class. This section explains how to animate the cover art image of an album page by making it fade in after the AlbumPage is displayed.

When a page is completely displayed, it emits the signal appeared(). The following example shows how to create a slot called fadeInAlbumCover() and connect it to the appeared() signal of the AlbumPage class.

The fadeInAlbumCover() slot is implemented as follows:

#include <QPropertyAnimation>

...

void AlbumPage::fadeInAlbumCover()
{
    QPropertyAnimation *fadeInAnimation = new QPropertyAnimation;
    fadeInAnimation->setTargetObject(albumCover);
    fadeInAnimation->setPropertyName("opacity");
    fadeInAnimation->setStartValue(0.0);
    fadeInAnimation->setEndValue(1.0);
    fadeInAnimation->setDuration(1000.0);
    fadeInAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}

This animates the opacity property of albumCover from 0.0 (fully transparent) to 1.0 (fully opaque) in one second (1000 milliseconds).

fadeInAlbumCover() slot is declared in albumpage.h as follows:

class AlbumPage : public MApplicationPage {
   ...
private slots:
    void fadeInAlbumCover();
   ...
};

Note: To enable AlbumPage::fadeInAlbumCover() to access albumCover, turn albumCover into a class variable of AlbumPage instead of an internal variable of AlbumPage::createContent().

Now you only need to connect the appeared() signal to the new fadeInAlbumCover() slot:

AlbumPage::AlbumPage(const Album *album, QGraphicsItem *parent)
    : MApplicationPage(parent), album(album), albumCover(0)
{
    ...
    connect(this, SIGNAL(appeared()), SLOT(fadeInAlbumCover()));
}

Further reading

Multiple animations can be combined into parallel or sequential animation groups which can form complex animation hierarchies. On top of that, MParallelAnimationGroup adds styling support for QParallelAnimationGroup, meaning that your animation classes can expose attributes through MeeGo Touch's theming system. Check put-tutorial-on-animations-here for details.

Gestures, multipoint touch and cancel event handling

MeeGo Touch supports multipoint touch screens. Therefore, interactions such as pinching gestures to zoom into and out of graphical elements can be easily implemented in MeeGo Touch applications.

The following example illustrates how to include gestures in the applications. In the example, the album cover image in the AlbumPage of our Music Catalogue application is made to support pinching gestures. By pinching the cover image you can zoom in and out.

We just have to reimplement the virtual method MWidget::pinchGestureEvent() to handle the pinching gestures.

Add the following to albumpage.h:

class QGestureEvent;
class QPinchGesture;

class AlbumPage : public MApplicationPage {
   ...
protected:
    // From MWidget
    virtual void pinchGestureEvent(QGestureEvent *event, QPinchGesture *gesture);
   ...
};

Add the following to albumpage.cpp:

#include <QGestureEvent>
#include <QPinchGesture>

...

void AlbumPage::pinchGestureEvent(QGestureEvent *event, QPinchGesture *gesture)
{
    static QPointF originalZoomFactor;

    if (gesture->state() == Qt::GestureStarted) {
        albumCover->zoomFactor(&originalZoomFactor.rx(), &originalZoomFactor.ry());

        // Disable panning while we're pinching the image
        setPannable(false);
    } else if (gesture->state() == Qt::GestureFinished ||
            gesture->state() == Qt::GestureCanceled) {
        // Re-enable panning after the pinching gesture has ended.
        setPannable(true);
    }

    albumCover->setZoomFactor(
            originalZoomFactor.x() * gesture->scaleFactor(),
            originalZoomFactor.y() * gesture->scaleFactor());

    // Force a repaint of the album cover.
    albumCover->update();

    event->accept(gesture);
}

QPinchGesture::scaleFactor() varies around 1.0. Values above 1.0 mean that the two fingers on the screen are further away from each other than when the gesture started. This causes the image to be stretched (zooming in). Values below 1.0 mean that the two fingers are closer to each other than when the gesture started, which causes the image to be shrunk (zooming out). The scaleFactor() is always relative to the state when the gesture started.

Further reading

You can also define other types of gestures besides pinching. For more information, see Gestures framework documentation of Qt.

Styling the application

MeeGo Touch provides a powerful styling engine for applications to customise their look and feel. You can specify, for instance, widget sizes, alignments, background images, and input feedback effects with CSS (Cascading Style Sheets) files.

Since MeeGo Touch also has themes, the look and feel can change completely from theme to theme. Applications can customise their look and feel for specific themes and/or the base one. This means that the custom styling is applied to the application regardless of the current theme.

This example illustrates how to change the background image of the Music Catalogue application pages. Instead of using the background image provided by the current theme, a new image is specified. To make things more interesting one background is set to be used when the pages are in landscape orientation and another one when they are in portrait orientation.

To achieve this, the following files are needed:

1. Create a CSS file with the same name as the binary file of the application. In this example, it is tutorial_music_catalogue.css. Its content are as follows:

MApplicationPageStyle.Landscape {
    background-image: "music-catalogue-background-landscape"
}

MApplicationPageStyle.Portrait {
    background-image: "music-catalogue-background-portrait"
}

2. MeeGo Touch supports both vector (SVG files) and raster images (such as JPEG or PNG files). This example illustrates how to use an SVG image.

Create an SVG file containing two rectangles: one whose ID is "music-catalogue-background-landscape" and the other "music-catalogue-background-portrait". MeeGo Touch uses the SVG ID attribute to identify SVG elements, not the name of the SVG file itself. Apply different colours or gradients to those two rectangles. Their proportions are not important as MeeGo Touch resizes them appropriately.

Note:
If you do not have an SVG editor you can download Inkscape from http://www.inkscape.org/ or simply use the SVG file from the finished example mentioned at the beginning of this document.

Put the two files in a location where MeeGo Touch can find them. To apply them to a given application in all themes, the available locations are:

<system-theme-directory>/base/meegotouch/<application-binary-name>/style/<application-binary-name>.css
<system-theme-directory>/base/meegotouch/<application-binary-name>/svg/*.svg

Thus, for the example application, in a Linux system, locations are:

/usr/share/themes/base/meegotouch/tutorial_music_catalogue/style/tutorial_music_catalogue.css
/usr/share/themes/base/meegotouch/tutorial_music_catalogue/svg/tutorial_music_catalogue.svg

After that, the pages of your application should have the backgrounds depicted in the SVG file. If bright red backgrounds are shown instead, MeeGo Touch was not able to find the resources specified in your CSS. If nothing changes at all, MeeGo Touch may not have been able to find your CSS file.

Further reading

For more information, see the Styling.


Copyright © 2010 Nokia Corporation
MeeGo Touch