Showing posts with label Cascades. Show all posts
Showing posts with label Cascades. Show all posts

Saturday, December 27, 2014

Showing Remote Image using ImageView in BB10 Cascades

While working on my BB10 App update, I required to show Remote Image using ImageView BB10 cascades API. ImageView or Image component by default does not support loading image from URL on internet. I implemented a custom component which serve the purpose and also easily integrated with ImageView.

Following is implementation for the same, I hope it will be useful to someone.

Let's start by showing how my custom component works. Following code shows how to use ImageDownloader custom component along with ImageView to display Grid of images from internet. Data Model contains two kind of URL, url for low resolution image and url for high resolution image.
import my.library 1.0

ListView {
  id: listView
  
  layout: GridListLayout {}
  dataModel: model;
  
  listItemComponents: [
      ListItemComponent {                        
          ImageView {
              id: imgView
              imageSource: "default_image.jpg"  
                                        
              attachedObjects: [
                  ImageDownloader {
                      id: imageLoader
                      url: ListItemData.mediumImage
                      onImageChanged: {                 
                          if (imageLoader.isVaid) {
                              imgView.image = imageLoader.image
                          }
                      }
                  }
              ]
          }
      }
  ]
}
ImageDownloader component is available in QML because we imported "my.library". We can make any C++ code available to QML by registering it to QML System, folloiwing snippet shows how we can register C++ component to QML system.

#include "ImageDownloader.h"

int main(int argc, char **argv) 
{
 Application app(argc, argv);

 qmlRegisterType<ImageDownloader>("my.library",1, 0, "ImageDownloader");

 QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(&app);

 AbstractPane *root = qml->createRootObject();
 app.setScene(root);

 return Application::exec();
}
Now that custom component is ready to be used with QML, let's see how its implemented. Below if header file for ImageDownloader class. We are defining few properties like url, image and isValid. By setting "url" we can initiate download of image, when "image" download is finished downloaded image can be accessed by using "image" property. We can check if image is valid or not by checking "isValid" property. we are also defining few signal like "urlChanged" and "imageChanged", which are emited when url is changed or image is downloaded. And we are using QNetworkAccessManager to download image from internet.
#ifndef IMAGEDOWNLOADER_H_
#define IMAGEDOWNLOADER_H_

#include 
#include 

class QNetworkAccessManager;

class ImageDownloader: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QVariant image READ image NOTIFY imageChanged)
    Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
    Q_PROPERTY(bool isVaid READ isValid CONSTANT);
public:
    ImageDownloader( QObject* parent = 0);
    virtual ~ImageDownloader();

signals:
    void urlChanged();
    void imageChanged();

private slots:
    QString url() const;
    void setUrl( const QString& url);

    QVariant image() const;
    bool isValid() const;

    void startDownload();
    void onReplyFinished();

private:
    QNetworkAccessManager mNetManager;
    QString mImageUrl;
    bb::cascades::Image mImage;
    bool mIsValid;
};

#endif /* IMAGEDOWNLOADER_H_ */
Implementation is also quite simple, let's see how its implemented. On URL change, we are initiating the image download using QNetworkAccessManager. When download is finished, we are reading image data in to buffer and creating Image using BB10 Image API.
#include "ImageDownloader.h"

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>

ImageDownloader::ImageDownloader(QObject* parent):
    QObject(parent), mIsValid(false) {}

ImageDownloader::~ImageDownloader() {}

QString ImageDownloader::url() const {
    return mImageUrl;
}

void ImageDownloader::setUrl( const QString& url)
{
    if(url != mImageUrl) {
        mImageUrl = url;
        mIsValid = false;
        mImage = bb::cascades::Image();
        emit urlChanged();
        emit imageChanged();
        startDownload();
    }
}

QVariant ImageDownloader::image() const {
    return QVariant::fromValue(mImage);
}

bool ImageDownloader::isValid() const {
    return mIsValid;
}

void ImageDownloader::startDownload() {
    QNetworkRequest request(mImageUrl);
    QNetworkReply* reply = mNetManager.get(request);
    connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
}

void ImageDownloader::onReplyFinished() {
    QNetworkReply* reply = qobject_cast(sender());
    QString response;
    if (reply) {
        if (reply->error() == QNetworkReply::NoError) {
            const int available = reply->bytesAvailable();
            if (available > 0) {
                const QByteArray data(reply->readAll());
                mImage = bb::cascades::Image(data);
                mIsValid = true;
                emit imageChanged();
            }
        }
        reply->deleteLater();
    }
}
That's all we have to do to do display remote image in BB10, hope you liked it.

Saturday, June 28, 2014

Resolving Folder reading issue from SD Card in BB10

There was a tricky bug in Audiobook Reader BB10 version for quite a some time which I was trying to resolve. Issue was that, for some folder in SD card, I was not able to browse it using QDir Qt API or Posix API. Though API was showing the folder, but when I try to find out folder's content, it reports folder as empty.

This was causing serious usability issue with Audiobook Reader application, as user were not able to add book from SD card. I debugged it long and tried to use various approach to resolve the issue but could not find any satisfactory solution.

I read it somewhere it causes issue if SD card was formatted with PC software and issue is resolved if you format SD card with BB10 setting. But this also looks hassle to user.

Finally, I decided to see how directory structure looks like and what is folders permission using SSH to device. When I used linux command like ls and cd, I was able to see folder's content fine. And I decide to use those command to resolve directory browsing issue.

Following is my code, if someone else is also facing similar issue. First I am escaping special character in filepath, else linux command will not work. Then I am creating "ls" command with sorting and with output which list directory content in absolute file path. Once we have proper command I am using popen API to run command and then using IO api to read command's output.
QStringList BookListModel::getFileList(const QString& dir) {

 QString filePath(dir);
 filePath.replace(" ","\\ ");
 filePath.replace("'","\\'");
 filePath.replace("`","\\`");
 filePath.replace("!","\\!");
 filePath.replace("$","\\$");
 filePath.replace("&","\\&");
 filePath.replace("*","\\*");
 filePath.replace("\"","\\\"");
 filePath.replace("(","\\(");
 filePath.replace(")","\\)");
 filePath.replace("[","\\[");
 filePath.replace("]","\\]");

 qDebug() << filePath;
 QString cmd = QString("ls -1 %1/*").arg(filePath);
 qDebug() << cmd;

 QStringList files;
 char path[1035];
 FILE* fp = popen(cmd.toStdString().c_str(), "r");
 if (fp == NULL) {
  qDebug() << "Failed to run command";
  return files;
 }

 while (fgets(path, sizeof(path)-1, fp) != NULL) {
  QString file = QString(path).trimmed();
  QFileInfo fileInfo(file);
  if (fileInfo.isFile() && FileModel::isSupportedMedia(fileInfo.fileName()) ){
   files << file;
  }
 }

 qDebug() << files;

 pclose(fp);
}
That's all, hope it will be helpful to someone.

Thursday, June 26, 2014

Audiobook Reader for BB10 now got Built for Blackberry status

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
  • Supports chapter skip
  • Supports 2 min, 1 min, 30 sec skip option
  • Supports auto pause on call
  • Supports Play/Pause using HW buttons
  • Supports custom cover art download
  • Advance file browser
  • Supports sleep timer
  • Supports shuffle
  • and some more...
Following is demo for current version.


Hope you will like the updates.

Saturday, June 7, 2014

Geting Call notification from Cascades QML code in BB10

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.
    <permission>access_phone</permission>
    <permission>control_phone</permission>
Now we need to link system lib to our app.
LIBS += -lbbsystem 
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.

import bb.system 1.0
import bb.system.phone 1.0

Page {
    attachedObjects: [
        Phone {
            id: phone
            onCallUpdated: {
                console.log(" ############## Phone::onCallUpdated ") ; 
                //do the needful
            }
        }
    ]
}
Thant's it. Hope it will be helpful.

Saturday, February 1, 2014

Handling GamePad events in BB10 Cascades App

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);
}
On destructor we should release screen context.
GamePadObserver::~GamePadObserver()
{
 screen_destroy_context(_screen_ctx);
}
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.
#include "GamePadObserver.h"

static QAbstractEventDispatcher::EventFilter previousEventFilter = 0;
static GamePadObserver _gamePadObserver;

static bool bpsEventFilter(void *message)
{
    bps_event_t * const event = static_cast(message);

    if( event && bps_event_get_domain(event) == screen_get_domain()) {
     _gamePadObserver.handleScreenEvent(event);
    }

    if (previousEventFilter)
        return previousEventFilter(message);
    else
        return false;
}

int main(int argc, char **argv)
{
    // Register GamePadObserver so we can use it in QML code
    qmlRegisterUncreatableType("GamePadObserver", 1, 0,"GamePadObserver", "");

    QApplication app(argc, argv);

    previousEventFilter = QAbstractEventDispatcher::instance()->setEventFilter(bpsEventFilter);

    QScopedPointer view(new QDeclarativeView());
    view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    view->setResizeMode( QDeclarativeView::SizeRootObjectToView );

    QDeclarativeContext *ctxt = view->rootContext();
    view->setSource(QUrl("app/native/assets/main.qml"));

    ctxt->setContextProperty("GamePad",&_gamePadObserver);
    view->showFullScreen();

    return app.exec();
}
And finally, out GamePadObserver class is ready to deliver GamePad events. To handle GamePad events in QML you can use GamePadObaserver class as below.
        
        Connections{
            target: GamePad
            onButtonPressed: {
                                
                if(button == GamePadObserver.DPAD_UP_BUTTON
                || button == GamePadObserver.DPAD_DOWN_BUTTON ){
                    handleUpButton();
                }else if(button ==  GamePadObserver.DPAD_LEFT_BUTTON 
                || button ==  GamePadObserver.DPAD_RIGHT_BUTTON) {
                    handleLeftButton();
                }          
                else if(button == GamePadObserver.MENU1_BUTTON                 
                || button == GamePadObserver.MENU2_BUTTON
                || button == GamePadObserver.X_BUTTON
                || button == GamePadObserver.Y_BUTTON 
                || button == GamePadObserver.A_BUTTON 
                || button == GamePadObserver.B_BUTTON ) {
                    handleButtonSelection();
                } 
            }        
        }
So, I hope this will help to utilize GamePad events in your games.

Friday, September 20, 2013

Sharing on Facebook, Twitter with BB10 Cascades app

I am currently adding sharing feature to my BB10 Audiobook Reader app. I wanted to allow sharing on Facebook, Twitter, BBM  and other supported platform.

BB10 support this via Invocation framework, Using Invocation framework you can share things using native application.

Using Invokation Framework is easy, you just need use InvokeActionItem and set some property and all is done.

Following is my code that share comment via Facebook or Twitter. Following code will allow to share using any application which support plain text invocation target.

By default it will also have sharing icon, you can use Image property to change it to custom icon.

Pressing share button on toolbar will show all application which can share the data specified by mimeType, User can select one of those and share data using that application.

Page {
    ...
    actions[
        InvokeActionItem {
            title: "Share"
            query {
                mimeType: "text/plain"           
                invokeActionId: "bb.action.SHARE"
            }
            onTriggered: {
                data = "Comments to share";
            }
        }
        ...
    ]
    ...
}
You can find more about, how to share with individual application from here. And here is documentation for InvokeActionItem.

Here is how it looks and works on device.




Monday, August 26, 2013

Using Invocation API with BB10 Cascades

I was working on update of my application Audiobook Reader. Currently my application has custom file browser that allow user to add file but I wanted to add allow user to add book/file directly from native BB10 file browser, like shown in below picture.



BB10 supports this usecase by Invocation API, your application can receive invocation from other application by using it. Your application can also use other application by the same way.

In my app, I just wanted to receive invocation from default File Browser. To do this we need to register our application as invocation target by entering details in to .bar file.

Following is entry for my app.

Here Target-Type mean can be Card or Application, Application will launch app as separate process, Card will launch app in scope of calling app.

Icon is your app's icon, that will be used by other app to display it in menu.

Action Open mean, suggested file can be opened by other app. Other possible value is View.

Mime-Type is type of file your application can support and exts mean, the extension of file that your app can handle.

    <invoke-target id="com.example.AudiobookReader">
      <invoke-target-type>application</invoke-target-type>
      <invoke-target-name>Audiobook Reader</invoke-target-name>
      <icon><image>icon.png</image></icon>
      <filter>
        <action>bb.action.OPEN</action>
        <mime-type>audio/*</mime-type>
        <property var="exts" value="mp3,..,..,..."/>
      </filter>
    </invoke-target>

Once this is done, Then we need to add handler code that will be called when other application invoke our app. To do that, we need to connect InvokeManager's onInvoke signal to our slot.

like below.

 bb::system::InvokeManager invokeManager;

 QObject::connect(&invokeManager, SIGNAL(invoked(const bb::system::InvokeRequest&)),
     &helper, SLOT(onInvoke(const bb::system::InvokeRequest&)));

When our slot is called, we can retrieve URL and other information from InvokeRequest and do the further processing.

void Helper::onInvoke(const bb::system::InvokeRequest& request) {

 mInvokationUrl = request.uri().toString(QUrl::RemoveScheme);

 QFile file( mInvokationUrl );
 QFileInfo fileInfo( file );

 if( !QFile::exists(mInvokationUrl) ) {
  showErrorDialog("Error Locating file!!");
  return;
 }

 if( !FileModel::isSupportedMedia(mInvokationUrl)) {
  showErrorDialog("Not valid Audio file!!");
  return;
 }

 showAddBookDialog(fileInfo);
}
This is it, you can learn more about invocation framework from here.

Saturday, August 10, 2013

Using VibrationController API wit BB10 Cascades QML

I tried to add vibration support to my BB10 application Counter. Application is simple, which just increment the count when you press add button and decrements count on minus button. Simple but useful, I use it quite often to count the stuffs.

But you need to see at screen to see if it button is really pressed, so I thought to add vibration support, so I know button is pressed even without looking at screen. This is first time I am adding Vibration support to any of BB10 application. So I thought to share code.

Code is much simple, only trick is that you need to register VibrationController c++ class to QML system.

Here is how it goes.

First we need to link device lib which has VibrationController API. Add following line to your .pro file and you are good to go.

LIBS += -lbbdevice

Now in main.cpp you need to add proper header to locate VibrarionController API and the register it to QML registry.
#include <bb/cascades/Application>
#include <bb/cascades/QmlDocument>
#include <bb/cascades/AbstractPane>

#include <bb/device/VibrationController>

using namespace bb::cascades;
using namespace bb::device;

int main(int argc, char **argv)
{
    Application app(argc, argv);

    qmlRegisterType<vibrationcontroller>
        ("bb.vibrationController", 1, 0, "VibrationController");

    new Counter(&app);

    return Application::exec();
}

Now we are ready to use VibrarionController with QML code. First you need to import VibraionController QML by import statement. Then we are creating it by defining it in attachedObjects. Once this is done, we can use it's start API to vibrate phone with certain intensity for certain durarion.

import bb.cascades 1.0
import bb.vibrationController 1.0

Page {
    id: page
    Container {
        id: root
        layout: DockLayout {}
        preferredHeight: 1280
        preferredWidth: 768
        touchPropagationMode: TouchPropagationMode.Full;
        
        attachedObjects: [
            VibrationController {
                id: vib
            }
        ]

        function vibrate() {
            //first parameter intensity, second parameter duration for vibration
            vib.start(80, 100);
        }

        Button{
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Center 
            onClicked: {
                root.vibrate();
            }
        }
    }
}

Thant's all, hope this will be helpful.

Tuesday, July 23, 2013

Getting Size of control in BB10 Cascades API

I was working on update for my Audiobook Reader application for BB10. I wanted to create a custom control like shown below and I required to use AbsoluteLayout to create it.



In this case layouting needs to be done manually and to do that we need to consider width and height of the controls and position controls using those. But unlike Qt Quick, BB10 Cascades controls does not provide access to width, height and x,y properties.

However Cascades API offers LayoutUpdateHandler control, this control gets notification when layout is complete and with notification it offers actual location and size of control and it also store these values in it's layoutFrame property. I used that to find out size of control and did required layouting.

Following code shows how LayoutUpdateHandler can be used to find out size of control.
    Container {
        id: indicators
        layout: AbsoluteLayout {}
        
        touchPropagationMode: TouchPropagationMode.Full
        
        background: Color.DarkGray

        attachedObjects: [
            LayoutUpdateHandler {
                id: handler
            }
        ]

        property alias width: handler.layoutFrame.width;
        property alias height: handler.layoutFrame.height;
    }
Now you can use width and height property to know control's actual size.

Friday, June 21, 2013

Inviting Application Review from Application in BB10 using Cascades API

Most time people leave a review for application when they are unhappy but if they are happy with your app, Chances are high that they will ignore the review. And this might leave impression on App World that your application if not working fine and is of low quality.

To overcome this we might offer user a option to review application from within application and some user might be happy enough to provide review.

BB10 has nice QML Cascades API, which makes the task very easy. Only few lines of code and you can have option to offer review from application.

Following is the code which I am using. You can use InvokeActionItem, with actions property of Page. "sys.appworld" this is invocation target id for App World. And in URI you need to mention your application's ID, which you can find from App World.
Page {
    ...
    actions: [
        ...
        InvokeActionItem {
            title: "Rate Application"
            ActionBar.placement: ActionBarPlacement.InOverflow
            imageSource: "rate.png"
            query {
                invokeTargetId: "sys.appworld"
                invokeActionId: "bb.action.OPEN"
                uri: "appworld://content/20200034"
            }
        }
        ...
    ]
    ...
}


Sunday, February 3, 2013

Audiobook Reader for BlackBerry BB10

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.

Here is live demo on BB10 dev alpha device.


Here are few snapshot of application for BB10.








Hope you will like the application.






Friday, February 1, 2013

Preventing display dimming in BB10 with Qt

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.
        QScopedPointer view(new QDeclarativeView());
        view->setSource(QUrl("app/native/assets/main.qml"));
        view->showFullScreen();

 int idle_mode = SCREEN_IDLE_MODE_KEEP_AWAKE;
 WId winId = view->winId();
 screen_set_window_property_iv(screen_window_t(winId), SCREEN_PROPERTY_IDLE_MODE,&idle_mode);
You will need to add following headers.
#include <bps/bps.h>
#include <bps/navigator.h>
#include <bps/screen.h>
And need to link screen library.
LIBS += -lscreen

Saturday, January 12, 2013

Signal Slot connection with C++ object and Cascades QML

In this blog post I shown how to achieve signal slot connection with QML and C++ object using Connections QML element.

In BB10 Cascades there are two ways to achieve the same.

First using java script connect method, like blow. Following code is from my previous post of Sprite animation.

This is preferred method as we are using only Cascades API.

Here timer is exported c++ object. By using timer.timeout.connect(renderNextFrame) statement. We are connecting the timeout signal of timer C++ object to renderNextFrame function.

import bb.cascades 1.0
...
        Container {
            id: sprite;
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            layout: AbsoluteLayout {}  
            
            onCreationCompleted: {
                timer.timeout.connect(renderNextFrame);
                timer.start(200);
            }
                      
            function renderNextFrame() {
                ...
            }
       }
...

Second, Using Connections element like below.
import bb.cascades 1.0
import QtQuick 1.0
...
        Container {
            id: sprite;
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            
            onCreationCompleted: {
                timer.start(200);
            }
            
            attachedObjects: [
                Connections {
                    target:timer;
                    onTimeout:{
                        sprite.renderNextFrame();
                    }
                }
            ]
            
            function renderNextFrame() {
             ...
            }
        }
...
To use Connections element, we need to import QtQuick components, as Connections element is part of QtQuick.

Once its imported, you will need to declare Connections element as part of attachedObjects. Then you can use it as usual in QML.

And just for reference, I create Timer object like below for above example.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    QTimer* timer = new QTimer(app);
    qml->setContextProperty("timer",timer);

Sprite animation from sprite sheet with BB10 Cascades QML

In past I have posted solution for how to achieve sprite animation from sprite sheet with Qt C++ widget framework and with QML framework.

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.

Following is my code for the dragon sprite.

        Container {
            id: sprite;
            preferredHeight: 100;
            preferredWidth: 100;
            clipContentToBounds: true
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            layout: AbsoluteLayout {}  
            
            onCreationCompleted: {
                timer.timeout.connect(renderNextFrame);
                timer.start(200);
            }
                        
            function renderNextFrame() {
                dragonSprite.currentFrame = 
                      (dragonSprite.currentFrame + 1) % dragonSprite.frameCount;
                dragonSprite.translationX = 
                     -(dragonSprite.currentFrame * sprite.preferredWidth);
            }
                              
            ImageView{
                id: dragonSprite
                imageSource: "dragon.png"
                scalingMethod: ScalingMethod.AspectFill
                preferredHeight: 100
                preferredWidth: 500 
                property int currentFrame: 0
                property int frameCount: 5
                
                attachedObjects: [
                    ImplicitAnimationController {
                        propertyName: "translationX"
                        enabled: false
                    }
                ]  
            }
        }
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.

Friday, January 4, 2013

Creating custom listview with BB10 Cascades API

As I indicated in my previous post, I am trying to port my Audiobook Reader application to BB10. I needed to make few changes to use BB10 Cascades API.

Previously I posted about how to create custom dialog box using Cascades QML API.

In this post I will talk about how to create custom list view.

For most case StandardListItem should be enough for use. Its looks like below.


But I wanted to learn creating custom component and also wanted to add some dynamic behavior to my list item.

I wanted to add Delete button on my list item. By default delete button is invisible, its get visible on long press event. It you tap on button the delete event is fired, touching anywhere else in item makes it invisible again. Also touching List item in normal state fire the selection event.

I was not sure how I can achieve this behavior with StandardListItem and I create my custom list item like below.

Normal condition, it looks like below.

On long press event on list item, it shows the delete button.

Now lets see how I created list item to satisfy above behavior.

First lets create list view, which uses the custom list item named ListDelegate and also have necessary function (selected and delete )to handle events from list item.
            ListView {
                id: listView
                dataModel: listModel.model
                listItemComponents: [
                    ListDelegate {
                    }
                ]

                //called by listdelegate manually
                function selected(index) {
                    console.log("###### Index selected:" + index);
                }

                //called by listdelegate manually
                function delete(index) {
                    console.log("###### Delete index:" + index);
                }
            }
Following is code for ListDelete component. I removed some code to keep code short and relevant.

First we need to set touchPropogationMode to Full on root container, so the all sub component get chance to handle their event. Then I added to sub container to main container to hold actual list item content and one to hold delete button and added gesture handler to both of them.

Unfortunately I was not able to find easy way to emit signal from ListItem to ListView, so I ended up calling ListView function manually to indicate select and delete event. By calling list view function
 root.ListItem.view.selected() and root.ListItem.view.delete()
.
To access current list items's index, we can use root.ListItem.indexPath property.
ListItemComponent {  
    
    Container{
        id: root
        layout: DockLayout {}
        touchPropagationMode: TouchPropagationMode.Full;
          
        Container{
            id: itemRoot
            layout: DockLayout {}
            preferredWidth: 768; preferredHeight: 120  
            
            gestureHandlers:[
                LongPressHandler {
                    onLongPressed: {
                        //make delete button visible
                        deleteBtn.opacity = 1;
                        itemRoot.opacity = 0.3 
                    }
                },
                TapHandler {
                    onTapped: {   
                        if( deleteBtn.opacity == 1 ) {   
                            //make delete button hide                
                            deleteBtn.opacity = 0;
                            itemRoot.opacity = 1; 
                        } else {
                            //fire selected event
                            root.ListItem.view.selected(root.ListItem.indexPath); 
                        }
                    }
                }
            ]  
                        
            Divider {
                verticalAlignment: VerticalAlignment.Bottom
                horizontalAlignment: HorizontalAlignment.Center  
            }
            
            Container {
                
                verticalAlignment: VerticalAlignment.Center
                layout:StackLayout {
                    orientation: LayoutOrientation.LeftToRight;                
                }
                                
                ImageView{
                    id: image
                    preferredHeight: 110; preferredWidth: 110
                    imageSource: ListItemData.image;
                    verticalAlignment: VerticalAlignment.Center
                }
                
                Container {             
                    layout:StackLayout {}
                    bottomPadding: 10
                    Label{
                        text: ListItemData.title;            
                    }
                    
                    Label{
                        text: ListItemData.subTitle;                            
                    } 
                }
            }
        
        } 
        
        Container {
            id: deleteBtn
            
            opacity: 0; preferredWidth: 150; preferredHeight: 100
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Right  
          
            ImageView{
                imageSource: "delete.png";
                verticalAlignment: VerticalAlignment.Center
                horizontalAlignment: HorizontalAlignment.Center
            } 
            
            gestureHandlers:[
                TapHandler {
                    onTapped: {                              
                        if( deleteBtn.opacity == 1 ) {                    
                            deleteBtn.opacity = 0;
                            itemRoot.opacity = 1;
                            root.ListItem.view.delete(root.ListItem.indexPath); 
                        } 
                    }
                }
            ]           
        }
    }
}
Thought it seems lot of code, its quite easy to create custom list item. Now lets see how we can provide data to ListView. For my case I created Data model in C++ and exported it to QML. You can export c++ Data model to QML using setContextProperty. listModel is instance of class derived from QObject.

  QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(&app);
  ListModel listModel;
 qml->setContextProperty("listModel", &listModel);
We are setting model to list view like dataModel: listModel.model, it means that listModel has property called model. Following is code from the same.

class ListModel : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bb::cascades::DataModel* model READ model);

public:
    void loadData();
    bb::cascades::DataModel* model(return mListModel;);
private:

    QMapListDataModel* mListModel;
};
And I am populating the ListModel like below. I am packaging my List ItemData inside QVariantMap, so that ListItem can easily access the data.

void ListModel::loadData()
{
    mListModel->clear();

    foreach( ... )
    {
  QVariantMap map;
 map["image"] = "image path";
 map["title"] = "title";
 map["subtitle"] = "subtitle";
 (*mListModel) << map;
   }
}
If you see ListItem's code, We can access the list item data like below.
                ImageView{
                    imageSource: ListItemData.image;
                }
                
                Container {             
                    Label{
                        text: ListItemData.title;
                    }            
                    Label{
                        text: ListItemData.subTitle;                            
                    } 
                }
Now finally its done. I hope it will be helpful to someone.

Tuesday, December 25, 2012

Creating custom Dialog using BB10 Cascades QML API

I recently got BB10 Dev Aplha Device to port my Harmattan Qt application to BB10 device.

I started with porting my audiobook reader application. Cascades QML components are quite similar to Harmattan QML components. Thought it requires some effort to port from Harmattan to Cascade.

I will post about my porting effort in this post and following posts.

In this post I will write about how to create simple Dialog box using Cascade QML component.

My final dialog looks like below.

For creating dialog you need to extend Dialog component. I added signal to indicate action from dialog to other component.

I think code does not required much explanation so following is code for creating and calling the dialog.
import bb.cascades 1.0

Dialog {
    id: dialog
      
    signal sampleSignal(string text);
    
    attachedObjects: [
      ...
    ]

    Container {
        id: mainContainer
        preferredWidth: 700 
        layout: DockLayout {}
        verticalAlignment: VerticalAlignment.Center;
        horizontalAlignment: HorizontalAlignment.Center;
           
        background: Color.create("#f8f8f8")
        
        Container {
            layout: StackLayout {}
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            
            Container {
                layout: DockLayout {}
                background: Color.create("#6121be");
                horizontalAlignment: HorizontalAlignment.Fill;
                preferredHeight: 120
                rightPadding: 10
                leftPadding: 10
                
                Label {
                    text: "Dialog title" ;              
                    verticalAlignment: VerticalAlignment.Center;
                    textStyle{
                        base: titleStyle.style
                    }
                }
                
                ImageView {            
                    verticalAlignment: VerticalAlignment.Center;
                    horizontalAlignment: HorizontalAlignment.Right;
                    imageSource: "close.png"
                    onTouch: {
                        dialog.close();
                    }
                }   
            }
            Container {
                layout: StackLayout {}
                topPadding: 20
                bottomPadding: 20
                rightPadding: 10
                leftPadding: 10
                
                TextField {
                    id: name
                    hintText: "Add text here..."              
                }
                
                Divider {}             
                
                Button {
                    id: doneButton
                    text: "Done"
                    horizontalAlignment: HorizontalAlignment.Fill;
                    onClicked: {
                        doneButton.textAdded();
                    }
                    
                    function textAdded() {
                        dialog.sampleSignal(name.text);
                        dialog.close();
                    }
                }
            }
        }
    }
}

To use system font style I am using TextStyleDefinition and providing SystemDefaults as base style.
    attachedObjects: [
        TextStyleDefinition {
            id: titleStyle
            base: SystemDefaults.TextStyles.BigText
            color: Color.White
        },
        TextStyleDefinition {
            id: titleTextStyle
            base: SystemDefaults.TextStyles.TitleText
            color: Color.Black
        }
    ]
To use this dialog you can use following code. To open dialog, i created openDialog() function. Function used dlgDef, ComponentDefinition to create dialog object at runtime. You also need to connection signal from dialog to function defined in current page, which is calling this function, like this.
 dialog.sampleSignal.connect( page.dialogClosed );
    property Dialog dialog;
    function openDialog() {     
        if ( !dialog ) {
            dialog = dlgDef.createObject();
            //connecting signal to function
            dialog.sampleSignal.connect( page.dialogClosed );
        }         
        dialog.open();
    }
Dialog definition and function which will be invoked on signal can be defined like below.
            attachedObjects: [
                ComponentDefinition {
                    id: dlgDef;
                    source: "Dialog.qml";
         }     
     ]


 function dialogClosed(text) {
     console.debug("Dialog closed do something");
 }
That's all. Its quite easy to create custom dialog with Cascade QML.

Sunday, December 23, 2012

Using Timer with BB10 Cascades QML code

It is possible to use code Qt and QML API for BB10 development. But to get more native look and feel, I started exploring BB10 Cascades API.

Soon I started working with cascade API, I realized Timer QML element is now no longer available with Cascade QML API.

However you can export QTimer to cascade QML and use QTimer with your QML code.

Following example shows how that can be done.

First export QTimer to QML, by registering it with metasystem. You can put this code in main.cpp.

qmlRegisterType<QTimer>("my.library", 1, 0, "QTimer");
Then in QML file where you want to use QTimer import namespace where QTimer is exported, like below.
import my.library 1.0

Then define QTimer as attached object in QML.
attachedObjects: [
        QTimer{
            id: timer
            //set singleshot property if requireds
            singleShot: true
            //set interval
            interval: 5000
            
            onTimeout:{
                //do some stuff
            }
        }
    ]

You can start and stop timer by using it's id and then calling appropriate slots.
    function startTimer(){
        timer.restart();
    }
    
    function stopTimer() {
        timer.stop();
    }