Your first MeeGo Touch application

This document explains, step-by-step, how to create a simple MeeGo Touch application. Knowledge of the basics of Qt framework, such as QObject and its signals and slots mechanism, is recommended. On the other hand, even though MeeGo Touch framework is built on top of Qt's Graphics Framework (QGraphicsViewQGraphicsSceneQGraphicsItem, etc) 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

Application class

Let's start by creating a new directory for our tutorial application and creating its main.cpp file.

The first object to be added is MApplication. It handles the main event loop and initializes some internal structures needed by the framework. main.cpp should therefore look like the following:

#include <MApplication>

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

    return app.exec();
}

In the next step we will already build and run it to see what happens.

Creating project file and building for the first time

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

First we tell qmake to generate a project file for us. Issue the follwing command from within the tutorial application directory.

$ qmake -project

That will generate a project file (ends with .pro) in the current directory. Its contents should look like the following:

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

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

# Input
SOURCES += main.cpp

That project file is suitable for building plain Qt applications but won't work as it is for MeeGo Touch ones. To bring in the MeeGo Touch libraries, headers and compilation options into the build all that is needed is the addition of the following line to the project file:

CONFIG += meegotouch

Now that we have our project file properly set, let's generate the actual build scripts (Makefile) from it.

$ qmake

And then finally build it.

$ make

For more information on qmake, check qmake's documentation.

Application window

If you run your application now, nothing will appear on the screen. Thus the next step is to add code to create our main application window so that we can get something displayed.

In MeeGo Touch, the main window is provided by the MApplicationWindow class. So let's add one and show it. The code of our minimal application should now look like this:

#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, a new window should appear containing on its top edge some bars and navigation controls. Otherwise the window is pretty much empty. That empty space is to be occupied by the application's actual content. In MeeGo Touch, application content is organized into pages, which is the topic of our next section.

Application page

An application page takes the entire screen and is covered by the bars and controls you've seen in the last section. It provides an unlimited area where a central widget is spread out. The page content goes inside this central widget. If the central widget happens to be bigger than the screen, all its content can still be accessed by panning the page.

The application page class is called MApplicationPage. Let's create one and assign a label (MLabel class) with the text "Hello World!" as its central widget so that we get to see some content. As with all scene windows (more on that soon), to get it displayed on our window we have to make it appear by calling its appear() method. Also it would be a good idea to give our page a title. You can do so by calling its setTitle() method.

With the addition of the application page our main.cpp should now look like the following:

#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 you should be able to see the title of your page being 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

Understand the MeeGo Touch scene and its scene windows.

Navigating between pages

A MeeGo Touch application can have several pages. An action done in a page may programatically trigger the appearance of a new one, which will cause the current one to disappear as only one page can be displayed at any given time.

Navigation between pages is most commonly done from a main page to child/sub pages, in a hierarchical fashion. This navigational pattern is known as "Drill Down".

To showcase this navigational pattern we will modify our "Hello World!" application to emulate a simple Music Catalogue app.

music_catalogue_navigation.jpg

Navigational pattern of our "Music Catalogue" application

From the figure above you can see that we have three different pages (from left to right): Main page, artist page and album page.

Our application start by showing an application window with our main page appearing in it. That's how our main.cpp will look like:

#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)
{
    ...
}

We don't have to worry about deleting mainPage later since the appear() call transfers the ownership of the page to the underlying scene.

For an implementation of fillOutData() that adds some hardcoded content check here.

Our data classes in data.h:

// 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

Our MainPage is a specialization of MApplicationPage. It takes as a parameter in its constructor the list of artists and it displays then as a list of buttons. When an artist's button gets clicked, a new page showing his information appears.

music_mainpage.jpg

Main page of our "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 his albums as buttons. When an album's button gets clicked 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 our last and innermost page, navigation-wise. It takes an Album object and displays its cover art, artist and song list.

music_albumpage.jpg

Album page of our "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

Update your project file with all those new sources and headers:

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

Regenerate the build script

$ qmake

And build it again.

$ make

Further reading

For our lists we used MButtons in a vertical QGraphicsLayout. This approach is straightforward but if our lists had hundreds or even thousands of items we would suffer significant perfomance issues, to say the least. For those cases MList should be used. Its usage is not as simple as what we did but it gracefully handles lists of any size. Read MList documentation and refactor the "Music Catalogue" example to use MList.

Read about Drill Down and other navigational patterns in: Navigation within your applications

Dealing with different screen orientations (portrait vs. landscape)

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

When changing from a landscape to a portrait orientation angle and vice-versa the available space for the GUI can change significantly (e.g., from being a broad page to be a very narrow one). Because of that, a page might 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 classMLayout.

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

Now let's modify the AlbumPage of our Music Catalogue application to make it use a vertical layout when in portrait and a horizontal one when in landscape, like shown in the figure below:

music_albumpage_layout_orientations.jpg

Different layouts for the AlbumPage according to the screen orientation

To achieve this we only have to modify the implementation of AlbumPage::createContent(). Instead of having all elements (cover art, artist name and song list) in a single layout we will have two separate sub-layouts: one holding the cover art and artist name, the other holding the song list. When in landscape orientation the two sub-layouts will be arranged side-by-side and in portrait orientation they will be 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 get emitted whenever the orientation changes. Read Screen Rotation for more information.

Animations

Properties of graphical items can be easily animated using QPropertyAnimation class. To exemplify this let's animate the cover art image of our album page by making it fade in after the AlbumPage gets displayed.

When a page finishes appearing it emits the signal appeared(). What we will do is create a slot called fadeInAlbumCover() that we will connect to the appeared() signal of our AlbumPage class.

That's the implementation of our fadeInAlbumCover() slot:

#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);
}

It will animate 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 in the following way:

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

Note that albumCover has to be made a class variable of AlbumPage instead of an internal variable of AlbumPage::createContent() so that AlbumPage::fadeInAlbumCover() can access it.

Now the only thing left is connecting the appeared() signal to our 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 put together into paralallel or sequential animation groups. Animation groups can then be combined to 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 and Multitouch

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

As an example on how gestures programming work, we will make the album cover image in the AlbumPage of our Music Catalogue application support pinching gestures. By pinching the cover image you will be able to zoom it in and out.

The first step is to tell the framework that AlbumPage is interested in getting touch events and to subscribe it to receive pinching gestures:

AlbumPage::AlbumPage(const Album *album, QGraphicsItem *parent)
    : MApplicationPage(parent), album(album), albumCover(0)
{
    ...
    setAcceptTouchEvents(true);
    grabGesture(Qt::PinchGesture);
}

Then we need to reimplement the virtual method MWidget::pinchGestureEvent() in order to handle the pinching gestures.

Addition to albumpage.h:

class QGestureEvent;
class QPinchGesture;

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

Addition 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 means that the two fingers on the screen are further away from each other than in the moment the gesture started, thus we're stretching the image (zooming in). Values below 1.0 means the the two fingers are closer to each other than in the moment the gesture started, thus we're shrinking the image (zooming out). The scaleFactor() is always relative to the state when the gesture started.

Further reading

There are other gestures besides pinching and it's also possible to define new types of gestures. Check Qt's gestures framework documentation.

Styling

MeeGo Touch provides a powerful styling engine for applications to customize their look & feel. You can specify things such as widget sizes, alignments, background images and input feedback effects via CSS (Cascading Style Sheets) files.

MeeGo Touch also has themes, thus the look & feel can change completely from theme to theme. Applications can customize their look & feel for specific themes and/or the base one (meaning that the custom styling will be applied to the application regardless of what's the current theme).

Let's change the background image of our Music Catalogue application pages. Instead of using the background image provided by the current theme we will specify our own. To make things more interesting we will set one background to be used when our pages are in landscape orientation and another one for when they're in portrait.

To achieve this wee need provide two files:

  • A CSS file telling MeeGo Touch that our application pages will use different background images.
  • The file (or files) with our custom background images.

Create a css file with the same name as the binary file of the app. In our case it will be tutorial_music_catalogue.css. Its content will be the following:

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

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

MeeGo Touch supports both vector (SVG files) and raster images (such as JPEG or PNG files). In this example we explain 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 colors or gradients to those two rectangles. Their proportions are not important as MeeGo Touch will resize them appropriately.

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

Now we have to put those two files in a location where MeeGo Touch can find them. To get them applied to a given application in all themes the 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 our application, in a linux system, those locations will be:

/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 they happen to get bright red backgrounds instead it means that MeeGo Touch wasn't able to find the resources specified in your CSS. If nothing changes at all it might mean that your CSS file wans't even found in the first place.

That completes the tutorial.

Further reading

Read the Styling page for more information.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值