You might have heard about Ubuntu Touch, and one of unique feature of Ubuntu Touch is Scope. Recently they also announced Ubuntu Touch Scope competition.
This got me interested, I wanted to learn about Scope development, so I thought to take part as well in the competition.
You can get more information about competition here.
Recently I discovered Your Shot photo community, I like pictures uploaded there and I visit site every day to checkout newly updated photos, so I thought it is good candidate for Scope development and I decided to write score for Your Shot photo community.
Your Shot has nice Jason based web API and I was able to create scope quite easily using Ubuntu Touch socpe's Jason template. In fact I was able to get decent working scope in few hours using template. Documentation is also quite good and rich with API Reference, Guide and Tutorials.
Here is demo for the same running on my desktop.
And Few snapshot.
It looks like now I am ready to write more complex Scope but may be later.
We can create QML object dynamically by using createComponent and createObject API.
Like follow,
var component = Qt.createComponent("Button.qml");
if (component.status == Component.Ready)
component.createObject(parent, {"x": 100, "y": 100});
You can find more information regarding same here.
Latest Qt release added new API named incubateObject. This allows object to be created in asynchronously.
You can find more information about this API here.
Following code I written for Ubuntu touch calendar application, which uses the same. Here thing to remember is that incubateObject returns the incubator and not created object itself. You need to get object from incubator by incubator.object call.
Recently I upgraded my Audiobook Reader application pro version for BB10.
This version contains many UI related changes and now it is more aligned to Blackberry design guideline and now it is also awarded Built for Blackberry status.
Apart form UI related changes, this version contains two major changes.
Now it Book auto pauses in case it detect phone call activity.
And there was a very critical bug in application which prevents book to be added from SD card. Though issue was related to BB10 OS, I found one work around and now adding books from SD card works as expected.
Full list of features in current version is as below.
Allows adding single file or whole folder with book audio data
Supports adding custom bookmark
Supports browsing mp3 chapter files and play selected mp3 chapter file
I was updating my Audiobook Reader application. I wanted to listen to our going or incoming call and pause book accordingly.
BB10 provides nice API to control call and listen to it.
This post will show how we can listen to call from Cascades QML code.
First we need to ask for access_phone and control_phone permission in our bar descriptor file.
Once this is done, we need to register Phone type to QML system so we can access it from QML.
#include <bb/system/phone/Phone>
...
using namespace bb::system;
int main(int argc, char **argv) {
qmlRegisterType("bb.system.phone", 1, 0, "Phone");
...
}
Now we are ready to use Phone API from QML side. Following is how QML code will look like. In code we are creating Phone object. On any call related event callUpdated signal will be called. So we are handling that signal in onCallUpdated event. Please note that we can not access Call's properties with QML, as its not derived from QObject, so if you need to access properties of Call then you need to forward call to C++ and handle it there.
I was often request for sample that shows how custom swipe handler can be created in QML. In this post I will show how same can be achieved.
Please note that code is just prototype level code and is not tested well with actual use. It has also lots of hard coded value that assume certain size of application.
But, you should be able to change those according to your use and can try sample with your app.
This sample implement three QML views and you can swipe on that to change view from one to next. This code also implement some parallax effect on view and view transition animation. In addition to swipe you can also change view using keyboard Left/Right arrow key. Following is demo for the sample app.
So let's start with code.
Following code is from SwipeHandler.qml, it extends MouseArea and try to detect swipe based on mouse's x position change. Swipe can be generated by two way, by flicking on view or dragging it.
Flick is detected, if there is large change in mouse x position in less time. In case of drag, if mouse travel certain distance then code consider it as a swipe.
import QtQuick 2.0
MouseArea{
id: root
property int oldX: mouseX;
property int swipeOffset: 100;
property int originX:mouseX;
property var gestureStartTime;
property bool gestureStarted: false;
signal swipeEnded(var diff);
signal swipeContinues(var diff);
anchors.fill: parent
onReleased: {
if( gestureStarted ) {
//swipe canceled
root.swipeEnded(0);
resetGesture();
}
//else swipe is already ended
}
onPressed: {
gestureStarted = true;
gestureStartTime = new Date();
}
onMouseXChanged: {
if( mouseX < parent.x
|| mouseX > parent.width || gestureStarted == false )
return;
if( originX == 0 ) {
originX = mouseX; oldX = mouseX;
return;
}
var diff = (oldX - mouseX);
if(handleFlick(diff)){
return;
}
if( haldleDrag(mouseX, diff)){
return;
}
oldX = mouseX;
root.swipeContinues(diff);
}
function resetGesture() {
originX = 0; oldX = 0;
gestureStarted = false;
}
function haldleDrag(xPos,xPosDiff){
if(xPosDiff < 0) {
if( Math.abs(originX-xPos) > swipeOffset ){
root.swipeEnded(xPosDiff);
resetGesture();
return true;
}
} else {
if( Math.abs(originX-xPos) > swipeOffset ){
root.swipeEnded(xPosDiff);
resetGesture();
return true;
}
}
return false;
}
function handleFlick(xPosDiff){
var now = new Date();
var timeDiff = now - gestureStartTime;
//high velocity and large diff between start end point
if(timeDiff < 40 && Math.abs(xPosDiff) > 10 ){
if(xPosDiff < 0) {
root.swipeEnded(xPosDiff);
resetGesture();
return true;
} else {
root.swipeEnded(xPosDiff);
resetGesture();
return true;
}
}
return false;
}
}
So, this was SwipeHandler which can detect if swipe is generated or not. To demonstrate its use, I created a small View Management component, that create's three views. On swipe, view changes form one to another base on direction of swipe movement. Here is code for the same.
I added GamePad support to my CrazyFlight game for BB10. You can see demo here.
In this post I will describe, how we can add GamePad support to BB10 cascades or Qt app.
First we should add use_gamepad permission to bar-descriptor.xml file. This is not necessary to enable gamepad support but its necessary for AppWorld to detect that your game supports GamePad, this helps in app discovery process.
<permission>use_gamepad</permission>
We should also add libscreen dependency to our .pro file.
LIBS += -lscreen
As far as I know there is no Cascades API for handling GamePad events, we need to rely on native API to get GamePad events.
I created a Helper class that handles Native API call back and send events to Cascades QML items.
Here is definition of my helper class's (GamePadObserver.h) header file.
#ifndef GAMEPADOBSERVER_H_
#define GAMEPADOBSERVER_H_
class GamePadObserver: public QObject {
Q_OBJECT
Q_ENUMS(GamePadButton)
// Structure representing a game controller.
struct GameController {
// Static device info.
screen_device_t handle;
int type;
int analogCount;
int buttonCount;
char id[64];
// Current state.
int buttons;
int analog0[3];
int analog1[3];
// Text to display to the user about this controller.
char deviceString[256];
};
public:
//Enum which we will use to send signal when GamePad event is detected
enum GamePadButton{
A_BUTTON=0,
B_BUTTON,
C_BUTTON,
X_BUTTON,
Y_BUTTON,
Z_BUTTON,
MENU1_BUTTON,
MENU2_BUTTON,
MENU3_BUTTON,
MENU4_BUTTON,
L1_BUTTON,
L2_BUTTON,
L3_BUTTON,
R1_BUTTON,
R2_BUTTON,
R3_BUTTON,
DPAD_UP_BUTTON,
DPAD_DOWN_BUTTON,
DPAD_LEFT_BUTTON,
DPAD_RIGHT_BUTTON,
NO_BUTTON
};
public:
GamePadObserver(QObject* parent = 0);
virtual ~GamePadObserver();
//Main event loop should send event to this handler if it can not handle event by itself
//This handler will try to handle event if its related to GamePad
void handleScreenEvent(bps_event_t *event);
signals:
//Signals will be emitted when Gamepad events is detected
void buttonReleased(int button);
void buttonPressed(int button);
private:
//Helper methods to discover the GamePad and device connection
void discoverControllers();
void initController(GameController* controller, int player);
void loadController(GameController* controller);
void handleDeviceConnection(screen_event_t screen_event);
// Methods to handle gamepad events
void handleGamePadInput(screen_event_t screen_event);
QString gamePadButtonAsString(GamePadButton button);
private:
screen_context_t _screen_ctx;
GameController _controllers[2];
bool _conneted;
GamePadButton _lastButton;
};
#endif /* GAMEPADOBSERVER_H_ */
Now let's see source file.
In constructor we are creating screen context and then trying to discover if there is GamePad connected to device already.
#define SCREEN_API(x, y) rc = x; \
if (rc) printf("\n%s in %s: %d, %d", y, __FUNCTION__,__LINE__, errno)
GamePadObserver::GamePadObserver( QObject* parent)
: QObject(parent),_screen_ctx(0),_conneted(false)
{
// Create a screen context that will be used to create an EGL surface to receive libscreen events.
SCREEN_API(screen_create_context(&_screen_ctx, SCREEN_APPLICATION_CONTEXT), "create_context");
discoverControllers();
}
void GamePadObserver::discoverControllers()
{
// Get an array of all available devices.
int deviceCount = 0;
SCREEN_API(screen_get_context_property_iv(_screen_ctx, SCREEN_PROPERTY_DEVICE_COUNT, &deviceCount), "SCREEN_PROPERTY_DEVICE_COUNT");
screen_device_t* devices = (screen_device_t*) calloc(deviceCount, sizeof(screen_device_t));
SCREEN_API(screen_get_context_property_pv(_screen_ctx, SCREEN_PROPERTY_DEVICES, (void**)devices), "SCREEN_PROPERTY_DEVICES");
// Scan the list for gamepad and joystick devices.
int controllerIndex = 0;
for (int i = 0; i < deviceCount; i++) {
int type;
SCREEN_API(screen_get_device_property_iv(devices[i], SCREEN_PROPERTY_TYPE, &type), "SCREEN_PROPERTY_TYPE");
if ( !rc && (type == SCREEN_EVENT_GAMEPAD || type == SCREEN_EVENT_JOYSTICK)) {
// Assign this device to control Player 1 or Player 2.
GameController* controller = &_controllers[controllerIndex];
controller->handle = devices[i];
loadController(controller);
// We'll just use the first compatible devices we find.
controllerIndex++;
if (controllerIndex == MAX_CONTROLLERS) {
break;
}
}
}
free(devices);
}
loadController setup our GameController structure, that we will use to store currently pressed buttons while handling the events.
void GamePadObserver::loadController(GameController* controller)
{
// Query libscreen for information about this device.
SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_TYPE, &controller->type), "SCREEN_PROPERTY_TYPE");
SCREEN_API(screen_get_device_property_cv(controller->handle, SCREEN_PROPERTY_ID_STRING, sizeof(controller->id), controller->id), "SCREEN_PROPERTY_ID_STRING");
SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_BUTTON_COUNT, &controller->buttonCount), "SCREEN_PROPERTY_BUTTON_COUNT");
// Check for the existence of analog sticks.
if (!screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG0, controller->analog0)) {
++controller->analogCount;
}
if (!screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG1, controller->analog1)) {
++controller->analogCount;
}
if (controller->type == SCREEN_EVENT_GAMEPAD) {
sprintf( controller->deviceString, "Gamepad device ID: %s", controller->id);
qDebug() << "Gamepad device ID" << controller->id;
} else {
sprintf( controller->deviceString, "Joystick device: %s", controller->id);
qDebug() << "Joystick device ID" << controller->id;
}
}
handleScreenEvent function should be called from main event loop when event is related to screen domain.
This function check if event type is GamePad device connection or its GamePad button event and handle it accordingly.
void GamePadObserver::handleScreenEvent(bps_event_t *event)
{
int eventType;
screen_event_t screen_event = screen_event_get_event(event);
screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &eventType);
switch (eventType) {
case SCREEN_EVENT_GAMEPAD:
case SCREEN_EVENT_JOYSTICK:
{
handleGamePadInput(screen_event);
break;
}
case SCREEN_EVENT_DEVICE:
{
// A device was attached or removed.
handleDeviceConnection(screen_event);
break;
}
}
}
handleDeviceConnection handles GamePad device connection. If it detects new connection then it loads new device, in case of device disconnection it remove device.
void GamePadObserver::handleDeviceConnection(screen_event_t screen_event)
{
// A device was attached or removed.
screen_device_t device;
int attached;
int type;
SCREEN_API(screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_DEVICE, (void**)&device), "SCREEN_PROPERTY_DEVICE");
SCREEN_API(screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_ATTACHED, &attached), "SCREEN_PROPERTY_ATTACHED");
if ( attached ) {
SCREEN_API(screen_get_device_property_iv(device, SCREEN_PROPERTY_TYPE, &type), "SCREEN_PROPERTY_TYPE");
}
int i;
if (attached && (type == SCREEN_EVENT_GAMEPAD || type == SCREEN_EVENT_JOYSTICK)) {
for (i = 0; i < MAX_CONTROLLERS; ++i) {
if (!_controllers[i].handle) {
_controllers[i].handle = device;
loadController(&_controllers[i]);
break;
}
}
} else {
for (i = 0; i < MAX_CONTROLLERS; ++i) {
if (device == _controllers[i].handle) {
initController(&_controllers[i], i);
break;
}
}
}
}
void GamePadObserver::initController(GameController* controller, int player)
{
// Initialize controller values.
controller->handle = 0;
controller->type = 0;
controller->analogCount = 0;
controller->buttonCount = 0;
controller->buttons = 0;
controller->analog0[0] = controller->analog0[1] = controller->analog0[2] = 0;
controller->analog1[0] = controller->analog1[1] = controller->analog1[2] = 0;
sprintf(controller->deviceString, "Player %d: No device detected.", player + 1);
}
handleGamePadInput function that handles GamePad events.
void GamePadObserver::handleGamePadInput(screen_event_t /*screen_event*/)
{
int i;
for (i = 0; i < MAX_CONTROLLERS; i++) {
GameController* controller = &_controllers[i];
if ( controller->handle ) {
GamePadButton gamePadButton = NO_BUTTON;
// Get the current state of a gamepad device.
SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_BUTTONS, &controller->buttons), "SCREEN_PROPERTY_BUTTONS");
if (controller->analogCount > 0) {
SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG0, controller->analog0), "SCREEN_PROPERTY_ANALOG0");
}
if (controller->analogCount == 2) {
SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG1, controller->analog1), "SCREEN_PROPERTY_ANALOG1");
}
for(int i = A_BUTTON ; i < NO_BUTTON ; ++i) {
if( controller->buttons & (1 << i) ) {
gamePadButton = (GamePadButton)(i);
break;
}
}
if( gamePadButton == NO_BUTTON ) {
emit buttonReleased( _lastButton );
_lastButton = NO_BUTTON;
}
else if( _lastButton != gamePadButton ) {
emit buttonReleased(_lastButton);
emit buttonPressed(gamePadButton);
_lastButton = gamePadButton;
}
}
}
}
Now we have all necessary implementation to handle the GamePad connection and GamePad button events.
But we should pass events related to GamePad to our helper class when we receive it in our main event handler.
To do that, In our main function we should register main bps event loop or filter as below and can use GamePadObserver as described.
And finally, out GamePadObserver class is ready to deliver GamePad events. To handle GamePad events in QML you can use GamePadObaserver class as below.
Now to be able to see this Enum to QML code we need to register GamePadObserver class to Qt Metaobject system.If you want to be able to create instance of class then you can use qmlRegisterType() macro.
In my case I dont want to create instace of GamePadObserver in QML, I just want to expose it enum to QML and for that purpose we can use qmlRegisterUncreatableType macro. Its useful for exposing enum and attached property. Following how we can use this macro.
I tried to compile one my application with Qt5. Application was using QML camera and sharing image to C++ code for further processing.
Following is sample code, it works with Qt5 and Qt Multimedia 5.
Lets start with ImageProcessor class, the C++ class which is called from QML to further image processing.
Following is header file for ImageProcessor class, it declares processImage() slot which can be invoked from QML code.
#ifndef IMAGEPROCESSOR_H
#define IMAGEPROCESSOR_H
#include <QObject>
class ImageProcessor : public QObject
{
Q_OBJECT
public:
explicit ImageProcessor(QObject *parent = 0);
public slots:
void processImage( const QString& image);
};
#endif // IMAGEPROCESSOR_H
Following is cpp file for ImageProcessor class. processImage() function retrieves Image from camera image provider. Once we have valid image, we can process it further.
I have been exploring and working on Ubuntu-Touch for some time now. I have wrote some QML code for official Ubuntu-Touch calendar app and mean time I was also working on porting my Audiobook Reader application. This porting exercise helped me to understand how mature SDK is and also helped me to learn the SDK.
There were some minor issue with Ubuntu-Touch SDK, but nothing serious that can affect the work. As per Ubuntu-Touch development guideline, they prefer QML/Javascript and suggest to avoid C++ code as much as possible. This will help to make sure most of application is compatible for most Ubuntu platform. I tried to follow guideline and tried to not use C++ till now for app. I found myself missing C++ now and then, sometimes there are plugins that we can use instead and sometime there is no alternative. Mostly I missed the File IO, surely SDK team will come up with something, but till now there is no solution.
Anyway, I did not faced any major issue and was able to create initial version of Audiobook Reader quite easily without much problem. Following is demo.
While I was working on Ubuntu-Touch core app calendar, I wanted to emit signal from java script to QML code.
As such there is no official way provided by Qt framework, but we can try some workaround for this. Initially I used observer pattern to notify QML code from javascript. but I needed to wrote quite handful of code, and I did not liked the solution, I asked my team mates in calendar team and Frank suggested quite neat solution for this.
His suggestion was to create a temporary QtObject in javascript and use it for notification. Following is code for the same.
In following code, I created a QtObject in javascript with dataChanged signal defined in it.
Sometime back I published a post that shows how to access Amazon Web Service using Qt. You can find original post here. Now that trend is to code everything from QML and Javascript ( at-least Ubuntu Touch is following that path to make it as device independent as possible), I tired to access AWS service from using pure QML and Javascript.
I required to use external Crypto library for HMACSHA1 algorithm but everything else can easily be done with pure QML/Javascript.
Following is code i used for downloading Book cover image from Amazon AWS.
First all to communicate with AWS you required to have AWS access key id and Secret Access key. You can get it from here. I am also importing the Crypto library, for using HmacSHA1 implementation. I am using crypto-js Library. You can download required the same from here.
.import "JSLib/rollups/hmac-sha1.js" as Crypto
//key and password, required to sign the request using HMACSHA1
var AWS_KEY = "KEY";
var AWS_PASS = "PASSWORD";
var END_POINT = "http://ecs.amazonaws.com/onca/xml";
var AWS_TAG = "TAG"
Now we have required information, we can are ready to create request. In following code, I am putting all required parameter in a map and then using it to create signature and URL. We also need to encode parameter, I am using encodeURIComponent for encoding required parameter.
Following is code for creating signature. Creating signature is the only tricky part to make code works as expected. I am using HmacSHA1 from Crypto-JS lib. This function return WordArray object, which can be converted to HEX string, binary string or base64. Once we have HmacSHA1 hash of request, we need to convert it to Base64. I was not able to use base64 function from Crypto-JS library due to some error. I decided to copy base64 function from library directly to my js file and use it. Then finally we need to encode this base64 output, which we can be used as signature while sending request.
function createSignature(queryItems) {
var strToSign = "GET\n";
strToSign += "ecs.amazonaws.com\n";
strToSign += "/onca/xml\n"
for( var prop in queryItems ) {
if( prop === "Signature") {
continue;
}
strToSign += ( prop+"="+ queryItems[prop]);
strToSign += "&"
}
//removing last &
strToSign = strToSign.slice(0,strToSign.length-1)
var signature = Crypto.CryptoJS.HmacSHA1(strToSign, AWS_PASS);
signature = base64(signature);
return encodeURIComponent(signature);
}
Now we are almost done, all we need to do is to use created signature and make HTTP request. Following code shows how. There is nothing new here. I am creating complete URL from all required parameter and making request using this URL by XMLHttpRequest object.
function createUrl(queryItems )
{
var url = END_POINT+"?";
for( var prop in queryItems ) {
url += ( prop+"="+ queryItems[prop]);
url += "&"
}
//removing last &
url = url.slice(0,url.length-1);
return url;
}
function sendNetworkRequest(url,callback) {
var http = new XMLHttpRequest();
http.onreadystatechange = function() {
if (http.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
console.log("Headers -->");
console.log(http.getAllResponseHeaders ());
console.log("Last modified -->");
console.log(http.getResponseHeader ("Last-Modified"));
} else if (http.readyState === XMLHttpRequest.DONE) {
console.log(http.responseText);
callback(http.responseText);
}
}
http.open("GET", url);
http.send();
}
Recently I needed to create database from QML application on which I am working. I wanted to separate database related code from QML and created javascript file to handle database related work. I had to struggle a little to make storage API work from javascript, below are my findings.
As you might already know to use sqllite database in QML you will need to import LocalStorage API.
import QtQuick.LocalStorage 2.0 as Sql
This will work for QML but if you want to use it from javascript then you will need to use following syntax.
.import QtQuick.LocalStorage 2.0 as Sql
Now you should be able to use LocalStorage API in javascript. LocalStorage related API can be accessed by using LocalStorage object and we imported LocalStorage module as Sql, we will need to use access LocalStorage object from Sql scope. Like below.
var db =
Sql.LocalStorage.openDatabaseSync("TestDB", "1.0", "Description", 100000);
Now you can use this db object to create table and select values from it just like you do in QML.
Following is sample example.
function getDatabase() {
var db =
Sql.LocalStorage.openDatabaseSync("TestDB", "1.0", "Description", 100000);
//create table
db.transaction(
function(tx) {
var query="CREATE TABLE IF NOT EXISTS TEST(Id INTEGER PRIMARY KEY, Title TEXT)";
tx.executeSql(query);
});
return db;
}
function printValues() {
var db = getDatabase();
db.transaction( function(tx) {
var rs = tx.executeSql("SELECT * FROM TEST");
for(var i = 0; i < rs.rows.length; i++) {
var dbItem = rs.rows.item(i);
console.log("ID:"+ dbItem.Id + ", TITLE:"+dbItem.Title);
}
});
}
For past some time I was working on porting my Audiobook Reader application to BlackBerry BB10 platform.
Application is quite similar to Meego version, though there are few feature missing in BB10 which are available on Meego version. I thought I will work on that once I have some feedback from existing version.
So if you have installed and have some feedback kindly provide those.
Application is available both as free version and paid version. Feature wise both version are same, only difference is free version is ad sponsored.
Recently I was creating an application for BB10, where I need to disable display dimming. This same approach also works to avoid auto lock.
There is already API for this if you are using cascades API.
Following should do for Cascades Application
//for C++
Application::mainWindow->screenIdleMode = ScreenIdleMode::KeepAwake;
//for QML, we need to use constant as it seems required enum is not exported properly
Application.mainWindow.screenIdleMode = 1
But my application was pure Qt application. For this I required to use native API. Following is code which i used to achieve desired behavior.
I tried to do the same with BB10 Cascades QML. Final output will look like below.
While it was quite straight forward to achieve this with Qt or QML, with Cascades you will need to tweak few things, like desabling the implicit Cascades animation and enabling cliping.
First, I am creating parent Container which will hold ImageView with sprite sheet as image source.
We need enable clipping by setting clipContentToBounds to true so only certain portion of image is visible not whole image and also need to set preferredHeight, preferredWidth to indicate clip region.
Then I am attaching the renderNextFrame function with timeout signal. renderNextFrame
takes care of which frame to display by changing current frame and translating the image view accordingly.
Second, I am creating ImageView with sprite sheet image as image source. We need to set scalingMethod to ScalingMethod.AspectFill so that unnecessary scaling is not performed. Again we need to set preferredWidth, preferredHeight so that proper frame is displayed.
I am also storing max frame count and current frame number with image.
And finally we need to disable the implicit animation for translationX property. Without this you will not be able to achieve smooth sprite animation. We can use ImplicitAnimationController element to disable implicit animation. However it allows only certain property to be disabled. Here is documentation about ImplicitAnimationController.
Once this is done, you will be able to see smooth animation.
Recently BB10 device is getting in to news for its Qt framework support. I got curious about it and decided to port my Harmattan Qt app to BB10 device. First of all I am glad that finally there will be a real main stream device that will support Qt as development framework.
After installing BB10 NDK and going through few sample application I realized that I need to create BlackBerry Cascades C++ project using its QNX Momentics IDE event though I just wanted to create plain Qt application. So I created the Cascades C++ project and merged my existing Qt App's code to BB10 project.
I was aware that in order to run the application I need to change Harmattan component used in QML to standard QML component but I wanted to check debugging support provided by Momentics IDE to debug Qt cpp code and QML code. So I started application and I was staring at while screen with no error message in console of IDE and seems like there is no support to debug QML code or Java script code.
I added few debug message to identify problem but still did not see any message in console. I still don't know how to see those debug log from Qt app in BB10. I think there must be some installation problem.
In addition to this debug log problem, Its emulator is not working in normal mode on my HP Elitebook 6930p laptop with Ubuntu as OS. I always have to run it in safe mode. While running it in Safe mode, I faced another major problem. The emulator spill out of my laptop's 15 inch screen. Default emulator resolution is so big, I have to scroll a lot to see the whole screen of device. It seems that controller utility provided with emulator is not supposed to work when emulator is running in safe mode. This is so discouraging. However I decided continue my porting effort.
So no debug log and huge emulator that my 15 inch laptop can not contain, I decided to first try to run my application using Qt Desktop version and once application is working fine on desktop Qt SDK. I merged my code back to BB10 project.
I needed to make some minor change relate to path before I can see anything running. In BB10, Qt application locate images and QML file in assets folder not from Qt's Resource file. So I made necessary changes to use Image and QML file from Assets folder. Actually you can specify where your image files and QML files are present in bar-descriptor.xml but I am using default assets folder only.
Following is how you can show QML file located in assets folder using QDeclarativeView.
So after this I was able to see my application in emulator. But my application was supported in landscape mode only so I need to make change to make it launch in landscape mode only. To make application support either Portrait or Landscape mode, you need to set aspectRatio tag in bar-descriptor.xml, and to disable auto orientation change you can set autoorients tag to false
Following is my entry into bar-descriptor.xml
So now my plain Qt Application is running fine in emulator, but my application keeps running even after its minimized. I realized that I need to capture BPS Event in order to detect application minimize and maximize event. I found good information here for this purpose.
Following is my code to detect application minimize event so I can pause my application.
static QAbstractEventDispatcher::EventFilter previousEventFilter = 0;
static bool bpsEventFilter(void *message)
{
bps_event_t * const event = static_cast<bps_event_t>(message);
if (event && bps_event_get_domain(event) == navigator_get_domain()) {
const int id = bps_event_get_code(event);
//unsigned int code = bps_event_get_code(event);
switch ( id ) {
case NAVIGATOR_WINDOW_INACTIVE:
qDebug() << "INFO: Window inactive";
break;
case NAVIGATOR_WINDOW_ACTIVE:
qDebug() << "INFO: Window active";
break;
case NAVIGATOR_WINDOW_STATE:
navigator_window_state_t state = navigator_event_get_window_state(event);
if (state == NAVIGATOR_WINDOW_FULLSCREEN) {
qDebug() << "INFO: Resume game";
} else {
qDebug() << "INFO: Pause game";
Utils::instance()->PauseGame();
}
break;
}
}
if (previousEventFilter)
return previousEventFilter(message);
else
return false;
}
This is how you add event handler in main function.
So After all this my porting activity is almost complete. Now I need to learn how to submit application to BB10 store. Let's see how that goes. So overall I feel BB10 provides nice Qt support for app development, emulator support needs to be improved though to be considered as useful ( at lest in my case).
Past some time I was working on update of my application Crazy Chickens for N9.
In new version, Game character recognizes gesture based on user motion and move his bucket accordingly to catch egg. Game uses the phone back camera to capture image and recognizes gesture by tracking motion of some predefined colored object, which user is holding and moving to move the character. Hope you will enjoy the new update.
There also some minor update in UI and game logic to make game more enjoyable.
Game need some setup before you play the game, You will need to connect phone to TV using TV out cable and place phone such that it back camera faces you. Game can recognized four color, Red, Blue, Yellow and Green. You need to choose one color and hold that colored object in hand. To move character to put bucket under hen, you need to move colored object in direction of hand.
Please also make sure that you are fully visible and color object's movement does not go out of camera frame. Also make sure that there is enough light in room else game might has problem recognizing color clearly.
If you don't have TV out cable then you can install VNC server on Phone and connect it from PC to project phone's screen on PC.
Hope you will like the update. Please let me know if you have any feedback. I will try to update game with your feedback.
Long time ago, I posted implementation of Priority Queue implemented using Qt's
QQueue data structure. Here is old post.
That code was offering o(n) performance for enqueue operation and o(1) performance for dequeue operation. This might be acceptable for small data set. But for large data set you might want to use Priority queue based on Binary heap implementation.
I tried to implement my old priority queue using Binary heap, here is my implementation.
Following code implements BinaryHeap using QList. BinaryHeap class implements enqueue, dequeue and count method.
template <class T>
class BinaryHeap {
public:
void enqueue(T item) {
mList.append(item);
int i = mList.count() - 1;
int parent = (i-1)/2;
while( parent >= 0 && mList[i] < mList[parent] ) {
T temp = mList[parent];
mList[parent] = mList[i];
mList[i] = temp;
i = parent;
parent = (i-1)/2;
}
}
T dequeue() {
if( mList.isEmpty()) {
return T();
}
T item = mList[0];
int i = 0;
mList[0] = mList[ count()-1];
mList.removeLast();
while( i < count() ) {
int left = 2*i+1;
int right = left + 1;
if( right > count() - 1) {
break;
}
int min = left;
if( mList[right] < mList[left] ) {
min = right;
}
if( mList[i] > mList[min] ) {
T data = mList[min];
mList[min] = mList[i];
mList[i] = data;
i = min;
} else {
break;
}
}
return item;
}
int count() const {
return mList.count();
}
private:
QList<T> mList;
};
And based on above BinaryHeap class, following is my PriorityQueue class.
enum Priority {
Low = 2,
Normal = 1,
High = 0
};
template <class T>
class PriorityQueue
{
public:
void enqueue( Priority priority, T data) {
Item item(priority,data);
mHeap.enqueue(item);
}
T dequeue() {
Item item = mHeap.dequeue();
return item.mData;
}
int count() const {
return mHeap.count();
}
private:
BinaryHeap<Item> mHeap;
};
And Item class looks like below.
class Item{
public:
Item() {
}
Item(Priority priority, T data ):
mPriority(priority),mData(data)
{}
bool operator<(const Item& other) {
return mPriority < other.mPriority;
}
Priority mPriority;
T mData;
};
In this blog post I have written how we can access the individual frame from QCamera, In this blog post I will show how to use those frame to track some particular colored object and detecting gesture from motion of that object.
Following is demo of my sample application running on N9.
Tracking colored object
I don't know the actual algorithm for detecting color in image but i created simple algorithm that will detects some predefined color in image. Please note that if image has multiple object with the color which we are tracking it will return rectangle which cover all objects, not individual rectangle of each object.
As I am not interested in details of captured image, just interested in checking if image has defined color object or not, I reduces size of image to half, so i have to process small number of pixel to detect color.
Then to detect color in image, I convert image capture from camera from RGB color spec to HSV color spec, as its quite easy to process HSV color spec to detect color.
After image is converted to HSV color spec, I converted image to black and white image, black portion will be detected object and rest of things will be in white. After getting this image I just need to scan image to find area of black portion of image.
So now I have coordinate of colored object which we are detecting.
Following code implements the above logic to detect the red colored object, in code I combined process of converting image to black and white and detect the black portion of image.
QRect ColorMotionDetector::detectColor( const QImage& origImage)
{
//reduce size of image
QImage image(origImage);
image = image.scaled(QSize(320,240));
emit originalImage(image);
//rectanlge of detected colored object
int maxX = -1;
int minX = 99999;
int maxY = -1;
int minY = 99999;
int width = image.width();
int height = image.height();
bool detected = false;
//black and white image
QImage converted(image.size(),image.format());
for (int y = 0; y< height; ++y ) {
for( int x = 0; x < width; ++x ) {
//convert individual pixel to HSV from RGB
QRgb pixel = image.pixel(x,y);
QColor color(pixel);
color = color.toHsv();
//default whitel color for other object
QRgb newPixel = qRgb(255, 255, 255);
//detecting red color
if( color.hue() >= 0 && color.hue() <= 22
&& color.saturation() <= 255 && color.saturation() >= 240
&& color.value() <= 255 && color.value() >= 100 ) {
detected = true;
if( x > maxX ) {
maxX = x;
} else if( x < minX ) {
minX = x;
}
if( y > maxY ) {
maxY = y;
} else if( x < minY ) {
minY = y;
}
//black color for detected object
newPixel = qRgb(0, 0, 0);
}
converted.setPixel(x,y,newPixel);
}
}
QRect rect;
if( detected) {
rect = QRect(minX,minY, maxX - minX, maxY-minY );
//drawing red rectangle around detected object
QPainter painter( &converted );
painter.setPen(QPen(Qt::red));
painter.drawRect(rect);
painter.end();
}
emit processedImage(converted);
return rect;
}
Detecting swipe gesture
When we detect the position of object using above color detection code, we can use that position to detect if position tracked from individual image create some kind of gesture.
I will show how to use captured position to detect horizontal swipe gesture, we can easily extend it to detect vertical swipe or diagonal swipe.
I used following logic to detect swipe gesture,
> As color detection code returns position of tracked object, We compare this new position with its old position.
> If there is any progress in motion of object, we add difference of x coordinate to total progress made. In case of no progress, we discard whole gesture and reset variable that keep track of motion.
> While doing so if we detect certain amount of movement in particular direction, we decide if gesture was left swipe or right swipe using difference in position of object and reset the variables.
Following code implement above logic.
Gesture ColorMotionDetector::detectGesture(QRect rect) {
//not valid rectangle, mean no object detected
if( !rect.isValid()) {
mLastRect = QRect();
mXDist = 0;
return Invalid;
}
//there is no previous cordinate, store rect
if( !mLastRect.isValid() ) {
mLastRect = rect;
mXDist= 0;
return Invalid;
}
Gesture gesture = Invalid;
int x = rect.x();
int lastX = mLastRect.x();
int diff = lastX - x;
mLastRect = rect;
//check if there is certain amount of movement
if( qAbs( diff ) > 10 ) {
//there is movement in x direction, store amout of movement in total movement
mXDist += diff;
//x motion match to amount required for perticular gesture
//check if motion of let to right or right to left
if( mXDist > 150 ) {
qDebug() << "Right horizontal swipe detected..." << mXDist;
mXDist = 0;
gesture = SwipeRight;
} else if ( mXDist < -150 ) {
qDebug() << "Left horizontal swipe detected..." << mXDist;
mXDist = 0;
gesture = SwipeLeft;
}
} else {
//discard the gesture
mXDist = 0;
mLastRect = QRect();
}
return gesture;
}
Putting all together
Now we have code that detect colored object and code that detect gesture. Following code shows how those function are used together.
//detection motion from captured image from camera
void ColorMotionDetector::detectMotion( const QImage& image) {
QRect rect = detectColor( image);
Gesture gesture = detectGesture( rect );
if( gesture != Invalid ) {
emit gestureDetected( gesture );
}
}
Following is vary simple gesture handler, which just print handled gesture.