Showing posts with label symbian. Show all posts
Showing posts with label symbian. Show all posts

Friday, February 10, 2012

Help! Virtual keyboard is hiding the input on Symbian

Modern GUIs often place labels, input fields and other controls on a scrollable pane - think of a Settings page for example. Implementing this in QML is easy: just lay out the controls in a Column, and place the Column in a Flickable.

For example:

Page {
  Flickable {
    anchors.fill: parent
    Column {
      Label {text: "Input 1:"}
      TextField {id: input1}
      Label {text: "Input 2:"}
      TextField {id: input2}
      // More controls
    }
  }
}


This works fine on Meego, but on Symbian it has a flaw: the virtual keyboard (VKB) can hide the input fields towards the bottom of the page. This is a serious shortcoming without a trivial solution, so I had to come up with a non-trivial one.

Basic idea: Whenever the virtual keyboard is displayed, the input field with the focus should be moved up within the Flickable, if it was originally covered by the VKB.

Here is the implementation:

Page {
  Flickable {
    id: flickable
    anchors.fill: parent
    Column {
      Label {text: "Input 1:"}
      TextField {id: input1}
      // More controls
    }

    // (1)
    Timer {
      id: adjuster
      interval: 200
      onTriggered: flickable.adjust()
    }

    // (2)
    Component.onCompleted: {
      inputContext.visibleChanged.connect(adjuster.restart)
    }

    // (3)
    function adjust() {
      if (!inputContext.visible) {
        return
      }

      var focusChild = null
      function findFocusChild(p) {
        if (p["activeFocus"] === true) {
          focusChild = p
        } else {
          for (var i = 0; i < p["children"].length; i++) {
            findFocusChild(p["children"][i])
            if (focusChild !== null) {
              break
            }
          }
        }
      }
      findFocusChild(flickable)

      if (focusChild === null) {
        return
      }
      var focusChildY = focusChild["y"]
      var focusChildHeight = focusChild["height"]
      if ((flickable.contentY + flickable.height) < (focusChildY + focusChildHeight)) {
        flickable.contentY = focusChildY + focusChildHeight - flickable.height
      }
    }
  }
}

Some explanations:

(1) We need a timer to adjust the focus item's position. We can't adjust it immediately after the virtual keyboard appears, because the the Flickable component will get resized with a nice animation, which takes time.

(2) So when the VKB gets visible, we just start a timer, that will adjust the focus item later

(3) Shifting the focus item (and all the others of course in the Flickable) happens in the adjust() function. The function tries to find a child item with active focus. If there is such an item, and it is currently hidden, the whole Flickable content will be elevated by changing it's contentY property.

Oh one more important remark: The PageStack component that owns the Page should have its platformSoftwareInputPanel property set to true. Otherwise the Page (and with it the Flickable) won't get resized when the VKB is shown.

Finally: QML is changing fast, so I'm sure one day this bug will be fixed by Nokia. The problem was found on, and the solution was written for the following environment:

  • Qt 4.7.4 on Symbian Anna or Belle
  • QtQuick 1.1
  • com.nokia.symbian 1.1

Wednesday, February 1, 2012

Count Your Steps

Steps, the open source step counter is available from Nokia Store. Besides counting steps, it can also log to Google Fusion Tables.

The Symbian version is downloadable now, Meego version is coming soon.

Sunday, November 13, 2011

Cross-Platform UI with Qt Components

Cross-platform UI development doesn't make much sense, if the platform UI paradigms are wildly different. It does however in my case: I develop Nokia Store applications targeting two very similar Nokia platforms: Meego and Symbian.

Both platforms are mobile phones with a touchscreen, and for both, Nokia provides UI toolkits (QML Components), that are similar, but full of maddening small differences. I'm sure these differences will disappear over time. In the meanwhile, I work around them using the Qt resource system.

So the idea is to wrap the platform-specific components into components providing uniform interfaces, and then put the wrappers into platform-specific resource files, with the content aliased to the same location. In my project, I have three resource files: meego.qrc, symbian.qrc, and for the common UI code, common.qrc.

This is how meego.qrc looks like:

<rcc>
 <qresource prefix="/qml">
  <file alias="MainPage.qml">qml/meego/MainWindow.qml</file>
 </qresource>
</rcc>

And this is how symbian.qrc looks like:

<rcc>
 <qresource prefix="/qml">
  <file alias="MainPage.qml">qml/symbian/MainWindow.qml</file>
 </qresource>
</rcc>


Lastly, this is in common.qml:

<rcc>
 <qresource prefix="/">
  <file>qml/main.qml</file>
 </qresource>
</rcc>

MainWindow,qml is implementing the wrapper for the platform-specific application main windows, that can be used from the platform-independent main.qml. To achieve this, both meego/MainWindow.qml and symbian/MainWindow.qml are aliased to the same resource file location (/qml/MainWindow.qml), while they physically reside in different source files (qml/meego/MainPage.qml and qml/symbian/MainPage.qml).

The project file of course only includes meego.qrc when building for Meego, and symbian.qrc when building for Symbian.

Final touches

When editing main.qml in Qt Creator, it won't find MainWindow.qml on the import path (because it doesn't exist in the current directory). To work around this, we should add the sub-directories "meego" and/or "symbian" to the import path. The final main.qml looks like this:

import symbian
import meego
import MainWindow.qml


MainWindow {
    ... (common UI code goes here)
}

Unfortunately, this is still not enough. Qt Creator is now happy, but when running main.qml from the resource file on the target platform, it will complain the directories "meego" and "symbian" do not exist.

This can be resolved by adding some dummy QML files (meego/dummy.qml and symbian/dummy.qml), and including them to the common resource file, which now looks like this:

<rcc>
 <qresource prefix="/">
  <file>qml/main.qml</file>
  <file>qml/meego/Dummy.qml</file>
  <file>qml/symbian/dummy.qml</file>
 </qresource>
</rcc>

Summary

What we have achieved with this somewhat complicated setup is simple: We can now wrap platform-specific QML components with a cross-platform API, and use them from a platform-agnostic UI code.