A Peek into Maemo 6 UI Framework, Part III – Layouts

One of the greatest thing in Qt has always been a sophisticated layout management. There are actually two different layout systems in Qt: a layout management for QWidget based widgets and a layout management for QGraphicsWidget based widgets. This article will focus on the latter one, but the basic idea in both layout management systems is similar – the application developer doesn’t need to hardcode widget’s size and position in relative its parent, all this is done automatically by the Qt. Qt also tries to fill in all the available space so for example when resizing the application window, the layout always fills the free space (I know this can be prevented also). At this point if you are not familiar of Qt’s layout management it is worth to spend some time with reading documentation  from here.

Layout Management Basics

I will go through some basic ideas behind the Graphics View Framework’s layout management in this article. Some of them also applies to QWidget based layout management. Before going to the layouts in more detail, here are couple of things to remember. Only QGraphicsLayoutItem based items are possible to insert to the QGraphicsLayouts. This means that you can’t add there QGraphicsItem based items, but you can add there QGraphicsWidget based items.

One reason for this is that QGraphicsItem doesn’t have methods for size hint and for size policies which are heavily used by QGraphicsLayouts. If you check the inheritance tree of the layout classes, you can notice that actually each layout is also a QGraphicsLayoutItem. This makes possible to have nested layouts.

One more thing to remember is that if you change the existing layout to the new layout, all the items in the old layout will be deleted… But hey there is also a workaround for that! You can call this QGraphicsLayoutItem::setOwnedByLayout ( false ) method in order to avoid item deletion when the layout is deleted.

There are three pre-defined layouts: QGraphicsGridLayout, QGraphicsLinearLayout and QGraphicsAnchorLayout in QGraphicsView based layout system. The QGraphicsGridLayout is quite self explanatory i.e. you can layout items in a grid. QGraphicsLinearLayout provides a layout where items can be placed in a layout either in vertically or horizontally. The QGraphicsAnchorLayout provides a layout for anchoring e.g. two widgets together. You can also do nested layouts.  Once again a picture tells you more so here is an example:

An example of nested layouts.

An example of nested layouts.

In the example picture there is one top level, horizontal linear layout which contains two other layouts. The first nested layout is a vertical linear layout with three buttons on top of each others and the second nested layout is a grid layout with 3 rows and 2 columns. It is up to the developer to design this. In the end a layout needs to be set to a widget for example you can set a layout directly to QGraphicsWidget and add that widget to the QGraphicsScene. Btw, the layout doesn’t own the items that are added in it. The widget that contains the layout is a parent for all the items in a layout.

So what do we gain by using layouts? The order of the items is always the same, you can resize the application window and the layout management takes care that each button is in right position. Believe me, it makes life a lot easier. Btw, this same ideology works in QWidget based layout management also.

Maemo 6 UI Framework – Layouts

Now you should have a some kind of idea what you can do with the layouts in Qt. How do the Maemo 6 UI Framework’s layouts work then? In the beginning I need to say that you can still use the QGraphicsLayout based layouts in Harmattan platform also. Nothing prevents you to use QGraphicsLayout! That’s about the code compatibility and let’s end the discussion here:) I don’t want to raise similar discussion than last time (see this link) even though it’s always good to raise discussion.

In Maemo 6 UI Framework, there is a base layout class called DuiLayout. DuiLayout is a subclass of the QGraphicsLayout so it extends its functionality at least with two major things: it requires that items are added to DuiAbstractLayoutPolicy based policy or policies and it provides animations. So what is a policy then? A policy can be compared e.g. to some concrete layout like QGraphicsLinearLayout, but instead of adding the policy to the widget, you set a policy to the DuiLayout.

So what is the difference to compared to QGraphicsLayout then? You can have one item in more than one policy and you can switch between policies on the fly. You can also have an item only in one policy and when that policy is changed to another policy the item will be hidden. If the policy where item belong is set to the active policy again, the same item will be shown. With this approach you can define two different policies for different orientation modes e.g. one policy for landscape and the other policy for portrait. DuiLayout also makes possible to animate layout switching, but the animation can also be disabled. To make things more clear let’s take a small code example how to use this layout system:

    ...
    //Create a DuiLayout and the policies
    DuiLayout *layout = new DuiLayout;
    DuiLinearLayoutPolicy * portrait= new DuiLinearLayoutPolicy(layout, Qt::Horizontal);
    DuiLinearLayoutPolicy * landscape = new DuiLinearLayoutPolicy(layout, Qt::Horizontal);

    // Add DuiLabels to the both policies
    for (int i = 0; i < 3; i++) {
        DuiLabel *label = new DuiLabel(QString("Item %1").arg(i + 1));
        label->setAlignment(Qt::AlignCenter);
        portrait->addItem(label);
        landscape->addItem(label);
    }

    // Widget for inner layout
    QGraphicsWidget *widget = new QGraphicsWidget;

    // NOTE: QGraphicsGridLayout
    QGraphicsGridLayout *innerLayout = new QGraphicsGridLayout(widget);
    for (int i = 0; i < 4; i++) {
        DuiLabel *label = new DuiLabel(QString("Inner Item %1").arg(i + 1));
        label->setAlignment(Qt::AlignCenter);
        innerLayout->addItem(label, i / 2, i % 2);
    }

    // Add the widget to the landscape policy only, so that the innerLayout
    // is hidden when device is rotated
    landscape->addItem(widget);
    layout->setLandscapePolicy(landscape);
    layout->setPortraitPolicy(portrait);

    // Attach the layout to the page
    page.centralWidget()->setLayout(layout);
    page.appear();
    ...

For me this looks pretty nice approach and as you can see you can even mix the Qt Graphics View layouts with the Dui layout stuff. Not bad at all. It also gives you free eye-candy with animations. At least for me, as a developer, I appreciate this kind functionality provided by the platform. At the time of writing this article there were four different policies available according the doc: DuiLinearLayoutPolicy (vertical/horizontal), DuiGridLayoutPolicy, DuiFlowLayoutPolicy and DuiFreestyleLayoutPolicy. Nothing prevents you yo provide a custom policy if none of the default policies suite for your purposes.

Code Review

This is a technical review so let’s check what layout system’s source code contains. Below you can see a draft class diagram how classes interact with each others. I have written a small (not perfect) description of each class and after that I show some findings of design and implementation issues.

A class diagram of DuiLayout system

A class diagram of DuiLayout system

DuiLayout

DuiLayout inherits QGraphicsLayout class and extends it quite a lot because of the policy and animation support. I think it is quite natural solution to inherit QGraphicsLayout  because then it is possible to mix-use with QGraphicsLayout and DuiLayout(policies). As shown in the previous example you can set one or more DuiAbstractLayoutPolicies to DuiLayout. DuiLayout depends on several different classes as you can see from the diagram.

DuiAbstractLayoutPolicy

DuiAbstractLayoutPolicy doesn’t inherit any class. It defines common properties for all the policy classes. In this class diagram there is also DuiLinearLayoutPolicy as an example that each policy must inherit DuiAbstractLayoutPolicy class. Each policy class must re-implement DuiAbstractLayoutPolicy::relayout() method. This method does the calculation for each widget and it is called automatically when there is a need for relayouting items. Another method that should be implemented by a inheriting class is the DuiAbstractLayoutPolicy::sizeHint(..) method. This method should return the size of the area which contains all the items in a policy. Each item in a policy should have properly set preferred size, which is used for layouting. Item can also define a stretch factor for more advanced layouting.

DuiPolicySwitcher

DuiPolicySwitcher is an interesting convenience class. This is used only for making DuiLayout to switch a policy on orientation changes. This class is used by DuiLayout and it uses two policies to switch between when the orientation changes. So you can explicitly set a specified policy for landscape and for portrait modes. The switching is done automatically by listening DuiSceneManager::orientationChange(..) signal.

DuiItemState

DuiItemState is a class for item state as the name implies. This class contains information about item opacity, item target and source geometry, item state e.g. is it visible, going to be visible or hidden, going to be deleted or going to be shown. The DuiItemState is used quite heavily e.g. when an animation is activated. To make things more clear, an item in this context means an item that is added to the layout policy.

DuiAbstractLayoutAnimator

DuiAbstractLayoutAnimator is an abstract interface for animations. It inherits QAbstractAnimation from Qt’s Animation Framework and defines couple of abstract methods that must  be implemented by a subclass. The main method to re-implement in a  subclass is: DuiAbstractLayoutAnimator::layoutAnimationStarted(DuiLayout * const layout, DuiItemState *itemstate) method.

DuiBasicLayoutAnimator

DuiBasicLayoutAnimator is a concrete class for making animations. In this class the method: DuiAbstractLayoutAnimator::layoutAnimationStarted(DuiLayout * const layout, DuiItemState *itemstate) is reimplemented such as other methods that are required in order to make animation to work.

Review Findings

DuiLayout

  • Lots of NULL keywords in the code. NULL comes from C and nothing “guarantees” C++ to support that in the future. Qt uses 0 for indicating NULL.
  • In destructor deleted items are not set to 0 after deletion.
  • Line: 187: possible index out of range. No index checking
  • dynamic_cast in use
  • I think DuiLayout should not include DuiPolicySwitcher because logically its place is not there in my opinion. If that kind of policy switcher would be needed, could that be provided as an individual class that can be taken in use separately? See also comments about DuiPolicySwitcher.
  • DuiLayout depends on DuiPolicySwitcher and vice versa. It’s not so good design to have these kind of dependencies.
QGraphicsLayout::getContentsMargins(left ? (*left < 0 ? left : NULL) : NULL,
top ? (*top < 0 ? top : NULL) : NULL,
right ? (*right < 0 ? right : NULL) : NULL,
bottom ? (*bottom < 0 ? bottom : NULL) : NULL);
  • Interesting piece of code: QGraphicsLayout::getContentsMargins(left ? (*left < 0 ? left : NULL) : NULL, top ? (*top < 0 ? top : NULL) : right ? (*right < 0 ? right : NULL) : NULL, bottom ? (*bottom < 0 ? bottom : NULL) : NULL); Not so readable:)

DuiAbstractLayoutPolicy

  • Mixed use of d_ptr and d  pointer even in a same method.
  • NULL keywords in the code.  0 would be a better. See previous comment about NULL keyword.
  • Line: 213: (void) style() could be replaced with Q_UNUSED( style() ) and it would do exactly the same.
  • DuiAbstractLayoutPolicy depends on DuiLayout and DuiLayout depends on DuiAbstractLayoutPolicy. I guess this is not so good design?
  • DuiAbstractLayoutPolicyPrivate has an empty init() which is being called in DuiAbstractLayoutPolicyPrivate class’ constructor. Is it needed for anything?

DuiPolicySwitcher

  • Is this class really needed? DuiLayout could have for example  QObject based private class which could listen DuiSceneManager::orientationChange(..) signal and connect that signal to the private class’ slot. This slot could have an access via (Q_Q() macro) q_ptr to the parent’s methods which changes the layout policy. Another option could be that the policy  could be an orientation aware object by itself and if a policy is registered to be as a default policy for certain mode, then it could “command” the DuiLayout to switch the policy.
  • I guess this class doesn’t really need a pointer to DuiLayout. The DuiLayout can either be accessed directly from the DuiAbstractLayoutPolicy::layout() method. Only thing where DuiLayout is really used in this class is for calling updateStyle() for policy class. Somehow this doesn’t seem to be very elegant solution here and I guess there probably is a better solution for that. One thing came my mind that could the policies themselves be aware of orientation changes and let them handle the change? Like register a one policy to be for portrait and the other for landscape etc? Not sure would this work, though.
  • This class in general doesn’t seem to be very useful?
  • This class depends on DuiLayout and DuiLayout depends on DuiPolicySwitcher.

DuiItemState

  • Some of the private class member variables were not initialized in the constructor. Even though there are default constructors, it’s a good convention to initialize members in the constructor.
  • I’m not so sure how needed this class would be if the layout management would use e.g. Qt Animation Framework combined with Qt State Machine Framework and  QPropertyAnimation. See the next chapter for more details.

Guestions & Proposals:

  • I was confused because an item that is in a policy or in two policies can be removed from the DuiLayout or from the policy. If I remove an item from DuiLayout and that item belongs to two policies, but I want to remove it only from the first policy what happens? Should this kind of functionality to be prevented somehow? Maybe the documentation could be improved?
  • I was bit surprised that animation part did not use the Qt Animation Framework and Qt State Machine Framework more. Is there a specific reason for this because IMHO is that it could have brought more benefits. For example:
    • If the DuiLayout would contain an instance to the QStateMachine and each DuiAbstractLayoutPolicy would contain one QState object.
    • When an item is added to the policy, the policy will assign an item property to the state (or when the layout is calculated e.g. in relayout() method). This can be done by using QState::assignProperty(..) method and the property can be e.g. geometry, pos, orientation or opacity.
    • When a policy is added to the DuiLayout, the DuiLayout requests the state object from the policy and adds it to the QStateMachine instance. It also makes that state active. The animation part would work out of box when a QState is set to active and it also handles the transitions between states.
    • This should do the trick, but of course there might be some issues behind the current design that I’m not aware.
  • For my eyes the code quality didn’t look so good. It looked more like proof-of-consept quality. Maybe this layout code should be reviewed more often?

Summa Summarum

I think the idea of how the Maemo 6 UI Framework layout management should work is great and it provides benefits compared to the QGraphicsLayout. The good thing is that the code compatibility with QGraphicsLayout and you can mix these two layout types if you want or you can just use one of them. I like the idea that application developer doesn’t need to put effort on providing animations – all this comes free.

The other part, the source code and the design in practice wasn’t not so good as I expected. There were couple cases where two classes were dependent of each others which usually implies (IMO)  not-so-good-design. I guess the animation part could have been done using the combination of Qt State Machine and Qt Animation Frameworks. The current implementation looked little bit too confusing and with the Qt State Machine you can sort of automate the animation part easily. I proposed an alternative solution with working video example in this post.

That’s all folks for now. I guess I can still come up yet another “Peek to..” article. Let’s see what it will be. Till then, thanks for reading.

Tags: , , , ,

Leave a Reply

You must be logged in to post a comment.