diff options
-rw-r--r-- | examples/declarative/clock/Clock.qml | 78 | ||||
-rw-r--r-- | examples/declarative/clock/background.png | bin | 0 -> 46895 bytes | |||
-rw-r--r-- | examples/declarative/clock/display.qml | 5 | ||||
-rw-r--r-- | examples/declarative/clock/hour.png | bin | 0 -> 391 bytes | |||
-rw-r--r-- | examples/declarative/clock/minute.png | bin | 0 -> 445 bytes | |||
-rw-r--r-- | examples/declarative/clock/second.png | bin | 0 -> 345 bytes | |||
-rw-r--r-- | examples/declarative/loader/Browser.qml | 42 | ||||
-rw-r--r-- | src/declarative/fx/qfxtextedit.cpp | 221 | ||||
-rw-r--r-- | src/declarative/fx/qfxtextedit.h | 27 | ||||
-rw-r--r-- | src/declarative/fx/qfxtextedit_p.h | 11 | ||||
-rw-r--r-- | src/declarative/util/qmlanimation.cpp | 29 | ||||
-rw-r--r-- | src/declarative/util/qmlanimation_p.h | 2 | ||||
-rw-r--r-- | src/declarative/util/qmlfollow.cpp | 57 | ||||
-rw-r--r-- | src/declarative/util/qmlfollow.h | 4 | ||||
-rw-r--r-- | tests/auto/declarative/qfxtextedit/data/cursorTest.qml | 6 | ||||
-rw-r--r-- | tests/auto/declarative/qfxtextedit/tst_qfxtextedit.cpp | 171 |
16 files changed, 606 insertions, 47 deletions
diff --git a/examples/declarative/clock/Clock.qml b/examples/declarative/clock/Clock.qml new file mode 100644 index 0000000..4779bd5 --- /dev/null +++ b/examples/declarative/clock/Clock.qml @@ -0,0 +1,78 @@ +Item { + id: Clock + width: 200; height: 200 + property var time + property var hours + property var minutes + property var seconds + onTimeChanged: { + var date = new Date; + hours = date.getHours(); + minutes = date.getMinutes(); + seconds = date.getSeconds(); + } + Timer { + interval: 500; running: true; repeat: true; triggeredOnStart: true + onTriggered: Clock.time = new Date() + } + + Image { source: "background.png" } + Image { + x: 95 + y: 54 + source: "hour.png" + smooth: true + transform: Rotation { + id: HourRotation + originX: 4; originY: 45 + angle: 0 + angle: Follow { + spring: 2 + damping: .2 + source: Clock.hours * 50 * 3 + Clock.minutes / 2 + } + } + } + Image { + x: 95 + y: 30 + source: "minute.png" + smooth: true + transform: Rotation { + id: MinuteRotation + originX: 4; originY: 70 + angle: 0 + angle: Follow { + spring: 2 + damping: .2 + source: Clock.minutes * 6 + } + } + } + Image { + x: 96 + y: 40 + source: "second.png" + smooth: true + transform: Rotation { + id: SecondRotation + originX: 2; originY: 60 + angle: 0 + angle: Follow { + spring: 5 + damping: .25 + modulus: 360 + source: Clock.seconds * 6 + } + } + } + + Rect { + x: 93 + y: 94 + width: 11 + height: 11 + radius: 5 + color: "black" + } +} diff --git a/examples/declarative/clock/background.png b/examples/declarative/clock/background.png Binary files differnew file mode 100644 index 0000000..a885950 --- /dev/null +++ b/examples/declarative/clock/background.png diff --git a/examples/declarative/clock/display.qml b/examples/declarative/clock/display.qml new file mode 100644 index 0000000..0187a7e --- /dev/null +++ b/examples/declarative/clock/display.qml @@ -0,0 +1,5 @@ +Rect { + width: contents.width + height: contents.height + Clock { id: Clock } +} diff --git a/examples/declarative/clock/hour.png b/examples/declarative/clock/hour.png Binary files differnew file mode 100644 index 0000000..603466b --- /dev/null +++ b/examples/declarative/clock/hour.png diff --git a/examples/declarative/clock/minute.png b/examples/declarative/clock/minute.png Binary files differnew file mode 100644 index 0000000..0207405 --- /dev/null +++ b/examples/declarative/clock/minute.png diff --git a/examples/declarative/clock/second.png b/examples/declarative/clock/second.png Binary files differnew file mode 100644 index 0000000..bfcef68 --- /dev/null +++ b/examples/declarative/clock/second.png diff --git a/examples/declarative/loader/Browser.qml b/examples/declarative/loader/Browser.qml index 640ea0c..280273b 100644 --- a/examples/declarative/loader/Browser.qml +++ b/examples/declarative/loader/Browser.qml @@ -5,17 +5,23 @@ Rect { FolderListModel { id: folders nameFilters: [ "*.qml" ] +// folder: "E:" } Component { id: FolderDelegate - Text { + Rect { id: Wrapper width: Root.width - text: fileName - font.bold: true - font.size: 14 + height: NameText.height + Text { + id: NameText + text: fileName + font.bold: true + font.size: 12 + } MouseRegion { + id: Mouse anchors.fill: parent onClicked: { if (folders.isFolder(index)) { @@ -25,17 +31,41 @@ Rect { } } } + states: [ + State { + name: "pressed" + when: Mouse.pressed + SetProperties { target: Wrapper; color: "#bbbbbb" } + } + ] } } - Text { id: DirText; text: folders.folder } + Script { + function up(path) { + var pos = path.toString().lastIndexOf("/"); + return path.toString().substring(0, pos); + } + } + + Rect { + id: UpButton + width: 30 + height: UpText.height + color: "grey" + MouseRegion { anchors.fill: parent; onClicked: folders.folder = up(folders.folder) } + Text { id: UpText; text: "Up" } + } + + Text { anchors.left: UpButton.right; text: folders.folder } ListView { - anchors.top: DirText.bottom + anchors.top: UpButton.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom model: folders delegate: FolderDelegate + clip: true } } diff --git a/src/declarative/fx/qfxtextedit.cpp b/src/declarative/fx/qfxtextedit.cpp index 7162bdf..c2bda6f 100644 --- a/src/declarative/fx/qfxtextedit.cpp +++ b/src/declarative/fx/qfxtextedit.cpp @@ -324,6 +324,36 @@ void QFxTextEdit::setHighlightColor(const QColor &color) } /*! + \qmlproperty color TextEdit::highlightedTextColor + + The highlighted text color, used in selections. +*/ + +/*! + \property QFxTextEdit::highlightedTextColor + \brief the text edit's default highlighted text color +*/ +QColor QFxTextEdit::highlightedTextColor() const +{ + Q_D(const QFxTextEdit); + return d->highlightColor; +} + +void QFxTextEdit::setHighlightedTextColor(const QColor &color) +{ + Q_D(QFxTextEdit); + if (d->highlightedTextColor == color) + return; + + clearCache(); + d->highlightedTextColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::HighlightedText, color); + d->control->setPalette(pal); + update(); +} + +/*! \qmlproperty enumeration TextEdit::hAlign \qmlproperty enumeration TextEdit::vAlign @@ -451,6 +481,145 @@ void QFxTextEdit::setCursorPosition(int pos) if (cursor.position() == pos) return; cursor.setPosition(pos); + d->control->setTextCursor(cursor); +} + +/*! + \qmlproperty TextEdit::cursorDelegate + \brief The delegate for the cursor in the TextEdit. + + If you set a cursorDelegate for a TextEdit, this delegate will be used for + drawing the cursor instead of the standard cursor. An instance of the + delegate will be created and managed by the text edit when a cursor is + needed, and the x and y properties of delegate instance will be set so as + to be one pixel before the top left of the current character. + + Note that the root item of the delegate component must be a QFxItem or + QFxItem derived item. +*/ +QmlComponent* QFxTextEdit::cursorDelegate() const +{ + Q_D(const QFxTextEdit); + return d->cursorComponent; +} + +void QFxTextEdit::setCursorDelegate(QmlComponent* c) +{ + Q_D(QFxTextEdit); + if(d->cursorComponent){ + delete d->cursorComponent; + if(d->cursor){ + disconnect(d->control, SIGNAL(cursorPositionChanged()), + this, SLOT(moveCursorDelegate())); + d->control->setCursorWidth(-1); + dirtyCache(cursorRect()); + delete d->cursor; + d->cursor = 0; + } + } + d->cursorComponent = c; + if(c && c->isReady()){ + loadCursorDelegate(); + }else{ + if(c) + connect(c, SIGNAL(statusChanged()), + this, SLOT(loadCursorDelegate())); + } +} + +void QFxTextEdit::loadCursorDelegate() +{ + Q_D(QFxTextEdit); + if(d->cursorComponent->isLoading()) + return; + d->cursor = qobject_cast<QFxItem*>(d->cursorComponent->create(qmlContext(this))); + if(d->cursor){ + connect(d->control, SIGNAL(cursorPositionChanged()), + this, SLOT(moveCursorDelegate())); + d->control->setCursorWidth(0); + dirtyCache(cursorRect()); + d->cursor->setItemParent(this); + d->cursor->setHeight(QFontMetrics(d->font.font()).height()); + moveCursorDelegate(); + }else{ + qWarning() << "Error loading cursor delegate for TextEdit:" + objectName(); + } +} + +/*! + \qmlproperty int TextEdit::selectionStart + + The cursor position before the first character in the current selection. + Setting this and selectionEnd allows you to specify a selection in the + text edit. + + Note that if selectionStart == selectionEnd then there is no current + selection. If you attempt to set selectionStart to a value outside of + the current text, selectionStart will not be changed. + + \sa selectionEnd, cursorPosition, selectedText +*/ +int QFxTextEdit::selectionStart() const +{ + Q_D(const QFxTextEdit); + return d->control->textCursor().selectionStart(); +} + +void QFxTextEdit::setSelectionStart(int s) +{ + Q_D(QFxTextEdit); + if(d->lastSelectionStart == s || s < 0 || s > text().length()) + return; + d->lastSelectionStart = s; + d->updateSelection();// Will emit the relevant signals +} + +/*! + \qmlproperty int TextEdit::selectionEnd + + The cursor position after the last character in the current selection. + Setting this and selectionStart allows you to specify a selection in the + text edit. + + Note that if selectionStart == selectionEnd then there is no current + selection. If you attempt to set selectionEnd to a value outside of + the current text, selectionEnd will not be changed. + + \sa selectionStart, cursorPosition, selectedText +*/ +int QFxTextEdit::selectionEnd() const +{ + Q_D(const QFxTextEdit); + return d->control->textCursor().selectionEnd(); +} + +void QFxTextEdit::setSelectionEnd(int s) +{ + Q_D(QFxTextEdit); + if(d->lastSelectionEnd == s || s < 0 || s > text().length()) + return; + d->lastSelectionEnd = s; + d->updateSelection();// Will emit the relevant signals +} + +/*! + \qmlproperty string TextEdit::selectedText + + This read-only property provides the text currently selected in the + text edit. + + It is equivalent to the following snippet, but is faster and easier + to use. + \code + //myTextEdit is the id of the TextEdit + myTextEdit.text.toString().substring(myTextEdit.selectionStart, + myTextEdit.selectionEnd); + \endcode +*/ +QString QFxTextEdit::selectedText() const +{ + Q_D(const QFxTextEdit); + return d->control->textCursor().selectedText(); } /*! @@ -853,6 +1022,10 @@ void QFxTextEdit::fontChanged() Q_D(QFxTextEdit); clearCache(); d->document->setDefaultFont(d->font.font()); + if(d->cursor){ + d->cursor->setHeight(QFontMetrics(d->font.font()).height()); + moveCursorDelegate(); + } updateSize(); emit update(); } @@ -879,6 +1052,9 @@ void QFxTextEditPrivate::init() QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateImgCache(QRectF))); QObject::connect(control, SIGNAL(textChanged()), q, SLOT(q_textChanged())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SLOT(updateSelectionMarkers())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(updateSelectionMarkers())); QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged())); document = control->document(); @@ -896,6 +1072,51 @@ void QFxTextEdit::q_textChanged() emit textChanged(text()); } +void QFxTextEdit::moveCursorDelegate() +{ + Q_D(QFxTextEdit); + if(!d->cursor) + return; + QRectF cursorRect = d->control->cursorRect(); + d->cursor->setX(cursorRect.x()); + d->cursor->setY(cursorRect.y()); +} + +void QFxTextEditPrivate::updateSelection() +{ + Q_Q(QFxTextEdit); + QTextCursor cursor = control->textCursor(); + bool startChange = (lastSelectionStart != cursor.selectionStart()); + bool endChange = (lastSelectionEnd != cursor.selectionEnd()); + //### Is it worth calculating a more minimal set of movements? + cursor.beginEditBlock(); + cursor.setPosition(lastSelectionStart, QTextCursor::MoveAnchor); + cursor.setPosition(lastSelectionEnd, QTextCursor::KeepAnchor); + cursor.endEditBlock(); + control->setTextCursor(cursor); + if(startChange) + q->selectionStartChanged(); + if(endChange) + q->selectionEndChanged(); + startChange = (lastSelectionStart != control->textCursor().selectionStart()); + endChange = (lastSelectionEnd != control->textCursor().selectionEnd()); + if(startChange || endChange) + qWarning() << "QFxTextEditPrivate::updateSelection() has failed you."; +} + +void QFxTextEdit::updateSelectionMarkers() +{ + Q_D(QFxTextEdit); + if(d->lastSelectionStart != d->control->textCursor().selectionStart()){ + d->lastSelectionStart = d->control->textCursor().selectionStart(); + emit selectionStartChanged(); + } + if(d->lastSelectionEnd != d->control->textCursor().selectionEnd()){ + d->lastSelectionEnd = d->control->textCursor().selectionEnd(); + emit selectionEndChanged(); + } +} + //### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't // need to do all the calculations each time void QFxTextEdit::updateSize() diff --git a/src/declarative/fx/qfxtextedit.h b/src/declarative/fx/qfxtextedit.h index 6988822..80636e2 100644 --- a/src/declarative/fx/qfxtextedit.h +++ b/src/declarative/fx/qfxtextedit.h @@ -70,6 +70,7 @@ class Q_DECLARATIVE_EXPORT QFxTextEdit : public QFxPaintedItem Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(QColor highlightColor READ highlightColor WRITE setHighlightColor) + Q_PROPERTY(QColor highlightedTextColor READ highlightedTextColor WRITE setHighlightedTextColor) Q_PROPERTY(QmlFont * font READ font) Q_PROPERTY(HAlignment hAlign READ hAlign WRITE setHAlign) Q_PROPERTY(VAlignment vAlign READ vAlign WRITE setVAlign) @@ -77,7 +78,11 @@ class Q_DECLARATIVE_EXPORT QFxTextEdit : public QFxPaintedItem Q_PROPERTY(TextFormat textFormat READ textFormat WRITE setTextFormat) Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible) - Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition) + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) + Q_PROPERTY(QmlComponent* cursorDelegate READ cursorDelegate WRITE setCursorDelegate) + Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectionChanged) Q_PROPERTY(bool focusOnPress READ focusOnPress WRITE setFocusOnPress) Q_PROPERTY(bool preserveSelection READ preserveSelection WRITE setPreserveSelection) Q_PROPERTY(qreal textMargin READ textMargin WRITE setTextMargin) @@ -118,6 +123,9 @@ public: QColor highlightColor() const; void setHighlightColor(const QColor &c); + QColor highlightedTextColor() const; + void setHighlightedTextColor(const QColor &c); + HAlignment hAlign() const; void setHAlign(HAlignment align); @@ -133,6 +141,17 @@ public: int cursorPosition() const; void setCursorPosition(int pos); + QmlComponent* cursorDelegate() const; + void setCursorDelegate(QmlComponent*); + + int selectionStart() const; + void setSelectionStart(int); + + int selectionEnd() const; + void setSelectionEnd(int); + + QString selectedText() const; + bool focusOnPress() const; void setFocusOnPress(bool on); @@ -168,6 +187,9 @@ public: Q_SIGNALS: void textChanged(const QString &); void cursorPositionChanged(); + void selectionStartChanged(); + void selectionEndChanged(); + void selectionChanged(); public Q_SLOTS: void selectAll(); @@ -176,6 +198,9 @@ private Q_SLOTS: void fontChanged(); void updateImgCache(const QRectF &rect); void q_textChanged(); + void updateSelectionMarkers(); + void moveCursorDelegate(); + void loadCursorDelegate(); private: void updateSize(); diff --git a/src/declarative/fx/qfxtextedit_p.h b/src/declarative/fx/qfxtextedit_p.h index f733a4c..b7d667e 100644 --- a/src/declarative/fx/qfxtextedit_p.h +++ b/src/declarative/fx/qfxtextedit_p.h @@ -69,8 +69,9 @@ class QFxTextEditPrivate : public QFxPaintedItemPrivate public: QFxTextEditPrivate() : font(0), color("black"), imgDirty(true), hAlign(QFxTextEdit::AlignLeft), vAlign(QFxTextEdit::AlignTop), - dirty(false), wrap(false), richText(false), cursorVisible(false), focusOnPress(false), preserveSelection(true), - textMargin(0.0), format(QFxTextEdit::AutoText), document(0) + dirty(false), wrap(false), richText(false), cursorVisible(false), focusOnPress(false), + preserveSelection(true), textMargin(0.0), lastSelectionStart(0), lastSelectionEnd(0), + cursor(0), cursorComponent(0), format(QFxTextEdit::AutoText), document(0) { } @@ -78,11 +79,13 @@ public: void updateDefaultTextOption(); void relayoutDocument(); + void updateSelection(); QString text; QmlFont font; QColor color; QColor highlightColor; + QColor highlightedTextColor; QString style; QColor styleColor; bool imgDirty; @@ -100,6 +103,10 @@ public: bool focusOnPress; bool preserveSelection; qreal textMargin; + int lastSelectionStart; + int lastSelectionEnd; + QmlComponent* cursorComponent; + QFxItem* cursor; QFxTextEdit::TextFormat format; QTextDocument *document; QTextControl *control; diff --git a/src/declarative/util/qmlanimation.cpp b/src/declarative/util/qmlanimation.cpp index c0d6481..ff070c1 100644 --- a/src/declarative/util/qmlanimation.cpp +++ b/src/declarative/util/qmlanimation.cpp @@ -1436,11 +1436,11 @@ void QmlParallelAnimation::transition(QmlStateActions &actions, QML_DEFINE_TYPE(QmlParallelAnimation,ParallelAnimation) //convert a variant from string type to another animatable type -//### should use any registered string convertor -void QmlPropertyAnimationPrivate::convertVariant(QVariant &variant, QVariant::Type type) +void QmlPropertyAnimationPrivate::convertVariant(QVariant &variant, int type) { if (variant.type() != QVariant::String) { - variant.convert(type); + if ((uint)type < QVariant::UserType) + variant.convert((QVariant::Type)type); return; } @@ -1474,7 +1474,12 @@ void QmlPropertyAnimationPrivate::convertVariant(QVariant &variant, QVariant::Ty break; } default: - variant.convert(type); + if ((uint)type >= QVariant::UserType) { + QmlMetaType::StringConverter converter = QmlMetaType::customStringConverter(type); + if (converter) + variant = converter(variant.toString()); + } else + variant.convert((QVariant::Type)type); break; } } @@ -1722,7 +1727,7 @@ void QmlPropertyAnimationPrivate::valueChanged(qreal r) if (!fromSourced) { if (!fromIsDefined) { from = property.read(); - convertVariant(from, (QVariant::Type)(interpolatorType ? interpolatorType : property.propertyType())); + convertVariant(from, interpolatorType ? interpolatorType : property.propertyType()); //### check for invalid variant if using property type } fromSourced = true; @@ -1751,9 +1756,9 @@ void QmlPropertyAnimation::prepare(QmlMetaProperty &p) d->property = d->userProperty; int propType = d->property.propertyType(); - d->convertVariant(d->to, (QVariant::Type)(d->interpolatorType ? d->interpolatorType : propType)); + d->convertVariant(d->to, d->interpolatorType ? d->interpolatorType : propType); if (d->fromIsDefined) - d->convertVariant(d->from, (QVariant::Type)(d->interpolatorType ? d->interpolatorType : propType)); + d->convertVariant(d->from, d->interpolatorType ? d->interpolatorType : propType); if (!d->interpolatorType) { //### check for invalid variants @@ -1795,7 +1800,7 @@ void QmlPropertyAnimation::transition(QmlStateActions &actions, if (action.fromValue.isNull()) { action.fromValue = action.property.read(); if (interpolatorType) - QmlPropertyAnimationPrivate::convertVariant(action.fromValue, (QVariant::Type)interpolatorType); + QmlPropertyAnimationPrivate::convertVariant(action.fromValue, interpolatorType); } if (!interpolatorType) { int propType = action.property.propertyType(); @@ -1856,8 +1861,8 @@ void QmlPropertyAnimation::transition(QmlStateActions &actions, if (d->toIsDefined) myAction.toValue = d->to; - d->convertVariant(myAction.fromValue, (QVariant::Type)(d->interpolatorType ? d->interpolatorType : myAction.property.propertyType())); - d->convertVariant(myAction.toValue, (QVariant::Type)(d->interpolatorType ? d->interpolatorType : myAction.property.propertyType())); + d->convertVariant(myAction.fromValue, d->interpolatorType ? d->interpolatorType : myAction.property.propertyType()); + d->convertVariant(myAction.toValue, d->interpolatorType ? d->interpolatorType : myAction.property.propertyType()); modified << action.property; @@ -1875,10 +1880,10 @@ void QmlPropertyAnimation::transition(QmlStateActions &actions, continue; if (d->fromIsDefined) { - d->convertVariant(d->from, (QVariant::Type)(d->interpolatorType ? d->interpolatorType : myAction.property.propertyType())); + d->convertVariant(d->from, d->interpolatorType ? d->interpolatorType : myAction.property.propertyType()); myAction.fromValue = d->from; } - d->convertVariant(d->to, (QVariant::Type)(d->interpolatorType ? d->interpolatorType : myAction.property.propertyType())); + d->convertVariant(d->to, d->interpolatorType ? d->interpolatorType : myAction.property.propertyType()); myAction.toValue = d->to; data->actions << myAction; } diff --git a/src/declarative/util/qmlanimation_p.h b/src/declarative/util/qmlanimation_p.h index 00759e1..87d480f 100644 --- a/src/declarative/util/qmlanimation_p.h +++ b/src/declarative/util/qmlanimation_p.h @@ -346,7 +346,7 @@ public: QmlTimeLineValueProxy<QmlPropertyAnimationPrivate> value; static QVariant interpolateVariant(const QVariant &from, const QVariant &to, qreal progress); - static void convertVariant(QVariant &variant, QVariant::Type type); + static void convertVariant(QVariant &variant, int type); }; QT_END_NAMESPACE diff --git a/src/declarative/util/qmlfollow.cpp b/src/declarative/util/qmlfollow.cpp index c6d806a..b8e6685 100644 --- a/src/declarative/util/qmlfollow.cpp +++ b/src/declarative/util/qmlfollow.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include <limits.h> +#include <math.h> #include <QtCore/qdebug.h> #include "private/qobject_p.h" #include "qmlfollow.h" @@ -55,7 +56,7 @@ class QmlFollowPrivate : public QObjectPrivate public: QmlFollowPrivate() : sourceValue(0), maxVelocity(0), lastTime(0) - , mass(1.0), spring(0.), damping(0.), velocity(0), epsilon(0.005), enabled(true), mode(Track), clock(this) {} + , mass(1.0), spring(0.), damping(0.), velocity(0), epsilon(0.005), modulus(0.0), enabled(true), mode(Track), clock(this) {} QmlMetaProperty property; qreal currentValue; @@ -68,6 +69,7 @@ public: qreal damping; qreal velocity; qreal epsilon; + qreal modulus; bool enabled; enum Mode { @@ -92,6 +94,11 @@ void QmlFollowPrivate::tick(int time) int elapsed = time - lastTime; if (!elapsed) return; + qreal srcVal = sourceValue; + if (modulus != 0.0) { + currentValue = fmod(currentValue, modulus); + srcVal = fmod(srcVal, modulus); + } if (mode == Spring) { if (elapsed < 16) // capped at 62fps. return; @@ -99,7 +106,13 @@ void QmlFollowPrivate::tick(int time) // We'll do something much simpler which gives a result that looks fine. int count = (elapsed+8) / 16; for (int i = 0; i < count; ++i) { - qreal diff = sourceValue - currentValue; + qreal diff = srcVal - currentValue; + if (modulus != 0.0 && qAbs(diff) > modulus / 2) { + if (diff < 0) + diff += modulus; + else + diff -= modulus; + } velocity = velocity + spring * diff - damping * velocity; // The following line supports mass. Not sure its worth the extra divisions. // velocity = velocity + spring / mass * diff - damping / mass * velocity; @@ -111,24 +124,39 @@ void QmlFollowPrivate::tick(int time) velocity = -maxVelocity; } currentValue += velocity * 16.0 / 1000.0; + if (modulus != 0.0) { + currentValue = fmod(currentValue, modulus); + if (currentValue < 0.0) + currentValue += modulus; + } } - if (qAbs(velocity) < epsilon && qAbs(sourceValue - currentValue) < epsilon) { + if (qAbs(velocity) < epsilon && qAbs(srcVal - currentValue) < epsilon) { velocity = 0.0; - currentValue = sourceValue; + currentValue = srcVal; clock.stop(); } lastTime = time - (elapsed - count * 16); } else { qreal moveBy = elapsed * velocityms; - qreal diff = sourceValue - currentValue; + qreal diff = srcVal - currentValue; + if (modulus != 0.0 && qAbs(diff) > modulus / 2) { + if (diff < 0) + diff += modulus; + else + diff -= modulus; + } if (diff > 0) { currentValue += moveBy; + if (modulus != 0.0) + currentValue = fmod(currentValue, modulus); if (currentValue > sourceValue) { currentValue = sourceValue; clock.stop(); } } else { currentValue -= moveBy; + if (modulus != 0.0 && currentValue < 0.0) + currentValue = fmod(currentValue, modulus) + modulus; if (currentValue < sourceValue) { currentValue = sourceValue; clock.stop(); @@ -326,6 +354,25 @@ void QmlFollow::setEpsilon(qreal epsilon) } /*! + \qmlproperty qreal Follow::modulus + This property holds the modulus value. + + Setting a \a modulus forces the target value to "wrap around" at the modulus. + For example, setting the modulus to 360 will cause a value of 370 to wrap around to 10. +*/ +qreal QmlFollow::modulus() const +{ + Q_D(const QmlFollow); + return d->modulus; +} + +void QmlFollow::setModulus(qreal modulus) +{ + Q_D(QmlFollow); + d->modulus = modulus; +} + +/*! \qmlproperty qreal Follow::followValue The current value. */ diff --git a/src/declarative/util/qmlfollow.h b/src/declarative/util/qmlfollow.h index 0953f2c..07e15e9 100644 --- a/src/declarative/util/qmlfollow.h +++ b/src/declarative/util/qmlfollow.h @@ -64,8 +64,10 @@ class Q_DECLARATIVE_EXPORT QmlFollow : public QmlPropertyValueSource, Q_PROPERTY(qreal spring READ spring WRITE setSpring) Q_PROPERTY(qreal damping READ damping WRITE setDamping) Q_PROPERTY(qreal epsilon READ epsilon WRITE setEpsilon) + Q_PROPERTY(qreal modulus READ modulus WRITE setModulus) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled) Q_PROPERTY(qreal followValue READ value NOTIFY valueChanged) + Q_PROPERTY(qreal modulus READ modulus WRITE setModulus NOTIFY modulusChanged) public: QmlFollow(QObject *parent=0); @@ -83,6 +85,8 @@ public: void setDamping(qreal damping); qreal epsilon() const; void setEpsilon(qreal epsilon); + qreal modulus() const; + void setModulus(qreal modulus); bool enabled() const; void setEnabled(bool enabled); diff --git a/tests/auto/declarative/qfxtextedit/data/cursorTest.qml b/tests/auto/declarative/qfxtextedit/data/cursorTest.qml new file mode 100644 index 0000000..10ac2fd --- /dev/null +++ b/tests/auto/declarative/qfxtextedit/data/cursorTest.qml @@ -0,0 +1,6 @@ +Rect { width: 300; height: 300; color: "white" + TextEdit { text: "Hello world!"; focusable: true; id: textEditObject + resources: [ Component { id:cursor; Item { id:cursorInstance } } ] + cursorDelegate: cursor + } +} diff --git a/tests/auto/declarative/qfxtextedit/tst_qfxtextedit.cpp b/tests/auto/declarative/qfxtextedit/tst_qfxtextedit.cpp index 7be5b50..241dbad 100644 --- a/tests/auto/declarative/qfxtextedit/tst_qfxtextedit.cpp +++ b/tests/auto/declarative/qfxtextedit/tst_qfxtextedit.cpp @@ -1,6 +1,8 @@ #include <qtest.h> #include <QTextDocument> #include <QtDeclarative/qmlengine.h> +#include <QtDeclarative/qmlcontext.h> +#include <QtDeclarative/qmlexpression.h> #include <QtDeclarative/qmlcomponent.h> #include <QtDeclarative/qfxtextedit.h> #include <QFontMetrics> @@ -22,6 +24,9 @@ private slots: void vAlign(); void font(); void color(); + void selection(); + + void cursorDelegate(); private: QStringList standard; @@ -84,7 +89,7 @@ tst_qfxtextedit::tst_qfxtextedit() void tst_qfxtextedit::text() { { - QmlComponent texteditComponent(&engine, "TextEdit { text: \"\" }"); + QmlComponent texteditComponent(&engine, "TextEdit { text: \"\" }", QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); QVERIFY(textEditObject != 0); @@ -94,7 +99,7 @@ void tst_qfxtextedit::text() for (int i = 0; i < standard.size(); i++) { QString componentStr = "TextEdit { text: \"" + standard.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); QVERIFY(textEditObject != 0); @@ -104,7 +109,7 @@ void tst_qfxtextedit::text() for (int i = 0; i < richText.size(); i++) { QString componentStr = "TextEdit { text: \"" + richText.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); QVERIFY(textEditObject != 0); @@ -121,9 +126,10 @@ void tst_qfxtextedit::width() { // uses Font metrics to find the width for standard and document to find the width for rich { - QmlComponent texteditComponent(&engine, "TextEdit { text: \"\" }"); + QmlComponent texteditComponent(&engine, "TextEdit { text: \"\" }", QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->width(), 0.); } @@ -134,9 +140,10 @@ void tst_qfxtextedit::width() int metricWidth = fm.size(Qt::TextExpandTabs && Qt::TextShowMnemonic, standard.at(i)).width(); QString componentStr = "TextEdit { text: \"" + standard.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->width(), qreal(metricWidth)); } @@ -149,9 +156,10 @@ void tst_qfxtextedit::width() int documentWidth = document.idealWidth(); QString componentStr = "TextEdit { text: \"" + richText.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->width(), qreal(documentWidth)); } } @@ -160,27 +168,30 @@ void tst_qfxtextedit::wrap() { // for specified width and wrap set true { - QmlComponent texteditComponent(&engine, "TextEdit { text: \"\"; wrap: true; width: 300 }"); + QmlComponent texteditComponent(&engine, "TextEdit { text: \"\"; wrap: true; width: 300 }", QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->width(), 300.); } for (int i = 0; i < standard.size(); i++) { QString componentStr = "TextEdit { wrap: true; width: 300; text: \"" + standard.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->width(), 300.); } for (int i = 0; i < richText.size(); i++) { QString componentStr = "TextEdit { wrap: true; width: 300; text: \"" + richText.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->width(), 300.); } @@ -196,9 +207,10 @@ void tst_qfxtextedit::hAlign() for (int j=0; j < hAlignmentStrings.size(); j++) { QString componentStr = "TextEdit { hAlign: \"" + hAlignmentStrings.at(j) + "\"; text: \"" + standard.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE((int)textEditObject->hAlign(), (int)hAlignments.at(j)); } } @@ -208,9 +220,10 @@ void tst_qfxtextedit::hAlign() for (int j=0; j < hAlignmentStrings.size(); j++) { QString componentStr = "TextEdit { hAlign: \"" + hAlignmentStrings.at(j) + "\"; text: \"" + richText.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE((int)textEditObject->hAlign(), (int)hAlignments.at(j)); } } @@ -226,9 +239,10 @@ void tst_qfxtextedit::vAlign() for (int j=0; j < vAlignmentStrings.size(); j++) { QString componentStr = "TextEdit { vAlign: \"" + vAlignmentStrings.at(j) + "\"; text: \"" + standard.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE((int)textEditObject->vAlign(), (int)vAlignments.at(j)); } } @@ -238,9 +252,10 @@ void tst_qfxtextedit::vAlign() for (int j=0; j < vAlignmentStrings.size(); j++) { QString componentStr = "TextEdit { vAlign: \"" + vAlignmentStrings.at(j) + "\"; text: \"" + richText.at(i) + "\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE((int)textEditObject->vAlign(), (int)vAlignments.at(j)); } } @@ -252,9 +267,10 @@ void tst_qfxtextedit::font() //test size, then bold, then italic, then family { QString componentStr = "TextEdit { font.size: 40; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->font()->size(), qreal(40)); QCOMPARE(textEditObject->font()->bold(), false); QCOMPARE(textEditObject->font()->italic(), false); @@ -262,27 +278,30 @@ void tst_qfxtextedit::font() { QString componentStr = "TextEdit { font.bold: true; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->font()->bold(), true); QCOMPARE(textEditObject->font()->italic(), false); } { QString componentStr = "TextEdit { font.italic: true; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->font()->italic(), true); QCOMPARE(textEditObject->font()->bold(), false); } { QString componentStr = "TextEdit { font.family: \"Helvetica\"; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->font()->family(), QString("Helvetica")); QCOMPARE(textEditObject->font()->bold(), false); QCOMPARE(textEditObject->font()->italic(), false); @@ -290,9 +309,10 @@ void tst_qfxtextedit::font() { QString componentStr = "TextEdit { font.family: \"\"; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->font()->family(), QString("")); } } @@ -303,9 +323,10 @@ void tst_qfxtextedit::color() for (int i = 0; i < colorStrings.size(); i++) { QString componentStr = "TextEdit { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); //qDebug() << "textEditObject: " << textEditObject->color() << "vs. " << QColor(colorStrings.at(i)); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->color(), QColor(colorStrings.at(i))); } @@ -315,12 +336,122 @@ void tst_qfxtextedit::color() testColor.setAlpha(170); QString componentStr = "TextEdit { color: \"" + colorStr + "\"; text: \"Hello World\" }"; - QmlComponent texteditComponent(&engine, componentStr.toLatin1()); + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); QCOMPARE(textEditObject->color(), testColor); } } + +void tst_qfxtextedit::selection() +{ + QString testStr = standard[0];//TODO: What should happen for multiline/rich text? + QString componentStr = "TextEdit { text: \""+ testStr +"\"; }"; + QmlComponent texteditComponent(&engine, componentStr.toLatin1(), QUrl()); + QFxTextEdit *textEditObject = qobject_cast<QFxTextEdit*>(texteditComponent.create()); + QVERIFY(textEditObject != 0); + + + //Test selection follows cursor + for(int i=0; i<= testStr.size(); i++) { + textEditObject->setCursorPosition(i); + QCOMPARE(textEditObject->cursorPosition(), i); + QCOMPARE(textEditObject->selectionStart(), i); + QCOMPARE(textEditObject->selectionEnd(), i); + QVERIFY(textEditObject->selectedText().isNull()); + } + + textEditObject->setCursorPosition(0); + QVERIFY(textEditObject->cursorPosition() == 0); + QVERIFY(textEditObject->selectionStart() == 0); + QVERIFY(textEditObject->selectionEnd() == 0); + QVERIFY(textEditObject->selectedText().isNull()); + + //Test selection + for(int i=0; i<= testStr.size(); i++) { + textEditObject->setSelectionEnd(i); + QCOMPARE(testStr.mid(0,i), textEditObject->selectedText()); + } + for(int i=0; i<= testStr.size(); i++) { + textEditObject->setSelectionStart(i); + QCOMPARE(testStr.mid(i,testStr.size()-i), textEditObject->selectedText()); + } + + textEditObject->setCursorPosition(0); + QVERIFY(textEditObject->cursorPosition() == 0); + QVERIFY(textEditObject->selectionStart() == 0); + QVERIFY(textEditObject->selectionEnd() == 0); + QVERIFY(textEditObject->selectedText().isNull()); + + for(int i=0; i< testStr.size(); i++) { + textEditObject->setSelectionStart(i); + QCOMPARE(textEditObject->selectionEnd(), i); + QCOMPARE(testStr.mid(i,0), textEditObject->selectedText()); + textEditObject->setSelectionEnd(i+1); + QCOMPARE(textEditObject->selectionStart(), i); + QCOMPARE(testStr.mid(i,1), textEditObject->selectedText()); + } + + for(int i= testStr.size() - 1; i>0; i--) { + textEditObject->setSelectionEnd(i); + QCOMPARE(testStr.mid(i,0), textEditObject->selectedText()); + textEditObject->setSelectionStart(i-1); + QCOMPARE(testStr.mid(i-1,1), textEditObject->selectedText()); + } + + //Test Error Ignoring behaviour + textEditObject->setCursorPosition(0); + QVERIFY(textEditObject->selectedText().isNull()); + textEditObject->setSelectionStart(-10); + QVERIFY(textEditObject->selectedText().isNull()); + textEditObject->setSelectionStart(100); + QVERIFY(textEditObject->selectedText().isNull()); + textEditObject->setSelectionEnd(-10); + QVERIFY(textEditObject->selectedText().isNull()); + textEditObject->setSelectionEnd(100); + QVERIFY(textEditObject->selectedText().isNull()); + textEditObject->setSelectionStart(0); + textEditObject->setSelectionEnd(10); + QVERIFY(textEditObject->selectedText().size() == 10); + textEditObject->setSelectionStart(-10); + QVERIFY(textEditObject->selectedText().size() == 10); + textEditObject->setSelectionStart(100); + QVERIFY(textEditObject->selectedText().size() == 10); + textEditObject->setSelectionEnd(-10); + QVERIFY(textEditObject->selectedText().size() == 10); + textEditObject->setSelectionEnd(100); + QVERIFY(textEditObject->selectedText().size() == 10); +} + +#include <QFxView> +void tst_qfxtextedit::cursorDelegate() +{ + QFxView* view = new QFxView(0); + view->show(); + view->setUrl(QUrl("data/cursorTest.qml")); + view->execute(); + QFxTextEdit *textEditObject = view->root()->findChild<QFxTextEdit*>("textEditObject"); + QVERIFY(textEditObject != 0); + QVERIFY(textEditObject->findChild<QFxItem*>("cursorInstance")); + //Test Delegate gets created + textEditObject->setFocus(true); + QFxItem* delegateObject = textEditObject->findChild<QFxItem*>("cursorInstance"); + QVERIFY(delegateObject); + //Test Delegate gets moved + for(int i=0; i<= textEditObject->text().length(); i++){ + textEditObject->setCursorPosition(i); + QCOMPARE(textEditObject->cursorRect().x(), qRound(delegateObject->x())); + QCOMPARE(textEditObject->cursorRect().y(), qRound(delegateObject->y())); + } + textEditObject->setCursorPosition(0); + QCOMPARE(textEditObject->cursorRect().x(), qRound(delegateObject->x())); + QCOMPARE(textEditObject->cursorRect().y(), qRound(delegateObject->y())); + //Test Delegate gets deleted + textEditObject->setCursorDelegate(0); + QVERIFY(!textEditObject->findChild<QFxItem*>("cursorInstance")); +} + QTEST_MAIN(tst_qfxtextedit) #include "tst_qfxtextedit.moc" |