Showing posts with label Qt Quick Components. Show all posts
Showing posts with label Qt Quick Components. 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.

Thursday, October 30, 2014

Creating dynamic QML object asynchronously

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.
var incubator = delegate.incubateObject(bubbleOverLay);
if (incubator.status !== Component.Ready) {
    incubator.onStatusChanged = function(status) {
        if (status === Component.Ready) {
            incubator.object.objectName = children.length;
            assignBubbleProperties( incubator.object, event);
        }
    }
} else {
    incubator.object.objectName = children.length;
    assignBubbleProperties(incubator.object, event);
}

Sunday, April 13, 2014

Creating Custom Swipe handler in QML

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.
import QtQuick 2.0

Rectangle {
    id: root
    width: 200
    height: 300

    property var delegate: comp;

    property var centralView;
    property var nextView;
    property var prevView;

    focus: true

    Component.onCompleted: {
        var colors = ["red","blue","green"];
        var objs = [];
        for(var i =0; i < 3; ++i){
            var obj = comp.createObject(root);
            obj.text = i+1;
            obj.color = colors[i];
            objs.push(obj);
        }

        centralView = objs[0]
        nextView = objs[1]
        prevView = objs[2]

        setViewPos();
    }

    function setViewPos(oldX){
        centralView.animate(50,0);
        nextView.animate(50,root.width);
        prevView.animate(50,-root.width);

        centralView.z = 1;
        nextView.z = 0;
        prevView.z = 0;
    }

    Keys.onRightPressed: {
        var tempView = centralView;
        centralView = prevView;
        prevView = nextView;
        nextView = tempView;

        centralView.animate(150,0);
        nextView.animate(150,root.width);
        prevView.x = -width
    }

    Keys.onLeftPressed: {
        var tempView = centralView;
        centralView = nextView;
        nextView = prevView;
        prevView = tempView;

        centralView.animate(150,0);
        prevView.animate(150,-root.width);
        nextView.x = width
    }

    SwipeArea{
        onSwipeEnded: {
            if(diff === 0) {
                root.setViewPos();
                return;
            }

            var tempView = centralView;
            if(diff < 0) {
                centralView = prevView;
                prevView = nextView;
                nextView = tempView;
            } else {
                centralView = nextView;
                nextView = prevView;
                prevView = tempView;
            }
            root.setViewPos();
        }

        onSwipeContinues: {
            centralView.x = centralView.x - diff;
            if(diff < 0) {
                prevView.x = prevView.x  + Math.abs(diff*1.6);
                prevView.z = 1
                centralView.z = 0;
            } else {
                nextView.x = nextView.x - Math.abs(diff*1.6) ;
                nextView.z = 1
                centralView.z = 0;
            }
        }
    }

    Component{
        id: comp
        Rectangle{
            id: rect
            property alias text: label.text

            width: parent.width; height: parent.height
            Text{
                id: label; anchors.centerIn: parent
            }

            function animate(duration, to){
                anim.to = to; anim.duration = duration
                anim.running = true
            }

            PropertyAnimation{
                id: anim; target:rect; property: "x";duration: 50
            }
        }
    }
}

Saturday, February 11, 2012

Qml FileDialog for symbian anna and belle

I recently ported my Audiobook Reader app for symbian using symbian components. As there is no common QML based file dialog, I need to create my own.

I uploaded my implementation to Gitorious repository here. Currently this code works for only symbian as its used symbian components, but it can be easily ported to work on meego as well.

In this post I will try to explain how this file dialog can be used. Repository also include full working sample.

Lets start with main.cpp file. We need to create instance of FileModel and share it with QML and rest of code is to launch our mainl Qml file.

int main( int argc, char* argv[] ) {
    QApplication app(argc,argv);

    QDeclarativeView view;

    FileModel fileModel;
    QDeclarativeContext *ctxt = view.rootContext();
    ctxt->setContextProperty("fileModel", &fileModel);

    view.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    view.setResizeMode( QDeclarativeView::SizeRootObjectToView );
    view.setSource(QUrl("qrc:/main.qml"));
    view.show();

    return app.exec();
}
Now QML file which needs to use FileDialog, following code can be used. Here in openFile function, if you want to select Folder/Directory then dirMode should be set to true, if you want to select File than set it to false.
    Page{
        id:page

        function openFile( dirMode ) {
            var component = Qt.createComponent("FileDialog.qml");
            var dialog = component.createObject(page);
            if( dialog !== null ) {
                if( dirMode) {
                    dialog.dirMode = true;
                }
                dialog.fileSelected.connect(fileSelected);
                dialog.directorySelected.connect(directorySelected);
                dialog.open();
            }
        }

        function fileSelected( filePath ) {
            console.debug("File selected:" + filePath);
        }

        function directorySelected( dirPath ) {
            console.debug("Folder selected:" + dirPath);
        }

        tools: ToolBarLayout {
            ToolButton {
                iconSource: "toolbar-back";
                onClicked: {
                    Qt.quit();
                }
            }
            ToolButton {
                text: "File Selection"
                onClicked: {
                    page.openFile(false);
                }
            }
            ToolButton {
                text: "Folder Selection"
                onClicked: {
                    page.openFile(true);
                }
            }
        }
    }
Following are few snaps of FileDialog component running from my Audiobook reader application.

Here is demo of file dialog.

Audiobook Reader for symbian Belle and Anna



I recently ported my Audiobook Reader application to Symbian (supports Anna and Belle).

I struggled a lot while porting this to symbian. For example phonon has some problem with Qt 4.7 on symbian and I wested almost one day to find out solution. If you are also facing similar problem. Then visit this link and this.

Capturing volume key was another problem. I required to use native symbian remote control api to resolve this issue. Visit this link for more info.

And as I use ubuntu machine as development machine, debugging and compilation was another problem.

But finally I was able to port it successfully and now its available on Nokia Store.
Please download it from below.
- Fixed web browser: http://store.ovi.com/content/120217
- Nokia mobile browser: http://store.ovi.mobi/content/120217
Following is demo. Hope you will like it.