diff -r 7516d6d86cf5 -r ed14f46c0e55 src/hbwidgets/editors/hbabstractedit_p.cpp --- a/src/hbwidgets/editors/hbabstractedit_p.cpp Mon Oct 04 17:49:30 2010 +0300 +++ b/src/hbwidgets/editors/hbabstractedit_p.cpp Mon Oct 18 18:23:13 2010 +0300 @@ -43,12 +43,19 @@ #include "hbselectioncontrol_p.h" #include "hbcolorscheme.h" #include "hbsmileyengine_p.h" -#include "hbtextmeasurementutility_p.h" -#include "hbfeaturemanager_r.h" +#ifdef HB_TEXT_MEASUREMENT_UTILITY +#include "hbtextmeasurementutility_r.h" +#include "hbtextmeasurementutility_r_p.h" +#endif #include "hbinputeditorinterface.h" #include "hbinputvkbhost.h" #include "hbinputmethod.h" #include "hbinputfocusobject.h" +#include "hbtapgesture.h" +#include "hbpangesture.h" +#include "hbnamespace_p.h" +#include "hbwidgetfeedback.h" + #include @@ -63,6 +70,12 @@ #include #include #include +#include + + +namespace { + static const int DOUBLE_TAP_DELAY = 400; +} static inline bool firstFramePosLessThanCursorPos(QTextFrame *frame, int position) { @@ -91,16 +104,34 @@ return r; } + +class HbEditScrollArea: public HbScrollArea +{ +public: + explicit HbEditScrollArea(HbAbstractEdit* edit, QGraphicsItem* parent = 0):HbScrollArea(parent), mEdit(edit) {} + virtual ~HbEditScrollArea() {} + + void resizeEvent(QGraphicsSceneResizeEvent *event) + { + HbScrollArea::resizeEvent(event); + mEdit->updatePrimitives(); + } + +private: + HbAbstractEdit* mEdit; +}; + + class HbEditItem : public HbWidget { public: HbEditItem(HbAbstractEdit *parent) : HbWidget(parent), edit(parent) { - setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); - }; + setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); + } - virtual ~HbEditItem() {}; + virtual ~HbEditItem() {} void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) { @@ -179,6 +210,8 @@ HbWidgetPrivate(), doc(0), placeholderDoc(0), + previousCursorAnchor(-1), + previousCursorPosition(-1), validator(0), imEditInProgress(false), imPosition(0), @@ -187,6 +220,8 @@ interactionFlags(Qt::TextEditorInteraction), tapPosition(-1, -1), cursorOn(false), + tapCounter(0), + showContextMenu(false), preeditCursor(0), preeditCursorVisible(true), apiCursorVisible(true), @@ -195,6 +230,7 @@ scrollable(false), hadSelectionOnMousePress(false), selectionControl(0), + enableSelectionControl(true), acceptSignalContentsChange(true), acceptSignalContentsChanged(true), validRevision(0), @@ -202,7 +238,9 @@ smileysEnabled(false), smileyEngine(0), formatDialog(0), - updatePrimitivesInProgress(false) + updatePrimitivesInProgress(false), + enableMagnifier(true), + cursorType(CursorTypeBlinking) { } @@ -215,8 +253,8 @@ Q_Q(HbAbstractEdit); canvas = new HbEditItem(q); - canvas->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored); + q->setFlag(QGraphicsItem::ItemAcceptsInputMethod); setContent(Qt::RichText, QString()); @@ -227,22 +265,22 @@ updatePaletteFromTheme(); - scrollArea = new HbScrollArea(q); + scrollArea = new HbEditScrollArea(q,q); scrollArea->setClampingStyle(HbScrollArea::StrictClamping); scrollArea->setFrictionEnabled(true); scrollArea->setScrollDirections(Qt::Vertical); scrollArea->setVerticalScrollBarPolicy(HbScrollArea::ScrollBarAlwaysOff); scrollArea->setContentWidget(canvas); + canvas->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); scrollArea->setFlag(QGraphicsItem::ItemIsFocusable, false); QObject::connect(scrollArea, SIGNAL(scrollingStarted()), q, SLOT(_q_scrollStarted())); QObject::connect(scrollArea, SIGNAL(scrollingEnded()), q, SLOT(_q_scrollEnded())); - QObject::connect(q, SIGNAL(selectionChanged(QTextCursor,QTextCursor)), q, SLOT(_q_selectionChanged())); + QObject::connect(q, SIGNAL(selectionChanged(const QTextCursor&,const QTextCursor&)), q, SLOT(_q_selectionChanged(const QTextCursor&,const QTextCursor&))); HbStyle::setItemName(scrollArea, QString("text")); // These are the default values which are then overridden in subclasses // and when different options are enabled. - q->setFlag(QGraphicsItem::ItemIsFocusable); - q->setFlag(QGraphicsItem::ItemAcceptsInputMethod); + q->setFlag(QGraphicsItem::ItemIsFocusable); q->setFlag(QGraphicsItem::ItemSendsScenePositionChanges); q->setFlag(QGraphicsItem::ItemHasNoContents, false); q->setFocusPolicy(Qt::StrongFocus); @@ -277,7 +315,7 @@ // for localization text support QString txt( text ); #ifdef HB_TEXT_MEASUREMENT_UTILITY - if ( HbFeatureManager::instance()->featureStatus( HbFeatureManager::TextMeasurement ) ) { + if (HbTextMeasurementUtility::instance()->locTestMode()) { if (text.endsWith(QChar(LOC_TEST_END))) { int index = text.indexOf(QChar(LOC_TEST_START)); q->setProperty( HbTextMeasurementUtilityNameSpace::textIdPropertyName, @@ -348,7 +386,6 @@ ensureCursorVisible(); - smileyEngineInstance()->setDocument(doc); if(q->isSmileysEnabled()) { smileyEngineInstance()->insertSmileys(); } @@ -525,7 +562,7 @@ } else { if (!oldSelection.isNull()) canvas->update(selectionRect(oldSelection) | cursorRectPlusUnicodeDirectionMarkers(oldSelection.position())); - canvas->update(selectionRect() | cursorRectPlusUnicodeDirectionMarkers(cursor.position())); + canvas->update(selectionRect(cursor) | cursorRectPlusUnicodeDirectionMarkers(cursor.position())); } } @@ -542,18 +579,46 @@ return rectForPositionInCanvasCoords(position,QTextLine::Leading).adjusted(-4, 0, 4, 0); } -void HbAbstractEditPrivate::setBlinkingCursorEnabled(bool enable) +void HbAbstractEditPrivate::updateCursorType() +{ + updateCursorType(hasInputFocus()); +} + +void HbAbstractEditPrivate::updateCursorType(bool hasInputFocus) +{ + CursorType type = hasInputFocus?CursorTypeBlinking:CursorTypeNone; + + if (hasInputFocus) { + if (cursor.hasSelection()) { + type = CursorTypeNone; + } else if (selectionControl && selectionControl->isVisible()) { + type = CursorTypeStill; + } + } + setCursorType(type); +} + +void HbAbstractEditPrivate::setCursorType(CursorType type) { Q_Q(HbAbstractEdit); - if (enable && QApplication::cursorFlashTime() > 0) - cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, q); - else - cursorBlinkTimer.stop(); + if (cursorType != type) { + cursorType = type; + + if (cursorType == CursorTypeBlinking && QApplication::cursorFlashTime() > 0) + cursorBlinkTimer.start(QApplication::cursorFlashTime() / 2, q); + else + cursorBlinkTimer.stop(); - cursorOn = enable; + cursorOn = (cursorType != CursorTypeNone); - repaintCursor(); + repaintCursor(); + } +} + +void HbAbstractEditPrivate::setCursorColor(const QColor& color) +{ + cursorColor = color; } void HbAbstractEditPrivate::repaintCursor() @@ -565,11 +630,26 @@ { if (scrollArea && scrollable) { QRectF rect = rectForPositionInCanvasCoords(position, QTextLine::Leading); - rect.adjust(0, -doc->documentMargin(), 0, doc->documentMargin()); + qreal docMargin = doc->documentMargin(); + rect.adjust(0, -docMargin, 0, docMargin); // TODO: it seems that scrollArea->ensureVisible() expects the point // in its content coordinates. Probably it should use viewport // coordinates i.e. its own item coordinate system //QRectF recScroll = canvas->mapToItem(scrollArea, rect).boundingRect(); + + const QTextBlock block = doc->findBlock(position); + if (block.isValid()) { + const QTextLayout *layout = block.layout(); + if(layout->preeditAreaText().length()) { + // Adjust cursor rect so that predictive text will be also visible + int preeditStart = layout->preeditAreaPosition(); + int preeditStop = preeditStart+layout->preeditAreaText().length(); + QTextLine line = layout->lineForTextPosition(preeditStart); + qreal preeditWidth = qAbs(line.cursorToX(preeditStop)-line.cursorToX(preeditStart)); + // rect.adjust(0,0,preeditWidth*0.5,0); + rect.setWidth(rect.width()+preeditWidth*0.5); + } + } scrollArea->ensureVisible(rect.center(), rect.width(), rect.height()/2); } } @@ -587,10 +667,6 @@ if (flags == interactionFlags) return; interactionFlags = flags; - - if (hasInputFocus()) { - setBlinkingCursorEnabled(flags & Qt::TextEditable); - } } void HbAbstractEditPrivate::_q_updateRequest(QRectF rect) @@ -617,6 +693,8 @@ emit q->contentsChanged(); + canvas->setPreferredSize(calculatePreferredDocSize()); + acceptSignalContentsChanged = true; // end of prevent recurence } } @@ -630,19 +708,24 @@ } } -void HbAbstractEditPrivate::_q_selectionChanged() +void HbAbstractEditPrivate::_q_selectionChanged(const QTextCursor& oldSelection, const QTextCursor& newSelection) { Q_Q(HbAbstractEdit); - if (cursor.hasSelection()) { + if (newSelection.hasSelection()) { selectionControl = HbSelectionControl::attachEditor(q); - selectionControl->showHandles(); + selectionControl->setMagnifierEnabled(q->isSelectionControlEnabled()&&enableMagnifier); + if (q->isSelectionControlEnabled()) { + selectionControl->showHandles(); + } } else if (selectionControl){ - selectionControl->hideHandles(); + if (q->isReadOnly()) { + selectionControl->hideHandles(); + } + selectionControl->updatePrimitives(); } - QTextCursor oldSelection(selectionCursor); - selectionCursor = cursor; + updateCursorType(); repaintOldAndNewSelection(oldSelection); } @@ -746,14 +829,13 @@ return; } } - emit q->selectionChanged(selectionCursor, cursor); + selectionCursor = cursor; } void HbAbstractEditPrivate::acceptKeyPressEvent(QKeyEvent *event) { event->accept(); - cursorOn = true; ensureCursorVisible(); updateCurrentCharFormat(); @@ -785,7 +867,7 @@ HbStyleOption opt; q->initStyleOption(&opt); - if (qApp->style()->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, 0)) { + if (qApp->style()->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, 0)) { selection.format.setProperty(QTextFormat::FullWidthSelection, true); } ctx.selections.append(selection); @@ -856,10 +938,6 @@ return r; } -QRectF HbAbstractEditPrivate::selectionRect() const -{ - return selectionRect(selectionCursor); -} QRectF HbAbstractEditPrivate::rectForPositionInCanvasCoords(int position, QTextLine::Edge edge) const { @@ -902,10 +980,6 @@ r = QRectF(layoutPos.x(), layoutPos.y(), cursorWidth, 10); // #### correct height } - if(layout->preeditAreaText().length()) { - r.adjust(0,0,q->blockBoundingRect(block).width()/2,0); - } - return r; } @@ -920,6 +994,20 @@ return viewRect; } +/* + Returns the constrained hit test point in editor coordinate system. +*/ +QPointF HbAbstractEditPrivate::constrainHitTestPoint(const QPointF& point) +{ + QRectF viewRect = viewPortRect(); + + // Constrain hitTestPos within docRect with 1 top/bottom margins. + QPointF hitTestPos = QPointF(qMin(qMax(point.x(),viewRect.left()),viewRect.right()), + qMin(qMax(point.y(),viewRect.top()+1),viewRect.bottom()-1)); + + return hitTestPos; +} + int HbAbstractEditPrivate::contentLength() const { QTextBlock block = doc->lastBlock(); @@ -974,6 +1062,95 @@ return preeditCursorVisible && apiCursorVisible; } +/* + base implementation of void gestureEvent(QGestureEvent* event) designed to be used by widgets + that delegate gesture event to the editor. Client widgets calling this method should pass themselves + as parameter in widget. If widget is 0 it is assumed that it is called by this HbAbstractEdit object. + +*/ +void HbAbstractEditPrivate::gestureEvent(QGestureEvent* event, HbWidget* widget) +{ + Q_Q(HbAbstractEdit); + + bool ownEvent = !widget; + + if(HbTapGesture *tap = qobject_cast(event->gesture(Qt::TapGesture))) { + // QTapGesture::position() is in screen coordinates and thus + // needs to be transformed into items own coordinate system. + // The QGestureEvent knows the viewport through which the gesture + // was triggered. + QPointF pos = q->mapFromScene(event->mapToGraphicsScene(tap->position())); + pos = constrainHitTestPoint(pos); + switch(tap->state()) { + case Qt::GestureStarted: + if (tapCounter == 0) { + tapPosition = pos; + } + HbWidgetFeedback::triggered(q, Hb::InstantPressed); + if (!doubleTapTimer.isActive()) { + doubleTapTimer.start(DOUBLE_TAP_DELAY,q); + } + break; + case Qt::GestureUpdated: + if(q->isReadOnly() && tap->tapStyleHint() == HbTapGesture::TapAndHold && !cursor.hasSelection() && ownEvent && + contextMenuShownOn.testFlag(Hb::ShowTextContextMenuOnLongPress)) { + q->showContextMenu(q->mapToScene(tapPosition)); + doubleTapTimer.stop(); + tapCounter = 0; + } + break; + case Qt::GestureFinished: + tapCounter++; + + // if doubleTapTimer is already triggered + if (!doubleTapTimer.isActive()){ + if (ownEvent){ + tapGesture(tapPosition,false); + } else { + if (contextMenuShownOn.testFlag(Hb::ShowTextContextMenuOnSelectionClicked)) { + q->showContextMenu(q->mapToScene(tapPosition)); + } + } + tapCounter = 0; + showContextMenu = false; + } else { + // if tapCounter is 1 the doubleTapTimer is still active so delay showing the context menu + if (tapCounter == 1){ + if (ownEvent){ + tapGesture(tapPosition,true); + } else { + showContextMenu = true; + } + } + // if tapCounter is 2 the tap gesture is double-tap cancel showing the context menu + else if (tapCounter == 2) { + doubleTapTimer.stop(); + tapCounter = 0; + showContextMenu = false; + if (contextMenuShownOn.testFlag(Hb::ShowTextContextMenuOnSelectionClicked) || + contextMenuShownOn.testFlag(Hb::ShowTextContextMenuOnLongPress)) { + q->selectClickedWord(); + } + } + } + + HbWidgetFeedback::triggered(q, Hb::InstantReleased); + + break; + case Qt::GestureCanceled: + doubleTapTimer.stop(); + tapCounter = 0; + break; + default: + break; + } + event->accept(); + } else { + event->ignore(); + } +} + + void HbAbstractEditPrivate::sendMouseEventToInputContext(const QPointF &tapPos) const { Q_Q(const HbAbstractEdit); @@ -1021,6 +1198,9 @@ doc = newDoc; cursor = QTextCursor(doc); + if(smileysEnabled) { + smileyEngineInstance()->setDocument(doc); + } QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_contentsChanged())); QObject::connect(doc, SIGNAL(contentsChange(int, int, int)), q, SLOT(_q_contentsChange(int, int, int))); @@ -1032,39 +1212,15 @@ q->documentLayoutChanged(); } -void HbAbstractEditPrivate::longTapGesture(const QPointF &point) +void HbAbstractEditPrivate::tapGesture(const QPointF &point, bool delayMenu) { Q_Q(HbAbstractEdit); - if(contextMenuShownOn.testFlag(Hb::ShowTextContextMenuOnLongPress)) { - - int cursorPos = hitTest(point, Qt::FuzzyHit); - if (cursorPos == -1) - return; - - // don't do anything if longpress inside the selection - if (cursor.hasSelection() - && cursorPos >= cursor.selectionStart() - && cursorPos <= cursor.selectionEnd()){ - return; - } - q->showContextMenu(q->mapToScene(point)); - } -} - -void HbAbstractEditPrivate::tapGesture(const QPointF &point) -{ - Q_Q(HbAbstractEdit); - - if (interactionFlags & Qt::NoTextInteraction) - return; - bool removeSelection = (hitTest(point, Qt::ExactHit) == -1); - if (removeSelection && cursor.hasSelection()) { - const QTextCursor oldCursor = cursor; + if (removeSelection && cursor.hasSelection()) { cursor.clearSelection(); - emit q->selectionChanged(oldCursor, cursor); + selectionChanged(); } int newCursorPos = hitTest(point, Qt::FuzzyHit); @@ -1074,33 +1230,43 @@ newCursorPos <= cursor.selectionEnd()){ // we have a selection under mouse click if (contextMenuShownOn.testFlag(Hb::ShowTextContextMenuOnSelectionClicked)) { - q->showContextMenu(q->mapToScene(point)); + if (delayMenu) { + showContextMenu = true; + } else { + q->showContextMenu(q->mapToScene(point)); + } } - } else { + } else { // Currently focused widget to listen to InputContext before updating the cursor position sendMouseEventToInputContext(point); - // translate the point to have the Y ccordinate inside the viewport - QPointF translatedPoint(point); - if(translatedPoint.y() < viewPortRect().top()) { - translatedPoint.setY(viewPortRect().top() + 1); - } else if(translatedPoint.y() > viewPortRect().bottom()){ - translatedPoint.setY(viewPortRect().bottom() - 1); - } - // need to get the cursor position again since input context can change the document - newCursorPos = hitTest(translatedPoint, Qt::FuzzyHit); + newCursorPos = hitTest(point, Qt::FuzzyHit); setCursorPosition(newCursorPos); if (interactionFlags & Qt::TextEditable) { updateCurrentCharFormat(); } - QString anchor(q->anchorAt(translatedPoint)); - if(!anchor.isEmpty()) { - emit q->anchorTapped(anchor); + // workaround for a Qt crash + // If there preedit are isn't empty the QTextDocumentLayout::anchorAt crashes + if (cursor.block().layout()->preeditAreaText().isEmpty()) { + QString anchor(q->anchorAt(point)); + if(!anchor.isEmpty()) { + emit q->anchorTapped(anchor); + } } } + + if(!q->isReadOnly()) { + if (selectionControl && hasInputFocus() && isCursorVisible()) { + if (q->isSelectionControlEnabled()) { + selectionControl->showHandles(); + } + updateCursorType(); + } + openInputPanel(); + } } @@ -1111,14 +1277,22 @@ } -void HbAbstractEditPrivate::drawSelectionEdges(QPainter *painter, QAbstractTextDocumentLayout::PaintContext ctx) +void HbAbstractEditPrivate::drawCursor(QPainter *painter, QAbstractTextDocumentLayout::PaintContext ctx) { - if (cursor.hasSelection() && selectionControl && selectionControl->isVisible()){ - painter->setPen(ctx.palette.color(QPalette::Text)); - painter->setBrush(ctx.palette.color(QPalette::Text)); - painter->drawRect(rectForPositionInCanvasCoords(cursor.selectionStart(), QTextLine::Leading)); - painter->drawRect(rectForPositionInCanvasCoords(cursor.selectionEnd(), QTextLine::Trailing)); + // save painter state + QPen oldPen = painter->pen(); + QBrush oldBrush = painter->brush(); + + // Draw cursor + if (ctx.cursorPosition != -1) { + painter->setPen(cursorColor); + painter->setBrush(cursorColor); + painter->drawRect(rectForPositionInCanvasCoords(ctx.cursorPosition, QTextLine::Leading)); } + + // restore painter state + painter->setPen(oldPen); + painter->setBrush(oldBrush); } /* @@ -1257,13 +1431,36 @@ void HbAbstractEditPrivate::setInputFocusEnabled(bool enable) { - QGraphicsItem *focusItem = focusPrimitive(HbWidget::FocusHighlightActive); - if (focusItem) { - focusItem->setVisible(enable); + setFocusHighlightVisible(HbWidget::FocusHighlightActive, enable); + if (!enable && selectionControl && !cursor.hasSelection()) { + selectionControl->hideHandles(); } - - setBlinkingCursorEnabled(enable); + updateCursorType(enable); repaintOldAndNewSelection(selectionCursor); } +QSizeF HbAbstractEditPrivate::calculatePreferredDocSize() const +{ + if (doc->isEmpty()) { + QFontMetricsF metrics(doc->defaultFont()); + qreal margins = doc->documentMargin()*2.0; + QSizeF result(5+margins, metrics.height()+margins); + if (placeholderDoc) { + result = result.expandedTo(placeholderDoc->size()); + } + return result; + } + QTextBlock block = doc->begin(); + qreal prefWidth = 5; // minimum value 5 + while (block.isValid()) { + const QTextLayout *layout = block.layout(); + prefWidth = qMax(layout->maximumWidth() + layout->position().x(), + prefWidth); + block=block.next(); + } + prefWidth += doc->documentMargin(); // only once since position was added + // so it is already contains right margin + return QSizeF(prefWidth, doc->size().height()); +} + #include "moc_hbabstractedit.cpp"