diff -r 7516d6d86cf5 -r ed14f46c0e55 src/hbwidgets/editors/hbselectioncontrol_p.cpp --- a/src/hbwidgets/editors/hbselectioncontrol_p.cpp Mon Oct 04 17:49:30 2010 +0300 +++ b/src/hbwidgets/editors/hbselectioncontrol_p.cpp Mon Oct 18 18:23:13 2010 +0300 @@ -40,17 +40,23 @@ #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 +#include #include +#include #include #include #include @@ -61,69 +67,185 @@ #include + +#define HB_DEBUG_PAINT_INFO 0 + typedef QHash 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(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 newHandlePos, + void updateHandle(int cursorPos, + int newHandlePos, Qt::AlignmentFlag handleAlignment, QGraphicsItem *handle, - QGraphicsItem *handleTouchArea, - HbStyle::Primitive handlePrimitive); + QGraphicsItem *handleTouchArea); + void updateMagnifier(); + void initMagnifier(); + QGraphicsItem * reparent(QGraphicsItem *item); void reparent(QGraphicsItem *item, QGraphicsItem *newParent); void reparentHandles(QGraphicsItem *newParent); - void tapGestureFinished (const QPointF& point); + 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; - QGraphicsItem *mTopLevelAncestor; + HbMagnifierDelegateEditor *mMagnifierDelegate; // Owned by mMagnifier + HbMagnifier* mMagnifier; - QGraphicsItem *mSelectionStartHandle; - QGraphicsItem *mSelectionEndHandle; + QGraphicsItem *mTopLevelAncestor; + // The offset between the gesture start point and first hit test point + QPointF mTouchOffsetFromHitTestPoint; + + HbIconItem *mSelectionStartHandle; + HbIconItem *mSelectionEndHandle; HbTouchArea* mSelectionStartTouchArea; HbTouchArea* mSelectionEndTouchArea; - HbSelectionControl::HandleType mPressed; - bool mPanInProgress; + 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), - mPressed(HbSelectionControl::HandleType(0)), - mPanInProgress(false) + 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); @@ -136,23 +258,25 @@ q->setFlags(itemFlags); q->setFocusPolicy(Qt::NoFocus); q->setActive(false); - - // Control will handle all events going to different handlers. - q->setHandlesChildEvents(true); } void HbSelectionControlPrivate::createPrimitives() { Q_Q(HbSelectionControl); if (!mSelectionStartHandle) { - mSelectionStartHandle = q->style()->createPrimitive(HbStyle::P_SelectionControl_selectionstart, q); + + 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 = q->style()->createPrimitive(HbStyle::P_SelectionControl_selectionend, q); + 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); @@ -163,9 +287,10 @@ mSelectionStartTouchArea->setFlag(QGraphicsItem::ItemIsPanel); mSelectionStartTouchArea->setFlag(QGraphicsItem::ItemIsFocusable,false); mSelectionStartTouchArea->setActive(false); - HbStyle::setItemName(mSelectionStartTouchArea, "handle-toucharea"); + HbStyle::setItemName(mSelectionStartTouchArea, QLatin1String("handle-toucharea")); mSelectionStartTouchArea->grabGesture(Qt::TapGesture); mSelectionStartTouchArea->grabGesture(Qt::PanGesture); + mSelectionStartTouchArea->installEventFilter(q); } if (!mSelectionEndTouchArea) { @@ -173,31 +298,60 @@ mSelectionEndTouchArea->setFlag(QGraphicsItem::ItemIsPanel); mSelectionEndTouchArea->setFlag(QGraphicsItem::ItemIsFocusable,false); mSelectionEndTouchArea->setActive(false); - HbStyle::setItemName(mSelectionEndTouchArea, "handle-toucharea"); + 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. - handlePrimitive identifies handle graphics. */ -void HbSelectionControlPrivate::updateHandle(int newHandlePos, +void HbSelectionControlPrivate::updateHandle( + int cursorPos, + int newHandlePos, Qt::AlignmentFlag handleAlignment, QGraphicsItem *handle, - QGraphicsItem *handleTouchArea, - HbStyle::Primitive handlePrimitive) + QGraphicsItem *handleTouchArea) { Q_Q(HbSelectionControl); - HbStyleOption option; + 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); - q->initStyleOption(&option); - mEdit->style()->updatePrimitive(handle, handlePrimitive, &option); - - QRectF rect = mEdit->rectForPosition(newHandlePos,Qt::AlignTop?QTextLine::Leading:QTextLine::Trailing); + 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); @@ -205,29 +359,132 @@ // 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) { - boundingRect.moveBottom(rect.top()); + if (positionHandlesTouchEachOther) { + boundingRect.moveBottom(rect.center().y()); + } else { + boundingRect.moveTop(rect.top()-mHandleMarginFromLine); + } } else { - boundingRect.moveTop(rect.bottom()); + if (positionHandlesTouchEachOther) { + boundingRect.moveTop(rect.center().y()); + } else { + boundingRect.moveBottom(rect.bottom()+mHandleMarginFromLine); + } } handle->setPos(boundingRect.topLeft()); - // Center handle touch area around center pos of handle + // 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 (!mPanInProgress) { + 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 @@ -256,23 +513,23 @@ void HbSelectionControlPrivate::reparent(QGraphicsItem *item, QGraphicsItem *newParent) { - Q_Q(HbSelectionControl); - if (item && newParent && newParent != item->parentItem()) { // Reparent handle items to newParent QPointF pos = newParent->mapFromItem(item->parentItem(),item->pos()); - // If the item is parented to other then q we have to - // turn off the QGraphicsItem::ItemIsPanel flag because - // otherwise the new parent loses its activeness. - bool enablePanel = (newParent == q); + // ---------- + // 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(static_cast(item))) { + item->scene()->invalidate(item->sceneBoundingRect()); + } + } + // ---------------------------------------------------------------------------------- - item->setFlag(QGraphicsItem::ItemIsPanel,enablePanel); - - // TODO: This is a workaround for a Qt bug when reparenting from a clipping parent to a - // non-clipping parent - item->setParentItem(0); item->setParentItem(newParent); item->setPos(pos); } @@ -288,36 +545,58 @@ } -void HbSelectionControlPrivate::tapGestureFinished(const QPointF &pos) -{ - if (mEdit->contextMenuFlags().testFlag(Hb::ShowTextContextMenuOnSelectionClicked)) { - mEdit->showContextMenu(pos); - } -} - -void HbSelectionControlPrivate::panGestureStarted(HbPanGesture *gesture) +/* + 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); - QPointF point = q->mapFromScene(gesture->sceneStartPos()); - mPressed = HbSelectionControl::DummyHandle; + 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->contains(q->mapToItem(mSelectionStartTouchArea, point))) { - mPressed = HbSelectionControl::SelectionStartHandle; + if (mSelectionStartTouchArea->isVisible() && + mSelectionStartTouchArea->contains(q->mapToItem(mSelectionStartTouchArea, point))) { + pressed = SelectionStartHandle; } if (mSelectionEndTouchArea->contains(q->mapToItem(mSelectionEndTouchArea, point))) { bool useArea = true; - if(mPressed != HbSelectionControl::DummyHandle) { + 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 = mSelectionStartTouchArea->boundingRect(); - rect.moveTopLeft(mSelectionStartTouchArea->pos()); + QRectF rect = mSelectionStartHandle->boundingRect(); + rect.moveTopLeft(mSelectionStartHandle->pos()); QLineF lineEventPosSelStartCenter(point,rect.center()); - rect = mSelectionEndTouchArea->boundingRect(); - rect.moveTopLeft(mSelectionEndTouchArea->pos()); + rect = mSelectionEndHandle->boundingRect(); + rect.moveTopLeft(mSelectionEndHandle->pos()); QLineF lineEventPosSelEndCenter(point,rect.center()); if (lineEventPosSelStartCenter.length() < lineEventPosSelEndCenter.length()) { @@ -325,22 +604,41 @@ } } if (useArea) { - mPressed = HbSelectionControl::SelectionEndHandle; + pressed = SelectionEndHandle; } } - if (mPressed == HbSelectionControl::DummyHandle) { + 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 == HbSelectionControl::SelectionStartHandle) { + if (mPressed == SelectionStartHandle) { cursor.setPosition(selEndPos); cursor.setPosition(selStartPos, QTextCursor::KeepAnchor); } else { @@ -348,7 +646,60 @@ 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(); + } } @@ -373,8 +724,21 @@ mEdit->setTextCursor(cursor); } - mPressed = HbSelectionControl::DummyHandle; + 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; } @@ -382,67 +746,53 @@ { Q_Q(HbSelectionControl); - QRectF docRect = QRectF(mEdit->mapFromItem(HbAbstractEditPrivate::d_ptr(mEdit)->canvas->parentItem(), - HbAbstractEditPrivate::d_ptr(mEdit)->canvas->pos()), - HbAbstractEditPrivate::d_ptr(mEdit)->doc->size()); - - QPointF editPos = mEdit->mapFromScene(gesture->sceneStartPos() + gesture->sceneOffset()); - QPointF origEditPos = editPos; - bool outsideCanvas = !docRect.contains(origEditPos); - - // Constrain editPos within docRect - editPos = QPointF(qMin(qMax(editPos.x(),docRect.left()),docRect.right()), - qMin(qMax(editPos.y(),docRect.top()),docRect.bottom())); - - QRectF handleRect = mSelectionStartHandle->boundingRect(); - - handleRect.moveCenter(editPos); + // Calculate new hittest point + QPointF point = q->mapFromScene(gesture->sceneStartPos() + gesture->sceneOffset()); + mTouchPoint = point; + mHitTestPoint = (point + mTouchOffsetFromHitTestPoint); + mHitTestPoint = constrainHitTestPoint(mHitTestPoint); - // Set hitTestPos based on which handle was grabbed - QPointF hitTestPos = handleRect.center(); - - if (mPressed == HbSelectionControl::SelectionStartHandle) { - hitTestPos.setY(handleRect.bottom()+1); - } else { - hitTestPos.setY(handleRect.top()-1); - } - - // Override hitTestPos if origEditPos was outside the canvas - if (outsideCanvas) { - if (origEditPos.y() < docRect.top()) { - hitTestPos.setY(handleRect.bottom()+1); - } else if (docRect.bottom() < origEditPos.y()) { - hitTestPos.setY(handleRect.top()-1); - } - } +#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(hitTestPos,Qt::FuzzyHit); - // if no valid hit pos or empty selection return - if (hitPos == -1 || hitPos == cursor.anchor()) { + // 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 handlesMoved(false); + bool isCursorMoved = false; if (hitPos != cursor.position()) { - handlesMoved = true; + isCursorMoved = true; } - cursor.setPosition(hitPos, QTextCursor::KeepAnchor); - if (handlesMoved) { + + cursor.setPosition(hitPos, ((mInteractionMode==Selection||mPressed == SelectionStartHandle) + ?QTextCursor::KeepAnchor:QTextCursor::MoveAnchor)); + + if (isCursorMoved) { if (mEdit) { HbWidgetFeedback::triggered(mEdit, Hb::InstantDraggedOver); } - // Restart timer every time when a selection handle moved - mWordSnapTimer.start(SNAP_DELAY, q); + 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() { @@ -456,8 +806,9 @@ } if (q->scene() != mEdit->scene() && mEdit->scene()) { - mEdit->scene()->addItem(q); - } + mEdit->scene()->addItem(q); + } + q->show(); q->updatePrimitives(); } @@ -469,6 +820,7 @@ if (mEdit && q->isVisible()) { mEdit->deselect(); + q->hideHandles(); } } @@ -488,6 +840,16 @@ } } +#if HB_DEBUG_PAINT_INFO +void HbSelectionControlPrivate::updateDebugPaintInfo() +{ + Q_Q(HbSelectionControl); + mEdit->update(); + q->update(); +} +#endif + + HbSelectionControl::HbSelectionControl() : HbWidget(*new HbSelectionControlPrivate(),0) { @@ -519,18 +881,47 @@ 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(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()){}; + 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); } @@ -554,7 +945,10 @@ Q_D(HbSelectionControl); if (!isVisible() && d->mEdit) { d->show(); - } + } + // selection start handles might be hidden + d->mSelectionStartTouchArea->show(); + d->mSelectionStartHandle->show(); } void HbSelectionControl::scrollStarted() @@ -562,9 +956,13 @@ Q_D(HbSelectionControl); if (isVisible() && d->mEdit) { - d->mPanInProgress = true; + 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(); } } @@ -573,7 +971,7 @@ Q_D(HbSelectionControl); if (isVisible() && d->mEdit) { - d->mPanInProgress = false; + d->mScrollInProgress = false; updatePrimitives(); } } @@ -585,6 +983,9 @@ if (event->timerId() == d->mWordSnapTimer.timerId()) { d->mWordSnapTimer.stop(); + } else if (event->timerId() == d->mDelayedTapTimer.timerId()) { + d->mDelayedTapTimer.stop(); + d->delayedTapFinished(); } } @@ -592,11 +993,67 @@ { Q_D(HbSelectionControl); - HbWidget::polish(params); - QSizeF size = d->mSelectionStartTouchArea->preferredSize(); - d->mSelectionStartTouchArea->resize(size); - d->mSelectionEndTouchArea->resize(size); - updatePrimitives(); + 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) @@ -610,26 +1067,26 @@ void HbSelectionControl::gestureEvent(QGestureEvent* event) { Q_D(HbSelectionControl); + if(HbTapGesture *tap = qobject_cast(event->gesture(Qt::TapGesture))) { - QPointF pos = event->mapToGraphicsScene(tap->position()); + 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: - if (d->mEdit) { - HbWidgetFeedback::triggered(this, Hb::InstantPressed); - } - break; - case Qt::GestureUpdated: + d->tapGestureStarted(tap); break; - case Qt::GestureFinished: - if (d->mEdit) { - d->tapGestureFinished(pos); - HbWidgetFeedback::triggered(this, Hb::InstantReleased); - } + case Qt::GestureCanceled: + case Qt::GestureFinished: + d->tapGestureFinished(); break; - case Qt::GestureCanceled: - break; - default: - break; + default: + break; } } @@ -652,6 +1109,10 @@ } break; case Qt::GestureCanceled: + if (d->mEdit) { + d->panGestureCanceled(); + HbWidgetFeedback::triggered(d->mEdit, Hb::InstantReleased); + } break; default: break; @@ -659,6 +1120,19 @@ } } +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(event)); + return true; + } + return false; +} + + bool HbSelectionControl::event(QEvent *event) { Q_D(HbSelectionControl); @@ -666,28 +1140,103 @@ if (event->type() == HbEvent::DeviceProfileChanged && d->mEdit) { HbDeviceProfileChangedEvent* dpEvent = static_cast(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) { - if (d->mEdit->textCursor().hasSelection() ) { - - int selStartPos = qMin(d->mEdit->textCursor().anchor(),d->mEdit->textCursor().position()); - int selEndPos = qMax(d->mEdit->textCursor().anchor(),d->mEdit->textCursor().position()); + + 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); + } - d->updateHandle(selStartPos,Qt::AlignTop,d->mSelectionStartHandle,d->mSelectionStartTouchArea,HbStyle::P_SelectionControl_selectionstart); - d->updateHandle(selEndPos,Qt::AlignBottom,d->mSelectionEndHandle,d->mSelectionEndTouchArea,HbStyle::P_SelectionControl_selectionend); + 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")); } - else { - hide(); - } + + 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" +