src/hbwidgets/editors/hbselectioncontrol_p.cpp
author hgs
Mon, 18 Oct 2010 18:23:13 +0300
changeset 34 ed14f46c0e55
parent 7 923ff622b8b9
permissions -rw-r--r--
201041

/****************************************************************************
**
** 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.
**
****************************************************************************/

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Hb API.  It exists purely as an
// implementation detail.  This file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include "hbselectioncontrol_p.h"
#include "hbstyleoption_p.h"
#include "hbeffect.h"
#include "hbdialog_p.h"
#include "hbabstractedit.h"
#include "hbabstractedit_p.h"
#include "hblineedit.h"
#include "hbtoucharea.h"
#include "hbpangesture.h"
#include "hbtapgesture.h"
#include "hbevent.h"
#include "hbpopup.h"
#include "hbmagnifier_p.h"
#include "hbnamespace_p.h"
#include "hbmainwindow.h"
#include "hbdeviceprofile.h"
#include "hbiconitem.h"


#include <QTextCursor>
#include <QTextBlock>
#include <QGraphicsItem>
#include <QGraphicsWidget>
#include <QAbstractTextDocumentLayout>
#include <QGraphicsSceneMouseEvent>
#include <QBasicTimer>
#include <QSizeF>
#include <QPointF>
#include <QHash>
#include <QGraphicsScene>

#include <hbwidgetfeedback.h>


#define HB_DEBUG_PAINT_INFO 0

typedef QHash<HbMainWindow*,HbSelectionControl*> HbSelectionControlHash;
Q_GLOBAL_STATIC(HbSelectionControlHash, globalSelectionControlHash)

namespace {
    static const int SNAP_DELAY = 300;
    static const int MAGNIFIER_OPEN_DELAY = 200;
    static const qreal MAGNIFIER_SCALE_FACTOR = 1.5;
}

class MyEditor: public HbAbstractEdit
{
public:
    using HbAbstractEdit::drawContents;
};


class HbMagnifierDelegateEditor : public HbMagnifierDelegate
{
public:
    HbMagnifierDelegateEditor(HbAbstractEdit * editor) {this->editor = editor;}
    virtual void drawContents(QPainter *painter, const QStyleOptionGraphicsItem *option)
    {
        if (option && editor) {
            static_cast<MyEditor*>(editor)->drawContents(painter,*option);
        }
    }
    void setEditor(HbAbstractEdit * editor){this->editor = editor;}
private:
    HbAbstractEdit * editor;
};


class HbSelectionControlPrivate :public HbDialogPrivate
{
    Q_DECLARE_PUBLIC(HbSelectionControl)

public:
    enum InteractionMode {
        Selection,
        CursorPositioning
    };

    enum HandleType {
        DummyHandle,
        SelectionStartHandle,
        SelectionEndHandle
    };


    HbSelectionControlPrivate();
    ~HbSelectionControlPrivate();
    void init();
    void createPrimitives();
    void updateHandle(int cursorPos,
                      int newHandlePos,
                      Qt::AlignmentFlag handleAlignment,
                      QGraphicsItem *handle,
                      QGraphicsItem *handleTouchArea);
    void updateMagnifier();
    void initMagnifier();

    QGraphicsItem * reparent(QGraphicsItem *item);
    void reparent(QGraphicsItem *item, QGraphicsItem *newParent);
    void reparentHandles(QGraphicsItem *newParent);
    QPointF constrainHitTestPoint(const QPointF& point);
    HbSelectionControlPrivate::HandleType handleForPoint(const QPointF& point);
    void gestureStarted(const QPointF &point);
    void tapGestureStarted(HbTapGesture *gesture);
    void tapGestureFinished();
    void delayedTapFinished();
    void panGestureStarted (HbPanGesture *gesture);
    void panGestureUpdated (HbPanGesture *gesture);
    void panGestureFinished (HbPanGesture *gesture);
    void panGestureCanceled();
    void show();
    void _q_aboutToChangeView();
    void detachEditor(bool updateAtthachedEditorState);

#if HB_DEBUG_PAINT_INFO
    void updateDebugPaintInfo();
#endif


public:

    HbAbstractEdit *mEdit;
    HbMagnifierDelegateEditor *mMagnifierDelegate; // Owned by mMagnifier
    HbMagnifier* mMagnifier;

    QGraphicsItem *mTopLevelAncestor;    
    // The offset between the gesture start point and first hit test point
    QPointF mTouchOffsetFromHitTestPoint;

    HbIconItem *mSelectionStartHandle;
    HbIconItem *mSelectionEndHandle;
    HbTouchArea* mSelectionStartTouchArea;
    HbTouchArea* mSelectionEndTouchArea;

    InteractionMode mInteractionMode;
    HandleType mPressed;
    bool mScrollInProgress;
    QPointF mHitTestPoint;                 // HbSelectionControl's coordinate system
    QPointF mTouchPoint;                   // HbSelectionControl's coordinate system
    QPointF mStartHandleHitTestPoint;      // HbSelectionControl's coordinate system
    QPointF mEndHandleHitTestPoint;        // HbSelectionControl's coordinate system
    QBasicTimer mWordSnapTimer;
    QBasicTimer mDelayedTapTimer;

    bool mMagnifierEnabled;
    qreal mLastCursorHeight;
    qreal mHandleMarginFromLine;
    qreal mMagnifierMarginFromLine;
    qreal mMagnifierLeftRightMarginFromHandle;
    qreal mMagnifierMaxDescent;
    qreal mVerticalScreenMargin;
    qreal mTouchYOffsetFromMagnifierReferenceLine; // HbSelectionControl's coordinate system
    qreal mStartHandleMagnifierReferenceLine;     // HbSelectionControl's coordinate system
    qreal mEndHandleMagnifierReferenceLine;       // HbSelectionControl's coordinate system
    qreal mMagnifierMaxYOffsetFromTouchPoint;
    qreal mMagnifierMinYOffsetFromTouchPoint;
    qreal mMaxHitTestPointOffsetYFromLine;
    bool mIsPanActive;

#if HB_DEBUG_PAINT_INFO
    QRectF  mDocRectDebug;      // HbSelectionControl's coordinate system
#endif
};


HbSelectionControlPrivate::HbSelectionControlPrivate():
    mEdit(0),
    mMagnifierDelegate(0),
    mMagnifier(0),
    mTopLevelAncestor(0),
    mSelectionStartHandle(0),
    mSelectionEndHandle(0),
    mSelectionStartTouchArea(0),
    mSelectionEndTouchArea(0),
    mInteractionMode(InteractionMode(0)),
    mPressed(HandleType(0)),
    mScrollInProgress(false),
    mMagnifierEnabled(true),
    mLastCursorHeight(0.0),
    mHandleMarginFromLine(0.0),
    mMagnifierMarginFromLine(0.0),
    mMagnifierLeftRightMarginFromHandle(0.0),
    mMagnifierMaxDescent(0.0),
    mVerticalScreenMargin(0.0),
    mTouchYOffsetFromMagnifierReferenceLine(0.0),
    mStartHandleMagnifierReferenceLine(0.0),
    mEndHandleMagnifierReferenceLine(0.0),
    mMagnifierMaxYOffsetFromTouchPoint(0.0),
    mMagnifierMinYOffsetFromTouchPoint(0.0),
    mMaxHitTestPointOffsetYFromLine(0.0),
    mIsPanActive(false)
{    
}

HbSelectionControlPrivate::~HbSelectionControlPrivate()
{
}

void HbSelectionControlPrivate::init()
{
    Q_Q(HbSelectionControl);

    // Set the size of the control to 0
    q->resize(0,0);

#if HB_DEBUG_PAINT_INFO
    // Override 0 size to be able to paint paint
    q->resize(1,1);
#endif

    q->style()->parameter(QLatin1String("hb-param-margin-gene-middle-vertical"), mVerticalScreenMargin);

    createPrimitives();

    q->setVisible(false);
    QGraphicsItem::GraphicsItemFlags itemFlags = q->flags();
#if QT_VERSION >= 0x040600
    itemFlags |=  QGraphicsItem::ItemSendsGeometryChanges;
#endif
    itemFlags &= ~QGraphicsItem::ItemIsFocusable;
    itemFlags |=  QGraphicsItem::ItemIsPanel;
    q->setFlags(itemFlags);
    q->setFocusPolicy(Qt::NoFocus);
    q->setActive(false);
}

void HbSelectionControlPrivate::createPrimitives()
{
    Q_Q(HbSelectionControl);
    if (!mSelectionStartHandle) {

        mSelectionStartHandle = new HbIconItem(q);
        mSelectionStartHandle->setIconName(QLatin1String("qtg_graf_editor_handle_begin"));
        HbStyle::setItemName(mSelectionStartHandle, QLatin1String("handle-icon"));
        mSelectionStartHandle->setFlag(QGraphicsItem::ItemIsPanel);
        mSelectionStartHandle->setFlag(QGraphicsItem::ItemIsFocusable,false);
        mSelectionStartHandle->setActive(false);
    }

    if (!mSelectionEndHandle) {
        mSelectionEndHandle = new HbIconItem(q);
        mSelectionEndHandle->setIconName(QLatin1String("qtg_graf_editor_handle_end"));
        HbStyle::setItemName(mSelectionEndHandle, QLatin1String("handle-icon"));
        mSelectionEndHandle->setFlag(QGraphicsItem::ItemIsPanel);
        mSelectionEndHandle->setFlag(QGraphicsItem::ItemIsFocusable,false);
        mSelectionEndHandle->setActive(false);
    }

    if (!mSelectionStartTouchArea) {
        mSelectionStartTouchArea = new HbTouchArea(q);
        mSelectionStartTouchArea->setFlag(QGraphicsItem::ItemIsPanel);
        mSelectionStartTouchArea->setFlag(QGraphicsItem::ItemIsFocusable,false);
        mSelectionStartTouchArea->setActive(false);
        HbStyle::setItemName(mSelectionStartTouchArea, QLatin1String("handle-toucharea"));
        mSelectionStartTouchArea->grabGesture(Qt::TapGesture);
        mSelectionStartTouchArea->grabGesture(Qt::PanGesture);
        mSelectionStartTouchArea->installEventFilter(q);
    }

    if (!mSelectionEndTouchArea) {
        mSelectionEndTouchArea = new HbTouchArea(q);
        mSelectionEndTouchArea->setFlag(QGraphicsItem::ItemIsPanel);
        mSelectionEndTouchArea->setFlag(QGraphicsItem::ItemIsFocusable,false);
        mSelectionEndTouchArea->setActive(false);
        HbStyle::setItemName(mSelectionEndTouchArea, QLatin1String("handle-toucharea"));
        mSelectionEndTouchArea->grabGesture(Qt::TapGesture);
        mSelectionEndTouchArea->grabGesture(Qt::PanGesture);
        mSelectionEndTouchArea->installEventFilter(q);
    }
    if(!mMagnifier) {
        mMagnifier = new HbMagnifier(q);
        mMagnifier->setFlag(QGraphicsItem::ItemIsPanel);
        mMagnifier->setFlag(QGraphicsItem::ItemIsFocusable,false);
        mMagnifier->setActive(false);
        HbStyle::setItemName(mMagnifier, QLatin1String("magnifier"));
        mMagnifierDelegate = new HbMagnifierDelegateEditor(mEdit);
        mMagnifier->setContentDelegate(mMagnifierDelegate);
        mMagnifier->hide();
        initMagnifier();
    }
}

/*
   Updates given handle associated with handleTouchArea to place it
   newHandlePos in the selected text.
*/
void HbSelectionControlPrivate::updateHandle(
                  int cursorPos,
                  int newHandlePos,
                  Qt::AlignmentFlag handleAlignment,
                  QGraphicsItem *handle,
                  QGraphicsItem *handleTouchArea)
{    
    Q_Q(HbSelectionControl);

    QRectF rect = mEdit->rectForPosition(newHandlePos,(Qt::AlignTop||mInteractionMode == CursorPositioning)?
                                                       QTextLine::Leading:QTextLine::Trailing);
    qreal lineHeight = rect.height();

    if(cursorPos == newHandlePos) {
        mLastCursorHeight = lineHeight;
    }

    // Store the current hit test point for the given handle
    // Convert rect to HbSelectionControl's coordinate system
    QRectF rectInSelectionControlCoord = q->mapRectFromItem(mEdit,rect);

    if (handleAlignment == Qt::AlignTop) {
        mStartHandleHitTestPoint = rectInSelectionControlCoord.center();
        mStartHandleHitTestPoint.setY(qMin(mStartHandleHitTestPoint.y(),
                                           rectInSelectionControlCoord.top()+mMaxHitTestPointOffsetYFromLine));
        mStartHandleMagnifierReferenceLine = rectInSelectionControlCoord.top();
    } else {
        mEndHandleHitTestPoint = rectInSelectionControlCoord.center();
        mEndHandleHitTestPoint.setY(qMax(mEndHandleHitTestPoint.y(),
                                         rectInSelectionControlCoord.bottom()-mMaxHitTestPointOffsetYFromLine));
        mEndHandleMagnifierReferenceLine = rectInSelectionControlCoord.bottom();
    }

    // Convert rect to handle's parent coordinate system
    rect = handle->parentItem()->mapRectFromItem(mEdit,rect);

    // Center handle around center point of rect
    QRectF boundingRect = handle->boundingRect();

    bool positionHandlesTouchEachOther = (boundingRect.height()*2 > lineHeight);

    boundingRect.moveCenter(rect.center());

    // Position handle either on top of rect using margin mHandleMarginFromLine or so that
    // the two handles touch eachother
    if (handleAlignment == Qt::AlignTop) {
        if (positionHandlesTouchEachOther) {
            boundingRect.moveBottom(rect.center().y());
        } else {
            boundingRect.moveTop(rect.top()-mHandleMarginFromLine);
        }
    } else {
        if (positionHandlesTouchEachOther) {
            boundingRect.moveTop(rect.center().y());
        } else {
            boundingRect.moveBottom(rect.bottom()+mHandleMarginFromLine);
        }
    }

    handle->setPos(boundingRect.topLeft());

    // Position handle touch area around center-top of handle
    QPointF centerPos = boundingRect.center();
    rect = boundingRect;
    boundingRect = handleTouchArea->boundingRect();
    boundingRect.moveCenter(centerPos);

    if (handleAlignment != Qt::AlignTop) {
        boundingRect.moveTop(rect.top());
    }

    handleTouchArea->setPos(boundingRect.topLeft());

    if (!mScrollInProgress) {
        QGraphicsItem * newParent = reparent(handle);
        reparent(handleTouchArea, newParent);
    }
}


void HbSelectionControlPrivate::updateMagnifier()
{
    Q_Q(HbSelectionControl);

    if (mMagnifier->isVisible()) {
        QPointF canvasHitTestPos = q->mapToItem(HbAbstractEditPrivate::d_ptr(mEdit)->canvas,mHitTestPoint);
        QRectF canvasCursorRect = HbAbstractEditPrivate::d_ptr(mEdit)->rectForPositionInCanvasCoords(mEdit->textCursor().position(),QTextLine::Leading);
        QPointF centerOfMagnification = canvasCursorRect.center();
        centerOfMagnification.setX(canvasHitTestPos.x());
        canvasCursorRect.moveCenter(centerOfMagnification);

        // Set the visible content to be magnified
        qreal magnifierHeight = mMagnifier->boundingRect().height();

        // Check if the line fits vertically to the magnifier if not align the bottom of the line with the bottom of the
        // magnifier constraining a maximum line descent.
        if (magnifierHeight < canvasCursorRect.height()*mMagnifier->contentScale()) {
            const QTextCursor cursor = mEdit->textCursor();
            const QTextBlock block = cursor.block();
            const int positionInBlock = cursor.position() - block.position();
            QTextLine line = block.layout()->lineForTextPosition(positionInBlock);
            qreal lineDescent = line.descent();
            qreal lineBottom  = canvasCursorRect.bottom();
            if (mMagnifierMaxDescent < lineDescent) {
                lineBottom-= lineDescent-mMagnifierMaxDescent;
            }
            // Compensate for rounding error by adding +1
            centerOfMagnification.setY(lineBottom-magnifierHeight/(2*mMagnifier->contentScale())+1);
        }

        mMagnifier->centerOnContent(centerOfMagnification);

        // -- Position magnifier --

        // Convert cursorRect to HbSelectionControl's coordinate system
        QRectF rect = q->mapRectFromItem(HbAbstractEditPrivate::d_ptr(mEdit)->canvas,canvasCursorRect);

        QRectF boundingRect = mMagnifier->boundingRect();
        boundingRect.moveCenter(rect.center());

        const qreal KMagnifierBottomUpperBound = rect.top()-mMagnifierMarginFromLine+mTouchYOffsetFromMagnifierReferenceLine;
        const qreal KMagnifierBottomLowerBound = rect.bottom()-mMagnifierMarginFromLine+mTouchYOffsetFromMagnifierReferenceLine;

        qreal newMagnifierBottom = (mPressed == SelectionStartHandle?KMagnifierBottomUpperBound:KMagnifierBottomLowerBound);

        // -- Constrain vertical position --

        // Constrain maximum distance of bottom of the magnifier from touch point
        newMagnifierBottom = qMax(newMagnifierBottom,mTouchPoint.y()-mMagnifierMaxYOffsetFromTouchPoint);

        // Constrain minimum distance of bottom of the magnifier from touch point
        newMagnifierBottom = qMin(newMagnifierBottom,mTouchPoint.y()-mMagnifierMinYOffsetFromTouchPoint);

        // Constrain the bottom of the magnifier to be within the feasible bounds
        newMagnifierBottom = qMin(qMax(newMagnifierBottom,KMagnifierBottomUpperBound),KMagnifierBottomLowerBound);

        boundingRect.moveBottom(newMagnifierBottom);
        boundingRect.moveTop(qMax(mVerticalScreenMargin,boundingRect.top()));

        // Readjust magnifier position if there is not enough space at the top of the screen
        if (mTouchPoint.y()-mMagnifierMinYOffsetFromTouchPoint < boundingRect.bottom()) {

            boundingRect.moveRight(mTouchPoint.x()-mMagnifierLeftRightMarginFromHandle);

            if(boundingRect.left() < 0) {
                boundingRect.moveLeft(mTouchPoint.x()+mMagnifierLeftRightMarginFromHandle);
            }
        }
        mMagnifier->setPos(boundingRect.topLeft());
#if HB_DEBUG_PAINT_INFO
    updateDebugPaintInfo();
#endif
    }

}

void HbSelectionControlPrivate::initMagnifier()
{   
    mMagnifier->setContentScale(MAGNIFIER_SCALE_FACTOR);
    mMagnifier->setZValue(100); 
    mMagnifier->setMask(QLatin1String("qtg_fr_editor_magnifier_mask"));
    mMagnifier->setOverlay(QLatin1String("qtg_fr_editor_magnifier_overlay"));
    updateMagnifier();
}


/*
   Reparents item to q if item's bounding rect intersects mEdit's viewPort rectangle or otherwise to
   HbAbstractEditPrivate::d_ptr(d->mEdit)->canvas.
   Returns new parent.
*/
QGraphicsItem * HbSelectionControlPrivate::reparent(QGraphicsItem *item)
{
    Q_Q(HbSelectionControl);

    QGraphicsItem *newParent = HbAbstractEditPrivate::d_ptr(mEdit)->canvas;

    // Convert bounding rect to mEdit's coordinate system
    QRectF rect = item->boundingRect();
    rect = item->mapRectToItem(mEdit,rect);

    QRectF scrollAreaRect = HbAbstractEditPrivate::d_ptr(mEdit)->scrollArea->geometry();

    if (rect.intersects(scrollAreaRect)) {
        newParent = q;
    }

    reparent(item, newParent);
    return newParent;
}

void HbSelectionControlPrivate::reparent(QGraphicsItem *item, QGraphicsItem *newParent)
{
    if (item && newParent && newParent != item->parentItem()) {

        // Reparent handle items to newParent
        QPointF pos = newParent->mapFromItem(item->parentItem(),item->pos());

        // ----------
        // Workaround for Qt bug: Missing repaint when QGraphicsItem is reparented to another
        // item with flag QGraphicsItem::ItemClipsChildrenToShape is set.
        // ----------
        // Remove when the bug is fixed
        if(item->isWidget()) {
            if (qobject_cast<HbIconItem *>(static_cast<QGraphicsWidget *>(item))) {
                item->scene()->invalidate(item->sceneBoundingRect());
            }
        }
        // ----------------------------------------------------------------------------------

        item->setParentItem(newParent);
        item->setPos(pos);
    }
}

void HbSelectionControlPrivate::reparentHandles(QGraphicsItem *newParent)
{
    // Reparent handle items to newParent
    reparent(mSelectionStartHandle, newParent);
    reparent(mSelectionStartTouchArea, newParent);
    reparent(mSelectionEndHandle, newParent);
    reparent(mSelectionEndTouchArea, newParent);
}


/*
    Returns the constrained hit test point calculated from point.
    point has to be in HbSelectionControl's coordinate system.
    The returned pos is also in HbSelectionControl's coordinate system.
*/
QPointF HbSelectionControlPrivate::constrainHitTestPoint(const QPointF& point)
{
    Q_Q(HbSelectionControl);

    QRectF docRect = QRectF(q->mapFromItem(HbAbstractEditPrivate::d_ptr(mEdit)->canvas->parentItem(),
                            HbAbstractEditPrivate::d_ptr(mEdit)->canvas->pos()),
                            HbAbstractEditPrivate::d_ptr(mEdit)->doc->size());

    // Constrain hitTestPos within docRect with mLastCursorHeight/2 top/bottom margins.
    QPointF hitTestPos = QPointF(qMin(qMax(point.x(),docRect.left()),docRect.right()),
                         qMin(qMax(point.y(),docRect.top()+mLastCursorHeight/2),docRect.bottom()-mLastCursorHeight/2));

#if HB_DEBUG_PAINT_INFO
    mDocRectDebug = docRect;
#endif

    return hitTestPos;
}


/*
    Returns the handle whose touch area contains point.
    point has to be in HbSelectionControl's coordinate system.
*/
HbSelectionControlPrivate::HandleType HbSelectionControlPrivate::handleForPoint(const QPointF& point) {

    Q_Q(HbSelectionControl);

    HandleType pressed = DummyHandle;

    // Find out which handle is being moved
    if (mSelectionStartTouchArea->isVisible() &&
        mSelectionStartTouchArea->contains(q->mapToItem(mSelectionStartTouchArea, point))) {
        pressed = SelectionStartHandle;
    }
    if (mSelectionEndTouchArea->contains(q->mapToItem(mSelectionEndTouchArea, point))) {
        bool useArea = true;
        if(pressed != DummyHandle) {

            // The press point was inside in both of the touch areas
            // choose the touch area whose center is closer to the press point
            QRectF rect = mSelectionStartHandle->boundingRect();
            rect.moveTopLeft(mSelectionStartHandle->pos());
            QLineF  lineEventPosSelStartCenter(point,rect.center());

            rect = mSelectionEndHandle->boundingRect();
            rect.moveTopLeft(mSelectionEndHandle->pos());
            QLineF  lineEventPosSelEndCenter(point,rect.center());

            if (lineEventPosSelStartCenter.length() < lineEventPosSelEndCenter.length()) {
                useArea = false;
            }
        }
        if (useArea) {
            pressed = SelectionEndHandle;
        }
    }

    return pressed;
}

void HbSelectionControlPrivate::gestureStarted(const QPointF &point)
{
    mPressed = handleForPoint(point);
    mTouchPoint = point;

    if (mPressed == DummyHandle) {
        // Hit is outside touch areas, ignore
        return;
    }

    // Calculate touch offsets
    mHitTestPoint = mStartHandleHitTestPoint;
    qreal magnifierReferenceLine = mStartHandleMagnifierReferenceLine;
    if (mPressed == SelectionEndHandle) {
        mHitTestPoint = mEndHandleHitTestPoint;
        magnifierReferenceLine = mEndHandleMagnifierReferenceLine;
    }

    mTouchYOffsetFromMagnifierReferenceLine = point.y() - magnifierReferenceLine;
    mTouchOffsetFromHitTestPoint = mHitTestPoint - point;

    // Position cursor at the pressed selection handle

    QTextCursor cursor = mEdit->textCursor();
    int selStartPos = qMin(mEdit->textCursor().anchor(),mEdit->textCursor().position());
    int selEndPos = qMax(mEdit->textCursor().anchor(),mEdit->textCursor().position());

    if (mPressed == SelectionStartHandle) {
        cursor.setPosition(selEndPos);
        cursor.setPosition(selStartPos, QTextCursor::KeepAnchor);
    } else {
        cursor.setPosition(selStartPos);
        cursor.setPosition(selEndPos, QTextCursor::KeepAnchor);
    }
    mEdit->setTextCursor(cursor);
}

void HbSelectionControlPrivate::tapGestureStarted(HbTapGesture *gesture)
{
    Q_Q(HbSelectionControl);

    mMagnifier->hideWithEffect();
    if (!mDelayedTapTimer.isActive()) {
        mDelayedTapTimer.start(MAGNIFIER_OPEN_DELAY,q);
    }
    QPointF point = q->mapFromScene(gesture->sceneStartPos());
    gestureStarted(point);
}

void HbSelectionControlPrivate::tapGestureFinished()
{
    mDelayedTapTimer.stop();
    if (!mIsPanActive) {
        mSelectionStartTouchArea->show();
        mSelectionStartHandle->show();
        mMagnifier->hideWithEffect();
    }
}

void HbSelectionControlPrivate::delayedTapFinished()
{
    Q_Q(HbSelectionControl);

    // Reset gesture override to have enable more responsive pan
    q->scene()->setProperty(HbPrivate::OverridingGesture.latin1(),QVariant());

    if (mPressed == HbSelectionControlPrivate::SelectionEndHandle && mInteractionMode == HbSelectionControlPrivate::CursorPositioning) {
        mSelectionStartTouchArea->hide();
        mSelectionStartHandle->hide();
    }
    if (mMagnifierEnabled) {
        mMagnifier->showWithEffect();
        q->updatePrimitives();
    }
}



void HbSelectionControlPrivate::panGestureStarted(HbPanGesture *gesture)
{
    Q_Q(HbSelectionControl);

    mIsPanActive = true;
    QPointF point = q->mapFromScene(gesture->sceneStartPos());
    gestureStarted(point);
    if (mPressed == SelectionEndHandle && mInteractionMode == HbSelectionControlPrivate::CursorPositioning) {
        mSelectionStartTouchArea->hide();
        mSelectionStartHandle->hide();
    }
}


void HbSelectionControlPrivate::panGestureFinished(HbPanGesture *gesture)
{
    Q_Q(HbSelectionControl);
    Q_UNUSED(gesture)

    if (mWordSnapTimer.isActive()) {

        // Snap selection to word beginning or end
        QTextCursor cursor = mEdit->textCursor();
        int curPos = mEdit->textCursor().position();
        int anchPos = mEdit->textCursor().anchor();
        cursor.select(QTextCursor::WordUnderCursor);

        // Snap direction depends on cursor position
        curPos = ((curPos > anchPos)?cursor.position():cursor.anchor());

        cursor.setPosition(anchPos);
        cursor.setPosition(curPos, QTextCursor::KeepAnchor);
        mEdit->setTextCursor(cursor);
    }

    mSelectionStartTouchArea->show();
    mSelectionStartHandle->show();
    // This has to be set before updatePrimitives call
    mIsPanActive = false;
    q->updatePrimitives();
    mMagnifier->hideWithEffect();

    // This has to be set at last
    mPressed = DummyHandle;
}

void HbSelectionControlPrivate::panGestureCanceled()
{
    mMagnifier->hideWithEffect();
    mIsPanActive = false;
}


void HbSelectionControlPrivate::panGestureUpdated(HbPanGesture *gesture)
{
    Q_Q(HbSelectionControl);

    // Calculate new hittest point
    QPointF point = q->mapFromScene(gesture->sceneStartPos() + gesture->sceneOffset());
    mTouchPoint = point;
    mHitTestPoint = (point + mTouchOffsetFromHitTestPoint);
    mHitTestPoint = constrainHitTestPoint(mHitTestPoint);

#if HB_DEBUG_PAINT_INFO
    updateDebugPaintInfo();
#endif

    QTextCursor cursor;
    cursor = mEdit->textCursor();

    // Hit test for the center of current selection touch area
    int hitPos = HbAbstractEditPrivate::d_ptr(mEdit)->hitTest(q->mapToItem(mEdit,mHitTestPoint),Qt::FuzzyHit);

    // if no valid hit pos or empty selection in read-only mode return
    if (hitPos == -1 || (mEdit->isReadOnly() && hitPos == cursor.anchor() && mInteractionMode == Selection)) {
        return;
    }

    bool isCursorMoved = false;
    if (hitPos != cursor.position()) {
        isCursorMoved = true;
    }

    cursor.setPosition(hitPos, ((mInteractionMode==Selection||mPressed == SelectionStartHandle)
                               ?QTextCursor::KeepAnchor:QTextCursor::MoveAnchor));

    if (isCursorMoved) {
        if (mEdit) {
            HbWidgetFeedback::triggered(mEdit, Hb::InstantDraggedOver);
        }
        if (mInteractionMode==Selection) {
            // Restart timer every time when a selection handle moved
            mWordSnapTimer.start(SNAP_DELAY, q);
        }
        mEdit->setTextCursor(cursor);
    }

    // Ensure that the hitPos is visible
    HbAbstractEditPrivate::d_ptr(mEdit)->ensurePositionVisible(hitPos);
    if (mMagnifierEnabled) {
        mMagnifier->showWithEffect();
    }
    q->updatePrimitives();

}

void HbSelectionControlPrivate::show() {
    Q_Q(HbSelectionControl);

    // Set the z-value of the selection control above its top-level ancestor
    if (mTopLevelAncestor) {
        qreal zValue = mTopLevelAncestor->zValue() + HbPrivate::SelectionControlHandlesValueUnit;

        q->setZValue(zValue);
    }

    if (q->scene() != mEdit->scene() && mEdit->scene()) {
        mEdit->scene()->addItem(q);    
    }    

    q->show();    
    q->updatePrimitives();
}


void HbSelectionControlPrivate::_q_aboutToChangeView()
{
    Q_Q(HbSelectionControl);

    if (mEdit && q->isVisible()) {
        mEdit->deselect();
        q->hideHandles();
    }
}

void HbSelectionControlPrivate::detachEditor(bool updateAtthachedEditorState)
{
    Q_Q(HbSelectionControl);
    if (mEdit) {
        q->hideHandles();
        reparentHandles(q);
        if (updateAtthachedEditorState) {
            mEdit->disconnect(q);
            mEdit->d_func()->selectionControl = 0;
            mEdit->deselect();
        }
        mEdit = 0;
        mTopLevelAncestor = 0;
    }
}

#if HB_DEBUG_PAINT_INFO
void HbSelectionControlPrivate::updateDebugPaintInfo()
{
    Q_Q(HbSelectionControl);
    mEdit->update();
    q->update();
}
#endif


HbSelectionControl::HbSelectionControl() : HbWidget(*new HbSelectionControlPrivate(),0)

{
    Q_D(HbSelectionControl);
    d->q_ptr = this;
    d->init();
    //TODO: selection control could be singleton per main window
    //      since only one selection control is used at a time
}

HbSelectionControl* HbSelectionControl::attachEditor(HbAbstractEdit *edit)
{
    if(!edit || !edit->mainWindow()) {
        qWarning("HbSelectionControl: attempting to attach to null editor pointer!");
    }

    HbSelectionControl *control = globalSelectionControlHash()->value(edit->mainWindow());

    if (!control) {
        control = new HbSelectionControl();
        globalSelectionControlHash()->insert(edit->mainWindow(),control);
        QObject::connect(edit->mainWindow(), SIGNAL(aboutToChangeView(HbView *, HbView *)), control, SLOT(_q_aboutToChangeView()));
    }

    HbSelectionControlPrivate *d = control->d_func();

    if (edit != d->mEdit) {
        control->detachEditor();
        d->mEdit = edit;        
        QObject::connect(d->mEdit, SIGNAL(cursorPositionChanged(int, int)), control, SLOT(updatePrimitives()));
        QObject::connect(d->mEdit, SIGNAL(contentsChanged()), control, SLOT(updatePrimitives()));
        d->mMagnifierDelegate->setEditor(d->mEdit);


        // Set the background of magnifier to the background of HbLineEdit or HbTextEdit
        QLatin1String magnifierBackground("qtg_fr_lineedit_normal_c");
        if (!qobject_cast<HbLineEdit*>(d->mEdit)) {
            magnifierBackground = QLatin1String("qtg_fr_textedit_normal_c");

        }
        d->mMagnifier->setBackground(magnifierBackground);

        // find first top-level ancestor of d->mEdit
        for(d->mTopLevelAncestor = d->mEdit;
            d->mTopLevelAncestor->parentItem();
            d->mTopLevelAncestor = d->mTopLevelAncestor->parentItem()){

            // Workaround for Qt bug: QTBUG-13473
            // This line could be removed after Qt fixes this bug.
            d->mTopLevelAncestor->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);

        };
    }
    return control;
}

void HbSelectionControl::detachEditor(HbAbstractEdit *edit)
{
    if(!edit || !edit->mainWindow()) {
        qWarning("HbSelectionControl: attempting to detach to null editor pointer!");
    }

    HbSelectionControl *control = globalSelectionControlHash()->value(edit->mainWindow());
    if (control) {
        control->detachEditor();
    }
}

void HbSelectionControl::detachEditor()
{
    Q_D(HbSelectionControl);
    d->mMagnifierDelegate->setEditor(0);
    d->detachEditor(true);
}

void HbSelectionControl::detachEditorFromDestructor()
{
    Q_D(HbSelectionControl);
    d->detachEditor(false);
}

void HbSelectionControl::hideHandles()
{
    Q_D(HbSelectionControl);
    if (isVisible() && d->mEdit) {
        hide();
        d->reparentHandles(this);
    }
}

void HbSelectionControl::showHandles()
{
    Q_D(HbSelectionControl);
    if (!isVisible() && d->mEdit) {
        d->show();
    }    
    // selection start handles might be hidden
    d->mSelectionStartTouchArea->show();
    d->mSelectionStartHandle->show();
}

void HbSelectionControl::scrollStarted()
{
    Q_D(HbSelectionControl);

    if (isVisible() && d->mEdit) {
        d->mScrollInProgress = true;
        // Reparent handle items to editor canvas on pan start
        d->reparentHandles(HbAbstractEditPrivate::d_ptr(d->mEdit)->canvas);
        d->mMagnifier->hideWithEffect();
        // Note: This call is not needed if it is guaranteed that this method
        // will be called before the scrolling started.
        updatePrimitives();
    }
}

void HbSelectionControl::scrollFinished()
{
    Q_D(HbSelectionControl);

    if (isVisible() && d->mEdit) {
        d->mScrollInProgress = false;
        updatePrimitives();
    }
}


void HbSelectionControl::timerEvent(QTimerEvent *event)
{
    Q_D(HbSelectionControl);

    if (event->timerId() == d->mWordSnapTimer.timerId()) {
        d->mWordSnapTimer.stop();
    } else  if (event->timerId() == d->mDelayedTapTimer.timerId()) {
        d->mDelayedTapTimer.stop();
        d->delayedTapFinished();
    }
}

void HbSelectionControl::polish( HbStyleParameters& params )
{
    Q_D(HbSelectionControl);

    if (isVisible()) {

        const QLatin1String KHandleMarginFromLine("handle-margin-from-line");
        const QLatin1String KMagnifierMarginFromLine("magnifier-margin-from-line");
        const QLatin1String KMagnifierLeftRightMarginFromHandle("magnifier-left-right-margin-from-handle");
        const QLatin1String KMagnifierMaxDescent("magnifier-max-descent");

        params.addParameter(KHandleMarginFromLine);
        params.addParameter(KMagnifierMarginFromLine);
        params.addParameter(KMagnifierLeftRightMarginFromHandle);
        params.addParameter(KMagnifierMaxDescent);

        HbWidget::polish(params);

        // Set size of handles
        QSizeF size = d->mSelectionStartHandle->preferredSize();
        d->mSelectionStartHandle->setSize(size);
        d->mSelectionEndHandle->setSize(size);

        // Set max y offset for hit test point
        // TODO: consider setting this value from css
        d->mMaxHitTestPointOffsetYFromLine = size.height();

        // Set size of touch areas
        size = d->mSelectionEndTouchArea->preferredSize();
        d->mSelectionEndTouchArea->resize(size);


        // Increase the height of the touch area of the start selection handle
        size.setHeight(size.height()*2-d->mSelectionStartHandle->size().height());
        d->mSelectionStartTouchArea->resize(size);



        // Set size of magnifier
        d->mMagnifier->resize(d->mMagnifier->preferredSize());

        if (params.value(KHandleMarginFromLine).isValid()) {
            d->mHandleMarginFromLine = params.value(KHandleMarginFromLine).toReal();
        }

        if (params.value(KMagnifierMarginFromLine).isValid()) {
            d->mMagnifierMarginFromLine = params.value(KMagnifierMarginFromLine).toReal();
        }
        if (params.value(KMagnifierLeftRightMarginFromHandle).isValid()) {
            d->mMagnifierLeftRightMarginFromHandle = params.value(KMagnifierLeftRightMarginFromHandle).toReal();
        }

        if (params.value(KMagnifierMaxDescent).isValid()) {
            d->mMagnifierMaxDescent = params.value(KMagnifierMaxDescent).toReal();
        }

        // Set the min/max magnifier touch offsets
        // TODO: consider setting this value from css
        d->mMagnifierMaxYOffsetFromTouchPoint = d->mMagnifierMarginFromLine*2;
        d->mMagnifierMinYOffsetFromTouchPoint = d->mMagnifierMarginFromLine/2;

        updatePrimitives();
    } else {
        HbWidget::polish(params);
    }
}

QVariant HbSelectionControl::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if (change == QGraphicsItem::ItemPositionChange) {
        return qVariantFromValue(QPointF(0,0));
    }

    return HbWidget::itemChange(change, value);
}

void HbSelectionControl::gestureEvent(QGestureEvent* event) {
    Q_D(HbSelectionControl);

    if(HbTapGesture *tap = qobject_cast<HbTapGesture*>(event->gesture(Qt::TapGesture))) {
        if (d->mEdit) {
            // GestureFinshed is only delegated while the mDelayedTapTimer is active
            if (tap->state() != Qt::GestureFinished || d->mDelayedTapTimer.isActive()) {
                HbAbstractEditPrivate::d_ptr(d->mEdit)->gestureEvent(event,this);
            }
        }

        switch(tap->state()) {

        case Qt::GestureStarted:
            d->tapGestureStarted(tap);
            break;
        case Qt::GestureCanceled:
        case Qt::GestureFinished:
            d->tapGestureFinished();
            break;
        default:
              break;
        }
    }

    if(HbPanGesture *pan = qobject_cast<HbPanGesture*>(event->gesture(Qt::PanGesture))) {
        switch(pan->state()) {
        case Qt::GestureStarted:
            if (d->mEdit) {
                d->panGestureStarted(pan);
            }
            break;
        case Qt::GestureUpdated:
            if (d->mEdit) {
                d->panGestureUpdated(pan);
            }
            break;
        case Qt::GestureFinished:
            if (d->mEdit) {
                d->panGestureFinished(pan);
                HbWidgetFeedback::triggered(this, Hb::InstantReleased);
            }
            break;
      case Qt::GestureCanceled:
            if (d->mEdit) {
                d->panGestureCanceled();
                HbWidgetFeedback::triggered(d->mEdit, Hb::InstantReleased);
            }
            break;
      default:
            break;
        }
    }
}

bool HbSelectionControl::eventFilter(QObject * watched, QEvent *event)
{
    Q_UNUSED(watched)

    // Filter gesture events and delegate to gestureEvent()
    if (event->type() == QEvent::Gesture || event->type() == QEvent::GestureOverride) {
        gestureEvent(static_cast<QGestureEvent*>(event));
        return true;
    }
    return false;
}


bool HbSelectionControl::event(QEvent *event)
{
    Q_D(HbSelectionControl);

    if (event->type() == HbEvent::DeviceProfileChanged && d->mEdit) {
        HbDeviceProfileChangedEvent* dpEvent = static_cast<HbDeviceProfileChangedEvent*>(event);
        if ( dpEvent->profile().alternateProfileName() == dpEvent->oldProfile().name() ) {
            d->initMagnifier();
            updatePrimitives();
        }
    }
    return HbWidget::event(event);
}


void HbSelectionControl::setMagnifierEnabled(bool enable)
{
    Q_D(HbSelectionControl);
    d->mMagnifierEnabled = enable;
}

bool HbSelectionControl::isMagnifierEnabled() const
{
    Q_D(const HbSelectionControl);
    return d->mMagnifierEnabled;
}

void HbSelectionControl::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)
    Q_UNUSED(option)    
    Q_UNUSED(painter)

#if HB_DEBUG_PAINT_INFO
    Q_D(HbSelectionControl);
    painter->save();

    // draw mHitTestPoint
    painter->setPen(Qt::yellow);
    painter->setBrush(Qt::yellow);
    painter->drawEllipse(d->mHitTestPoint, 3,3);

    // draw mDocRectDebug
    painter->setBrush(Qt::NoBrush);
    painter->drawRect(d->mDocRectDebug);

    // draw line representing mMagnifierMaxYOffsetFromTouchPoint
    painter->setPen(Qt::red);
    qreal magnifierMaxYOffsetLine = d->mTouchPoint.y()-d->mMagnifierMaxYOffsetFromTouchPoint;
    painter->drawLine(QPointF(0,magnifierMaxYOffsetLine),QPointF(500,magnifierMaxYOffsetLine));

    // draw line representing mMagnifierMinYOffsetFromTouchPoint
    painter->setPen(Qt::green);
    qreal magnifierMinYOffsetLine = d->mTouchPoint.y()-d->mMagnifierMinYOffsetFromTouchPoint;
    painter->drawLine(QPointF(0,magnifierMinYOffsetLine),QPointF(500,magnifierMinYOffsetLine));

    // draw line representing magnifierStartYOffsetLine
    painter->setPen(Qt::black);
    qreal magnifierStartYOffsetLine = d->mStartHandleMagnifierReferenceLine - d->mMagnifierMarginFromLine;
    painter->drawLine(QPointF(0,magnifierStartYOffsetLine),QPointF(500,magnifierStartYOffsetLine));

    // draw line representing magnifierStartYOffsetLine
    painter->setPen(Qt::magenta);
    qreal magnifierEndYOffsetLine = d->mEndHandleMagnifierReferenceLine - d->mMagnifierMarginFromLine;
    painter->drawLine(QPointF(0,magnifierEndYOffsetLine),QPointF(500,magnifierEndYOffsetLine));


    painter->drawRect(boundingRect());

    painter->restore();
#endif

}


void HbSelectionControl::updatePrimitives()
{
    Q_D(HbSelectionControl);

    if (isVisible() && d->polished && d->mEdit) {

        const bool hasSelection = d->mEdit->textCursor().hasSelection();

        // The interaction mode can be change only when there is no pan in progress
        if (!d->mIsPanActive) {
            d->mInteractionMode = (hasSelection?HbSelectionControlPrivate::Selection:
                                                HbSelectionControlPrivate::CursorPositioning);
        }

        const int cursorPos = d->mEdit->textCursor().position();
        int selStartPos = cursorPos;
        int selEndPos = cursorPos;
        d->mSelectionEndHandle->setIconName(QLatin1String("qtg_graf_editor_handle_finetune"));

        if (hasSelection) {
            selStartPos = qMin(d->mEdit->textCursor().anchor(),cursorPos);
            selEndPos = qMax(d->mEdit->textCursor().anchor(),cursorPos);
            d->mSelectionEndHandle->setIconName(QLatin1String("qtg_graf_editor_handle_end"));
        }

        d->updateHandle(cursorPos,selStartPos,Qt::AlignTop,d->mSelectionStartHandle,d->mSelectionStartTouchArea);
        d->updateHandle(cursorPos,selEndPos,Qt::AlignBottom,d->mSelectionEndHandle,d->mSelectionEndTouchArea);
        d->updateMagnifier();
    }
}
#include "moc_hbselectioncontrol_p.cpp"