src/hbwidgets/widgets/hbcombobox_p.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 27 May 2010 13:10:59 +0300
changeset 3 11d3954df52a
parent 2 06ff229162e9
child 6 c3690ec91ef8
permissions -rw-r--r--
Revision: 201019 Kit: 2010121

/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbWidgets module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include "hbcombobox_p.h"

#include <hblistview.h>
#include <hbabstractviewitem.h>
#include <hbdeviceprofile.h>
#include <hbtoucharea.h>
#include <hbmainwindow.h>
#include <hbview.h>
#include <hbtextitem.h>
#include <hbstyleoptioncombobox_p.h>
#include <hbgraphicsscene.h>

#include <QSortFilterProxyModel>
#include <QCompleter>
#include <QItemSelectionModel>

#ifdef HB_EFFECTS
#include <hbeffect.h>
#include "hbeffectinternal_p.h"
#define HB_DROPD0WN_ITEM_TYPE "HB_DROPDOWN"
#endif

#include <hbtapgesture.h>

HbComboBoxPrivate::HbComboBoxPrivate( ):
    HbWidgetPrivate ( ),
    mLineEdit ( 0 ),
    mTextItem ( 0 ),
    mButton ( 0 ),
    mDropDown ( 0 ),
    mModel ( 0 ),
    mProxyModel ( 0 ),
    mCompleter ( 0 ),
    insertPolicy ( HbComboBox::InsertAtBottom ),
    mBackgroundItem ( 0 ),
    mButtonTouchAreaItem ( 0 ),
    mIsDown ( false ),
    mEditable ( false ),
    mIsDorpdownCreated( false ),
    mIsDropwnToSceneAdded( false ),
    mHasDownEffect ( false ),
    mHasUpEffect ( false ),
    mListItemHeight( -1 ),
    mDropDownRowsInPortrait( -1 ),
    mDropDownRowsInLandscape( -1 )
{
}

HbComboBoxPrivate::~HbComboBoxPrivate( )
{
    Q_Q( HbComboBox );
    if( mButtonTouchAreaItem ) {
        static_cast<HbTouchArea*>( mButtonTouchAreaItem )->removeEventFilter( q );
    }
    if ( !q->scene( ) || !q->scene( )->property( "destructed" ).isValid( ) ) {
        if( mDropDown ) {
            delete mDropDown;
            mDropDown = 0;
        }
    }
}

void HbComboBoxPrivate::init( )
{
    createPrimitives( );
}

void HbComboBoxPrivate::createPrimitives( )
{
    Q_Q( HbComboBox );

    mTextItem = q->style( )->createPrimitive( HbStyle::P_ComboBox_text, q );
    HbStyle::setItemName( mTextItem, "combobox_labelfield" );

    mBackgroundItem = q->style( )->createPrimitive( HbStyle::P_ComboBox_background, q );
    HbStyle::setItemName( mBackgroundItem, "text_background" );

    mButton = q->style( )->createPrimitive( HbStyle::P_ComboBox_button, q );
    HbStyle::setItemName( mButton, "combobox_button" );

    mButtonTouchAreaItem = q->style( )->createPrimitive( HbStyle::P_ComboBoxButton_toucharea, q );
    static_cast<HbTouchArea*>( mButtonTouchAreaItem )->installEventFilter( q );
    q->setHandlesChildEvents( true );

    static_cast<HbTouchArea*>( mButtonTouchAreaItem )->grabGesture( Qt::TapGesture );
}

void HbComboBoxPrivate::touchAreaPressEvent( )
{    
    Q_Q( HbComboBox );
    if ( q->count( ) > 0 ) {
        HbWidgetFeedback::triggered( q, Hb::InstantPressed );
    }
    mIsDown = true;
    q->updatePrimitives( );
    q->setProperty( "state", "pressed" );
}

void HbComboBoxPrivate::touchAreaReleaseEvent(  )
{
    Q_Q( HbComboBox );
    mIsDown = false;
    touchAreaClicked( );
    q->updatePrimitives( );
    if ( q->count( ) > 0 ) {
        HbWidgetFeedback::triggered( q, Hb::InstantReleased );
    }

    q->setProperty( "state", "normal" ); 
}

void HbComboBoxPrivate::touchAreaClicked( )
{
    Q_Q( HbComboBox );
    if ( mModel && mModel->rowCount( ) ) {
        addDropDownToScene( );
        mDropDown->setVisible( true );
        if( !mDropDown->mList ) {
            mDropDown->createList( );
            mDropDown->mList->setModel( mModel );
            q->connect( mDropDown->mList, SIGNAL( activated( QModelIndex ) ), q,
                SLOT( _q_textChanged( QModelIndex ) ) );
        }
        if ( mCurrentIndex.isValid( ) ) {
            if( mDropDown->mList->model( ) != mModel ) {
                mDropDown->mList->setModel( mModel );
            }
            mDropDown->mList->scrollTo( mCurrentIndex, HbAbstractItemView::PositionAtTop );
            mDropDown->mList->setCurrentIndex( mCurrentIndex, QItemSelectionModel::Select );
        } else {
            if( mDropDown->mList->model( ) != mModel ) {
                mDropDown->mList->setModel( mModel );
            }
            mDropDown->mList->scrollTo( mModel->index( 0, 0 ) );
            mDropDown->mList->setCurrentIndex( 
                mModel->index( 0, 0 ), QItemSelectionModel::Select );
        }
        #ifdef HB_EFFECTS
        HbEffect::start( mDropDown, HB_DROPD0WN_ITEM_TYPE, "appear" );
        #endif
        positionDropDown( );
    }
}

void HbComboBoxPrivate::vkbOpened( )
{

}

void HbComboBoxPrivate::vkbClosed( )
{
    if( mDropDown->isVisible( ) ) {
        positionDropDown( );
    }
}

void HbComboBoxPrivate::showPopup( QAbstractItemModel *aModel, QModelIndex aIndex )
{
    Q_Q( HbComboBox );
    if ( aModel && aModel->rowCount( ) ) {
        addDropDownToScene( );
        if( !mDropDown->mList ) {
            mDropDown->createList( );
            q->connect( mDropDown->mList, SIGNAL( activated( QModelIndex ) ), q,
                SLOT( _q_textChanged( QModelIndex ) ) );
        }
        mDropDown->mList->setModel( aModel );
        if ( aIndex.isValid( ) ) {
            mDropDown->mList->scrollTo( aIndex, HbAbstractItemView::PositionAtTop );
            mDropDown->mList->setCurrentIndex( mCurrentIndex, QItemSelectionModel::Select );
        } else {
            mDropDown->mList->scrollTo( aModel->index( 0, 0 ) );
        }
        positionDropDown( );
        mDropDown->setVisible( true );
    }
}

void HbComboBoxPrivate::createDropDown( )
{
    if( !mIsDorpdownCreated ) {
        mDropDown = new HbComboDropDown( this );
        mIsDorpdownCreated = true;
        mDropDown->setVisible( false );
    }
}

void HbComboBoxPrivate::calculateListItemHeight( )
{
    QAbstractItemModel *model = mDropDown->mList->model( );
    if( mCurrentIndex.isValid( ) && mDropDown->mList->itemByIndex( mCurrentIndex ) ) {
        mListItemHeight = mDropDown->mList->itemByIndex( mCurrentIndex )->geometry( ).height( );
    } else if( model->index( 0, 0 ).isValid( ) && 
        mDropDown->mList->itemByIndex( model->index( 0, 0 ) ) ) {

        mListItemHeight = 
            mDropDown->mList->itemByIndex( model->index( 0, 0 ) )->geometry( ).height( );
    } else {
        HbListViewItem *proto = mDropDown->mList->listItemPrototype( );
        HbListViewItem *temp = static_cast<HbListViewItem*>( proto->createItem( ) );
        mListItemHeight = temp->effectiveSizeHint( Qt::PreferredSize ).height( );
        delete temp;
        temp = 0;
    }
}

void HbComboBoxPrivate::positionDropDown( )
{
    Q_Q( HbComboBox );
    QRectF popupRect;
    QRectF sceneRect( QPointF( ), HbDeviceProfile::profile( q ).logicalSize( ) );
    QPointF widgetPos = q->scenePos( );
    QAbstractItemModel *model = mDropDown->mList->model( );
    calculateListItemHeight( );
    qreal totalHeightRequd = model->rowCount( ) * mListItemHeight;
    qreal maxPopupHeight = 0.0;

    //read the maximum rows in drop down for different orientation from css
    if( q->mainWindow( )->orientation( ) == Qt::Horizontal ) {
        if( mDropDownRowsInLandscape == -1 ) {
            HbStyleParameters params;
            q->style( )->parameters( params );
            params.addParameter( "max-rows-in-dropdown" );
            q->polish( params );
            mDropDownRowsInLandscape = params.value( "max-rows-in-dropdown" ).toInt( );
        }
        maxPopupHeight = mDropDownRowsInLandscape * mListItemHeight;
    } else if( q->mainWindow( )->orientation( ) == Qt::Vertical ) {
        if( mDropDownRowsInPortrait == -1 ) {
            HbStyleParameters params;
            q->style( )->parameters( params );
            params.addParameter( "max-rows-in-dropdown" );
            q->polish( params );
            mDropDownRowsInPortrait = params.value( "max-rows-in-dropdown" ).toInt( );
        }
        maxPopupHeight = mDropDownRowsInPortrait * mListItemHeight;
    }

    if ( totalHeightRequd < maxPopupHeight ) {
        maxPopupHeight = totalHeightRequd;
    }
    QSizeF popupSize = QSizeF( q->rect( ).width( ), maxPopupHeight );
    QPointF popupPos;
    if( !mDropDown->vkbOpened ) {
        //position of drop down in both editable and non-editable combobox depends upon
        //the available space above and below combobox
        if( ( widgetPos.y( ) + q->rect( ).height( ) + maxPopupHeight) < sceneRect.height( ) ) {
            popupPos = QPointF( widgetPos.x( ), widgetPos.y( ) + q->rect( ).height( ) );
            #ifdef HB_EFFECTS
            if ( !mHasDownEffect ) {
                 mHasDownEffect = true;
                 mHasUpEffect = false;
                 // this is temporary until proper effect theming comes.
                 //this Effect will be shown when there is space in the view bottom.
                 HbEffectInternal::add( mDropDown, "combo_appear_down", "appear" );
                 HbEffectInternal::add( mDropDown, "combo_disappear_downl", "disappear" );
            }
            #endif
        } else if( widgetPos.y( ) - maxPopupHeight  > 0.0 ) {
            popupPos = QPointF( widgetPos.x( ), widgetPos.y( ) - maxPopupHeight );
            #ifdef HB_EFFECTS
            if ( !mHasUpEffect ) {
                 // this is temporary until proper effect theming comes.
                 //this Effect will be shown when there is no space in the view bottom
                 mHasUpEffect = true;
                 mHasDownEffect = false;
                 HbEffectInternal::add( mDropDown, "combo_appear_up", "appear" );
                 HbEffectInternal::add( mDropDown, "combo_disappear_up", "disappear" );
            }
            #endif
        } else {
            qreal topScreenHeight = sceneRect.height( ) - maxPopupHeight;
            if( topScreenHeight > sceneRect.height( ) - topScreenHeight ) {
                popupPos = QPointF( widgetPos.x( ), 0.0 );
                #ifdef HB_EFFECTS
                if ( !mHasDownEffect ) {
                    mHasDownEffect = true;
                    mHasUpEffect = false;
                    // this is temporary until proper effect theming comes.
                    //this Effect will be shown when there is more space in the view bottom.
                    HbEffectInternal::add( mDropDown, "combo_appear_down", "appear" );
                    HbEffectInternal::add( mDropDown, "combo_disappear_down", "disappear" );
                }
                #endif
            } else {
                popupPos = QPointF( widgetPos.x( ), sceneRect.height( ) - maxPopupHeight );
                #ifdef HB_EFFECTS
                if ( !mHasUpEffect ) {
                     mHasUpEffect = true;
                     mHasDownEffect = false;
                     // this is temporary until proper effect theming comes.
                     //this Effect will be shown when there is more space in the view bottom.
                     HbEffectInternal::add( mDropDown, "combo_appear_up", "appear" );
                     HbEffectInternal::add( mDropDown, "combo_disappear_up", "disappear" );
                }
                #endif
            }
        }
    } else {
        // positioning drop down when vkb is positioned
        // drop down will come on top/below of combo based upon which side has more space
        // available 
        HbEditorInterface editorInterface( q );
        HbVkbHost *host = editorInterface.vkbHost( );
        if ( host ) {
            QSizeF keyBoardArea = host->keyboardArea( );
            QSize screenSize = HbDeviceProfile::profile( q ).logicalSize( );

            qreal heightDifference = screenSize.height( ) - keyBoardArea.height( );
            qreal topSpace = widgetPos.y( );
            qreal bottomSpace = heightDifference - topSpace - q->boundingRect( ).height( );

            if( topSpace > bottomSpace ) {
                //display drop down at top
                if( widgetPos.y( ) - maxPopupHeight  > 0.0 ) {
                    popupPos = QPointF( widgetPos.x( ), widgetPos.y( ) - maxPopupHeight );
                } else {
                    popupPos = QPointF( widgetPos.x( ), 0.0 );
                    popupSize.setHeight( topSpace );
                }
                #ifdef HB_EFFECTS
                if ( !mHasUpEffect ) {
                     mHasUpEffect = true;
                     mHasDownEffect = false;
                     // this is temporary until proper effect theming comes.
                     //this Effect will be shown when there is more space in the view bottom.
                     HbEffectInternal::add( mDropDown, "combo_appear_up", "appear" );
                     HbEffectInternal::add( mDropDown, "combo_disappear_up", "disappear" );
                }
                #endif
            } else {
                //display drop down at bottom
                popupPos = QPointF( widgetPos.x( ), widgetPos.y( ) + q->rect( ).height( ) );
                if( bottomSpace < maxPopupHeight ) {
                    popupSize.setHeight( bottomSpace );
                }
                #ifdef HB_EFFECTS
                if ( !mHasDownEffect ) {
                    mHasDownEffect = true;
                    mHasUpEffect = false;
                    // this is temporary until proper effect theming comes.
                    //this Effect will be shown when there is more space in the view bottom.
                    HbEffectInternal::add( mDropDown, "combo_appear_down", "appear" );
                    HbEffectInternal::add( mDropDown, "combo_disappear_down", "disappear" );
                }
                #endif
            }
        }
    }
    mDropDown->setPreferredSize( popupSize );
    mDropDown->setMinimumSize( popupSize );
    mDropDown->setMaximumSize( popupSize );
    mDropDown->setPos( popupPos );
    QGraphicsWidget *p = q;
    while ( p->parentWidget( ) ) {
        p = p->parentWidget( );
    }
    mDropDown->setZValue( p->zValue( ) + 1 );
}

void HbComboBoxPrivate::_q_textChanged( const QModelIndex & aIndex )
{
    Q_Q( HbComboBox );
    QVariant data = mDropDown->mList->model( )->data( aIndex );
    mText = data.toString( );
    if( !mEditable ) {
        if( mLineEdit ) {
            mLineEdit->setText( mText );
        } else {
            HbStyleOptionComboBox comboBoxOption;
            q->initStyleOption( &comboBoxOption );
            q->style( )->updatePrimitive( mTextItem, HbStyle::P_ComboBox_text, &comboBoxOption );
        }
        mCurrentIndex = aIndex;
    } else {
       q->disconnect( mLineEdit, SIGNAL( textChanged ( QString ) ), q,
           SLOT( _q_textChanged( QString ) ) );
       mLineEdit->setText( mText );
       mCurrentIndex = findData( mText );
       q->connect( mLineEdit, SIGNAL( textChanged ( QString ) ), q, 
           SLOT( _q_textChanged( QString ) ) );
    }
    if ( mDropDown->isVisible( ) ) {
        mDropDown->setVisible( false );
    }
    currentIndexChanged( mCurrentIndex );
}

void HbComboBoxPrivate::_q_textCompleted( const QModelIndex & aIndex )
{
    if( aIndex.isValid( ) ) {
        showPopup( mCompleter->completionModel( ) );
    }
}

void HbComboBoxPrivate::_q_textChanged( const QString & aString )
{
    Q_Q( HbComboBox );

    if( !aString.isEmpty( ) ) {
        if ( mCompleter ) {
            mCompleter->setCompletionPrefix( aString );
            mCompleter->complete( );
            if( mCompleter->currentRow( ) == -1 ) {
                if ( ( mDropDown ) && ( mDropDown->isVisible( ) ) ) {
                    mDropDown->setVisible( false );
                }
            }
        }
    } else {
        if( mDropDown ) {
            mDropDown->setVisible( false );
        }
        //showPopup( mModel, mCurrentIndex);
    }
    emit q->editTextChanged( aString );
}

void HbComboBoxPrivate::setModel( QAbstractItemModel * model )
{
    Q_Q ( HbComboBox );
    createDropDown( );    
    if ( mDropDown->isVisible( ) ) {
        mDropDown->setVisible( false );
    }
    q->clear( );
    delete mModel;
    mModel = model;
    mModel->setParent( q );
    setCompletion( mEditable );
    if( mModel->rowCount( ) ) {
        q->setCurrentIndex( 0 );
    }
}

void HbComboBoxPrivate::setCompletion( bool completion )
{
    Q_Q( HbComboBox );
    if ( completion ) {
        if ( mCompleter ) {
            mProxyModel->setSourceModel( mModel );
            mProxyModel->sort( Qt::AscendingOrder );
            mCompleter->setModel( mProxyModel );
        } else {
            mProxyModel = new QSortFilterProxyModel( q );
            mProxyModel->setDynamicSortFilter( true );
            mProxyModel->setSortCaseSensitivity( Qt::CaseInsensitive );
            mProxyModel->setSourceModel( mModel );
            mProxyModel->sort( Qt::AscendingOrder );
            mCompleter = new QCompleter( mProxyModel, q );
            mCompleter->setCaseSensitivity( Qt::CaseInsensitive );
            mCompleter->setCompletionRole( Qt::DisplayRole );
            mCompleter->setCompletionMode( QCompleter::InlineCompletion );
            q->connect( mCompleter, SIGNAL( highlighted( QModelIndex ) ), q, 
                SLOT( _q_textCompleted( QModelIndex ) ) );
        }
    } else {
        if ( mCompleter ) {
            delete mCompleter;
            mCompleter = 0;
        }
        if ( mProxyModel ) {
            delete mProxyModel;
            mProxyModel = 0;
        }
    }
}

void HbComboBoxPrivate::setEditable(  bool editable )
{
    Q_Q( HbComboBox );
    if( mEditable == editable ) {
        return;
    }
    mEditable = editable;
    if( editable ) {
        if( mTextItem ) {
            HbStyle::setItemName( mTextItem, "" );
            delete mTextItem;
            mTextItem = 0;
            mLineEdit = new HbCustomLineEdit( q, this );
            q->connect( mLineEdit, SIGNAL( editingFinished( ) ), q, SIGNAL( editingFinished( ) ) );
            HbStyle::setItemName( mLineEdit, "combobox_labelfield" );
            mLineEdit->backgroundItem( )->setVisible( false );
        }
        q->setHandlesChildEvents( false );
        mLineEdit->setReadOnly( false );
        mLineEdit->setCursorVisibility( Hb::TextCursorVisible );
        mLineEdit->setLongPressEnabled( );
        q->repolish( );
        q->connect( mLineEdit, SIGNAL( textChanged ( QString ) ),
            q, SLOT( _q_textChanged( QString ) ) );
        setCompletion( true );
    } else {
        q->disconnect( mLineEdit, SIGNAL( textChanged ( QString ) ),
            q, SLOT( _q_textChanged( QString ) ) );
        q->setHandlesChildEvents( true );
        mLineEdit->setReadOnly( true );
        mLineEdit->setLongPressEnabled( false );
        setCompletion( false );
        mLineEdit->setCursorVisibility( Hb::TextCursorHidden );
        if( mModel && mModel->rowCount( ) ) {
            QModelIndex mi = mModel->index( 0, 0 );
            if( mi.isValid( ) ) {
                mCurrentIndex = QModelIndex( mi );
                mText = q->itemText( mCurrentIndex.row( ) );
                mLineEdit->setText( mText );
            }
        }
        q->repolish( );
    }
}

QString HbComboBoxPrivate::itemText( const QModelIndex &index ) const
{
    return mModel->data( index, itemRole( ) ).toString( );
}

QIcon HbComboBoxPrivate::itemIcon( const QModelIndex &index ) const
{
    QVariant decoration = mModel->data( index, Qt::DecorationRole );
    if ( decoration.type( ) == QVariant::Icon ) {
        return QIcon( qvariant_cast<QIcon>( decoration ) );
    }
    return qvariant_cast<QIcon>( decoration );
}

int HbComboBoxPrivate::itemRole( ) const
{
    return q_func( )->isEditable( ) ? Qt::EditRole : Qt::DisplayRole;
}

void HbComboBoxPrivate::addDropDownToScene( )
{
    Q_Q( HbComboBox );
    if( !mIsDropwnToSceneAdded ) {
        if ( q->scene( ) ) {
            q->scene( )->addItem( mDropDown );
        }
        HbGraphicsScene *scene1 = static_cast<HbGraphicsScene*>( mDropDown->scene( ) );
        if( scene1 ) {
            scene1->installEventFilter( mDropDown );
            //scene1->grabGesture( Qt::TapGesture );
        }
        mIsDropwnToSceneAdded = true;
    }
}
void HbComboBoxPrivate::setCurrentIndex( const QModelIndex &mi )
{
    Q_Q( HbComboBox );
    bool indexChanged = ( mi != mCurrentIndex );
    if ( indexChanged ) {
        mCurrentIndex = QModelIndex( mi );
        mText = q->itemText( mCurrentIndex.row( ) );
        if( mEditable ) {
            q->disconnect( mLineEdit, SIGNAL( textChanged ( QString ) ), q,
                SLOT( _q_textChanged( QString ) ) );
            mLineEdit->setText( mText );
            q->connect( mLineEdit, SIGNAL( textChanged ( QString ) ),
                q, SLOT( _q_textChanged( QString ) ) );
        } else {
            if( mLineEdit ) {
                mLineEdit->setText( mText );
            } else {
                HbStyleOptionComboBox comboBoxOption;
                q->initStyleOption(&comboBoxOption);
                q->style( )->updatePrimitive(
                    mTextItem, HbStyle::P_ComboBox_text, &comboBoxOption );
            }
        }
        currentIndexChanged( mCurrentIndex );
    }
}

void HbComboBoxPrivate::currentIndexChanged( const QModelIndex &index )
{
    Q_Q( HbComboBox );
    emit q->currentIndexChanged( index.row( ) );
    emit q->currentIndexChanged( q->itemText ( mCurrentIndex.row( ) ) );
}

/*
 Returns the index of the item containing the given \a data for the
 given \a role; otherwise returns QModelIndex.

 The \a flags specify how the items in the combobox are searched.
 */
QModelIndex HbComboBoxPrivate::findData( const QVariant &data ) const
{
    QModelIndexList result;
    if ( mModel ) {
        QModelIndex start = mModel->index( 0, 0 );
        result = mModel->match(
            start, Qt::DisplayRole, data, 1, Qt::MatchExactly|Qt::MatchCaseSensitive );
    }
    if ( result.isEmpty( ) ) {
        return QModelIndex( );
    } else {
        return result.first( );
    }
}

#include "moc_hbcombobox.cpp"