Sunday, January 22, 2012

Animating element on curve in QML

I got comment on my post "Animating object along with curve in Qt using QPainterPath" that how similar thing can be achieved in QML. 

I tried to answer that comment in this post.

Originally I intend to achieve it using only QML but did not find any easy way. So I created a custom QDeclarativeItem that uses QPainterPath and implement required functionality.

Well code it quite simple, but it required little hack so that I can use some QML element like PathQuad or PathLine into my c++ code. 

So let's now move to code, I will try to explain as much as I can.

So let's begin with QML, what I want my QML code to look like. My QML has one rect item which act as circle and my custom QML element which act as curve.

I want my curve element to be drawn on screen and I also want circle to follow the curve. In code path is list which contain different kind of path element.

I was not able to intrepreat QML Path element into C++ class , so I end up creating my own path property. My curve also has pointAtPercentage function which provide point at certain progress on curve, which I use with timer to make dot follow that curve.
import QtQuick 1.0
import MyCurve 1.0

Rectangle {
    width: 500; height: 500

    Rectangle {
        id:dot
        width:10;height:10; radius: 5
        color:"steelblue"
    }

    MyCurve {
        id:curve
        path:[
                PathQuad { x: 400; y: 400; controlX: 100; controlY: 600 },
                PathLine{x: 120; y: 120},
                PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 },
                PathQuad { x: 120; y: 100; controlX: -20; controlY: 75 }
            ]
    }

    Timer{
        property variant progress:0;
        interval: 100; running: true; repeat: true
        onTriggered: {
            progress += 0.005;
            if( progress > 1 ) {
                progress  = 0;
            }
            var point = curve.pointAtPercent(progress);
            dot.x = point.x
            dot.y = point.y
        }
    }
}
Now lets look at MyCurve, custom QDeclaraiveItem class, which holds the QPainterPath. MyCurve is quite simple class if we don't include code to interpret PathQuad or PathLine QML element. We could have populated QPainterPath directly in c++ class. But I choose not to do so, to make it more reusable and also that I can try my hands on QDeclarativeListProperty and can show how we can read property of QML element.

#ifndef MYCURVE_H
#define MYCURVE_H

#include <QDeclarativeItem>
#include <QPainterPath>
#include <QGraphicspathItem>
#include <QDeclarativeListProperty>

class MyCurve : public QDeclarativeItem
{
    Q_OBJECT
    Q_PROPERTY(QDeclarativeListProperty<QObject> path READ path)
public:
    explicit MyCurve(QDeclarativeItem *parent = 0);

public slots:
    QPointF pointAtPercent(double Percent);

public:
    QDeclarativeListProperty<QObject> path();

private:
    static void appendItem(QDeclarativeListProperty<QObject> *list,
         QObject *pathItem);

    void appendPathQuad( QObject* pathQual);
    void appendPathLine( QObject* pathLine);

private:    
    QGraphicsPathItem* _path;
    QPainterPath _painterPath;
};
#endif // MYCURVE_H
Only Interesting about header is appendItem static method, which I will explain after showing its implementation. So here is implementation of header.
#include "mycurve.h"
#include <QDebug>
#include <QDeclarativeItem>
#include <QPen>

MyCurve::MyCurve(QDeclarativeItem *parent) :
    QDeclarativeItem(parent),_path(0),_startX(0),_startY(0)
{
    _path = new QGraphicsPathItem(this);
    _path->setPen(QPen(Qt::blue));
}

QPointF MyCurve::pointAtPercent(double Percent) {
    return _painterPath.pointAtPercent(Percent);
}

QDeclarativeListProperty<QObject> MyCurve::path()
{
    return QDeclarativeListProperty<QObject>(this,0,&MyCurve::appendItem);
}

void MyCurve::appendItem(QDeclarativeListProperty<QObject> *list, 
    QObject *pathItem)
{
    MyCurve *myCurve = qobject_cast<MyCurve *>(list->object);
    if (myCurve) {
         if( QString(pathItem->metaObject()->className())
                == QString("QDeclarativePathQuad") ) {
             myCurve->appendPathQuad(pathItem);
         } else if (QString(pathItem->metaObject()->className()) 
               == QString("QDeclarativePathLine")){
             myCurve->appendPathLine(pathItem);
         }
       }
}

void MyCurve::appendPathQuad( QObject* pathQuad) {
    double x = pathQuad->property("x").toDouble();
    double y = pathQuad->property("y").toDouble();
    double controlX = pathQuad->property("controlX").toDouble();
    double controlY = pathQuad->property("controlY").toDouble();

    _painterPath.quadTo(controlX,controlY,x,y);
    _path->setPath(_painterPath);
}

void MyCurve::appendPathLine( QObject* pathLine) {
    double x = pathLine->property("x").toDouble();
    double y = pathLine->property("y").toDouble();
    _painterPath.lineTo(x,y);
    _path->setPath(_painterPath);
}
There are quite a few interesting things here.

Usage of QDeclarativeListProperty(I am using it for first time), QDeclarativeListProperty is used to create list of property in QML. To operate on QDeclarativeListProperty, we need to provide static method which take pointer of list object and list item. appendItem is similar static method which add path element to our QPainterPath. To know more how other operation can be implemented on QDeclarativeListProperty, please visit this or this link.

In above code I am using metaObject to identify which QML Path element is being added by appendItem ( which is kind of dirty hack ) and after identifying element I am using property method to get its value and finally using those value to call appropriate QPainterPath method.

And finally here is how it looks,

Friday, January 13, 2012

Posting on facebook wall from Qt

Recently I was working on upgrade of my application. As a part of upgrade I decided to allow user to post their game score to facebook wall.

I had done similar work for iPhone, here is link. But never from Qt.

I am sharing my experience here, in hope it will help someone.

To be able to post on wall first you need to create facebook application. You can do the from here.

After creating application, you will get application id. We need this application id for authorization.

We need to use following url for authorization.

In below request client_id is your facebook application id, score is required permission to call facebook graph API. for posting on user's wall read_stream and publish_stream is enough. Here is full list of permission.
https://www.facebook.com/dialog/oauth?
client_id=YOUR_APPP_ID&
redirect_uri=https://www.facebook.com/connect/login_success.html&
response_type=token&scope=read_stream,publish_stream 
Above url will offer user a login dialog and user needs to enter user name and password. if process is successful then we will get access token. I used QWebView for presenting dialog to user.

You can also use "https://m.facebook.com/dialog/oauth" for mobile version of login dialog.

Following is my Qt Code.
void FacebookHelper::login( const QString& scope ){

    QUrl url("https://www.facebook.com/dialog/oauth");
    url.addQueryItem("client_id",mAppId);
    url.addQueryItem("redirect_uri",
        "https://www.facebook.com/connect/login_success.html");
    url.addQueryItem("response_type","token");
    url.addQueryItem("scope",scope);

    //view = new QWebView();
    view->load( url );
    view->show();
    connect(view,SIGNAL(loadFinished(bool)),this,SLOT(loginResponse(bool)));
}

void FacebookHelper::loginResponse( bool status ){
    QUrl url= view->url();
    QString strUrl = url.toString();

    int sIndex = strUrl.indexOf("access_token=");
    int eIndex = strUrl.indexOf("&expires_in");
    if( sIndex != -1 && eIndex != -1 ){
        mAccessToken= strUrl.mid(sIndex, eIndex - sIndex);
        mAccessToken = mAccessToken.remove("access_token=");
        emit authStatus( mAccessToken);
    }
}
Access token will come as part of re directional url, like following.
http://YOUR_URL#access_token=166942940015970%7C2.sa0&expires_in=64090
Now we have access token, Using this token we can submit post to facebook wall.
You can use following curl request to test your access token and post a message to your facebook wall.

If you see post you your wall then authorization process is successful. Here https://graph.facebook.com/me/feed, url is used to post message on logged in user's wall. If you want to post message on friends post then you need to replace me with that user's id.
 curl -F 'access_token=ACCESS_TOKEN' \
     -F 'picture=URL OF SOME PICTURE' \
     -F 'caption=TITLE OF POST' \
     -F 'description=DETAILS OF POST' \
     -F 'name=NAME OF POST' \
     https://graph.facebook.com/me/feed
Following is my Qt code for sending message to user's wall.
....

        QMap data;
        data["access_token"] = mAccessToken;
        data["name"] = "Test ";
        data["caption"] = "Testing graph api";
        data["description"] = "Testing description field";
        data["picture"] = "URL";
        setMessage(data);
......

void FacebookHelper::setMessage( QMap<qstring,qstring>& post) {

    QString postData;
    foreach( QString key, post.keys()) {
        postData.append(key +"=" +post[key]+"&");
    }

    QNetworkRequest request(QUrl("https://graph.facebook.com/me/feed");
    mCurrentRequest  = mNetManager.post(request,postData.toAscii());
    connect(mCurrentRequest,SIGNAL(finished()),this,SLOT(messageResponse()));
}
Thant's all, Hope you liked it.

Saturday, January 7, 2012

Playing audio file and getting metadata in Android

As I indicated in my previous post that I am currently learing Android to port my audiobook reader app to android.

I managed to create initial book list screen, and now trying to learn how to play audio file and fetch metadata file.

Playing audio file with android is quite easy, Audio and Video are played using MediaPlayer class and quite easy to use.

Following is sample code to play media file in android.

private MediaPlayer mPlayer;

mPlayer = new MediaPlayer();
mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
        String path = "/sdcard/music/sample music.mp3";
 mPlayer.setDataSource(path);
 mPlayer.prepare();
 mPlayer.start();    
} catch (Exception e) {
 e.printStackTrace();
}

In above code we are setting data source and then preparing mediaplayer then calling start to play media file. It can be paused by pause API and stopped by calling stop.

Now that I am able to play audio file, I tried to fetch its metadata file to show detais of Audio file to user. Fetching metadata was not as easy as playing file as its requied to use ContentResolver API and at first glance it looks quite strange to use it. After spending some time I managed to fetch metadata using following code.

To fetch metadata first we need to decide which attributes we are instrested in. Following are quite frequently used attributes.
       
String[] TRACK_COLUMNS = new String[] {          
 MediaStore.Audio.AudioColumns.ALBUM,
 MediaStore.Audio.AudioColumns.ARTIST,
 MediaStore.Audio.AudioColumns.DURATION,
 MediaStore.Audio.AudioColumns.TRACK,
 MediaStore.Audio.AudioColumns.YEAR, 
 MediaStore.MediaColumns.SIZE,
 MediaStore.MediaColumns.TITLE,
 MediaStore.MediaColumns.DATA,
 MediaStore.MediaColumns.DISPLAY_NAME
 };
Then we need to use ContentResolver to query audio content provider to fetch metadata. I am instrested in query metadata for perticular file. So I am using MediaStore.MediaColumns.DISPLAY_NAME (filename) as selection and then specifying instrested filename in values array.
ContentResolver resolver = getBaseContext().getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
//Uri uri = Media.getContentUriForPath(path);

String selection = MediaStore.MediaColumns.DISPLAY_NAME + "=?";        
String[] values = new String[] { new File(path).getName() };
Finally calling query API using instrested attributes and specifying required condition, Using Cursor we can retrive required metadata.
Cursor cursor = resolver.query(uri, TRACK_COLUMNS, selection, values, null);
while(cursor.moveToNext()) {             
   for( int i = 0 ; i < cursor.getColumnCount(); ++i ) {
      Log.d("TEST", cursor.getString(i));
   }
}
So that's all, Hope it will be helpful.