Comments, Code and Qt. Some words about the wonderful world of software engineering

12Dec/117

Porting MeeGo Qt Components apps to Symbian

Of course I don't have all the answers to what needs to be done for any given MeeGo Qt Component based application to have it running on Symbian, but I think I have three general actions that need to be done when porting an app from MeeGo to Symbian; have it run with the resources, make the code compile and tweak the UI.

Thanks to Symbian Annan both MeeGo and Symbian run Qt 4.7.4 with Qt Quick and Qt Components which is of course the reason why this kind of porting is even possible with a reasonable effort. As I wrote in my earlier blog post, even though both Symbian and MeeGo run Qt and Qt Components, it's not perfect. I think too much manual work still has to be done, but it's a reasonable effort and when you read through my hints, I think the tasks ahead become clearer.

Have it run with the resources

You have your MeeGo Qt app open in Qt Creator and you aim to have it deployed on the Symbian device to start with and hopefully show at least some UI, right. There are a few steps to check to start with.

The Symbian specific .pro file

In the Qt project file, the .pro, you can define sections specific to the platform that the project is currently being built for. You should put all the Symbian specific Qt or other platform definitions in a section defined with symbian: {}. There you would also put all the same general Qt definitions as before, like SOURCES, HEADERS or OTHER_FILES if you have Symbian specific declarations (which you probably have but I will come back to that later) for these Qt generic definitions.
The Symbian specific section in your .pro file looks like this.

symbian: {
     SYMBIANVARIABLE += SOME_STUFF
     SOURCES += mysymbianengine.cpp \
                mysymbinaobject.cpp
     HEADERS += ...
}

In this symbian: {} section you also need to put a few Symbian specific values that are used when creating a Symbian app by qmake.

You might ask what about all the MeeGo specific definitions that your project already has. Can we put them in a similar harmattan section? That's a good question and the answer is unfortunately that MeeGo Harmattan does not have an existing section label. So you need to circumvent this by going back to checking if the platform is Maemo and if the MeeGo version number is set. Like this (yes, ugly) and put all MeeGo specific definitions in this section.

maemo5 | !isEmpty(MEEGO_VERSION_MAJOR): {
     LIBS += -llinuxspecific
     system(cp meegostuff/icon_meego.png ../icon.png)
}

Remember to still keep all the definitions that apply to both MeeGo and Symbian outside of both of these definitions, such as QT +=, CONFIG += and MOBILITY += that you might share in common between the MeeGo and Symbian versions.

Let's get back to adding stuff that is Symbian specific. The most obvious one is the TARGET.CAPABILITY definition. Here you define all platform capabilities that your application requires. This is similar to what Android has, but the difference from MeeGo is that for example accessing the network requires a capability and needs to be defined here. The same thing if you want to access the location service and so on. Check from the link earlier what capabilities your app needs and define them here. I only had to specify the network capability.

TARGET.CAPABILITY += NetworkServices

Each Symbian app has its own UID number. If you submit your app to the Nokia Store, they will assign a UID for your app but to start with the UID is generated by Qt Creator (you need to create a new Qt based Symbian app to have a new project template generated with the UID defined). You need to define your app's UID in the .pro file like so inside the symbian: definition:

TARGET.UID3 = 0xABABABAB

Last but definitely not least we have the Symbian stack and heap size declarations. I don't have a right answer to this, but basically you need to tell your Qt app that on Symbian it has to allocate enough heap and stack memory to be able to run. What I did here is I only specified the heap size with a value I got from @zchydem which seemed to work. So you can probably do the same :)

TARGET.EPOCHEAPSIZE = 0x20000 0x2000000

That's more or less all the Symbian specific changes that you have to do for your .pro file. Eventually you will probably define different or additional source and header files, so that Symbian specific files are included into the build while MeeGo specific ones are not (and MeeGo specific ones are in their own section as mentioned above).

Application resources

In Qt you can bundle files with your application with the Qt resource system and add files to the Resource Collection File (.qrc) and reference the resources later from your code. I suggest you do this with any QML, image or other type of file you want to ship with your application. If you do so you don't need to worry about deployment differences between Symbian and MeeGo. You just define one .qrc file to be used in MeeGo, with a given set of files, and another .qrc files in Symbian with another set of files. So for Symbian I have  in the symbian: section the following definition that includes all the resources that should be shipped with the Symbian app (that probably shares many, if not all, resource from the MeeGo version).

RESOURCES +=  res_symbian.qrc

Make the code compile

So far we've just been preparing the code to run on Symbian. Now it's the time to see if it even compiles. If you have only pure Qt C++ and no platform specific code your code will probably compile just fine and your good to go. But then again there can be platform differences, for example Qt Concurrent is not available in Symbian. Or you've might used some other MeeGo specific library that simply doesn't exist on Symbian. These both cases applied to my application, so I want to just present my way of dealing with porting the C++ part to Symbian. Obviously the goal is to have as far as possible a common code base, but see to it that the code compiles properly on both MeeGo and Symbian.

It is probably not possible to escape using #ifdefs in your code when porting apps between platforms. They make the code harder to understand, but that is the way to determine if the code is being compiled for a certain platform. For example the section below will only be compiled if compiling the code in Linux.

#ifdef Q_OS_LINUX
// The Linux Podcast manager
m_instance = new PodcastManagerMeego(parent);
#endif

And similarly for Symbian by replacing LINUX with SYMBIAN. Armed with this tool, we can combine it with some basic object-oriented programming techniques.

In my case most of a class called PodcastManager was compiling fine in Symbian but I've used some MeeGo specific libraries that would not work in Symbian. What I needed to do was to separate at compile time the MeeGo specific libraries from the Symbian code. The straightforward naive approach would have been to use #ifdefs around or inside the methods that have the offending code for Symbian. But instead I created a separate class, called PodcastManagerMeego, where I put all the methods that had MeeGo specific library usage. The PodcastManagerMeeGo class would inherit the base class, called PodcastManager, where all the generic functionality for the manager was that would run just fine on both MeeGo and Symbian. I did the same with PodcastManagerSymbian - it would contain all Symbian specific library usages or even have Symbian specific methods.

Now I had to make the Linux version of the app to use the PodcastManagerLinux version of the class and the Symbian version use the PodcastManagerSymbian class respectively. To make this happen I used the factory, or actually the singleton, design pattern. Thanks to these object-oriented design patterns I could isolate the usage of the ugly #ifdefs to just one method, while the rest of the code differences are handled at runtime thanks to object-orientation. Beautiful! The code that needed a PodcastManager would request one, but it would not know which one it would get. Then thanks to polymorphism the real method, Linux or Symbian one, that is needed would be resolved at runtime. The same would apply to any other platform specific code that would be isolated into platform specific classes and you continue to do so until all non-generic platform specific code is isolated in their own classes.

This is when the symbian: specific section in the .pro file comes in; I included the generic podcastmanager.h|.cpp files in both MeeGo and Symbian but depending on the platform I added to SOURCES and HEADERS variables the platform specific files to compile in.

I think this is a design approach that should be striven after when porting apps from one platform to another. Of course in many cases it's more work than here, but the basic idea is that the main logic of the app doesn't know which platform methods it is calling.

Here is essentially what I did in my case. First I defined the classes and isolated all platform specific code.

// podcastmanagersymbian.h
#include "podcastmanager.h"
/* All the Symbian specific includes. */

class PodcastManagerSymbian : public PodcastManager {
// All Symbian specific methods and library usages.
};

// podcastmanagermeego.h
#include "podcastmanager.h"

/* All the Linux specific includes. */

class PodcastManagerMeego : public PodcastManager {
// All MeeGo specific methods and library usages.
};

Then I ensured that the code that before was instantiating the base class directly, PodcastManager, would not compile anymore because it needs to use either of the platform specific subclasses. When this is done it is easy to hunt down the places in the code that needed attention. At the same time I introduced a factory method which can create PodcastManager objects of the correct type for the caller. The caller knows only about the PodcastManager class.

class PodcastManager : public QObject
{
Q_OBJECT
public:
static PodcastManager* getInstance(QObject *parent);

/* More */

protected:
explicit PodcastManager(QObject *parent = 0);

/* And more */

};

And last hide all the nasty #ifdefs into this one method that will be creating the objects.

// Initialize the static instance of this class.
PodcastManager* PodcastManager::m_instance = NULL;

PodcastManager * PodcastManager::getInstance(QObject *parent = NULL)
{
if (m_instance == NULL) {
#ifdef Q_OS_LINUX
// The Linux Podcast manager
m_instance = new PodcastManagerMeego(parent);
#endif
#ifdef Q_OS_SYMBIAN
// The Symbian Podcast manager
m_instance = new PodcastManagerSymbian(parent);
#endif
}

return m_instance;
}

And the calling code doesn't need to care if we are using MeeGo or Symbian!

PodcastManager *m_pManager = PodcastManager::getInstance(this);

Tweak the UI

Unfortunately Qt Components is not fully platform independent (although it could be). There are some steps to go through when having Qt Components ported over from MeeGo to Symbian.

The includes

For some reason Qt cannot itself determine which Qt Component library it should load on MeeGo or Symbian, so there are two separate include statements for MeeGo and Symbian. For MeeGo

import com.nokia.meego 1.0
import com.nokia.extras 1.0

and for Symbian respectively

import com.nokia.symbian 1.1
import com.nokia.extras 1.1

It is worth noticing that the QtQuick import itself doesn't need to be changed. For any QML file with Qt Components you must define either the MeeGo import or the Symbian import.

I had a wish that I could handle this in some clever way instead of copying all the QML files and then replacing the imports manually. Unfortunately I found out that this was the best way to go forward. I created a separate QML directory for Symbian QML files, I copied all the original MeeGo QML files there and replace the import statements to suit the Symbian platform.

Now when I load the QML UI in C++ I need to load the correct file (this is inside a QDeclarativeView derived class).

#ifdef Q_OS_LINUX
setSource(QUrl("qrc:/qml_harmattan/main.qml"));
#endif

#ifdef Q_OS_SYMBIAN
setSource(QUrl("qrc:/qml_symbian/main.qml"));
#endif

Because all the Symbian files are now in their own folder, the rest of the UI will load, thanks to QML, the correct files as needed. Although you now have two sets of UI files I think it's worth doing because you anyway need to tweak the UI files depending on the platform, as we'll see below, and trying to do it in some clever way would be more complicated.

The components

After you've changed the import statements to platform specific ones, you can probably already see some UI if you deploy the app on a Symbian Annan or Belle device. But maybe not. The reason is that the Qt Components widgets nor the API for common widgets is the same. I can't quite understand this, but you need to essentially port the Qt Component widgets to suite Symbian. Below are the differences I noted when coming from MeeGo to Symbian:

  • ToolbarLayout in Symbian contains ToolBottons, while in MeeGo ToolIcons. Just replace ToolIcon with ToolBotton and change the variable to define the icon from iconId to iconSource. See the toolbar icon names for Symbian from here.
  • You don't need to define anchors for the buttons inside a ToolbarLayout.
  • A InfoBanner is shown in Symbian with the open() method, so just change show() to that.
  • The Sheet component is not available in Symbian. The source code for the MeeGo version is available here and porting it to Symbian is not that difficult. You just need to define your own values for the platform style variables and define a background for the component.
  • The CountBubble widget is not available in Symbian. The source code for the MeeGo version is available here, but I did not try to port it as it had too many for my taste MeeGo platform specifics. You just have to create your own or comment it out.
  • The QueryDialog widget has a bug in Symbian that it will cut your text you set to it if you don't add extra line spacing after the text.

Considering how large the Qt Components library is, it's really great to see that most of the Qt Component QML files worked without much changes!

The layouts

Here we get back again to the point of having Symbian QML files separately. The display sizes of the current Symbian devices are just smaller than the one on the N9. And if you are like me you've layouted your views with pixels to at least some degree, and you specified manually text sizes. So you really need to tweak the QML files to match the Symbian's smaller screen size. This is probably the thing that will take most of the time for you (after you notice the gotchas as mentioned above :)).

Also the default background colors are different. In MeeGo the default background color is light, in Symbian it's black. This may cause headache if you assume some color in some view.

Technorati Tags: , , , , ,

  • http://devhell.ro DevHell

    Great article, but you have a small error :D : to include Qt Components in MeeGo, use
    import com.nokia.meego 1.1

    • http://www.johanpaul.com/blog/ Johan Paul

      Oh! Well the 1.0 version works for me on N9 :) But I should probably change them to 1.1 if that works on N9 already.

    • http://www.johanpaul.com/blog/ Johan Paul

      Heh, I only now realized what you meant! Sorry about that! I fixed the typo. I must have been too tired when I finished the text :)

  • Blakharaz

    My solution to the different Qt Components was to not import them directly but have abstraction components. They can also level the different APIs.

    E.g.

    import “../qtcomponents”
    Page {
       Button {}
    }

    And then have two directories (one for MeeGo and one for Symbian) which are copied into qtcomponents at build time.
    E.g. the Button.qml would be

    import com.nokia.meego 1.0 as QtComp
    QtComp.Button {}

    and

    import com.nokia.symbian 1.1 as QtComp
    QtComp.Button {}

    That way you can use the same code for MeeGo and Symbian, it’s just a little complicated to maintain because you have to do that for each type you use.

    • http://www.johanpaul.com/blog/ Johan Paul

      Thanks for sharing this tip!

      I had something like this in mind too, but I did not go for it because
       – I would have to do it for every component I used
       – Not all components are available for Symbian
       – I anyway had to tweak the UI for Symbian

      And especially the last point was the main reason I decided that just having two sets of files was better for me.

  • riverlake

    Hello, friend, can you give me some suggestion of how to compile a
    whole meego image for N9 or N950 from sourcecode?

    I searched and browsed all the materal on google, but only find some material
    on N900, such as http://repo.meego.com/MeeGo/builds/1.2.90/latest/builddata/image-configs/,
    but couldn’t find how to build N9 or N950? for only ks files for N900 found in
    repo.meego. There’re also some web links to desribe how to assmble an image
    using the already compiled rpms, but that’s not what I’m looking for. I want to
    learn how to build a N9 from scratch.

    Any body can help me? I can send you one N9 for your helf once it’s proven
    workable.

    BTW, I’m a fans of meego, and I’ve one N9 and N950. my email is: riverlakezhao@sina.com

    • http://www.johanpaul.com/blog/ Johan Paul

      Not sure how much I can help, but here goes. MeeGo as such is dead. MeeGo Harmattan that Nokia used for N9 and N950 is closed source for many parts. This means there’s no way to compile a whole MeeGo for N9 or N950.