Friday, December 17, 2010

Simple view switching animation with Qt Quick (QML)

Code updated after comment received from "mich", now code looks much better.
---------------------

Now that I have some free time and no work to do, I thought to play with QML and created simple demo that implement views and changes view on swipe event.

As of now I don't have latest QML installed, therefor I am not able to use GestureArea QML element to detect swipe gesture and decided to implement my own swipe detection code in QML.

In demo, view is quite simple just showing colored rectangle and string. My intention for demo was to create reusable view element and to change view from one to another.

My view code is as below, I am calling it as screen in code and named QML file as Screen.qml. Mainly screen element maintain states to hide and show view and animate the transition. In real world it might required lots of other property and function but my implementation have only few.
import Qt 4.7

Item {
    id: screen
    property string color: "red"    
    property string title: "title"
    opacity: 1    

    function hide() {
        screen.state = 'hide';
        screen.x = 0;
    }

    function show(xVal) {        
        screen.x = xVal;        
        screen.state  = 'show';
    }

    Rectangle {
        id: rect
        width: 480
        height: 640
        color:  screen.color
    }

    Text {
        id: title
        text: screen.title;
    }

    states: [
             State {
                 name: "show"
                 PropertyChanges {
                     target: screen
                     x: 0
                     opacity:1
                 }
             },
             State {
                 name: "hide"
                 PropertyChanges {
                     target: screen
                     opacity:0
                 }
             }
         ]

    transitions: [             
             Transition {
                 from:"hide"
                 to:"show"
                 NumberAnimation { properties: "x"; duration:500}                 
                 NumberAnimation { properties: "opacity"; duration: 700 }
             },
             Transition {
                 //from: "show"
                 to: "hide"
                 NumberAnimation { properties: "opacity"; duration: 700 }
             }
         ]
}
In my main.qml entry qml code, I have swipe detection code and view switching code as below.
import Qt 4.7

Rectangle {
    id: container
    width: 480
    height: 640    
    property int currentScreen: 0
    property int previousScreen: 0

    //creating array of screens
    property list<item> screens: [
    Screen {
            parent: container
            id: firstScreen
            title:"First Screen"
            color: "blue"
        },
        Screen {
            parent: container
            id: secondScreen
            title:"Second Screen"
            color: "red"
        },
        Screen {
            parent: container
            id:thirdScreen
            title:"Third Screen"
            color:"green"
        },
        Screen {
            parent: container
            //anchors.fill: parent
            id:fourthScreen
            title:"Fourth Screen"
            color:"orange"
        }
        ]

    Component.onCompleted: {
           console.log("Startup script");
           container.currentScreen = 0;
           container.previousScreen = 0;
           for(var i=0; i < 4; ++i) {
           screens[i].hide();
       }
       screens[0].show(0);
    }

    // function to show particular view 
    function showScreen(screenNo,direction) {
      screens[previousScreen].hide();
       var xVal = direction == -1 ? 400 : -400;
       screens[screenNo].show(xVal);
    }
    
    // function to switch view on swipe
    function onLeftSwipe() {
        previousScreen = currentScreen;
        currentScreen = currentScreen +1;
        if(currentScreen > 3 ) {
            currentScreen = 0;
        }
        showScreen (currentScreen,-1)  ;
    }

    function onRightSwipe() {
        previousScreen = currentScreen;
        currentScreen = currentScreen -1;
        if(currentScreen < 0 ) {
            currentScreen = 3;
        }
        showScreen (currentScreen,1)  ;
    }

    // swipe detection code
    MouseArea {
        id: mouseArea
        anchors.fill: parent;

        property int oldX: 0
        property int oldY: 0

      onPressed: {
        oldX = mouseX;
        oldY = mouseY;
      }

      onReleased: {
          var xDiff = oldX - mouseX;
          var yDiff = oldY - mouseY;
          if( Math.abs(xDiff) > Math.abs(yDiff) ) {
              if( oldX > mouseX) {
                    onLeftSwipe();
              } else {
                    onRightSwipe();
              }
          } else {
              if( oldY > mouseY) {/*up*/ }
              else {/*down*/ }
          }
       }

    }
}

Overall I felt there is lot more to do to create truly reusable view code,but this is all for now.

Friday, December 10, 2010

Creating custom QItemDelegate with QPushButton in Qt

Recently I was working on Audiobook Reader application for maemo. For this application I have created one custom item delegate for QListView which contains two clickable item and generate signal accordingly.

For application I was using some different mechanism to create button inside custom item delegate, but now I found better way to create custom item delegate with QPushButton. So thought to share code.

Following is code for custom item delegate derived from QItemDelegate.

Update: I have uploaded following code to gitorous, Please visit this link if you want a working sample code.

#include <QItemDelegate>
class CustomItemDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    CustomItemDelegate(QObject *parent = 0);
    virtual void paint(QPainter *painter,
                       const QStyleOptionViewItem &option,
                       const QModelIndex &index) const ;

    virtual QSize sizeHint(const QStyleOptionViewItem &option,
                           const QModelIndex &index) const ;

    bool editorEvent(QEvent *event, QAbstractItemModel *model, 
                           const QStyleOptionViewItem &option, 
                           const QModelIndex &index);

signals:
    void buttonClicked(const QModelIndex &index);
private:
    QStyle::State  _state;
};

#include "customitemdelegate.h"
...

CustomItemDelegate::CustomItemDelegate(QObject *parent) :
    QItemDelegate(parent)
{
    _state =  QStyle::State_Enabled;
}

void CustomItemDelegate::paint(QPainter *painter,
                   const QStyleOptionViewItem &option,
                   const QModelIndex &index) const
{
   const QStandardItemModel* model = 
   static_cast<const QStandardItemModel*>(index.model());
   QStandardItem* item = model->item(index.row());

   QString text = item->text();
   QRect rect = option.rect;

    QRect textRect( rect);
    textRect.setHeight( 30);
    painter->drawText(textRect,text);

    QRect buttonRect( rect);
    buttonRect.setY(textRect.y()+ 35);
    buttonRect.setHeight( 30);
    QStyleOptionButton button;
    button.rect = buttonRect;
    button.text = text;
    button.state = _state | QStyle::State_Enabled;

    QApplication::style()->drawControl
        (QStyle::CE_PushButton, &button, painter);
}

QSize CustomItemDelegate::sizeHint(const QStyleOptionViewItem &/*option*/,
                       const QModelIndex &/*index*/) const
{
    //hard coding size for test purpose, 
    //actual size hint can be calculated from option param
    return QSize(800,70);
}

bool CustomItemDelegate::editorEvent(QEvent *event, 
    QAbstractItemModel *model, 
    const QStyleOptionViewItem &option, 
    const QModelIndex &index)
{
    if( event->type() == QEvent::MouseButtonPress ||
        event->type() == QEvent::MouseButtonRelease ) {
    } else {
         //ignoring other mouse event and reseting button's state
         _state = QStyle::State_Raised;
        return true;
    }

    QRect buttonRect( option.rect);
    buttonRect.setY(option.rect.y()+ 35);
    buttonRect.setHeight( 30);

    QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
    if( !buttonRect.contains( mouseEvent->pos()) ) {
        _state = QStyle::State_Raised;
        return true;
    }

    if( event->type() == QEvent::MouseButtonPress) {            
        _state = QStyle::State_Sunken;
    } else if( event->type() == QEvent::MouseButtonRelease) {
        _state = QStyle::State_Raised;
        emit buttonClicked( index);
    }    
    return true;
}
Basically in above code, I am calculating rect where I want to draw my button and drawing QPushButton on list item using QStyleOptionButton class.

And in editor event on mouse press and release event, I am checking if mouse position on click falls into my button's rect or not. If it falls inside my button's rect then I am emitting signal.

I am using item's signal as shown in below code.
CustomList::CustomList(QWidget *parent) :
    QWidget(parent),_view(0)
{
    _view = new QListView();
    _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    _view->setEditTriggers(QAbstractItemView::NoEditTriggers);

    //creating custom item delegate and setting  it to view
    CustomItemDelegate* itemDelegate = new CustomItemDelegate(_view);
    _view->setItemDelegate( itemDelegate );

    _view->setModel(&_model); 
   
    //connecting delegate's signal to this class's slot
    connect(itemDelegate,SIGNAL(buttonClicked(QModelIndex)),
    this,SLOT(listButtonClicked(QModelIndex)));

    QHBoxLayout* mainLayout = new QHBoxLayout(this);
    mainLayout->addWidget( _view);

    //creating and adding data to model
    QStandardItem* item = new QStandardItem;
    item->setText("testing");

    QStandardItem* item1 = new QStandardItem;
    item1->setText("testing1");

    _model.appendRow(item);
    _model.appendRow(item1);
}

// following slot will be invoked when delegate's button is clicked
void CustomList::listButtonClicked(const QModelIndex &index)
{
    qDebug() << "######### listbutton clicked ######### " << index.row();
}
Following is snap of how custom item delegate looks.

Sunday, December 5, 2010

Audiobook Reader for N900 usign Qt and Phonon

So after long time I found enough time to complete my Audiobook Reader application. This application was in extra-devel repository for long time but there were few problem with it, now finally I worked on those bugs and I am satisfied enough to share information about it.

Now days almost all novels are available in audio format and I am huge fan of those audio book. Previously I was using n900's music player to hear those audio books. But music player dose not preserve last playing position and this create problem when phone restart.

So I created this Audiobook Reader application.Key features are listed below.
  • You can add multiple audiobook.
  • It support both individual audio file which contain whole book, or can add folder which contain books audio file.
  • You can bookmark position in audiobook and can resume from same position.
  • It remember bookmark for all audiobook.This mean you can play multiple audiobook simultaneously.
Following are snaps from  application.


Above snaps is of Book list view. This view list currently added books, 
  • Add Audio button is used to add single audio file which contain whole book content.
  • Add Folder button is used to add folder, which contain multiple audio file for book. Note that you need to select folder which contain audio files, not the individual files.
  • Pokemon icon is used to start playing individual audio book.
  • Cross mark icon is used to remove this book from book list.





In above snaps are from  Reading view.This view list currently played book's title, track name and artist's name.
  • > button is used to play book
  • || button is used to pause book
  • x button is used to stop book, it will put reading position to beginning.
  • <)) button is used to increase/ decrease volume
Currently you can download this application to your n900 from extra-devel repository.