Wednesday, August 13, 2014

#BB10 NDK Creating and Using Static Libraries

Update 4:

This will likely be the last time I revisit this topic. Momentics 2.1, released a while ago, now supports creating a Cascades library, static or shared.


When the library project is created the is a detailed readme.txt file included that describes how to create and use the library. It is far from automatic, but for libraries that are growing and expanding along with a set of applications it is a very useful addition to the tool. And once the library stabilizes you can copy the library and include files out of the project and add them to other application projects with the Library Wizard as explained below.

Update 1:

After some more digging I have fine tuned the procedure a bit.

Update 2:

This BlackBerry Support Community post describes the official method which will eventually be a KB article.

Update 3:

With the release of BB 10.2 beta NDK I decided to revisit this issue and completely re-tool it. I haven't found any better description of how to build static libraries that contain Cascades classes. Full disclosure, I'm still an Eclipse neophyte. You probably know a better way to do this. If you don't I found that this works if you want to sequester some of your development into a static library for code re-use all that good stuff. These instructions are for BB10 NDK 10.2 beta running on Windows 7, but it should translate fairly easily to other supported OSs. I make use of bash scripting available from Git for Windows.

This procedure assumes that your library of classes is already fully debugged.

To start, I suggest you create a directory in the NDK tree to contain your libraries. I use LocalLibs so I created G:\bbndk_102\LocalLibs and G:\bbndk_102\LocalLibs\bin. The bin directory is to hold the scripts needed to call the Qt MOC compiler, and install the libraries. I have tried to make the scripts as location agnostic as I could, though there is some hacking required to move from the Windows environment to the bash environment. You will need 5 files in the bin directory:

domoc.bat:
@ECHO OFF
%~dp0%domoc.sh

libins.bat:
@ECHO OFF
%~dp0%libins.sh

These are just stub windows batch files that call their bash counterparts:
domoc.sh:
#!/bin/bash
# To run this in Eclips on Windows use the following command
#C:\Windows\SysWOW64\cmd.exe /c <bash_script>
# eg) C:\Windows\SysWOW64\cmd.exe /c G:\bbndk\LocalLibs\bin\domoc.sh

n=$(basename $PWD)
echo $n

SCRIPT_DIR=$(echo ${BASH_SOURCE[0]} | sed -e "s/domoc.sh//")
SCRIPT_DIR=$(cd "${SCRIPT_DIR}"; pwd)
. ${SCRIPT_DIR}/MOC_Env.sh

cd src

for h in *.h 
do
 echo $h
 b=$(basename $h .h)
 if grep -q Q_OBJECT $h
 then
  "$QNX_HOST\win32\x86\usr\bin\moc.exe" $h -o moc_${b}.cpp
 fi
 mkdir -p ../public/${n}
 cp $h ../public/${n}/${b}

done

libins.sh:
#!/bin/bash

SCRIPT_DIR=$(echo ${BASH_SOURCE[0]} | sed -e "s/libins.sh//")
SCRIPT_DIR=$(cd "${SCRIPT_DIR}"; pwd)
. ${SCRIPT_DIR}/MOC_Env.sh

n=$(basename $PWD)
mkdir -p $LIB_INCLUDE
mkdir -p $LIB_TARGET/$n/Device
mkdir -p $LIB_TARGET/$n/Simulator
cp -r public/$n $LIB_INCLUDE
cp Device-Release/lib${n}.a $LIB_TARGET/$n/Device
cp Simulator-Debug/lib${n}.a $LIB_TARGET/$n/Simulator

domoc.sh goes into the source directory, checks all the .h files to see if they define a Qt or Cascades class (they have the Q_OBJECT macro) and if they do runs the Qt MOC compiler to generate the required moc_Class.cpp file. It also copies all the Class.h files into public/LibraryName/Class files which will be installed in the local library include directory by libins.sh.

libins.sh creates the required directories and copies the include files, Device-Release and Simulator-Debug versions of the libraries into the LocalLibs tree.

Finally you'll need MOC_Env.sh which sets up a number of variables to point at appropriate locations.
MOC_Env.sh:
#!/bin/bash

TARGET=target_10_2_0_339
HOST=host_10_2_0_1

SCRIPT_DIR=$(echo ${BASH_SOURCE[0]} | sed -e "s/MOC_Env.sh//")
SCRIPT_DIR=$(cd "${SCRIPT_DIR}"; pwd)
export LOCAL_LIBS=$(dirname ${SCRIPT_DIR})
export BB_NDK=$(dirname ${LOCAL_LIBS})

export QNX_TARGET=${BB_NDK}/${TARGET}
export QNX_HOST=${BB_NDK}/${HOST}
export LIB_INSTALL=${BB_NDK}/LocalLibs/
export LIB_INCLUDE=${LIB_INSTALL}/include
export LIB_TARGET=${LIB_INSTALL}/${TARGET}

Now it is time to create a static library project with File ->New->BlackBerry C/C++ Project, select Library and Static Library:

Click Next, give the library a name and set some parameters:


Click Next, select the API level and click Finish:

You want to use Cascades objects in your library so you will have to configure the project so Eclipse know you want those include files. By default they aren't included in the project:

Right click on your new project and select Properties. Then go to C/C++ General, Paths and Symbols. Select All Configurations and the Includes tab and set Language to GCC:

Then press the Add button. Select Add to All Languages (may not be needed), then add the Directory for /usr/include/qt4:


Do the same thing for /usr/include/qt4/QtCore:


Click OK, and OK again to add these paths:


Now your includes should look something like this:


Now we have to add two new build rules. These will be external commands (domoc.sh and libins.sh) that run before and after compilation respectively.

Right click on the library project and select Properties then select Builders:

Click New, select Program and click OK:

Give the builder a name, I used MOC_Builder, the click on Browse File System below Location and select domoc.bat from your LocalLibs/bin directory:

Click on Variables below Working Directory and select projec_loc then click OK:

Click on the Refresh tab and set up the refresh conditions:

Then on the Build Options tab set up the build options:

Finally click OK to complete the builder. Then select the new builder and click the Up button until it is on the top of the list:

Repeat the whole procedure again to create a LIB_Install builder (it doesn't need to refresh anything so no changes are needed on the Refresh tab):

And it stays on the bottom of the list:

Now you can put your source files in the src directory. I suggest you start out with some simple classes to make sure everything is working. You can also un-check the builders to isolate the steps for diagnosing problems. Here is my project with one simple class:

And build the Device and Simulator libraries:

After the build your project should look like this:

And your LocalLibs tree should look like this:

You can now go to a Workspace with a Project you need the library for. Right click on the Project, select Configure -> Add Library. Select External Library, click Next and set up the libraries and include path:


If something doesn't work for you, or needs a better explanation, or if you have a better idea just leave a comment below. Otherwise enjoy.

Wednesday, May 21, 2014

Segmented control option change

There was a recent question on StackOverflow that was put on hold almost immediately, but I believed it could easily be answered as posted:

I have used Segmented Control in my application. The qml page name, where I have used Segmented Control, is Details.qml page. Remaining part of Details.qml page contains a Container. As option change from segmented control, I want to show customised page in the Container. Suppose there are two options in segmented control i.e. Option1, Option2. As I selected Option1, I want to show CustomOption1.qml page and if I selected Option2, I want to show CustomOption2.qml page in the Container. If you know how to do this, then please help me.
The way I would do this is with a ControlDelegate which allows the loading of QML source code at run time into a control. This of course requires time to load, compile and display the QML, but it has the advantage of only having the QML that the application actually needs at the moment loaded into memory. For small amounts of QML it may be better to use a different method. My solution is all in QML, I just create an empty Cascades application, modify the main.qml file and add two other files.

Here is my main.qml:

import bb.cascades 1.2

Page {
    Container {
        layout: StackLayout {

        }
        SegmentedControl {
            id: segmented
            Option {
                id: option1
                text: qsTr("Option1") + Retranslate.onLocaleOrLanguageChanged
                selected: true
            }
            
            Option {
                id: option2
                text: qsTr("Option2") + Retranslate.onLocaleOrLanguageChanged
            }
            
            onSelectedIndexChanged: {
                console.log("onSelectedIndexChanged: " + segmented.selectedIndex);
                switch (segmented.selectedIndex) {
                    case 0:
                        controlDelegate.source = "option1.qml";
                        break;
                    case 1:
                        controlDelegate.source = "option2.qml";
                        break;
                }
            }
            
        }
        ControlDelegate {
            id: controlDelegate
            delegateActive: true
            source: "option1.qml"
            onError: {
                console.log("Error loading delegate " + errorMessage);
            }
            horizontalAlignment: HorizontalAlignment.Center
        }
    }
}

The SegmentedControl provides the option selection, the onSelectedIndexChanged signal is used to change the source QML used by the ControlDelegate. The ControlDelegate is set up to be active and start with option1.qml loaded.

The two other files, option1.qml and option2.qml are very simple, and similar, but they need not be so. Almost any valid QML that could be used in place of the ControlDelegate may be used in these files.

option1.qml:
import bb.cascades 1.2

Container {
    Label {
        verticalAlignment: VerticalAlignment.Center
        horizontalAlignment: HorizontalAlignment.Center
        text: "Option 1 Delegate"
    }

}

and option2.qml:
import bb.cascades 1.2

Container {
    layout: DockLayout {
    
    }
    Label {
        verticalAlignment: VerticalAlignment.Center
        horizontalAlignment: HorizontalAlignment.Center
        text: "Option 2 Delegate"
    }

} 

Quite easily solved.

Monday, April 7, 2014

Adding Children to Extended Cascades Controls With QML

It doesn't take very long after starting to develop for BlackBerry 10 Cascades that a developer comes to a point when it is desirable to encapsulate some functionality into a user interface object by extending an existing control, or starting fresh with CustomControl. Not long after that a developer will want to add children to the control using QML something like this:


                RadialMenu {
                    id: radialMenu
                    easingCurve: StockCurve.ElasticOut
                    RadialMenuActionItem {
                        imageSource: "asset:///ic_share.png"
                    }
                } 


RadialMenu extends CustomControl, RadialMenuActionItem extends ImageView. If you are experienced with Qt development you probably already know how to make this happen, but Cascades is different enough from pure Qt that a BlackBerry developer quickly learns not to delve too deeply into Qt. If you follow the Cascades documentation and try this at run time you will see the following error:

bb::cascades::QmlDocument: error when loading QML from:   QUrl( "asset:///main.qml" )  
--- errors:  (asset:///main.qml:77:21: Cannot assign to non-existent default property) 
bb::cascades::QmlDocument:createRootObject document is not loaded or has errors, can't create root

Default property? To make a long story short this is the clue to solving the problem. In QML child nodes are assigned to a property even if it doesn't appear to be the case. It turns out that a container has a property called controls that works very much like the property attachedObjects. But we don't want to have to declare our children using a different, perhaps confusing syntax like this:


                RadialMenu {
                    id: radialMenu
                    easingCurve: StockCurve.ElasticOut
                    controls: [
                        RadialMenuActionItem {
                            imageSource: "asset:///ic_share.png"
                        },
                        ... // More children
                     ]
                } 

The secret is telling QML which property is the default property we want children assigned to unless otherwise specified. This is actually quite simple to do:


class RadialMenu :public CustomControl {
 Q_OBJECT

 Q_PROPERTY(QDeclarativeListProperty<QObject> actionItems READ actionItems)

 Q_CLASSINFO("DefaultProperty", "actionItems")

        // Other properties removed for clarity.
        ...
public:
 RadialMenu(Container *parent = 0);
 virtual ~RadialMenu();

 QDeclarativeListProperty<QObject> actionItems();

        ...

private:
 QList<QObject> mActionItems;
        ...
};


QDeclarativeListProperty<QObject> RadialMenu::actionItems() {
 qDebug() << __PRETTY_FUNCTION__;
 return QDeclarativeListProperty<QObject>(this, mActionItems);
}



This boilerplate is what allows QML to add children to the mActionItems member variable using the standard syntax. I have used QObject as the template type in this case. Ideally you may want to limit the type of children your control will accept. In my case I would like to limit children to being RadialMenuActionItems but the type must be know to the Qt system or it doesn't work.

Friday, March 28, 2014

QML Button Click Counter

A person posted a question on Stack Overflow asking how to count clicks of a button in QML for BlackBerry 10. As you can see below the question is quite easy to answer if you are familiar with QML. Unfortunately five members of Stack Overflow with enough reputation to put the question on hold couldn't figure out how to answer it and decided the question was unworthy as it is.

The problem of counting events in any programming language will have similar elements. A variable to hold the count, an event to be counted, a means to output the count and optionally a way to reset the count.

In this case the best way to store the count is using a property. In QML properties are variables but they have the added feature of generating signals when they are changed. So we have a property named count, initialized to zero. And we have a signal handler onCountChanged that updates the display whenever the count is changed.

We have two buttons, one that generates clicks to be counted, and one that resets the count. The onClicked signal handler manipulates the value of count. We don't need to worry about the display update because the onCountChanged takes care of that for us. This is a very powerful feature of QML. Any other objects that we create that also need to know when the value of count changes can use a similar signal handler.

I've created a label to display the value, put some properties on the Container that holds these elements to make it pretty. All the rest comes from a project template included with the Momentics IDE for BlackBerry 10 development.

/*
 * Copyright (c) 2011-2013 BlackBerry Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import bb.cascades 1.2

NavigationPane {
    id: navigationPane
    property int count: 0
    onCountChanged: {
        countDisplay.text = "The count is: " + count;
    }
    

    Page {
        titleBar: TitleBar {
            // Localized text with the dynamic translation and locale updates support
            title: qsTr("Page 1") + Retranslate.onLocaleOrLanguageChanged
        }
        
 
        Container {

           Button {
                id: counted
                text: "Click Me"
                
                onClicked: {
                    count++;
                }
                horizontalAlignment: HorizontalAlignment.Center

            }
            Label {
                id: countDisplay
                text: "No clicks yet."
                verticalAlignment: VerticalAlignment.Center
                horizontalAlignment: HorizontalAlignment.Center

            }
            Button {
                id: reset
                text: "Reset"
                
                onClicked: {
                    count = 0;
                }
                horizontalAlignment: HorizontalAlignment.Center
                verticalAlignment: VerticalAlignment.Bottom
            }
            

        }

        actions: ActionItem {
            title: qsTr("Second page") + Retranslate.onLocaleOrLanguageChanged
            ActionBar.placement: ActionBarPlacement.OnBar

            onTriggered: {
                // A second Page is created and pushed when this action is triggered.
                navigationPane.push(secondPageDefinition.createObject());
            }
        }
    }

    attachedObjects: [
        // Definition of the second Page, used to dynamically create the Page above.
        ComponentDefinition {
            id: secondPageDefinition
            source: "DetailsPage.qml"
        }
    ]

    onPopTransitionEnded: {
        // Destroy the popped Page once the back transition has ended.
        page.destroy();
    }
}


And here is a screen shot:


Once you are comfortable with that, you can actually get rid of the signal handler onCountChanged and re-write the Label as follows:

            Label {
                id: countDisplay
                text: "Number of clicks: " + count
                verticalAlignment: VerticalAlignment.Center
                horizontalAlignment: HorizontalAlignment.Center

            }
This works because QML is a curious mix of declarative and functional programming. Since the QML interpreter knows count is a property it "creates" the functional programming behind the scenes to make sure that any other properties that depend on count are kept current by using signal handlers.

Wednesday, March 5, 2014

Open Data Fusion

The Canadian Government has launched into an Open Data project and made a large number of various types of data sets available on a very open license. Paralleling this effort, or perhaps to enable it, departments that provide geographic information services (GIS) are supporting Web Map Service (WMS) servers that are publicly accessible. The really important thing is that since the data, though drawn from multiple sources, all conforms to a single standard format it can be fused with simple image manipulation.

For example, we can create a map of the Great Lakes surrounding Southern Ontario from Natural Resources Canada:


We can a forecast of wind speed and direction from Environment Canada:


And we can combine them for a more meaningful display of winds over the inland seas.


We can overlay rainfall rates on a map, mundane but useful:


Or we can create a Snow Mass map of the Rockies:


The possibilities are almost limitless.

Friday, September 20, 2013

Being Innumerate is not an Accident

There have been two incidents with an effect on Canadians this week in which corporations, one very large one perhaps smaller; but both with the resources to understand technology. Instead demonstrated that they have no grasp at all of what random means.

The first one is an incident where a recently deceased young woman had her photograph stolen by a dating site and used in targeted advertisements on Facebook. The owner of the company that perpetrated this apologized by calling it an accident. Which is ridiculous. What they did they did on purpose, using random images in ads. If one could recover all those adds, and track down the young women who's images were misused you would probably find the majority of them would not be happy. You would also likely find that a larger number than you expect would have as good a reason for outrage as the family of Rehtaeh Parsons. You might expect that the odds of someone seeing a person they know in one of these adds is small, as Ahn Dung was counting on, but that expectation neglects the fact than people join Facebook in cultural groups. It was going to happen to someone, Ahn Dung got caught this time. If you don't believe me, do the math. If you don't know enough math, ask Randall Munroe.

The odds of winning the grand prise in Lotto 649 are 1 in 13,983,816. That doesn't stop someone from winning every few weeks. Ahn Dung was betting he would not "win the lottery". Coke was also apparently counting on not winning the lottery. Let's combine a random English word with a random French word and print them under bottle caps. What could possibly go wrong?

The basic English vocabulary is 850 words. To make the arithmetic easy lets round that up to 1000 and assume the same of French. So such a campaign would generate 1000 × 1000 = 1,000,000 combinations. How many bottles of vitamin water does Coke produce in Canada? According to Coke they produce 1,700,000,000 servings of Coke per day world wide. So that is one serving of Coke for every 4.2 people on earth per day. Including the people in areas where enjoying an ice cold Coke is not on their daily to-do list. Vitamin water probably doesn't sell as well. Maybe 1 in 42 or 1 in 420 would be a good estimate? The population of Canada is near enough 35,000,000. Even at 1 in 4,200 per day, that would be 8,333 two word combinations per day. After 120 days you could expect one of every combination to be floating around. But I don't think Coke would be selling anything that had that poor a market so one might count on "you retard" being printed once every 12 days. Retard is not in the 850 word basic English vocabulary, so their word lists are larger than that. But even 2000 words from each language would only result in 4 million combinations. Retard is not the only French word that might be seen as offencive in an English context as one CBC reporter found out.

We pride ourselves on being the most technologically advanced culture the planet has ever seen. More scientists are alive today than have ever lived before. Sadly most of use don't have a single clue how it all works, even something as simple as basic probabilities. No wonder when it comes to complex decisions we get things so consistently and horribly wrong.