diff options
author | Gunnar Sletta <gunnar.sletta@nokia.com> | 2011-08-25 11:47:24 +0200 |
---|---|---|
committer | Gunnar Sletta <gunnar.sletta@nokia.com> | 2011-08-25 12:52:15 +0200 |
commit | 8e6ecb56e5d61ce661422779c9d9cbf22f081a34 (patch) | |
tree | 227aeb360800a49de58472bd39bc5f4c596c1b15 /src/declarative/items | |
parent | c9224b6cf5cceb7d5314f7504d44bfe72bc66950 (diff) | |
parent | 0d84e957297b4ffa6ab5b0b5bcf8b169d567d298 (diff) |
Merge branch 'master' into refactor
Conflicts:
src/declarative/items/qsgcanvas.cpp
src/declarative/items/qsgitem.cpp
src/declarative/items/qsgtextnode.cpp
tests/auto/declarative/examples/examples.pro
tools/qmlviewer/qmlviewer.pro
Change-Id: Icbb0ef5dc79b658c62fd2b2c25a66c9bb3cbeb10
Diffstat (limited to 'src/declarative/items')
44 files changed, 9615 insertions, 767 deletions
diff --git a/src/declarative/items/qsganimatedimage.cpp b/src/declarative/items/qsganimatedimage.cpp index 0a9fdd1783..ea36153ef0 100644 --- a/src/declarative/items/qsganimatedimage.cpp +++ b/src/declarative/items/qsganimatedimage.cpp @@ -52,6 +52,75 @@ #include <private/qdeclarativeengine_p.h> QT_BEGIN_NAMESPACE +/*! + \qmlclass AnimatedImage QSGAnimatedImage + \inqmlmodule QtQuick 2 + \inherits Image + \ingroup basic-visual-elements + + The AnimatedImage element extends the features of the \l Image element, providing + a way to play animations stored as images containing a series of frames, + such as those stored in GIF files. + + Information about the current frame and totla length of the animation can be + obtained using the \l currentFrame and \l frameCount properties. You can + start, pause and stop the animation by changing the values of the \l playing + and \l paused properties. + + The full list of supported formats can be determined with QMovie::supportedFormats(). + + \section1 Example Usage + + \beginfloatleft + \image animatedimageitem.gif + \endfloat + + The following QML shows how to display an animated image and obtain information + about its state, such as the current frame and total number of frames. + The result is an animated image with a simple progress indicator underneath it. + + \bold Note: Unlike images, animated images are not cached or shared internally. + + \clearfloat + \snippet doc/src/snippets/declarative/animatedimage.qml document + + \sa BorderImage, Image +*/ + +/*! + \qmlproperty url QtQuick2::AnimatedImage::source + + This property holds the URL that refers to the source image. + + AnimatedImage can handle any image format supported by Qt, loaded from any + URL scheme supported by Qt. + + \sa QDeclarativeImageProvider +*/ + +/*! + \qmlproperty bool QtQuick2::AnimatedImage::asynchronous + + Specifies that images on the local filesystem should be loaded + asynchronously in a separate thread. The default value is + false, causing the user interface thread to block while the + image is loaded. Setting \a asynchronous to true is useful where + maintaining a responsive user interface is more desirable + than having images immediately visible. + + Note that this property is only valid for images read from the + local filesystem. Images loaded via a network resource (e.g. HTTP) + are always loaded asynchonously. +*/ + +/*! + \qmlproperty bool QtQuick2::AnimatedImage::mirror + + This property holds whether the image should be horizontally inverted + (effectively displaying a mirrored image). + + The default value is false. +*/ QSGAnimatedImage::QSGAnimatedImage(QSGItem *parent) : QSGImage(*(new QSGAnimatedImagePrivate), parent) @@ -64,6 +133,14 @@ QSGAnimatedImage::~QSGAnimatedImage() delete d->_movie; } +/*! + \qmlproperty bool QtQuick2::AnimatedImage::paused + This property holds whether the animated image is paused. + + By default, this property is false. Set it to true when you want to pause + the animation. +*/ + bool QSGAnimatedImage::isPaused() const { Q_D(const QSGAnimatedImage); @@ -83,6 +160,14 @@ void QSGAnimatedImage::setPaused(bool pause) d->_movie->setPaused(pause); } +/*! + \qmlproperty bool QtQuick2::AnimatedImage::playing + This property holds whether the animated image is playing. + + By default, this property is true, meaning that the animation + will start playing immediately. +*/ + bool QSGAnimatedImage::isPlaying() const { Q_D(const QSGAnimatedImage); @@ -105,6 +190,16 @@ void QSGAnimatedImage::setPlaying(bool play) d->_movie->stop(); } +/*! + \qmlproperty int QtQuick2::AnimatedImage::currentFrame + \qmlproperty int QtQuick2::AnimatedImage::frameCount + + currentFrame is the frame that is currently visible. By monitoring this property + for changes, you can animate other items at the same time as the image. + + frameCount is the number of frames in the animation. For some animation formats, + frameCount is unknown and has a value of zero. +*/ int QSGAnimatedImage::currentFrame() const { Q_D(const QSGAnimatedImage); diff --git a/src/declarative/items/qsgborderimage.cpp b/src/declarative/items/qsgborderimage.cpp index 684ea0097b..8d54bce419 100644 --- a/src/declarative/items/qsgborderimage.cpp +++ b/src/declarative/items/qsgborderimage.cpp @@ -50,6 +50,119 @@ QT_BEGIN_NAMESPACE + +/*! + \qmlclass BorderImage QSGBorderImage + \inqmlmodule QtQuick 2 + \brief The BorderImage element provides an image that can be used as a border. + \inherits Item + \ingroup qml-basic-visual-elements + + The BorderImage element is used to create borders out of images by scaling or tiling + parts of each image. + + A BorderImage element breaks a source image, specified using the \l url property, + into 9 regions, as shown below: + + \image declarative-scalegrid.png + + When the image is scaled, regions of the source image are scaled or tiled to + create the displayed border image in the following way: + + \list + \i The corners (regions 1, 3, 7, and 9) are not scaled at all. + \i Regions 2 and 8 are scaled according to + \l{BorderImage::horizontalTileMode}{horizontalTileMode}. + \i Regions 4 and 6 are scaled according to + \l{BorderImage::verticalTileMode}{verticalTileMode}. + \i The middle (region 5) is scaled according to both + \l{BorderImage::horizontalTileMode}{horizontalTileMode} and + \l{BorderImage::verticalTileMode}{verticalTileMode}. + \endlist + + The regions of the image are defined using the \l border property group, which + describes the distance from each edge of the source image to use as a border. + + \section1 Example Usage + + The following examples show the effects of the different modes on an image. + Guide lines are overlaid onto the image to show the different regions of the + image as described above. + + \beginfloatleft + \image qml-borderimage-normal-image.png + \endfloat + + An unscaled image is displayed using an Image element. The \l border property is + used to determine the parts of the image that will lie inside the unscaled corner + areas and the parts that will be stretched horizontally and vertically. + + \snippet doc/src/snippets/declarative/borderimage/normal-image.qml normal image + + \clearfloat + \beginfloatleft + \image qml-borderimage-scaled.png + \endfloat + + A BorderImage element is used to display the image, and it is given a size that is + larger than the original image. Since the \l horizontalTileMode property is set to + \l{BorderImage::horizontalTileMode}{BorderImage.Stretch}, the parts of image in + regions 2 and 8 are stretched horizontally. Since the \l verticalTileMode property + is set to \l{BorderImage::verticalTileMode}{BorderImage.Stretch}, the parts of image + in regions 4 and 6 are stretched vertically. + + \snippet doc/src/snippets/declarative/borderimage/borderimage-scaled.qml scaled border image + + \clearfloat + \beginfloatleft + \image qml-borderimage-tiled.png + \endfloat + + Again, a large BorderImage element is used to display the image. With the + \l horizontalTileMode property set to \l{BorderImage::horizontalTileMode}{BorderImage.Repeat}, + the parts of image in regions 2 and 8 are tiled so that they fill the space at the + top and bottom of the element. Similarly, the \l verticalTileMode property is set to + \l{BorderImage::verticalTileMode}{BorderImage.Repeat}, the parts of image in regions + 4 and 6 are tiled so that they fill the space at the left and right of the element. + + \snippet doc/src/snippets/declarative/borderimage/borderimage-tiled.qml tiled border image + + \clearfloat + In some situations, the width of regions 2 and 8 may not be an exact multiple of the width + of the corresponding regions in the source image. Similarly, the height of regions 4 and 6 + may not be an exact multiple of the height of the corresponding regions. It can be useful + to use \l{BorderImage::horizontalTileMode}{BorderImage.Round} instead of + \l{BorderImage::horizontalTileMode}{BorderImage.Repeat} in cases like these. + + The \l{declarative/imageelements/borderimage}{BorderImage example} shows how a BorderImage + can be used to simulate a shadow effect on a rectangular item. + + \section1 Quality and Performance + + By default, any scaled regions of the image are rendered without smoothing to improve + rendering speed. Setting the \l smooth property improves rendering quality of scaled + regions, but may slow down rendering. + + The source image may not be loaded instantaneously, depending on its original location. + Loading progress can be monitored with the \l progress property. + + \sa Image, AnimatedImage + */ + +/*! + \qmlproperty bool QtQuick2::BorderImage::asynchronous + + Specifies that images on the local filesystem should be loaded + asynchronously in a separate thread. The default value is + false, causing the user interface thread to block while the + image is loaded. Setting \a asynchronous to true is useful where + maintaining a responsive user interface is more desirable + than having images immediately visible. + + Note that this property is only valid for images read from the + local filesystem. Images loaded via a network resource (e.g. HTTP) + are always loaded asynchonously. +*/ QSGBorderImage::QSGBorderImage(QSGItem *parent) : QSGImageBase(*(new QSGBorderImagePrivate), parent) { @@ -62,6 +175,99 @@ QSGBorderImage::~QSGBorderImage() d->sciReply->deleteLater(); } +/*! + \qmlproperty enumeration QtQuick2::BorderImage::status + + This property describes the status of image loading. It can be one of: + + \list + \o BorderImage.Null - no image has been set + \o BorderImage.Ready - the image has been loaded + \o BorderImage.Loading - the image is currently being loaded + \o BorderImage.Error - an error occurred while loading the image + \endlist + + \sa progress +*/ + +/*! + \qmlproperty real QtQuick2::BorderImage::progress + + This property holds the progress of image loading, from 0.0 (nothing loaded) + to 1.0 (finished). + + \sa status +*/ + +/*! + \qmlproperty bool QtQuick2::BorderImage::smooth + + Set this property if you want the image to be smoothly filtered when scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the image is displayed at its natural size, this property has no visual or + performance effect. + + By default, this property is set to false. + + \note Generally scaling artifacts are only visible if the image is stationary on + the screen. A common pattern when animating an image is to disable smooth + filtering at the beginning of the animation and enable it at the conclusion. +*/ + +/*! + \qmlproperty bool QtQuick2::BorderImage::cache + + Specifies whether the image should be cached. The default value is + true. Setting \a cache to false is useful when dealing with large images, + to make sure that they aren't cached at the expense of small 'ui element' images. +*/ + +/*! + \qmlproperty bool QtQuick2::BorderImage::mirror + + This property holds whether the image should be horizontally inverted + (effectively displaying a mirrored image). + + The default value is false. +*/ + +/*! + \qmlproperty url QtQuick2::BorderImage::source + + This property holds the URL that refers to the source image. + + BorderImage can handle any image format supported by Qt, loaded from any + URL scheme supported by Qt. + + This property can also be used to refer to .sci files, which are + written in a QML-specific, text-based format that specifies the + borders, the image file and the tile rules for a given border image. + + The following .sci file sets the borders to 10 on each side for the + image \c picture.png: + + \code + border.left: 10 + border.top: 10 + border.bottom: 10 + border.right: 10 + source: "picture.png" + \endcode + + The URL may be absolute, or relative to the URL of the component. + + \sa QDeclarativeImageProvider +*/ + +/*! + \qmlproperty QSize QtQuick2::BorderImage::sourceSize + + This property holds the actual width and height of the loaded image. + + In BorderImage, this property is read-only. + + \sa Image::sourceSize +*/ void QSGBorderImage::setSource(const QUrl &url) { Q_D(QSGBorderImage); @@ -160,12 +366,55 @@ void QSGBorderImage::load() emit statusChanged(d->status); } +/*! + \qmlproperty int QtQuick2::BorderImage::border.left + \qmlproperty int QtQuick2::BorderImage::border.right + \qmlproperty int QtQuick2::BorderImage::border.top + \qmlproperty int QtQuick2::BorderImage::border.bottom + + The 4 border lines (2 horizontal and 2 vertical) break the image into 9 sections, + as shown below: + + \image declarative-scalegrid.png + + Each border line (left, right, top, and bottom) specifies an offset in pixels + from the respective edge of the source image. By default, each border line has + a value of 0. + + For example, the following definition sets the bottom line 10 pixels up from + the bottom of the image: + + \qml + BorderImage { + border.bottom: 10 + // ... + } + \endqml + + The border lines can also be specified using a + \l {BorderImage::source}{.sci file}. +*/ + QSGScaleGrid *QSGBorderImage::border() { Q_D(QSGBorderImage); return d->getScaleGrid(); } +/*! + \qmlproperty enumeration QtQuick2::BorderImage::horizontalTileMode + \qmlproperty enumeration QtQuick2::BorderImage::verticalTileMode + + This property describes how to repeat or stretch the middle parts of the border image. + + \list + \o BorderImage.Stretch - Scales the image to fit to the available area. + \o BorderImage.Repeat - Tile the image until there is no more space. May crop the last image. + \o BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped. + \endlist + + The default tile mode for each property is BorderImage.Stretch. +*/ QSGBorderImage::TileMode QSGBorderImage::horizontalTileMode() const { Q_D(const QSGBorderImage); diff --git a/src/declarative/items/qsgcanvas.cpp b/src/declarative/items/qsgcanvas.cpp index 33d661e089..0d4001a393 100644 --- a/src/declarative/items/qsgcanvas.cpp +++ b/src/declarative/items/qsgcanvas.cpp @@ -332,7 +332,6 @@ QSGCanvasPrivate::QSGCanvasPrivate() : rootItem(0) , activeFocusItem(0) , mouseGrabberItem(0) - , hoverItem(0) , dirtyItemList(0) , context(0) , animationRunning(false) @@ -785,7 +784,6 @@ QSGCanvas::~QSGCanvas() // manually cleanup for the root item (item destructor only handles these when an item is parented) QSGItemPrivate *rootItemPrivate = QSGItemPrivate::get(d->rootItem); rootItemPrivate->removeFromDirtyList(); - rootItemPrivate->canvas = 0; delete d->rootItem; d->rootItem = 0; d->cleanupNodes(); @@ -813,17 +811,19 @@ QSGItem *QSGCanvas::mouseGrabberItem() const } -void QSGCanvasPrivate::clearHover() +bool QSGCanvasPrivate::clearHover() { Q_Q(QSGCanvas); - if (!hoverItem) - return; + if (hoverItems.isEmpty()) + return false; QPointF pos = QCursor::pos(); // ### refactor: q->mapFromGlobal(QCursor::pos()); - QSGItem *item = hoverItem; - hoverItem = 0; - sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QApplication::keyboardModifiers(), true); + bool accepted = false; + foreach (QSGItem* item, hoverItems) + accepted = sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QApplication::keyboardModifiers(), true) || accepted; + hoverItems.clear(); + return accepted; } @@ -874,6 +874,9 @@ bool QSGCanvas::event(QEvent *e) case QSGEvent::SGDragDrop: d->deliverDragEvent(static_cast<QSGDragEvent *>(e)); break; + case QEvent::WindowDeactivate: + rootItem()->windowDeactivateEvent(); + break; default: break; } @@ -1063,11 +1066,7 @@ void QSGCanvas::mouseMoveEvent(QMouseEvent *event) bool delivered = d->deliverHoverEvent(d->rootItem, event->pos(), last, event->modifiers(), accepted); if (!delivered) { //take care of any exits - if (d->hoverItem) { - QSGItem *item = d->hoverItem; - d->hoverItem = 0; - accepted = d->sendHoverEvent(QEvent::HoverLeave, item, event->pos(), last, event->modifiers(), accepted); - } + accepted = d->clearHover(); } event->setAccepted(accepted); return; @@ -1105,20 +1104,43 @@ bool QSGCanvasPrivate::deliverHoverEvent(QSGItem *item, const QPointF &scenePos, if (itemPrivate->hoverEnabled) { QPointF p = item->mapFromScene(scenePos); if (QRectF(0, 0, item->width(), item->height()).contains(p)) { - if (hoverItem == item) { + if (!hoverItems.isEmpty() && hoverItems[0] == item) { //move accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, accepted); } else { - //exit from previous - if (hoverItem) { - QSGItem *item = hoverItem; - hoverItem = 0; - accepted = sendHoverEvent(QEvent::HoverLeave, item, scenePos, lastScenePos, modifiers, accepted); + QList<QSGItem*> parents; + QSGItem* parent = item; + parents << item; + while ((parent = parent->parentItem())) + parents << parent; + + //exit from previous (excepting ancestors) + while (!hoverItems.isEmpty() && !parents.contains(hoverItems[0])){ + sendHoverEvent(QEvent::HoverLeave, hoverItems[0], scenePos, lastScenePos, modifiers, accepted); + hoverItems.removeFirst(); } - //enter new item - hoverItem = item; - accepted = sendHoverEvent(QEvent::HoverEnter, item, scenePos, lastScenePos, modifiers, accepted); + if (!hoverItems.isEmpty() && hoverItems[0] == item){//Not entering a new Item + accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, accepted); + } else { + //enter any ancestors that also wish to be hovered and aren't + int startIdx = -1; + if (!hoverItems.isEmpty()) + startIdx = parents.indexOf(hoverItems[0]); + if (startIdx == -1) + startIdx = parents.count() - 1; + + for (int i = startIdx; i >= 0; i--) { + if (QSGItemPrivate::get(parents[i])->hoverEnabled) { + hoverItems.prepend(parents[i]); + sendHoverEvent(QEvent::HoverEnter, parents[i], scenePos, lastScenePos, modifiers, accepted); + } + } + + //enter new item + hoverItems.prepend(item); + accepted = sendHoverEvent(QEvent::HoverEnter, item, scenePos, lastScenePos, modifiers, accepted); + } } return true; } @@ -1143,7 +1165,7 @@ bool QSGCanvasPrivate::deliverWheelEvent(QSGItem *item, QWheelEvent *event) QList<QSGItem *> children = itemPrivate->paintOrderChildItems(); for (int ii = children.count() - 1; ii >= 0; --ii) { QSGItem *child = children.at(ii); - if (!child->isEnabled()) + if (!child->isVisible() || !child->isEnabled()) continue; if (deliverWheelEvent(child, event)) return true; @@ -1562,6 +1584,7 @@ void QSGCanvasPrivate::updateDirtyNode(QSGItem *item) if (item->clip()) { Q_ASSERT(itemPriv->clipNode == 0); itemPriv->clipNode = new QSGDefaultClipNode(item->boundingRect()); + itemPriv->clipNode->update(); if (child) parent->removeChildNode(child); @@ -1646,7 +1669,7 @@ void QSGCanvasPrivate::updateDirtyNode(QSGItem *item) } } - if ((dirty & QSGItemPrivate::Size || clipEffectivelyChanged) && itemPriv->clipNode) { + if ((dirty & QSGItemPrivate::Size) && itemPriv->clipNode) { itemPriv->clipNode->setRect(item->boundingRect()); itemPriv->clipNode->update(); } diff --git a/src/declarative/items/qsgcanvas_p.h b/src/declarative/items/qsgcanvas_p.h index e16d91685d..bf65e95812 100644 --- a/src/declarative/items/qsgcanvas_p.h +++ b/src/declarative/items/qsgcanvas_p.h @@ -119,11 +119,11 @@ public: bool deliverHoverEvent(QSGItem *, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, bool &accepted); bool sendHoverEvent(QEvent::Type, QSGItem *, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, bool accepted); - void clearHover(); + bool clearHover(); void deliverDragEvent(QSGDragEvent *); bool deliverDragEvent(QSGItem *item, QSGDragEvent *); - QDeclarativeGuard<QSGItem> hoverItem; + QList<QSGItem*> hoverItems; enum FocusOption { DontChangeFocusProperty = 0x01, }; diff --git a/src/declarative/items/qsgevents.cpp b/src/declarative/items/qsgevents.cpp index 0c5d8cac34..2173b4a21f 100644 --- a/src/declarative/items/qsgevents.cpp +++ b/src/declarative/items/qsgevents.cpp @@ -43,4 +43,197 @@ QT_BEGIN_NAMESPACE +/*! + \qmlclass KeyEvent QSGKeyEvent + \inqmlmodule QtQuick 2 + \ingroup qml-event-elements + + \brief The KeyEvent object provides information about a key event. + + For example, the following changes the Item's state property when the Enter + key is pressed: + \qml +Item { + focus: true + Keys.onPressed: { if (event.key == Qt.Key_Enter) state = 'ShowDetails'; } +} + \endqml +*/ + +/*! + \qmlproperty int QtQuick2::KeyEvent::key + + This property holds the code of the key that was pressed or released. + + See \l {Qt::Key}{Qt.Key} for the list of keyboard codes. These codes are + independent of the underlying window system. Note that this + function does not distinguish between capital and non-capital + letters, use the text() function (returning the Unicode text the + key generated) for this purpose. + + A value of either 0 or \l {Qt::Key_unknown}{Qt.Key_Unknown} means that the event is not + the result of a known key; for example, it may be the result of + a compose sequence, a keyboard macro, or due to key event + compression. +*/ + +/*! + \qmlproperty string QtQuick2::KeyEvent::text + + This property holds the Unicode text that the key generated. + The text returned can be an empty string in cases where modifier keys, + such as Shift, Control, Alt, and Meta, are being pressed or released. + In such cases \c key will contain a valid value +*/ + +/*! + \qmlproperty bool QtQuick2::KeyEvent::isAutoRepeat + + This property holds whether this event comes from an auto-repeating key. +*/ + +/*! + \qmlproperty int QtQuick2::KeyEvent::count + + This property holds the number of keys involved in this event. If \l KeyEvent::text + is not empty, this is simply the length of the string. +*/ + +/*! + \qmlproperty bool QtQuick2::KeyEvent::accepted + + Setting \a accepted to true prevents the key event from being + propagated to the item's parent. + + Generally, if the item acts on the key event then it should be accepted + so that ancestor items do not also respond to the same event. +*/ + +/*! + \qmlproperty int QtQuick2::KeyEvent::modifiers + + This property holds the keyboard modifier flags that existed immediately + before the event occurred. + + It contains a bitwise combination of: + \list + \o Qt.NoModifier - No modifier key is pressed. + \o Qt.ShiftModifier - A Shift key on the keyboard is pressed. + \o Qt.ControlModifier - A Ctrl key on the keyboard is pressed. + \o Qt.AltModifier - An Alt key on the keyboard is pressed. + \o Qt.MetaModifier - A Meta key on the keyboard is pressed. + \o Qt.KeypadModifier - A keypad button is pressed. + \endlist + + For example, to react to a Shift key + Enter key combination: + \qml + Item { + focus: true + Keys.onPressed: { + if ((event.key == Qt.Key_Enter) && (event.modifiers & Qt.ShiftModifier)) + doSomething(); + } + } + \endqml +*/ + + +/*! + \qmlclass MouseEvent QSGMouseEvent + \inqmlmodule QtQuick 2 + \ingroup qml-event-elements + + \brief The MouseEvent object provides information about a mouse event. + + The position of the mouse can be found via the \l x and \l y properties. + The button that caused the event is available via the \l button property. + + \sa MouseArea +*/ + +/*! + \internal + \class QSGMouseEvent +*/ + +/*! + \qmlproperty int QtQuick2::MouseEvent::x + \qmlproperty int QtQuick2::MouseEvent::y + + These properties hold the coordinates of the position supplied by the mouse event. +*/ + + +/*! + \qmlproperty bool QtQuick2::MouseEvent::accepted + + Setting \a accepted to true prevents the mouse event from being + propagated to items below this item. + + Generally, if the item acts on the mouse event then it should be accepted + so that items lower in the stacking order do not also respond to the same event. +*/ + +/*! + \qmlproperty enumeration QtQuick2::MouseEvent::button + + This property holds the button that caused the event. It can be one of: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MiddleButton + \endlist +*/ + +/*! + \qmlproperty bool QtQuick2::MouseEvent::wasHeld + + This property is true if the mouse button has been held pressed longer the + threshold (800ms). +*/ + +/*! + \qmlproperty int QtQuick2::MouseEvent::buttons + + This property holds the mouse buttons pressed when the event was generated. + For mouse move events, this is all buttons that are pressed down. For mouse + press and double click events this includes the button that caused the event. + For mouse release events this excludes the button that caused the event. + + It contains a bitwise combination of: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MiddleButton + \endlist +*/ + +/*! + \qmlproperty int QtQuick2::MouseEvent::modifiers + + This property holds the keyboard modifier flags that existed immediately + before the event occurred. + + It contains a bitwise combination of: + \list + \o Qt.NoModifier - No modifier key is pressed. + \o Qt.ShiftModifier - A Shift key on the keyboard is pressed. + \o Qt.ControlModifier - A Ctrl key on the keyboard is pressed. + \o Qt.AltModifier - An Alt key on the keyboard is pressed. + \o Qt.MetaModifier - A Meta key on the keyboard is pressed. + \o Qt.KeypadModifier - A keypad button is pressed. + \endlist + + For example, to react to a Shift key + Left mouse button click: + \qml + MouseArea { + onClicked: { + if ((mouse.button == Qt.LeftButton) && (mouse.modifiers & Qt.ShiftModifier)) + doSomething(); + } + } + \endqml +*/ + + QT_END_NAMESPACE diff --git a/src/declarative/items/qsgflickable.cpp b/src/declarative/items/qsgflickable.cpp index 1d8f107abc..e63698eca7 100644 --- a/src/declarative/items/qsgflickable.cpp +++ b/src/declarative/items/qsgflickable.cpp @@ -456,6 +456,107 @@ is finished. touch/mouse button is released then a flick will start. */ +/*! + \qmlclass Flickable QSGFlickable + \inqmlmodule QtQuick 2 + \ingroup qml-basic-interaction-elements + + \brief The Flickable item provides a surface that can be "flicked". + \inherits Item + + The Flickable item places its children on a surface that can be dragged + and flicked, causing the view onto the child items to scroll. This + behavior forms the basis of Items that are designed to show large numbers + of child items, such as \l ListView and \l GridView. + + In traditional user interfaces, views can be scrolled using standard + controls, such as scroll bars and arrow buttons. In some situations, it + is also possible to drag the view directly by pressing and holding a + mouse button while moving the cursor. In touch-based user interfaces, + this dragging action is often complemented with a flicking action, where + scrolling continues after the user has stopped touching the view. + + Flickable does not automatically clip its contents. If it is not used as + a full-screen item, you should consider setting the \l{Item::}{clip} property + to true. + + \section1 Example Usage + + \div {class="float-right"} + \inlineimage flickable.gif + \enddiv + + The following example shows a small view onto a large image in which the + user can drag or flick the image in order to view different parts of it. + + \snippet doc/src/snippets/declarative/flickable.qml document + + \clearfloat + + Items declared as children of a Flickable are automatically parented to the + Flickable's \l contentItem. This should be taken into account when + operating on the children of the Flickable; it is usually the children of + \c contentItem that are relevant. For example, the bound of Items added + to the Flickable will be available by \c contentItem.childrenRect + + \section1 Limitations + + \note Due to an implementation detail, items placed inside a Flickable cannot anchor to it by + \c id. Use \c parent instead. +*/ + +/*! + \qmlsignal QtQuick2::Flickable::onMovementStarted() + + This handler is called when the view begins moving due to user + interaction. +*/ + +/*! + \qmlsignal QtQuick2::Flickable::onMovementEnded() + + This handler is called when the view stops moving due to user + interaction. If a flick was generated, this handler will + be triggered once the flick stops. If a flick was not + generated, the handler will be triggered when the + user stops dragging - i.e. a mouse or touch release. +*/ + +/*! + \qmlsignal QtQuick2::Flickable::onFlickStarted() + + This handler is called when the view is flicked. A flick + starts from the point that the mouse or touch is released, + while still in motion. +*/ + +/*! + \qmlsignal QtQuick2::Flickable::onFlickEnded() + + This handler is called when the view stops moving due to a flick. +*/ + +/*! + \qmlproperty real QtQuick2::Flickable::visibleArea.xPosition + \qmlproperty real QtQuick2::Flickable::visibleArea.widthRatio + \qmlproperty real QtQuick2::Flickable::visibleArea.yPosition + \qmlproperty real QtQuick2::Flickable::visibleArea.heightRatio + + These properties describe the position and size of the currently viewed area. + The size is defined as the percentage of the full view currently visible, + scaled to 0.0 - 1.0. The page position is usually in the range 0.0 (beginning) to + 1.0 minus size ratio (end), i.e. \c yPosition is in the range 0.0 to 1.0-\c heightRatio. + However, it is possible for the contents to be dragged outside of the normal + range, resulting in the page positions also being outside the normal range. + + These properties are typically used to draw a scrollbar. For example: + + \snippet doc/src/snippets/declarative/flickableScrollbar.qml 0 + \dots 8 + \snippet doc/src/snippets/declarative/flickableScrollbar.qml 1 + + \sa {declarative/ui-components/scrollbar}{scrollbar example} +*/ QSGFlickable::QSGFlickable(QSGItem *parent) : QSGItem(*(new QSGFlickablePrivate), parent) { @@ -474,6 +575,14 @@ QSGFlickable::~QSGFlickable() { } +/*! + \qmlproperty real QtQuick2::Flickable::contentX + \qmlproperty real QtQuick2::Flickable::contentY + + These properties hold the surface coordinate currently at the top-left + corner of the Flickable. For example, if you flick an image up 100 pixels, + \c contentY will be 100. +*/ qreal QSGFlickable::contentX() const { Q_D(const QSGFlickable); @@ -510,6 +619,19 @@ void QSGFlickable::setContentY(qreal pos) } } +/*! + \qmlproperty bool QtQuick2::Flickable::interactive + + This property describes whether the user can interact with the Flickable. + A user cannot drag or flick a Flickable that is not interactive. + + By default, this property is true. + + This property is useful for temporarily disabling flicking. This allows + special interaction with Flickable's children; for example, you might want + to freeze a flickable map while scrolling through a pop-up dialog that + is a child of the Flickable. +*/ bool QSGFlickable::isInteractive() const { Q_D(const QSGFlickable); @@ -535,6 +657,14 @@ void QSGFlickable::setInteractive(bool interactive) } } +/*! + \qmlproperty real QtQuick2::Flickable::horizontalVelocity + \qmlproperty real QtQuick2::Flickable::verticalVelocity + + The instantaneous velocity of movement along the x and y axes, in pixels/sec. + + The reported velocity is smoothed to avoid erratic output. +*/ qreal QSGFlickable::horizontalVelocity() const { Q_D(const QSGFlickable); @@ -547,6 +677,15 @@ qreal QSGFlickable::verticalVelocity() const return d->vData.smoothVelocity.value(); } +/*! + \qmlproperty bool QtQuick2::Flickable::atXBeginning + \qmlproperty bool QtQuick2::Flickable::atXEnd + \qmlproperty bool QtQuick2::Flickable::atYBeginning + \qmlproperty bool QtQuick2::Flickable::atYEnd + + These properties are true if the flickable view is positioned at the beginning, + or end respecively. +*/ bool QSGFlickable::isAtXEnd() const { Q_D(const QSGFlickable); @@ -576,6 +715,24 @@ void QSGFlickable::ticked() viewportMoved(); } +/*! + \qmlproperty Item QtQuick2::Flickable::contentItem + + The internal item that contains the Items to be moved in the Flickable. + + Items declared as children of a Flickable are automatically parented to the Flickable's contentItem. + + Items created dynamically need to be explicitly parented to the \e contentItem: + \code + Flickable { + id: myFlickable + function addItem(file) { + var component = Qt.createComponent(file) + component.createObject(myFlickable.contentItem); + } + } + \endcode +*/ QSGItem *QSGFlickable::contentItem() { Q_D(QSGFlickable); @@ -590,6 +747,21 @@ QSGFlickableVisibleArea *QSGFlickable::visibleArea() return d->visibleArea; } +/*! + \qmlproperty enumeration QtQuick2::Flickable::flickableDirection + + This property determines which directions the view can be flicked. + + \list + \o Flickable.AutoFlickDirection (default) - allows flicking vertically if the + \e contentHeight is not equal to the \e height of the Flickable. + Allows flicking horizontally if the \e contentWidth is not equal + to the \e width of the Flickable. + \o Flickable.HorizontalFlick - allows flicking horizontally. + \o Flickable.VerticalFlick - allows flicking vertically. + \o Flickable.HorizontalAndVerticalFlick - allows flicking in both directions. + \endlist +*/ QSGFlickable::FlickableDirection QSGFlickable::flickableDirection() const { Q_D(const QSGFlickable); @@ -1118,6 +1290,27 @@ QDeclarativeListProperty<QSGItem> QSGFlickable::flickableChildren() return QSGItemPrivate::get(d->contentItem)->children(); } +/*! + \qmlproperty enumeration QtQuick2::Flickable::boundsBehavior + This property holds whether the surface may be dragged + beyond the Fickable's boundaries, or overshoot the + Flickable's boundaries when flicked. + + This enables the feeling that the edges of the view are soft, + rather than a hard physical boundary. + + The \c boundsBehavior can be one of: + + \list + \o Flickable.StopAtBounds - the contents can not be dragged beyond the boundary + of the flickable, and flicks will not overshoot. + \o Flickable.DragOverBounds - the contents can be dragged beyond the boundary + of the Flickable, but flicks will not overshoot. + \o Flickable.DragAndOvershootBounds (default) - the contents can be dragged + beyond the boundary of the Flickable, and can overshoot the + boundary when flicked. + \endlist +*/ QSGFlickable::BoundsBehavior QSGFlickable::boundsBehavior() const { Q_D(const QSGFlickable); @@ -1133,6 +1326,23 @@ void QSGFlickable::setBoundsBehavior(BoundsBehavior b) emit boundsBehaviorChanged(); } +/*! + \qmlproperty real QtQuick2::Flickable::contentWidth + \qmlproperty real QtQuick2::Flickable::contentHeight + + The dimensions of the content (the surface controlled by Flickable). + This should typically be set to the combined size of the items placed in the + Flickable. + + The following snippet shows how these properties are used to display + an image that is larger than the Flickable item itself: + + \snippet doc/src/snippets/declarative/flickable.qml document + + In some cases, the the content dimensions can be automatically set + using the \l {Item::childrenRect.width}{childrenRect.width} + and \l {Item::childrenRect.height}{childrenRect.height} properties. +*/ qreal QSGFlickable::contentWidth() const { Q_D(const QSGFlickable); @@ -1189,6 +1399,19 @@ void QSGFlickable::setContentHeight(qreal h) d->updateBeginningEnd(); } +/*! + \qmlmethod QtQuick2::Flickable::resizeContent(real width, real height, QPointF center) + \preliminary + + Resizes the content to \a width x \a height about \a center. + + This does not scale the contents of the Flickable - it only resizes the \l contentWidth + and \l contentHeight. + + Resizing the content may result in the content being positioned outside + the bounds of the Flickable. Calling \l returnToBounds() will + move the content back within legal bounds. +*/ void QSGFlickable::resizeContent(qreal w, qreal h, QPointF center) { Q_D(QSGFlickable); @@ -1215,6 +1438,15 @@ void QSGFlickable::resizeContent(qreal w, qreal h, QPointF center) d->updateBeginningEnd(); } +/*! + \qmlmethod QtQuick2::Flickable::returnToBounds() + \preliminary + + Ensures the content is within legal bounds. + + This may be called to ensure that the content is within legal bounds + after manually positioning the content. +*/ void QSGFlickable::returnToBounds() { Q_D(QSGFlickable); @@ -1332,6 +1564,9 @@ bool QSGFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) } return stealThisEvent || d->delayedPressEvent || disabledItem; + } else if (d->lastPosTime.isValid()) { + d->lastPosTime.invalidate(); + returnToBounds(); } if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) { d->lastPosTime.invalidate(); @@ -1360,6 +1595,12 @@ bool QSGFlickable::childMouseEventFilter(QSGItem *i, QEvent *e) return QSGItem::childMouseEventFilter(i, e); } +/*! + \qmlproperty real QtQuick2::Flickable::maximumFlickVelocity + This property holds the maximum velocity that the user can flick the view in pixels/second. + + The default value is platform dependent. +*/ qreal QSGFlickable::maximumFlickVelocity() const { Q_D(const QSGFlickable); @@ -1375,6 +1616,12 @@ void QSGFlickable::setMaximumFlickVelocity(qreal v) emit maximumFlickVelocityChanged(); } +/*! + \qmlproperty real QtQuick2::Flickable::flickDeceleration + This property holds the rate at which a flick will decelerate. + + The default value is platform dependent. +*/ qreal QSGFlickable::flickDeceleration() const { Q_D(const QSGFlickable); @@ -1396,6 +1643,14 @@ bool QSGFlickable::isFlicking() const return d->flickingHorizontally || d->flickingVertically; } +/*! + \qmlproperty bool QtQuick2::Flickable::flicking + \qmlproperty bool QtQuick2::Flickable::flickingHorizontally + \qmlproperty bool QtQuick2::Flickable::flickingVertically + + These properties describe whether the view is currently moving horizontally, + vertically or in either direction, due to the user flicking the view. +*/ bool QSGFlickable::isFlickingHorizontally() const { Q_D(const QSGFlickable); @@ -1470,6 +1725,20 @@ void QSGFlickablePrivate::draggingEnding() } } +/*! + \qmlproperty int QtQuick2::Flickable::pressDelay + + This property holds the time to delay (ms) delivering a press to + children of the Flickable. This can be useful where reacting + to a press before a flicking action has undesirable effects. + + If the flickable is dragged/flicked before the delay times out + the press event will not be delivered. If the button is released + within the timeout, both the press and release will be delivered. + + Note that for nested Flickables with pressDelay set, the pressDelay of + inner Flickables is overridden by the outermost Flickable. +*/ int QSGFlickable::pressDelay() const { Q_D(const QSGFlickable); @@ -1485,6 +1754,15 @@ void QSGFlickable::setPressDelay(int delay) emit pressDelayChanged(); } +/*! + \qmlproperty bool QtQuick2::Flickable::moving + \qmlproperty bool QtQuick2::Flickable::movingHorizontally + \qmlproperty bool QtQuick2::Flickable::movingVertically + + These properties describe whether the view is currently moving horizontally, + vertically or in either direction, due to the user either dragging or + flicking the view. +*/ bool QSGFlickable::isMoving() const { diff --git a/src/declarative/items/qsgflipable.cpp b/src/declarative/items/qsgflipable.cpp index faceb29f73..ac68b3171c 100644 --- a/src/declarative/items/qsgflipable.cpp +++ b/src/declarative/items/qsgflipable.cpp @@ -87,6 +87,47 @@ public: bool wantBackYFlipped; }; +/*! + \qmlclass Flipable QSGFlipable + \inqmlmodule QtQuick 2 + \ingroup qml-basic-interaction-elements + \brief The Flipable item provides a surface that can be flipped. + \inherits Item + + Flipable is an item that can be visibly "flipped" between its front and + back sides, like a card. It is used together with \l Rotation, \l State + and \l Transition elements to produce a flipping effect. + + The \l front and \l back properties are used to hold the items that are + shown respectively on the front and back sides of the flipable item. + + \section1 Example Usage + + The following example shows a Flipable item that flips whenever it is + clicked, rotating about the y-axis. + + This flipable item has a \c flipped boolean property that is toggled + whenever the MouseArea within the flipable is clicked. When + \c flipped is true, the item changes to the "back" state; in this + state, the \c angle of the \l Rotation item is changed to 180 + degrees to produce the flipping effect. When \c flipped is false, the + item reverts to the default state, in which the \c angle value is 0. + + \snippet doc/src/snippets/declarative/flipable/flipable.qml 0 + + \image flipable.gif + + The \l Transition creates the animation that changes the angle over + four seconds. When the item changes between its "back" and + default states, the NumberAnimation animates the angle between + its old and new values. + + See \l {QML States} for details on state changes and the default + state, and \l {QML Animation and Transitions} for more information on how + animations work within transitions. + + \sa {declarative/ui-components/flipable}{Flipable example} +*/ QSGFlipable::QSGFlipable(QSGItem *parent) : QSGItem(*(new QSGFlipablePrivate), parent) { @@ -96,6 +137,13 @@ QSGFlipable::~QSGFlipable() { } +/*! + \qmlproperty Item QtQuick2::Flipable::front + \qmlproperty Item QtQuick2::Flipable::back + + The front and back sides of the flipable. +*/ + QSGItem *QSGFlipable::front() { Q_D(const QSGFlipable); @@ -153,6 +201,12 @@ void QSGFlipable::retransformBack() d->setBackTransform(); } +/*! + \qmlproperty enumeration QtQuick2::Flipable::side + + The side of the Flipable currently visible. Possible values are \c + Flipable.Front and \c Flipable.Back. +*/ QSGFlipable::Side QSGFlipable::side() const { Q_D(const QSGFlipable); diff --git a/src/declarative/items/qsgfocusscope.cpp b/src/declarative/items/qsgfocusscope.cpp index 156869120d..2018d5ce7d 100644 --- a/src/declarative/items/qsgfocusscope.cpp +++ b/src/declarative/items/qsgfocusscope.cpp @@ -43,7 +43,21 @@ QT_BEGIN_NAMESPACE -QSGFocusScope::QSGFocusScope(QSGItem *parent) +/*! + \qmlclass FocusScope QSGFocusScope + \inqmlmodule QtQuick 2 + \ingroup qml-basic-interaction-elements + + \brief The FocusScope object explicitly creates a focus scope. + \inherits Item + + Focus scopes assist in keyboard focus handling when building reusable QML + components. All the details are covered in the + \l {qmlfocus}{keyboard focus documentation}. + + \sa {declarative/keyinteraction/focus}{Keyboard focus example} +*/ +QSGFocusScope::QSGFocusScope(QSGItem *parent) : QSGItem(parent) { setFlag(ItemIsFocusScope); diff --git a/src/declarative/items/qsggridview.cpp b/src/declarative/items/qsggridview.cpp index 26347c1c93..bc4954a168 100644 --- a/src/declarative/items/qsggridview.cpp +++ b/src/declarative/items/qsggridview.cpp @@ -176,6 +176,8 @@ public: virtual qreal headerSize() const; virtual qreal footerSize() const; + virtual bool showHeaderForIndex(int index) const; + virtual bool showFooterForIndex(int index) const; virtual void updateHeader(); virtual void updateFooter(); @@ -602,7 +604,8 @@ void QSGGridViewPrivate::updateHighlight() { if ((!currentItem && highlight) || (currentItem && !highlight)) createHighlight(); - if (currentItem && autoHighlight && highlight && !movingHorizontally && !movingVertically) { + bool strictHighlight = haveHighlightRange && highlightRange == QSGGridView::StrictlyEnforceRange; + if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) { // auto-update highlight highlightXAnimator->to = currentItem->item->x(); highlightYAnimator->to = currentItem->item->y(); @@ -637,6 +640,16 @@ qreal QSGGridViewPrivate::footerSize() const return flow == QSGGridView::LeftToRight? footer->item->height() : footer->item->width(); } +bool QSGGridViewPrivate::showHeaderForIndex(int index) const +{ + return index / columns == 0; +} + +bool QSGGridViewPrivate::showFooterForIndex(int index) const +{ + return index / columns == (model->count()-1) / columns; +} + void QSGGridViewPrivate::updateFooter() { Q_Q(QSGGridView); @@ -955,6 +968,80 @@ void QSGGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, //---------------------------------------------------------------------------- +/*! + \qmlclass GridView QSGGridView + \inqmlmodule QtQuick 2 + \ingroup qml-view-elements + + \inherits Flickable + \brief The GridView item provides a grid view of items provided by a model. + + A GridView displays data from models created from built-in QML elements like ListModel + and XmlListModel, or custom model classes defined in C++ that inherit from + QAbstractListModel. + + A GridView has a \l model, which defines the data to be displayed, and + a \l delegate, which defines how the data should be displayed. Items in a + GridView are laid out horizontally or vertically. Grid views are inherently flickable + as GridView inherits from \l Flickable. + + \section1 Example Usage + + The following example shows the definition of a simple list model defined + in a file called \c ContactModel.qml: + + \snippet doc/src/snippets/declarative/gridview/ContactModel.qml 0 + + \div {class="float-right"} + \inlineimage gridview-simple.png + \enddiv + + This model can be referenced as \c ContactModel in other QML files. See \l{QML Modules} + for more information about creating reusable components like this. + + Another component can display this model data in a GridView, as in the following + example, which creates a \c ContactModel component for its model, and a \l Column element + (containing \l Image and \l Text elements) for its delegate. + + \clearfloat + \snippet doc/src/snippets/declarative/gridview/gridview.qml import + \codeline + \snippet doc/src/snippets/declarative/gridview/gridview.qml classdocs simple + + \div {class="float-right"} + \inlineimage gridview-highlight.png + \enddiv + + The view will create a new delegate for each item in the model. Note that the delegate + is able to access the model's \c name and \c portrait data directly. + + An improved grid view is shown below. The delegate is visually improved and is moved + into a separate \c contactDelegate component. + + \clearfloat + \snippet doc/src/snippets/declarative/gridview/gridview.qml classdocs advanced + + The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property, + and \c focus is set to \c true to enable keyboard navigation for the grid view. + The grid view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details). + + Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. + + GridView attaches a number of properties to the root item of the delegate, for example + \c {GridView.isCurrentItem}. In the following example, the root delegate item can access + this attached property directly as \c GridView.isCurrentItem, while the child + \c contactInfo object must refer to this property as \c wrapper.GridView.isCurrentItem. + + \snippet doc/src/snippets/declarative/gridview/gridview.qml isCurrentItem + + \note Views do not set the \l{Item::}{clip} property automatically. + If the view is not clipped by another item or the screen, it will be necessary + to set this property to true in order to clip the items that are partially or + fully outside the view. + + \sa {declarative/modelviews/gridview}{GridView example} +*/ QSGGridView::QSGGridView(QSGItem *parent) : QSGItemView(*(new QSGGridViewPrivate), parent) @@ -977,6 +1064,242 @@ void QSGGridView::setHighlightFollowsCurrentItem(bool autoHighlight) } } +/*! + \qmlattachedproperty bool QtQuick2::GridView::isCurrentItem + This attached property is true if this delegate is the current item; otherwise false. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty GridView QtQuick2::GridView::view + This attached property holds the view that manages this delegate instance. + + It is attached to each instance of the delegate. + + \snippet doc/src/snippets/declarative/gridview/gridview.qml isCurrentItem +*/ + +/*! + \qmlattachedproperty bool QtQuick2::GridView::delayRemove + This attached property holds whether the delegate may be destroyed. + + It is attached to each instance of the delegate. + + It is sometimes necessary to delay the destruction of an item + until an animation completes. + + The example below ensures that the animation completes before + the item is removed from the grid. + + \snippet doc/src/snippets/declarative/gridview/gridview.qml delayRemove +*/ + +/*! + \qmlattachedsignal QtQuick2::GridView::onAdd() + This attached handler is called immediately after an item is added to the view. +*/ + +/*! + \qmlattachedsignal QtQuick2::GridView::onRemove() + This attached handler is called immediately before an item is removed from the view. +*/ + + +/*! + \qmlproperty model QtQuick2::GridView::model + This property holds the model providing data for the grid. + + The model provides the set of data that is used to create the items + in the view. Models can be created directly in QML using \l ListModel, \l XmlListModel + or \l VisualItemModel, or provided by C++ model classes. If a C++ model class is + used, it must be a subclass of \l QAbstractItemModel or a simple list. + + \sa {qmlmodels}{Data Models} +*/ + +/*! + \qmlproperty Component QtQuick2::GridView::delegate + + The delegate provides a template defining each item instantiated by the view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + + The number of elements in the delegate has a direct effect on the + flicking performance of the view. If at all possible, place functionality + that is not needed for the normal display of the delegate in a \l Loader which + can load additional elements when needed. + + The GridView will layout the items based on the size of the root item + in the delegate. + + \note Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. +*/ + +/*! + \qmlproperty int QtQuick2::GridView::currentIndex + \qmlproperty Item QtQuick2::GridView::currentItem + + The \c currentIndex property holds the index of the current item, and + \c currentItem holds the current item. Setting the currentIndex to -1 + will clear the highlight and set currentItem to null. + + If highlightFollowsCurrentItem is \c true, setting either of these + properties will smoothly scroll the GridView so that the current + item becomes visible. + + Note that the position of the current item + may only be approximate until it becomes visible in the view. +*/ + + +/*! + \qmlproperty Item QtQuick2::GridView::highlightItem + + This holds the highlight item created from the \l highlight component. + + The highlightItem is managed by the view unless + \l highlightFollowsCurrentItem is set to false. + + \sa highlight, highlightFollowsCurrentItem +*/ + + +/*! + \qmlproperty int QtQuick2::GridView::count + This property holds the number of items in the view. +*/ + + +/*! + \qmlproperty Component QtQuick2::GridView::highlight + This property holds the component to use as the highlight. + + An instance of the highlight component is created for each view. + The geometry of the resulting component instance will be managed by the view + so as to stay with the current item, unless the highlightFollowsCurrentItem property is false. + + \sa highlightItem, highlightFollowsCurrentItem +*/ + +/*! + \qmlproperty bool QtQuick2::GridView::highlightFollowsCurrentItem + This property sets whether the highlight is managed by the view. + + If this property is true (the default value), the highlight is moved smoothly + to follow the current item. Otherwise, the + highlight is not moved by the view, and any movement must be implemented + by the highlight. + + Here is a highlight with its motion defined by a \l {SpringAnimation} item: + + \snippet doc/src/snippets/declarative/gridview/gridview.qml highlightFollowsCurrentItem +*/ + + +/*! + \qmlproperty int QtQuick2::GridView::highlightMoveDuration + This property holds the move animation duration of the highlight delegate. + + highlightFollowsCurrentItem must be true for this property + to have effect. + + The default value for the duration is 150ms. + + \sa highlightFollowsCurrentItem +*/ + +/*! + \qmlproperty real QtQuick2::GridView::preferredHighlightBegin + \qmlproperty real QtQuick2::GridView::preferredHighlightEnd + \qmlproperty enumeration QtQuick2::GridView::highlightRangeMode + + These properties define the preferred range of the highlight (for the current item) + within the view. The \c preferredHighlightBegin value must be less than the + \c preferredHighlightEnd value. + + These properties affect the position of the current item when the view is scrolled. + For example, if the currently selected item should stay in the middle of the + view when it is scrolled, set the \c preferredHighlightBegin and + \c preferredHighlightEnd values to the top and bottom coordinates of where the middle + item would be. If the \c currentItem is changed programmatically, the view will + automatically scroll so that the current item is in the middle of the view. + Furthermore, the behavior of the current item index will occur whether or not a + highlight exists. + + Valid values for \c highlightRangeMode are: + + \list + \o GridView.ApplyRange - the view attempts to maintain the highlight within the range. + However, the highlight can move outside of the range at the ends of the view or due + to mouse interaction. + \o GridView.StrictlyEnforceRange - the highlight never moves outside of the range. + The current item changes if a keyboard or mouse action would cause the highlight to move + outside of the range. + \o GridView.NoHighlightRange - this is the default value. + \endlist +*/ + + +/*! + \qmlproperty enumeration QtQuick2::GridView::layoutDirection + This property holds the layout direction of the grid. + + Possible values: + + \list + \o Qt.LeftToRight (default) - Items will be laid out starting in the top, left corner. The flow is + dependent on the \l GridView::flow property. + \o Qt.RightToLeft - Items will be laid out starting in the top, right corner. The flow is dependent + on the \l GridView::flow property. + \endlist + + \bold Note: If GridView::flow is set to GridView.LeftToRight, this is not to be confused if + GridView::layoutDirection is set to Qt.RightToLeft. The GridView.LeftToRight flow value simply + indicates that the flow is horizontal. +*/ + + +/*! + \qmlproperty enumeration QtQuick2::GridView::effectiveLayoutDirection + This property holds the effective layout direction of the grid. + + When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, + the visual layout direction of the grid will be mirrored. However, the + property \l {GridView::layoutDirection}{layoutDirection} will remain unchanged. + + \sa GridView::layoutDirection, {LayoutMirroring}{LayoutMirroring} +*/ +/*! + \qmlproperty bool QtQuick2::GridView::keyNavigationWraps + This property holds whether the grid wraps key navigation + + If this is true, key navigation that would move the current item selection + past one end of the view instead wraps around and moves the selection to + the other end of the view. + + By default, key navigation is not wrapped. +*/ +/*! + \qmlproperty int QtQuick2::GridView::cacheBuffer + This property determines whether delegates are retained outside the + visible area of the view. + + If non-zero the view will keep as many delegates + instantiated as will fit within the buffer specified. For example, + if in a vertical view the delegate is 20 pixels high and \c cacheBuffer is + set to 40, then up to 2 delegates above and 2 delegates below the visible + area may be retained. + + Note that cacheBuffer is not a pixel buffer - it only maintains additional + instantiated delegates. + + Setting this value can make scrolling the list smoother at the expense + of additional memory usage. It is not a substitute for creating efficient + delegates; the fewer elements in a delegate, the faster a view may be + scrolled. +*/ void QSGGridView::setHighlightMoveDuration(int duration) { Q_D(QSGGridView); @@ -989,6 +1312,17 @@ void QSGGridView::setHighlightMoveDuration(int duration) } } +/*! + \qmlproperty enumeration QtQuick2::GridView::flow + This property holds the flow of the grid. + + Possible values: + + \list + \o GridView.LeftToRight (default) - Items are laid out from left to right, and the view scrolls vertically + \o GridView.TopToBottom - Items are laid out from top to bottom, and the view scrolls horizontally + \endlist +*/ QSGGridView::Flow QSGGridView::flow() const { Q_D(const QSGGridView); @@ -1015,6 +1349,14 @@ void QSGGridView::setFlow(Flow flow) } +/*! + \qmlproperty int QtQuick2::GridView::cellWidth + \qmlproperty int QtQuick2::GridView::cellHeight + + These properties holds the width and height of each cell in the grid. + + The default cell size is 100x100. +*/ int QSGGridView::cellWidth() const { Q_D(const QSGGridView); @@ -1048,7 +1390,22 @@ void QSGGridView::setCellHeight(int cellHeight) d->layout(); } } - +/*! + \qmlproperty enumeration QtQuick2::GridView::snapMode + + This property determines how the view scrolling will settle following a drag or flick. + The possible values are: + + \list + \o GridView.NoSnap (default) - the view stops anywhere within the visible area. + \o GridView.SnapToRow - the view settles with a row (or column for \c GridView.TopToBottom flow) + aligned with the start of the view. + \o GridView.SnapOneRow - the view will settle no more than one row (or column for \c GridView.TopToBottom flow) + away from the first visible row at the time the mouse button is released. + This mode is particularly useful for moving one page at a time. + \endlist + +*/ QSGGridView::SnapMode QSGGridView::snapMode() const { Q_D(const QSGGridView); @@ -1065,6 +1422,24 @@ void QSGGridView::setSnapMode(SnapMode mode) } +/*! + \qmlproperty Component QtQuick2::GridView::footer + This property holds the component to use as the footer. + + An instance of the footer component is created for each view. The + footer is positioned at the end of the view, after any items. + + \sa header +*/ +/*! + \qmlproperty Component QtQuick2::GridView::header + This property holds the component to use as the header. + + An instance of the header component is created for each view. The + header is positioned at the beginning of the view, before any items. + + \sa footer +*/ void QSGGridView::viewportMoved() { Q_D(QSGGridView); @@ -1115,7 +1490,13 @@ void QSGGridView::viewportMoved() if (pos < viewPos + highlightStart) pos = viewPos + highlightStart; - static_cast<FxGridItemSG*>(d->highlight)->setPosition(static_cast<FxGridItemSG*>(d->highlight)->colPos(), qRound(pos)); + if (pos != d->highlight->position()) { + d->highlightXAnimator->stop(); + d->highlightYAnimator->stop(); + static_cast<FxGridItemSG*>(d->highlight)->setPosition(static_cast<FxGridItemSG*>(d->highlight)->colPos(), pos); + } else { + d->updateHighlight(); + } // update current index int idx = d->snapIndex(); @@ -1164,6 +1545,16 @@ void QSGGridView::keyPressEvent(QKeyEvent *event) event->ignore(); QSGItemView::keyPressEvent(event); } +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexUp() + + Move the currentIndex up one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. This method has no effect if the \l count is zero. + + \bold Note: methods should only be called after the Component has completed. +*/ + void QSGGridView::moveCurrentIndexUp() { @@ -1184,6 +1575,15 @@ void QSGGridView::moveCurrentIndexUp() } } +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexDown() + + Move the currentIndex down one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. This method has no effect if the \l count is zero. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGGridView::moveCurrentIndexDown() { Q_D(QSGGridView); @@ -1203,6 +1603,15 @@ void QSGGridView::moveCurrentIndexDown() } } +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexLeft() + + Move the currentIndex left one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. This method has no effect if the \l count is zero. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGGridView::moveCurrentIndexLeft() { Q_D(QSGGridView); @@ -1236,6 +1645,16 @@ void QSGGridView::moveCurrentIndexLeft() } } + +/*! + \qmlmethod QtQuick2::GridView::moveCurrentIndexRight() + + Move the currentIndex right one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. This method has no effect if the \l count is zero. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGGridView::moveCurrentIndexRight() { Q_D(QSGGridView); @@ -1273,7 +1692,7 @@ void QSGGridView::moveCurrentIndexRight() void QSGGridView::itemsInserted(int modelIndex, int count) { Q_D(QSGGridView); - if (!isComponentComplete()) + if (!isComponentComplete() || !d->model || !d->model->isValid()) return; int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0; @@ -1404,7 +1823,7 @@ void QSGGridView::itemsInserted(int modelIndex, int count) void QSGGridView::itemsRemoved(int modelIndex, int count) { Q_D(QSGGridView); - if (!isComponentComplete()) + if (!isComponentComplete() || !d->model || !d->model->isValid()) return; d->itemCount -= count; @@ -1486,7 +1905,7 @@ void QSGGridView::itemsRemoved(int modelIndex, int count) void QSGGridView::itemsMoved(int from, int to, int count) { Q_D(QSGGridView); - if (!isComponentComplete()) + if (!isComponentComplete() || !d->isValid()) return; d->updateUnrequestedIndexes(); @@ -1614,7 +2033,71 @@ void QSGGridView::itemsMoved(int from, int to, int count) d->layout(); } +/*! + \qmlmethod QtQuick2::GridView::positionViewAtIndex(int index, PositionMode mode) + + Positions the view such that the \a index is at the position specified by + \a mode: + + \list + \o GridView.Beginning - position item at the top (or left for \c GridView.TopToBottom flow) of the view. + \o GridView.Center - position item in the center of the view. + \o GridView.End - position item at bottom (or right for horizontal orientation) of the view. + \o GridView.Visible - if any part of the item is visible then take no action, otherwise + bring the item into view. + \o GridView.Contain - ensure the entire item is visible. If the item is larger than + the view the item is positioned at the top (or left for \c GridView.TopToBottom flow) of the view. + \endlist + + If positioning the view at the index would cause empty space to be displayed at + the beginning or end of the view, the view will be positioned at the boundary. + + It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view + at a particular index. This is unreliable since removing items from the start + of the view does not cause all other items to be repositioned. + The correct way to bring an item into view is with \c positionViewAtIndex. + + \bold Note: methods should only be called after the Component has completed. To position + the view at startup, this method should be called by Component.onCompleted. For + example, to position the view at the end: + + \code + Component.onCompleted: positionViewAtIndex(count - 1, GridView.Beginning) + \endcode +*/ + +/*! + \qmlmethod QtQuick2::GridView::positionViewAtBeginning() + \qmlmethod QtQuick2::GridView::positionViewAtEnd() + + Positions the view at the beginning or end, taking into account any header or footer. + + It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view + at a particular index. This is unreliable since removing items from the start + of the list does not cause all other items to be repositioned, and because + the actual start of the view can vary based on the size of the delegates. + + \bold Note: methods should only be called after the Component has completed. To position + the view at startup, this method should be called by Component.onCompleted. For + example, to position the view at the end on startup: + + \code + Component.onCompleted: positionViewAtEnd() + \endcode +*/ + +/*! + \qmlmethod int QtQuick2::GridView::indexAt(int x, int y) + + Returns the index of the visible item containing the point \a x, \a y in content + coordinates. If there is no item at the point specified, or the item is + not visible -1 is returned. + + If the item is outside the visible area, -1 is returned, regardless of + whether an item will exist at that point when scrolled into view. + \bold Note: methods should only be called after the Component has completed. +*/ QSGGridViewAttached *QSGGridView::qmlAttachedProperties(QObject *obj) { return new QSGGridViewAttached(obj); diff --git a/src/declarative/items/qsgimage.cpp b/src/declarative/items/qsgimage.cpp index dc5c8d11c7..cb12c177cd 100644 --- a/src/declarative/items/qsgimage.cpp +++ b/src/declarative/items/qsgimage.cpp @@ -95,7 +95,6 @@ QSGImagePrivate::QSGImagePrivate() /*! \qmlclass Image QSGImage - \since QtQuick 1.0 \inqmlmodule QtQuick 2 \ingroup qml-basic-visual-elements \brief The Image element displays an image in a declarative user interface @@ -446,7 +445,6 @@ qreal QSGImage::paintedHeight() const /*! \qmlproperty bool QtQuick2::Image::cache - \since Quick 1.1 Specifies whether the image should be cached. The default value is true. Setting \a cache to false is useful when dealing with large images, @@ -455,7 +453,6 @@ qreal QSGImage::paintedHeight() const /*! \qmlproperty bool QtQuick2::Image::mirror - \since Quick 1.1 This property holds whether the image should be horizontally inverted (effectively displaying a mirrored image). @@ -466,7 +463,6 @@ qreal QSGImage::paintedHeight() const /*! \qmlproperty enumeration QtQuick2::Image::horizontalAlignment \qmlproperty enumeration QtQuick2::Image::verticalAlignment - \since Quick 2.0 Sets the horizontal and vertical alignment of the image. By default, the image is top-left aligned. diff --git a/src/declarative/items/qsgitem.cpp b/src/declarative/items/qsgitem.cpp index 163cec13ee..a7797630c0 100644 --- a/src/declarative/items/qsgitem.cpp +++ b/src/declarative/items/qsgitem.cpp @@ -71,6 +71,168 @@ QT_BEGIN_NAMESPACE +/*! + \qmlclass Transform QSGTransform + \inqmlmodule QtQuick 2 + \ingroup qml-transform-elements + \brief The Transform elements provide a way of building advanced transformations on Items. + + The Transform element is a base type which cannot be instantiated directly. + The following concrete Transform types are available: + + \list + \o \l Rotation + \o \l Scale + \o \l Translate + \endlist + + The Transform elements let you create and control advanced transformations that can be configured + independently using specialized properties. + + You can assign any number of Transform elements to an \l Item. Each Transform is applied in order, + one at a time. +*/ + +/*! + \qmlclass Translate QSGTranslate + \inqmlmodule QtQuick 2 + \ingroup qml-transform-elements + \brief The Translate object provides a way to move an Item without changing its x or y properties. + + The Translate object provides independent control over position in addition to the Item's x and y properties. + + The following example moves the Y axis of the \l Rectangle elements while still allowing the \l Row element + to lay the items out as if they had not been transformed: + \qml + import QtQuick 1.0 + + Row { + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Translate { y: 20 } + } + Rectangle { + width: 100; height: 100 + color: "red" + transform: Translate { y: -20 } + } + } + \endqml + + \image translate.png +*/ + +/*! + \qmlproperty real QtQuick2::Translate::x + + The translation along the X axis. +*/ + +/*! + \qmlproperty real QtQuick2::Translate::y + + The translation along the Y axis. +*/ + +/*! + \qmlclass Scale QSGScale + \inqmlmodule QtQuick 2 + \ingroup qml-transform-elements + \brief The Scale element provides a way to scale an Item. + + The Scale element gives more control over scaling than using \l Item's \l{Item::scale}{scale} property. Specifically, + it allows a different scale for the x and y axes, and allows the scale to be relative to an + arbitrary point. + + The following example scales the X axis of the Rectangle, relative to its interior point 25, 25: + \qml + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Scale { origin.x: 25; origin.y: 25; xScale: 3} + } + \endqml + + \sa Rotation, Translate +*/ + +/*! + \qmlproperty real QtQuick2::Scale::origin.x + \qmlproperty real QtQuick2::Scale::origin.y + + The point that the item is scaled from (i.e., the point that stays fixed relative to the parent as + the rest of the item grows). By default the origin is 0, 0. +*/ + +/*! + \qmlproperty real QtQuick2::Scale::xScale + + The scaling factor for the X axis. +*/ + +/*! + \qmlproperty real QtQuick2::Scale::yScale + + The scaling factor for the Y axis. +*/ + +/*! + \qmlclass Rotation QSGRotation + \inqmlmodule QtQuick 2 + \ingroup qml-transform-elements + \brief The Rotation object provides a way to rotate an Item. + + The Rotation object gives more control over rotation than using \l Item's \l{Item::rotation}{rotation} property. + Specifically, it allows (z axis) rotation to be relative to an arbitrary point. + + The following example rotates a Rectangle around its interior point 25, 25: + \qml + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Rotation { origin.x: 25; origin.y: 25; angle: 45} + } + \endqml + + Rotation also provides a way to specify 3D-like rotations for Items. For these types of + rotations you must specify the axis to rotate around in addition to the origin point. + + The following example shows various 3D-like rotations applied to an \l Image. + \snippet doc/src/snippets/declarative/rotation.qml 0 + + \image axisrotation.png + + \sa {declarative/ui-components/dialcontrol}{Dial Control example}, {declarative/toys/clocks}{Clocks example} +*/ + +/*! + \qmlproperty real QtQuick2::Rotation::origin.x + \qmlproperty real QtQuick2::Rotation::origin.y + + The origin point of the rotation (i.e., the point that stays fixed relative to the parent as + the rest of the item rotates). By default the origin is 0, 0. +*/ + +/*! + \qmlproperty real QtQuick2::Rotation::axis.x + \qmlproperty real QtQuick2::Rotation::axis.y + \qmlproperty real QtQuick2::Rotation::axis.z + + The axis to rotate around. For simple (2D) rotation around a point, you do not need to specify an axis, + as the default axis is the z axis (\c{ axis { x: 0; y: 0; z: 1 } }). + + For a typical 3D-like rotation you will usually specify both the origin and the axis. + + \image 3d-rotation-axis.png +*/ + +/*! + \qmlproperty real QtQuick2::Rotation::angle + + The angle to rotate, in degrees clockwise. +*/ + QSGTransformPrivate::QSGTransformPrivate() { } @@ -104,7 +266,7 @@ void QSGTransform::update() } } -QSGContents::QSGContents(QSGItem *item) +QSGContents::QSGContents(QSGItem *item) : m_item(item), m_x(0), m_y(0), m_width(0), m_height(0) { //### optimize @@ -283,6 +445,69 @@ void QSGItemKeyFilter::componentComplete() { if (m_next) m_next->componentComplete(); } +/*! + \qmlclass KeyNavigation QSGKeyNavigationAttached + \inqmlmodule QtQuick 2 + \ingroup qml-basic-interaction-elements + \brief The KeyNavigation attached property supports key navigation by arrow keys. + + Key-based user interfaces commonly allow the use of arrow keys to navigate between + focusable items. The KeyNavigation attached property enables this behavior by providing a + convenient way to specify the item that should gain focus when an arrow or tab key is pressed. + + The following example provides key navigation for a 2x2 grid of items: + + \snippet doc/src/snippets/declarative/keynavigation.qml 0 + + The top-left item initially receives focus by setting \l {Item::}{focus} to + \c true. When an arrow key is pressed, the focus will move to the + appropriate item, as defined by the value that has been set for + the KeyNavigation \l left, \l right, \l up or \l down properties. + + Note that if a KeyNavigation attached property receives the key press and release + events for a requested arrow or tab key, the event is accepted and does not + propagate any further. + + By default, KeyNavigation receives key events after the item to which it is attached. + If the item accepts the key event, the KeyNavigation attached property will not + receive an event for that key. Setting the \l priority property to + \c KeyNavigation.BeforeItem allows the event to be used for key navigation + before the item, rather than after. + + If item to which the focus is switching is not enabled or visible, an attempt will + be made to skip this item and focus on the next. This is possible if there are + a chain of items with the same KeyNavigation handler. If multiple items in a row are not enabled + or visible, they will also be skipped. + + KeyNavigation will implicitly set the other direction to return focus to this item. So if you set + \l left to another item, \l right will be set on that item's KeyNavigation to set focus back to this + item. However, if that item's KeyNavigation has had right explicitly set then no change will occur. + This means that the above example could have been written, with the same behaviour, without specifing + KeyNavigation.right or KeyNavigation.down for any of the items. + + \sa {Keys}{Keys attached property} +*/ + +/*! + \qmlproperty Item QtQuick2::KeyNavigation::left + \qmlproperty Item QtQuick2::KeyNavigation::right + \qmlproperty Item QtQuick2::KeyNavigation::up + \qmlproperty Item QtQuick2::KeyNavigation::down + \qmlproperty Item QtQuick2::KeyNavigation::tab + \qmlproperty Item QtQuick2::KeyNavigation::backtab + + These properties hold the item to assign focus to + when the left, right, up or down cursor keys, or the + tab key are pressed. +*/ + +/*! + \qmlproperty Item QtQuick2::KeyNavigation::tab + \qmlproperty Item QtQuick2::KeyNavigation::backtab + + These properties hold the item to assign focus to + when the Tab key or Shift+Tab key combination (Backtab) are pressed. +*/ QSGKeyNavigationAttached::QSGKeyNavigationAttached(QObject *parent) : QObject(*(new QSGKeyNavigationAttachedPrivate), parent), @@ -429,6 +654,21 @@ void QSGKeyNavigationAttached::setBacktab(QSGItem *i) emit backtabChanged(); } +/*! + \qmlproperty enumeration QtQuick2::KeyNavigation::priority + + This property determines whether the keys are processed before + or after the attached item's own key handling. + + \list + \o KeyNavigation.BeforeItem - process the key events before normal + item key processing. If the event is used for key navigation, it will be accepted and will not + be passed on to the item. + \o KeyNavigation.AfterItem (default) - process the key events after normal item key + handling. If the item accepts the key event it will not be + handled by the KeyNavigation attached property handler. + \endlist +*/ QSGKeyNavigationAttached::Priority QSGKeyNavigationAttached::priority() const { return m_processPost ? AfterItem : BeforeItem; @@ -617,6 +857,393 @@ bool QSGKeysAttachedPrivate::isConnected(const char *signalName) return isSignalConnected(signalIndex(signalName)); } +/*! + \qmlclass Keys QSGKeysAttached + \inqmlmodule QtQuick 2 + \ingroup qml-basic-interaction-elements + \brief The Keys attached property provides key handling to Items. + + All visual primitives support key handling via the Keys + attached property. Keys can be handled via the onPressed + and onReleased signal properties. + + The signal properties have a \l KeyEvent parameter, named + \e event which contains details of the event. If a key is + handled \e event.accepted should be set to true to prevent the + event from propagating up the item hierarchy. + + \section1 Example Usage + + The following example shows how the general onPressed handler can + be used to test for a certain key; in this case, the left cursor + key: + + \snippet doc/src/snippets/declarative/keys/keys-pressed.qml key item + + Some keys may alternatively be handled via specific signal properties, + for example \e onSelectPressed. These handlers automatically set + \e event.accepted to true. + + \snippet doc/src/snippets/declarative/keys/keys-handler.qml key item + + See \l{Qt::Key}{Qt.Key} for the list of keyboard codes. + + \section1 Key Handling Priorities + + The Keys attached property can be configured to handle key events + before or after the item it is attached to. This makes it possible + to intercept events in order to override an item's default behavior, + or act as a fallback for keys not handled by the item. + + If \l priority is Keys.BeforeItem (default) the order of key event processing is: + + \list 1 + \o Items specified in \c forwardTo + \o specific key handlers, e.g. onReturnPressed + \o onKeyPress, onKeyRelease handlers + \o Item specific key handling, e.g. TextInput key handling + \o parent item + \endlist + + If priority is Keys.AfterItem the order of key event processing is: + + \list 1 + \o Item specific key handling, e.g. TextInput key handling + \o Items specified in \c forwardTo + \o specific key handlers, e.g. onReturnPressed + \o onKeyPress, onKeyRelease handlers + \o parent item + \endlist + + If the event is accepted during any of the above steps, key + propagation stops. + + \sa KeyEvent, {KeyNavigation}{KeyNavigation attached property} +*/ + +/*! + \qmlproperty bool QtQuick2::Keys::enabled + + This flags enables key handling if true (default); otherwise + no key handlers will be called. +*/ + +/*! + \qmlproperty enumeration QtQuick2::Keys::priority + + This property determines whether the keys are processed before + or after the attached item's own key handling. + + \list + \o Keys.BeforeItem (default) - process the key events before normal + item key processing. If the event is accepted it will not + be passed on to the item. + \o Keys.AfterItem - process the key events after normal item key + handling. If the item accepts the key event it will not be + handled by the Keys attached property handler. + \endlist +*/ + +/*! + \qmlproperty list<Object> QtQuick2::Keys::forwardTo + + This property provides a way to forward key presses, key releases, and keyboard input + coming from input methods to other items. This can be useful when you want + one item to handle some keys (e.g. the up and down arrow keys), and another item to + handle other keys (e.g. the left and right arrow keys). Once an item that has been + forwarded keys accepts the event it is no longer forwarded to items later in the + list. + + This example forwards key events to two lists: + \qml + Item { + ListView { + id: list1 + // ... + } + ListView { + id: list2 + // ... + } + Keys.forwardTo: [list1, list2] + focus: true + } + \endqml +*/ + +/*! + \qmlsignal QtQuick2::Keys::onPressed(KeyEvent event) + + This handler is called when a key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onReleased(KeyEvent event) + + This handler is called when a key has been released. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit0Pressed(KeyEvent event) + + This handler is called when the digit '0' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit1Pressed(KeyEvent event) + + This handler is called when the digit '1' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit2Pressed(KeyEvent event) + + This handler is called when the digit '2' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit3Pressed(KeyEvent event) + + This handler is called when the digit '3' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit4Pressed(KeyEvent event) + + This handler is called when the digit '4' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit5Pressed(KeyEvent event) + + This handler is called when the digit '5' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit6Pressed(KeyEvent event) + + This handler is called when the digit '6' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit7Pressed(KeyEvent event) + + This handler is called when the digit '7' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit8Pressed(KeyEvent event) + + This handler is called when the digit '8' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDigit9Pressed(KeyEvent event) + + This handler is called when the digit '9' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onLeftPressed(KeyEvent event) + + This handler is called when the Left arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onRightPressed(KeyEvent event) + + This handler is called when the Right arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onUpPressed(KeyEvent event) + + This handler is called when the Up arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDownPressed(KeyEvent event) + + This handler is called when the Down arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onTabPressed(KeyEvent event) + + This handler is called when the Tab key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onBacktabPressed(KeyEvent event) + + This handler is called when the Shift+Tab key combination (Backtab) has + been pressed. The \a event parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onAsteriskPressed(KeyEvent event) + + This handler is called when the Asterisk '*' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onEscapePressed(KeyEvent event) + + This handler is called when the Escape key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onReturnPressed(KeyEvent event) + + This handler is called when the Return key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onEnterPressed(KeyEvent event) + + This handler is called when the Enter key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onDeletePressed(KeyEvent event) + + This handler is called when the Delete key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onSpacePressed(KeyEvent event) + + This handler is called when the Space key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onBackPressed(KeyEvent event) + + This handler is called when the Back key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onCancelPressed(KeyEvent event) + + This handler is called when the Cancel key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onSelectPressed(KeyEvent event) + + This handler is called when the Select key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onYesPressed(KeyEvent event) + + This handler is called when the Yes key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onNoPressed(KeyEvent event) + + This handler is called when the No key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onContext1Pressed(KeyEvent event) + + This handler is called when the Context1 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onContext2Pressed(KeyEvent event) + + This handler is called when the Context2 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onContext3Pressed(KeyEvent event) + + This handler is called when the Context3 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onContext4Pressed(KeyEvent event) + + This handler is called when the Context4 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onCallPressed(KeyEvent event) + + This handler is called when the Call key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onHangupPressed(KeyEvent event) + + This handler is called when the Hangup key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onFlipPressed(KeyEvent event) + + This handler is called when the Flip key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onMenuPressed(KeyEvent event) + + This handler is called when the Menu key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onVolumeUpPressed(KeyEvent event) + + This handler is called when the VolumeUp key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal QtQuick2::Keys::onVolumeDownPressed(KeyEvent event) + + This handler is called when the VolumeDown key has been pressed. The \a event + parameter provides information about the event. +*/ + QSGKeysAttached::QSGKeysAttached(QObject *parent) : QObject(*(new QSGKeysAttachedPrivate), parent), QSGItemKeyFilter(qobject_cast<QSGItem*>(parent)) @@ -759,7 +1386,7 @@ QVariant QSGKeysAttached::inputMethodQuery(Qt::InputMethodQuery query) const if (d->item) { for (int ii = 0; ii < d->targets.count(); ++ii) { QSGItem *i = d->targets.at(ii); - if (i && i->isVisible() && (i->flags() & QSGItem::ItemAcceptsInputMethod) && i == d->imeItem) { + if (i && i->isVisible() && (i->flags() & QSGItem::ItemAcceptsInputMethod) && i == d->imeItem) { //### how robust is i == d->imeItem check? QVariant v = i->inputMethodQuery(query); if (v.userType() == QVariant::RectF) @@ -776,6 +1403,69 @@ QSGKeysAttached *QSGKeysAttached::qmlAttachedProperties(QObject *obj) return new QSGKeysAttached(obj); } +/*! + \qmlclass LayoutMirroring QSGLayoutMirroringAttached + \inqmlmodule QtQuick 2 + \ingroup qml-utility-elements + \brief The LayoutMirroring attached property is used to mirror layout behavior. + + The LayoutMirroring attached property is used to horizontally mirror \l {anchor-layout}{Item anchors}, + \l{Using QML Positioner and Repeater Items}{positioner} elements (such as \l Row and \l Grid) + and views (such as \l GridView and horizontal \l ListView). Mirroring is a visual change: left + anchors become right anchors, and positioner elements like \l Grid and \l Row reverse the + horizontal layout of child items. + + Mirroring is enabled for an item by setting the \l enabled property to true. By default, this + only affects the item itself; setting the \l childrenInherit property to true propagates the mirroring + behavior to all child elements as well. If the \c LayoutMirroring attached property has not been defined + for an item, mirroring is not enabled. + + The following example shows mirroring in action. The \l Row below is specified as being anchored + to the left of its parent. However, since mirroring has been enabled, the anchor is horizontally + reversed and it is now anchored to the right. Also, since items in a \l Row are positioned + from left to right by default, they are now positioned from right to left instead, as demonstrated + by the numbering and opacity of the items: + + \snippet doc/src/snippets/declarative/layoutmirroring.qml 0 + + \image layoutmirroring.png + + Layout mirroring is useful when it is necessary to support both left-to-right and right-to-left + layout versions of an application to target different language areas. The \l childrenInherit + property allows layout mirroring to be applied without manually setting layout configurations + for every item in an application. Keep in mind, however, that mirroring does not affect any + positioning that is defined by the \l Item \l {Item::}{x} coordinate value, so even with + mirroring enabled, it will often be necessary to apply some layout fixes to support the + desired layout direction. Also, it may be necessary to disable the mirroring of individual + child items (by setting \l {enabled}{LayoutMirroring.enabled} to false for such items) if + mirroring is not the desired behavior, or if the child item already implements mirroring in + some custom way. + + See \l {QML Right-to-left User Interfaces} for further details on using \c LayoutMirroring and + other related features to implement right-to-left support for an application. +*/ + +/*! + \qmlproperty bool QtQuick2::LayoutMirroring::enabled + + This property holds whether the item's layout is mirrored horizontally. Setting this to true + horizontally reverses \l {anchor-layout}{anchor} settings such that left anchors become right, + and right anchors become left. For \l{Using QML Positioner and Repeater Items}{positioner} elements + (such as \l Row and \l Grid) and view elements (such as \l {GridView}{GridView} and \l {ListView}{ListView}) + this also mirrors the horizontal layout direction of the item. + + The default value is false. +*/ + +/*! + \qmlproperty bool QtQuick2::LayoutMirroring::childrenInherit + + This property holds whether the \l {enabled}{LayoutMirroring.enabled} value for this item + is inherited by its children. + + The default value is false. +*/ + QSGLayoutMirroringAttached::QSGLayoutMirroringAttached(QObject *parent) : QObject(parent), itemPrivate(0) { @@ -880,6 +1570,137 @@ void QSGItemPrivate::setLayoutMirror(bool mirror) } } +/*! + \class QSGItem + \brief The QSGItem class provides the most basic of all visual items in QML. + + All visual items in Qt Declarative inherit from QSGItem. Although QSGItem + has no visual appearance, it defines all the properties that are + common across visual items - such as the x and y position, the + width and height, \l {anchor-layout}{anchoring} and key handling. + + You can subclass QSGItem to provide your own custom visual item that inherits + these features. Note that, because it does not draw anything, QSGItem sets the + QGraphicsItem::ItemHasNoContents flag. If you subclass QSGItem to create a visual + item, you will need to unset this flag. + +*/ + +/*! + \qmlclass Item QSGItem + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The Item is the most basic of all visual items in QML. + + All visual items in Qt Declarative inherit from Item. Although Item + has no visual appearance, it defines all the properties that are + common across visual items - such as the x and y position, the + width and height, \l {anchor-layout}{anchoring} and key handling. + + Item is also useful for grouping items together. + + \qml + Item { + Image { + source: "tile.png" + } + Image { + x: 80 + width: 100 + height: 100 + source: "tile.png" + } + Image { + x: 190 + width: 100 + height: 100 + fillMode: Image.Tile + source: "tile.png" + } + } + \endqml + + + \section1 Key Handling + + Key handling is available to all Item-based visual elements via the \l {Keys}{Keys} + attached property. The \e Keys attached property provides basic handlers such + as \l {Keys::onPressed}{onPressed} and \l {Keys::onReleased}{onReleased}, + as well as handlers for specific keys, such as + \l {Keys::onCancelPressed}{onCancelPressed}. The example below + assigns \l {qmlfocus}{focus} to the item and handles + the Left key via the general \e onPressed handler and the Select key via the + onSelectPressed handler: + + \qml + Item { + focus: true + Keys.onPressed: { + if (event.key == Qt.Key_Left) { + console.log("move left"); + event.accepted = true; + } + } + Keys.onSelectPressed: console.log("Selected"); + } + \endqml + + See the \l {Keys}{Keys} attached property for detailed documentation. + + \section1 Layout Mirroring + + Item layouts can be mirrored using the \l {LayoutMirroring}{LayoutMirroring} attached property. + +*/ + +/*! + \fn void QSGItem::childrenRectChanged(const QRectF &) + \internal +*/ + +/*! + \fn void QSGItem::baselineOffsetChanged(qreal) + \internal +*/ + +/*! + \fn void QSGItem::stateChanged(const QString &state) + \internal +*/ + +/*! + \fn void QSGItem::parentChanged(QSGItem *) + \internal +*/ + +/*! + \fn void QSGItem::smoothChanged(bool) + \internal +*/ + +/*! + \fn void QSGItem::clipChanged(bool) + \internal +*/ + +/*! \fn void QSGItem::transformOriginChanged(TransformOrigin) + \internal +*/ + +/*! + \fn void QSGItem::focusChanged(bool) + \internal +*/ + +/*! + \fn void QSGItem::activeFocusChanged(bool) + \internal +*/ +/*! + \fn QSGItem::QSGItem(QSGItem *parent) + + Constructs a QSGItem with the given \a parent. +*/ QSGItem::QSGItem(QSGItem* parent) : QObject(*(new QSGItemPrivate), parent) { @@ -887,6 +1708,8 @@ QSGItem::QSGItem(QSGItem* parent) d->init(parent); } +/*! \internal +*/ QSGItem::QSGItem(QSGItemPrivate &dd, QSGItem *parent) : QObject(dd, parent) { @@ -894,15 +1717,37 @@ QSGItem::QSGItem(QSGItemPrivate &dd, QSGItem *parent) d->init(parent); } +#ifndef QT_NO_DEBUG +static int qt_item_count = 0; + +static void qt_print_item_count() +{ + qDebug("Number of leaked items: %i", qt_item_count); + qt_item_count = -1; +} +#endif + +/*! + Destroys the QSGItem. +*/ QSGItem::~QSGItem() { +#ifndef QT_NO_DEBUG + --qt_item_count; + if (qt_item_count < 0) + qDebug("Item destroyed after qt_print_item_count() was called."); +#endif + Q_D(QSGItem); + if (d->parentItem) + setParentItem(0); + else if (d->canvas && d->itemNodeInstance) + QSGCanvasPrivate::get(d->canvas)->cleanup(d->itemNodeInstance); // cleanup root // XXX todo - optimize - setParentItem(0); while (!d->childItems.isEmpty()) d->childItems.first()->setParentItem(0); - + for (int ii = 0; ii < d->changeListeners.count(); ++ii) { QSGAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate(); if (anchor) @@ -915,7 +1760,7 @@ QSGItem::~QSGItem() if (anchor && anchor->item && anchor->item->parent() != this) //child will be deleted anyway anchor->updateOnComplete(); } - + for (int ii = 0; ii < d->changeListeners.count(); ++ii) { const QSGItemPrivate::ChangeListener &change = d->changeListeners.at(ii); if (change.types & QSGItemPrivate::Destroyed) @@ -928,6 +1773,38 @@ QSGItem::~QSGItem() delete d->_contents; d->_contents = 0; } +/*! + \qmlproperty enumeration QtQuick2::Item::transformOrigin + This property holds the origin point around which scale and rotation transform. + + Nine transform origins are available, as shown in the image below. + + \image declarative-transformorigin.png + + This example rotates an image around its bottom-right corner. + \qml + Image { + source: "myimage.png" + transformOrigin: Item.BottomRight + rotation: 45 + } + \endqml + + The default transform origin is \c Item.Center. + + To set an arbitrary transform origin point use the \l Scale or \l Rotation + transform elements. +*/ + +/*! + \qmlproperty Item QtQuick2::Item::parent + This property holds the parent of the item. +*/ + +/*! + \property QSGItem::parent + This property holds the parent of the item. +*/ void QSGItem::setParentItem(QSGItem *parentItem) { Q_D(QSGItem); @@ -952,10 +1829,10 @@ void QSGItem::setParentItem(QSGItem *parentItem) scopeItem = oldParentItem; while (!scopeItem->isFocusScope()) scopeItem = scopeItem->parentItem(); scopeFocusedItem = d->subFocusItem; - } + } - if (scopeFocusedItem) - QSGCanvasPrivate::get(d->canvas)->clearFocusInScope(scopeItem, scopeFocusedItem, + if (scopeFocusedItem) + QSGCanvasPrivate::get(d->canvas)->clearFocusInScope(scopeItem, scopeFocusedItem, QSGCanvasPrivate::DontChangeFocusProperty); op->removeChild(this); @@ -965,16 +1842,13 @@ void QSGItem::setParentItem(QSGItem *parentItem) QSGCanvas *parentCanvas = parentItem?QSGItemPrivate::get(parentItem)->canvas:0; if (d->canvas != parentCanvas) { - if (d->canvas && d->itemNodeInstance) - QSGCanvasPrivate::get(d->canvas)->cleanup(d->itemNodeInstance); - QSGItemPrivate::InitializationState initState; initState.clear(); d->initCanvas(&initState, parentCanvas); } d->dirty(QSGItemPrivate::ParentChanged); - + if (d->parentItem) QSGItemPrivate::get(d->parentItem)->addChild(this); @@ -991,7 +1865,7 @@ void QSGItem::setParentItem(QSGItem *parentItem) QSGItemPrivate::get(scopeFocusedItem)->focus = false; emit scopeFocusedItem->focusChanged(false); } else { - QSGCanvasPrivate::get(d->canvas)->setFocusInScope(scopeItem, scopeFocusedItem, + QSGCanvasPrivate::get(d->canvas)->setFocusInScope(scopeItem, scopeFocusedItem, QSGCanvasPrivate::DontChangeFocusProperty); } } @@ -1015,7 +1889,7 @@ void QSGItem::stackBefore(const QSGItem *sibling) int myIndex = parentPrivate->childItems.indexOf(this); int siblingIndex = parentPrivate->childItems.indexOf(const_cast<QSGItem *>(sibling)); - + Q_ASSERT(myIndex != -1 && siblingIndex != -1); if (myIndex == siblingIndex - 1) @@ -1029,7 +1903,7 @@ void QSGItem::stackBefore(const QSGItem *sibling) parentPrivate->dirty(QSGItemPrivate::ChildrenStackingChanged); - for (int ii = qMin(siblingIndex, myIndex); ii < parentPrivate->childItems.count(); ++ii) + for (int ii = qMin(siblingIndex, myIndex); ii < parentPrivate->childItems.count(); ++ii) QSGItemPrivate::get(parentPrivate->childItems.at(ii))->siblingOrderChanged(); } @@ -1045,7 +1919,7 @@ void QSGItem::stackAfter(const QSGItem *sibling) int myIndex = parentPrivate->childItems.indexOf(this); int siblingIndex = parentPrivate->childItems.indexOf(const_cast<QSGItem *>(sibling)); - + Q_ASSERT(myIndex != -1 && siblingIndex != -1); if (myIndex == siblingIndex + 1) @@ -1059,7 +1933,7 @@ void QSGItem::stackAfter(const QSGItem *sibling) parentPrivate->dirty(QSGItemPrivate::ChildrenStackingChanged); - for (int ii = qMin(myIndex, siblingIndex + 1); ii < parentPrivate->childItems.count(); ++ii) + for (int ii = qMin(myIndex, siblingIndex + 1); ii < parentPrivate->childItems.count(); ++ii) QSGItemPrivate::get(parentPrivate->childItems.at(ii))->siblingOrderChanged(); } @@ -1077,8 +1951,8 @@ QSGEngine *QSGItem::sceneGraphEngine() const return canvas()->sceneGraphEngine(); } -QSGCanvas *QSGItem::canvas() const -{ +QSGCanvas *QSGItem::canvas() const +{ Q_D(const QSGItem); return d->canvas; } @@ -1128,13 +2002,13 @@ void QSGItemPrivate::removeChild(QSGItem *child) emit q->childrenChanged(); } -void QSGItemPrivate::InitializationState::clear() -{ - focusScope = 0; +void QSGItemPrivate::InitializationState::clear() +{ + focusScope = 0; } -void QSGItemPrivate::InitializationState::clear(QSGItem *fs) -{ +void QSGItemPrivate::InitializationState::clear(QSGItem *fs) +{ focusScope = fs; } @@ -1160,14 +2034,17 @@ void QSGItemPrivate::initCanvas(InitializationState *state, QSGCanvas *c) c->itemsToPolish.remove(q); if (c->mouseGrabberItem == q) c->mouseGrabberItem = 0; + if ( hoverEnabled ) + c->hoverItems.removeAll(q); + if (itemNodeInstance) + c->cleanup(itemNodeInstance); } canvas = c; - if (canvas && polishScheduled) + if (canvas && polishScheduled) QSGCanvasPrivate::get(canvas)->itemsToPolish.insert(q); - // XXX todo - why aren't these added to the destroy list? itemNodeInstance = 0; opacityNode = 0; clipNode = 0; @@ -1248,6 +2125,55 @@ void QSGItemPrivate::itemToParentTransform(QTransform &t) const } } + +/*! + \qmlproperty real QtQuick2::Item::childrenRect.x + \qmlproperty real QtQuick2::Item::childrenRect.y + \qmlproperty real QtQuick2::Item::childrenRect.width + \qmlproperty real QtQuick2::Item::childrenRect.height + + The childrenRect properties allow an item access to the geometry of its + children. This property is useful if you have an item that needs to be + sized to fit its children. +*/ + + +/*! + \qmlproperty list<Item> QtQuick2::Item::children + \qmlproperty list<Object> QtQuick2::Item::resources + + The children property contains the list of visual children of this item. + The resources property contains non-visual resources that you want to + reference by name. + + Generally you can rely on Item's default property to handle all this for + you, but it can come in handy in some cases. + + \qml + Item { + children: [ + Text {}, + Rectangle {} + ] + resources: [ + Component { + id: myComponent + Text {} + } + ] + } + \endqml +*/ + +/*! + Returns true if construction of the QML component is complete; otherwise + returns false. + + It is often desirable to delay some processing until the component is + completed. + + \sa componentComplete() +*/ bool QSGItem::isComponentComplete() const { Q_D(const QSGItem); @@ -1255,11 +2181,11 @@ bool QSGItem::isComponentComplete() const } QSGItemPrivate::QSGItemPrivate() -: _anchors(0), _contents(0), baselineOffset(0), _anchorLines(0), _stateGroup(0), origin(QSGItem::Center), - - flags(0), widthValid(false), heightValid(false), componentComplete(true), +: _anchors(0), _contents(0), baselineOffset(0), _anchorLines(0), _stateGroup(0), origin(QSGItem::Center), + + flags(0), widthValid(false), heightValid(false), componentComplete(true), keepMouse(false), hoverEnabled(false), smooth(false), focus(false), activeFocus(false), notifiedFocus(false), - notifiedActiveFocus(false), filtersChildMouseEvents(false), explicitVisible(true), + notifiedActiveFocus(false), filtersChildMouseEvents(false), explicitVisible(true), effectiveVisible(true), explicitEnable(true), effectiveEnable(true), polishScheduled(false), inheritedLayoutMirror(false), effectiveLayoutMirror(false), isMirrorImplicit(true), inheritMirrorFromParent(false), inheritMirrorFromItem(false), childrenDoNotOverlap(false), @@ -1268,12 +2194,12 @@ QSGItemPrivate::QSGItemPrivate() subFocusItem(0), - x(0), y(0), width(0), height(0), implicitWidth(0), implicitHeight(0), + x(0), y(0), width(0), height(0), implicitWidth(0), implicitHeight(0), z(0), scale(1), rotation(0), opacity(1), attachedLayoutDirection(0), acceptedMouseButtons(0), imHints(Qt::ImhNone), - + keyHandler(0), dirtyAttributes(0), nextDirtyItem(0), prevDirtyItem(0), @@ -1285,6 +2211,15 @@ QSGItemPrivate::QSGItemPrivate() void QSGItemPrivate::init(QSGItem *parent) { +#ifndef QT_NO_DEBUG + ++qt_item_count; + static bool atexit_registered = false; + if (!atexit_registered) { + atexit(qt_print_item_count); + atexit_registered = true; + } +#endif + Q_Q(QSGItem); baselineOffset.invalidate(); @@ -1305,7 +2240,7 @@ void QSGItemPrivate::data_append(QDeclarativeListProperty<QObject> *prop, QObjec // This test is measurably (albeit only slightly) faster than qobject_cast<>() const QMetaObject *mo = o->metaObject(); while (mo && mo != &QSGItem::staticMetaObject) { - if (mo == &QGraphicsObject::staticMetaObject) + if (mo == &QGraphicsObject::staticMetaObject) qWarning("Cannot add a QtQuick 1.0 item (%s) into a QtQuick 2.0 scene!", o->metaObject()->className()); mo = mo->d.superdata; } @@ -1319,6 +2254,40 @@ void QSGItemPrivate::data_append(QDeclarativeListProperty<QObject> *prop, QObjec } } +/*! + \qmlproperty list<Object> QtQuick2::Item::data + \default + + The data property allows you to freely mix visual children and resources + in an item. If you assign a visual item to the data list it becomes + a child and if you assign any other object type, it is added as a resource. + + So you can write: + \qml + Item { + Text {} + Rectangle {} + Timer {} + } + \endqml + + instead of: + \qml + Item { + children: [ + Text {}, + Rectangle {} + ] + resources: [ + Timer {} + ] + } + \endqml + + data is a behind-the-scenes property: you should never need to explicitly + specify it. + */ + int QSGItemPrivate::data_count(QDeclarativeListProperty<QObject> *prop) { Q_UNUSED(prop); @@ -1483,6 +2452,227 @@ void QSGItemPrivate::transform_clear(QDeclarativeListProperty<QSGTransform> *pro p->dirty(QSGItemPrivate::Transform); } +/*! + \property QSGItem::childrenRect + \brief The geometry of an item's children. + + This property holds the (collective) position and size of the item's children. +*/ + +/*! + \qmlproperty real QtQuick2::Item::x + \qmlproperty real QtQuick2::Item::y + \qmlproperty real QtQuick2::Item::width + \qmlproperty real QtQuick2::Item::height + + Defines the item's position and size relative to its parent. + + \qml + Item { x: 100; y: 100; width: 100; height: 100 } + \endqml + */ + +/*! + \qmlproperty real QtQuick2::Item::z + + Sets the stacking order of sibling items. By default the stacking order is 0. + + Items with a higher stacking value are drawn on top of siblings with a + lower stacking order. Items with the same stacking value are drawn + bottom up in the order they appear. Items with a negative stacking + value are drawn under their parent's content. + + The following example shows the various effects of stacking order. + + \table + \row + \o \image declarative-item_stacking1.png + \o Same \c z - later children above earlier children: + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + } + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + \endqml + \row + \o \image declarative-item_stacking2.png + \o Higher \c z on top: + \qml + Item { + Rectangle { + z: 1 + color: "red" + width: 100; height: 100 + } + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + \endqml + \row + \o \image declarative-item_stacking3.png + \o Same \c z - children above parents: + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \row + \o \image declarative-item_stacking4.png + \o Lower \c z below: + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + Rectangle { + z: -1 + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \endtable + */ + +/*! + \qmlproperty bool QtQuick2::Item::visible + + This property holds whether the item is visible. By default this is true. + + Setting this property directly affects the \c visible value of child + items. When set to \c false, the \c visible values of all child items also + become \c false. When set to \c true, the \c visible values of child items + are returned to \c true, unless they have explicitly been set to \c false. + + (Because of this flow-on behavior, using the \c visible property may not + have the intended effect if a property binding should only respond to + explicit property changes. In such cases it may be better to use the + \l opacity property instead.) + + Setting this property to \c false automatically causes \l focus to be set + to \c false, and this item will longer receive mouse and keyboard events. + (In contrast, setting the \l opacity to 0 does not affect the \l focus + property and the receiving of key events.) + + \note This property's value is only affected by changes to this property or + the parent's \c visible property. It does not change, for example, if this + item moves off-screen, or if the \l opacity changes to 0. +*/ + + +/*! + \qmlproperty AnchorLine QtQuick2::Item::anchors.top + \qmlproperty AnchorLine QtQuick2::Item::anchors.bottom + \qmlproperty AnchorLine QtQuick2::Item::anchors.left + \qmlproperty AnchorLine QtQuick2::Item::anchors.right + \qmlproperty AnchorLine QtQuick2::Item::anchors.horizontalCenter + \qmlproperty AnchorLine QtQuick2::Item::anchors.verticalCenter + \qmlproperty AnchorLine QtQuick2::Item::anchors.baseline + + \qmlproperty Item QtQuick2::Item::anchors.fill + \qmlproperty Item QtQuick2::Item::anchors.centerIn + + \qmlproperty real QtQuick2::Item::anchors.margins + \qmlproperty real QtQuick2::Item::anchors.topMargin + \qmlproperty real QtQuick2::Item::anchors.bottomMargin + \qmlproperty real QtQuick2::Item::anchors.leftMargin + \qmlproperty real QtQuick2::Item::anchors.rightMargin + \qmlproperty real QtQuick2::Item::anchors.horizontalCenterOffset + \qmlproperty real QtQuick2::Item::anchors.verticalCenterOffset + \qmlproperty real QtQuick2::Item::anchors.baselineOffset + + \qmlproperty bool QtQuick2::Item::anchors.mirrored + + Anchors provide a way to position an item by specifying its + relationship with other items. + + Margins apply to top, bottom, left, right, and fill anchors. + The \c anchors.margins property can be used to set all of the various margins at once, to the same value. + Note that margins are anchor-specific and are not applied if an item does not + use anchors. + + Offsets apply for horizontal center, vertical center, and baseline anchors. + + \table + \row + \o \image declarative-anchors_example.png + \o Text anchored to Image, horizontally centered and vertically below, with a margin. + \qml + Item { + Image { + id: pic + // ... + } + Text { + id: label + anchors.horizontalCenter: pic.horizontalCenter + anchors.top: pic.bottom + anchors.topMargin: 5 + // ... + } + } + \endqml + \row + \o \image declarative-anchors_example2.png + \o + Left of Text anchored to right of Image, with a margin. The y + property of both defaults to 0. + + \qml + Item { + Image { + id: pic + // ... + } + Text { + id: label + anchors.left: pic.right + anchors.leftMargin: 5 + // ... + } + } + \endqml + \endtable + + \c anchors.fill provides a convenient way for one item to have the + same geometry as another item, and is equivalent to connecting all + four directional anchors. + + To clear an anchor value, set it to \c undefined. + + \c anchors.mirrored returns true it the layout has been \l {LayoutMirroring}{mirrored}. + + \note You can only anchor an item to siblings or a parent. + + For more information see \l {anchor-layout}{Anchor Layouts}. +*/ + +/*! + \property QSGItem::baselineOffset + \brief The position of the item's baseline in local coordinates. + + The baseline of a \l Text item is the imaginary line on which the text + sits. Controls containing text usually set their baseline to the + baseline of their text. + + For non-text items, a default baseline offset of 0 is used. +*/ QSGAnchors *QSGItemPrivate::anchors() const { if (!_anchors) { @@ -1494,7 +2684,7 @@ QSGAnchors *QSGItemPrivate::anchors() const return _anchors; } -QSGItemPrivate::AnchorLines *QSGItemPrivate::anchorLines() const +QSGItemPrivate::AnchorLines *QSGItemPrivate::anchorLines() const { Q_Q(const QSGItem); if (!_anchorLines) _anchorLines = @@ -1553,6 +2743,12 @@ void QSGItem::setClip(bool c) emit clipChanged(c); } + +/*! + This function is called to handle this item's changes in + geometry from \a oldGeometry to \a newGeometry. If the two + geometries are the same, it doesn't do anything. + */ void QSGItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QSGItem); @@ -1708,6 +2904,13 @@ bool QSGItem::childMouseEventFilter(QSGItem *, QEvent *) return false; } +void QSGItem::windowDeactivateEvent() +{ + foreach (QSGItem* item, childItems()) { + item->windowDeactivateEvent(); + } +} + Qt::InputMethodHints QSGItem::inputMethodHints() const { Q_D(const QSGItem); @@ -1866,7 +3069,7 @@ QTransform QSGItem::itemTransform(QSGItem *other, bool *ok) const { Q_D(const QSGItem); - // XXX todo - we need to be able to handle common parents better and detect + // XXX todo - we need to be able to handle common parents better and detect // invalid cases if (ok) *ok = true; @@ -1912,7 +3115,7 @@ void QSGItem::forceActiveFocus() while (parent) { if (parent->flags() & QSGItem::ItemIsFocusScope) { parent->setFocus(true); - } + } parent = parent->parentItem(); } } @@ -1942,11 +3145,11 @@ QDeclarativeListProperty<QObject> QSGItemPrivate::resources() QDeclarativeListProperty<QSGItem> QSGItemPrivate::children() { - return QDeclarativeListProperty<QSGItem>(q_func(), 0, QSGItemPrivate::children_append, + return QDeclarativeListProperty<QSGItem>(q_func(), 0, QSGItemPrivate::children_append, QSGItemPrivate::children_count, QSGItemPrivate::children_at, QSGItemPrivate::children_clear); - + } QDeclarativeListProperty<QDeclarativeState> QSGItemPrivate::states() @@ -2069,7 +3272,7 @@ void QSGItemPrivate::transformChanged() void QSGItemPrivate::deliverKeyEvent(QKeyEvent *e) { Q_Q(QSGItem); - + Q_ASSERT(e->isAccepted()); if (keyHandler) { if (e->type() == QEvent::KeyPress) @@ -2237,7 +3440,7 @@ QSGItem::TransformOrigin QSGItem::transformOrigin() const void QSGItem::setTransformOrigin(TransformOrigin origin) { Q_D(QSGItem); - if (origin == d->origin) + if (origin == d->origin) return; d->origin = origin; @@ -2267,19 +3470,146 @@ void QSGItem::setZ(qreal v) d->z = v; d->dirty(QSGItemPrivate::ZValue); - if (d->parentItem) + if (d->parentItem) QSGItemPrivate::get(d->parentItem)->dirty(QSGItemPrivate::ChildrenStackingChanged); emit zChanged(); } -qreal QSGItem::rotation() const -{ + +/*! + \qmlproperty real QtQuick2::Item::rotation + This property holds the rotation of the item in degrees clockwise. + + This specifies how many degrees to rotate the item around its transformOrigin. + The default rotation is 0 degrees (i.e. not rotated at all). + + \table + \row + \o \image declarative-rotation.png + \o + \qml + Rectangle { + color: "blue" + width: 100; height: 100 + Rectangle { + color: "red" + x: 25; y: 25; width: 50; height: 50 + rotation: 30 + } + } + \endqml + \endtable + + \sa transform, Rotation +*/ + +/*! + \qmlproperty real QtQuick2::Item::scale + This property holds the scale of the item. + + A scale of less than 1 means the item will be displayed smaller than + normal, and a scale of greater than 1 means the item will be + displayed larger than normal. A negative scale means the item will + be mirrored. + + By default, items are displayed at a scale of 1 (i.e. at their + normal size). + + Scaling is from the item's transformOrigin. + + \table + \row + \o \image declarative-scale.png + \o + \qml + Rectangle { + color: "blue" + width: 100; height: 100 + Rectangle { + color: "green" + width: 25; height: 25 + } + Rectangle { + color: "red" + x: 25; y: 25; width: 50; height: 50 + scale: 1.4 + } + } + \endqml + \endtable + + \sa transform, Scale +*/ + +/*! + \qmlproperty real QtQuick2::Item::opacity + + This property holds the opacity of the item. Opacity is specified as a + number between 0 (fully transparent) and 1 (fully opaque). The default is 1. + + When this property is set, the specified opacity is also applied + individually to child items. In almost all cases this is what you want, + but in some cases it may produce undesired results. For example in the + second set of rectangles below, the red rectangle has specified an opacity + of 0.5, which affects the opacity of its blue child rectangle even though + the child has not specified an opacity. + + \table + \row + \o \image declarative-item_opacity1.png + \o + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \row + \o \image declarative-item_opacity2.png + \o + \qml + Item { + Rectangle { + opacity: 0.5 + color: "red" + width: 100; height: 100 + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \endtable + + If an item's opacity is set to 0, the item will no longer receive mouse + events, but will continue to receive key events and will retain the keyboard + \l focus if it has been set. (In contrast, setting the \l visible property + to \c false stops both mouse and keyboard events, and also removes focus + from the item.) +*/ + +/*! + Returns a value indicating whether mouse input should + remain with this item exclusively. + + \sa setKeepMouseGrab() + */ + +qreal QSGItem::rotation() const +{ Q_D(const QSGItem); return d->rotation; } -void QSGItem::setRotation(qreal r) +void QSGItem::setRotation(qreal r) { Q_D(QSGItem); if (d->rotation == r) @@ -2294,13 +3624,13 @@ void QSGItem::setRotation(qreal r) emit rotationChanged(); } -qreal QSGItem::scale() const -{ +qreal QSGItem::scale() const +{ Q_D(const QSGItem); return d->scale; } -void QSGItem::setScale(qreal s) +void QSGItem::setScale(qreal s) { Q_D(QSGItem); if (d->scale == s) @@ -2326,7 +3656,7 @@ void QSGItem::setOpacity(qreal o) return; d->opacity = o; - + d->dirty(QSGItemPrivate::OpacityValue); d->itemChange(ItemOpacityHasChanged, o); @@ -2400,7 +3730,7 @@ void QSGItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) q->ungrabMouse(); } - for (int ii = 0; ii < childItems.count(); ++ii) + for (int ii = 0; ii < childItems.count(); ++ii) QSGItemPrivate::get(childItems.at(ii))->setEffectiveVisibleRecur(newEffectiveVisible); for(int ii = 0; ii < changeListeners.count(); ++ii) { @@ -2444,7 +3774,7 @@ void QSGItemPrivate::setEffectiveEnableRecur(bool newEffectiveEnable) q->ungrabMouse(); } - for (int ii = 0; ii < childItems.count(); ++ii) + for (int ii = 0; ii < childItems.count(); ++ii) QSGItemPrivate::get(childItems.at(ii))->setEffectiveEnableRecur(newEffectiveEnable); emit q->enabledChanged(); @@ -2625,12 +3955,37 @@ void QSGItemPrivate::itemChange(QSGItem::ItemChange change, const QSGItem::ItemC } } +/*! + \property QSGItem::smooth + \brief whether the item is smoothly transformed. + + This property is provided purely for the purpose of optimization. Turning + smooth transforms off is faster, but looks worse; turning smooth + transformations on is slower, but looks better. + + By default smooth transformations are off. +*/ + +/*! + Returns true if the item should be drawn with antialiasing and + smooth pixmap filtering, false otherwise. + + The default is false. + + \sa setSmooth() +*/ bool QSGItem::smooth() const { Q_D(const QSGItem); return d->smooth; } +/*! + Sets whether the item should be drawn with antialiasing and + smooth pixmap filtering to \a smooth. + + \sa smooth() +*/ void QSGItem::setSmooth(bool smooth) { Q_D(QSGItem); @@ -2643,14 +3998,14 @@ void QSGItem::setSmooth(bool smooth) emit smoothChanged(smooth); } -QSGItem::Flags QSGItem::flags() const -{ +QSGItem::Flags QSGItem::flags() const +{ Q_D(const QSGItem); return (QSGItem::Flags)d->flags; } void QSGItem::setFlag(Flag flag, bool enabled) -{ +{ Q_D(QSGItem); if (enabled) setFlags((Flags)(d->flags | (quint32)flag)); @@ -2658,8 +4013,8 @@ void QSGItem::setFlag(Flag flag, bool enabled) setFlags((Flags)(d->flags & ~(quint32)flag)); } -void QSGItem::setFlags(Flags flags) -{ +void QSGItem::setFlags(Flags flags) +{ Q_D(QSGItem); if ((flags & ItemIsFocusScope) != (d->flags & ItemIsFocusScope)) { @@ -2669,10 +4024,10 @@ void QSGItem::setFlags(Flags flags) } else if (d->flags & ItemIsFocusScope) { qWarning("QSGItem: Cannot unset FocusScope flag."); flags |= ItemIsFocusScope; - } + } } - if ((flags & ItemClipsChildrenToShape ) != (d->flags & ItemClipsChildrenToShape)) + if ((flags & ItemClipsChildrenToShape ) != (d->flags & ItemClipsChildrenToShape)) d->dirty(QSGItemPrivate::Clip); d->flags = flags; @@ -2786,13 +4141,54 @@ qreal QSGItemPrivate::getImplicitWidth() const { return implicitWidth; } - +/*! + Returns the width of the item that is implied by other properties that determine the content. +*/ qreal QSGItem::implicitWidth() const { Q_D(const QSGItem); return d->getImplicitWidth(); } +/*! + \qmlproperty real QtQuick2::Item::implicitWidth + \qmlproperty real QtQuick2::Item::implicitHeight + + Defines the natural width or height of the Item if no \l width or \l height is specified. + + The default implicit size for most items is 0x0, however some elements have an inherent + implicit size which cannot be overridden, e.g. Image, Text. + + Setting the implicit size is useful for defining components that have a preferred size + based on their content, for example: + + \qml + // Label.qml + import QtQuick 1.1 + + Item { + property alias icon: image.source + property alias label: text.text + implicitWidth: text.implicitWidth + image.implicitWidth + implicitHeight: Math.max(text.implicitHeight, image.implicitHeight) + Image { id: image } + Text { + id: text + wrapMode: Text.Wrap + anchors.left: image.right; anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + } + \endqml + + \bold Note: using implicitWidth of Text or TextEdit and setting the width explicitly + incurs a performance penalty as the text must be laid out twice. +*/ + +/*! + Sets the implied width of the item to \a w. + This is the width implied by other properties that determine the content. +*/ void QSGItem::setImplicitWidth(qreal w) { Q_D(QSGItem); @@ -2802,11 +4198,11 @@ void QSGItem::setImplicitWidth(qreal w) if (changed) d->implicitWidthChanged(); return; - } + } qreal oldWidth = d->width; d->width = w; - + d->dirty(QSGItemPrivate::Size); geometryChanged(QRectF(x(), y(), width(), height()), @@ -2816,6 +4212,9 @@ void QSGItem::setImplicitWidth(qreal w) d->implicitWidthChanged(); } +/*! + Returns whether the width property has been set explicitly. +*/ bool QSGItem::widthValid() const { Q_D(const QSGItem); @@ -2865,12 +4264,20 @@ qreal QSGItemPrivate::getImplicitHeight() const return implicitHeight; } +/*! + Returns the height of the item that is implied by other properties that determine the content. +*/ qreal QSGItem::implicitHeight() const { Q_D(const QSGItem); return d->getImplicitHeight(); } + +/*! + Sets the implied height of the item to \a h. + This is the height implied by other properties that determine the content. +*/ void QSGItem::setImplicitHeight(qreal h) { Q_D(QSGItem); @@ -2894,6 +4301,9 @@ void QSGItem::setImplicitHeight(qreal h) d->implicitHeightChanged(); } +/*! + Returns whether the height property has been set explicitly. +*/ bool QSGItem::heightValid() const { Q_D(const QSGItem); @@ -2963,19 +4373,19 @@ QSGItem *QSGItem::scopedFocusItem() const Q_D(const QSGItem); if (!isFocusScope()) return 0; - else + else return d->subFocusItem; } -Qt::MouseButtons QSGItem::acceptedMouseButtons() const -{ +Qt::MouseButtons QSGItem::acceptedMouseButtons() const +{ Q_D(const QSGItem); return d->acceptedMouseButtons; } -void QSGItem::setAcceptedMouseButtons(Qt::MouseButtons buttons) -{ +void QSGItem::setAcceptedMouseButtons(Qt::MouseButtons buttons) +{ Q_D(QSGItem); d->acceptedMouseButtons = buttons; } @@ -2986,13 +4396,13 @@ bool QSGItem::filtersChildMouseEvents() const return d->filtersChildMouseEvents; } -void QSGItem::setFiltersChildMouseEvents(bool filter) -{ +void QSGItem::setFiltersChildMouseEvents(bool filter) +{ Q_D(QSGItem); d->filtersChildMouseEvents = filter; } -bool QSGItem::isUnderMouse() const +bool QSGItem::isUnderMouse() const { Q_D(const QSGItem); if (!d->canvas) @@ -3001,17 +4411,17 @@ bool QSGItem::isUnderMouse() const QPoint cursorPos = QCursor::pos(); if (QRectF(0, 0, width(), height()).contains(mapFromScene(cursorPos))) // ### refactor: d->canvas->mapFromGlobal(cursorPos)))) return true; - return false; + return false; } -bool QSGItem::acceptHoverEvents() const -{ +bool QSGItem::acceptHoverEvents() const +{ Q_D(const QSGItem); return d->hoverEnabled; } -void QSGItem::setAcceptHoverEvents(bool enabled) -{ +void QSGItem::setAcceptHoverEvents(bool enabled) +{ Q_D(QSGItem); d->hoverEnabled = enabled; } @@ -3031,7 +4441,7 @@ void QSGItem::grabMouse() oldGrabber->mouseUngrabEvent(); } -void QSGItem::ungrabMouse() +void QSGItem::ungrabMouse() { Q_D(QSGItem); if (!d->canvas) @@ -3052,22 +4462,60 @@ bool QSGItem::keepMouseGrab() const return d->keepMouse; } +/*! + The flag indicating whether the mouse should remain + with this item is set to \a keep. + + This is useful for items that wish to grab and keep mouse + interaction following a predefined gesture. For example, + an item that is interested in horizontal mouse movement + may set keepMouseGrab to true once a threshold has been + exceeded. Once keepMouseGrab has been set to true, filtering + items will not react to mouse events. + + If the item does not indicate that it wishes to retain mouse grab, + a filtering item may steal the grab. For example, Flickable may attempt + to steal a mouse grab if it detects that the user has begun to + move the viewport. + + \sa keepMouseGrab() + */ void QSGItem::setKeepMouseGrab(bool keep) { Q_D(QSGItem); d->keepMouse = keep; } -QPointF QSGItem::mapToItem(const QSGItem *item, const QPointF &point) const -{ +/*! + \qmlmethod object QtQuick2::Item::mapFromItem(Item item, real x, real y) + + Maps the point (\a x, \a y), which is in \a item's coordinate system, to + this item's coordinate system, and returns an object with \c x and \c y + properties matching the mapped cooordinate. + + If \a item is a \c null value, this maps the point from the coordinate + system of the root QML view. +*/ +/*! + \qmlmethod object QtQuick2::Item::mapToItem(Item item, real x, real y) + + Maps the point (\a x, \a y), which is in this item's coordinate system, to + \a item's coordinate system, and returns an object with \c x and \c y + properties matching the mapped cooordinate. + + If \a item is a \c null value, this maps \a x and \a y to the coordinate + system of the root QML view. +*/ +QPointF QSGItem::mapToItem(const QSGItem *item, const QPointF &point) const +{ QPointF p = mapToScene(point); if (item) p = item->mapFromScene(p); return p; } -QPointF QSGItem::mapToScene(const QPointF &point) const -{ +QPointF QSGItem::mapToScene(const QPointF &point) const +{ Q_D(const QSGItem); return d->itemToCanvasTransform().map(point); } @@ -3081,26 +4529,26 @@ QRectF QSGItem::mapRectToItem(const QSGItem *item, const QRectF &rect) const return t.mapRect(rect); } -QRectF QSGItem::mapRectToScene(const QRectF &rect) const -{ +QRectF QSGItem::mapRectToScene(const QRectF &rect) const +{ Q_D(const QSGItem); return d->itemToCanvasTransform().mapRect(rect); } -QPointF QSGItem::mapFromItem(const QSGItem *item, const QPointF &point) const -{ +QPointF QSGItem::mapFromItem(const QSGItem *item, const QPointF &point) const +{ QPointF p = item?item->mapToScene(point):point; return mapFromScene(p); } -QPointF QSGItem::mapFromScene(const QPointF &point) const -{ +QPointF QSGItem::mapFromScene(const QPointF &point) const +{ Q_D(const QSGItem); return d->canvasToItemTransform().map(point); } -QRectF QSGItem::mapRectFromItem(const QSGItem *item, const QRectF &rect) const -{ +QRectF QSGItem::mapRectFromItem(const QSGItem *item, const QRectF &rect) const +{ Q_D(const QSGItem); QTransform t = item?QSGItemPrivate::get(item)->itemToCanvasTransform():QTransform(); t *= d->canvasToItemTransform(); @@ -3113,6 +4561,318 @@ QRectF QSGItem::mapRectFromScene(const QRectF &rect) const return d->canvasToItemTransform().mapRect(rect); } + +/*! + \qmlmethod QtQuick2::Item::forceActiveFocus() + + Forces active focus on the item. + + This method sets focus on the item and makes sure that all the focus scopes + higher in the object hierarchy are also given the focus. +*/ + +/*! + Forces active focus on the item. + + This method sets focus on the item and makes sure that all the focus scopes + higher in the object hierarchy are also given the focus. +*/ + +/*! + \qmlmethod QtQuick2::Item::childAt(real x, real y) + + Returns the visible child item at point (\a x, \a y), which is in this + item's coordinate system, or \c null if there is no such item. +*/ + +/*! + Returns the visible child item at point (\a x, \a y), which is in this + item's coordinate system, or 0 if there is no such item. +*/ + +/*! + \qmlproperty list<State> QtQuick2::Item::states + This property holds a list of states defined by the item. + + \qml + Item { + states: [ + State { + // ... + }, + State { + // ... + } + // ... + ] + } + \endqml + + \sa {qmlstate}{States} +*/ +/*! + \qmlproperty list<Transition> QtQuick2::Item::transitions + This property holds a list of transitions defined by the item. + + \qml + Item { + transitions: [ + Transition { + // ... + }, + Transition { + // ... + } + // ... + ] + } + \endqml + + \sa {QML Animation and Transitions}{Transitions} +*/ +/* + \qmlproperty list<Filter> QtQuick2::Item::filter + This property holds a list of graphical filters to be applied to the item. + + \l {Filter}{Filters} include things like \l {Blur}{blurring} + the item, or giving it a \l Reflection. Some + filters may not be available on all canvases; if a filter is not + available on a certain canvas, it will simply not be applied for + that canvas (but the QML will still be considered valid). + + \qml + Item { + filter: [ + Blur { + // ... + }, + Reflection { + // ... + } + // ... + ] + } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick2::Item::clip + This property holds whether clipping is enabled. The default clip value is \c false. + + If clipping is enabled, an item will clip its own painting, as well + as the painting of its children, to its bounding rectangle. + + Non-rectangular clipping regions are not supported for performance reasons. +*/ + +/*! + \property QSGItem::clip + This property holds whether clipping is enabled. The default clip value is \c false. + + If clipping is enabled, an item will clip its own painting, as well + as the painting of its children, to its bounding rectangle. If you set + clipping during an item's paint operation, remember to re-set it to + prevent clipping the rest of your scene. + + Non-rectangular clipping regions are not supported for performance reasons. +*/ + +/*! + \qmlproperty string QtQuick2::Item::state + + This property holds the name of the current state of the item. + + This property is often used in scripts to change between states. For + example: + + \js + function toggle() { + if (button.state == 'On') + button.state = 'Off'; + else + button.state = 'On'; + } + \endjs + + If the item is in its base state (i.e. no explicit state has been + set), \c state will be a blank string. Likewise, you can return an + item to its base state by setting its current state to \c ''. + + \sa {qmlstates}{States} +*/ + +/*! + \qmlproperty list<Transform> QtQuick2::Item::transform + This property holds the list of transformations to apply. + + For more information see \l Transform. +*/ + +/*! + \enum QSGItem::TransformOrigin + + Controls the point about which simple transforms like scale apply. + + \value TopLeft The top-left corner of the item. + \value Top The center point of the top of the item. + \value TopRight The top-right corner of the item. + \value Left The left most point of the vertical middle. + \value Center The center of the item. + \value Right The right most point of the vertical middle. + \value BottomLeft The bottom-left corner of the item. + \value Bottom The center point of the bottom of the item. + \value BottomRight The bottom-right corner of the item. +*/ + + +/*! + \qmlproperty bool QtQuick2::Item::activeFocus + + This property indicates whether the item has active focus. + + An item with active focus will receive keyboard input, + or is a FocusScope ancestor of the item that will receive keyboard input. + + Usually, activeFocus is gained by setting focus on an item and its enclosing + FocusScopes. In the following example \c input will have activeFocus. + \qml + Rectangle { + FocusScope { + focus: true + TextInput { + id: input + focus: true + } + } + } + \endqml + + \sa focus, {qmlfocus}{Keyboard Focus} +*/ + +/*! + \qmlproperty bool QtQuick2::Item::focus + This property indicates whether the item has focus within the enclosing focus scope. If true, this item + will gain active focus when the enclosing focus scope gains active focus. + In the following example, \c input will be given active focus when \c scope gains active focus. + \qml + Rectangle { + FocusScope { + id: scope + TextInput { + id: input + focus: true + } + } + } + \endqml + + For the purposes of this property, the scene as a whole is assumed to act like a focus scope. + On a practical level, that means the following QML will give active focus to \c input on startup. + + \qml + Rectangle { + TextInput { + id: input + focus: true + } + } + \endqml + + \sa activeFocus, {qmlfocus}{Keyboard Focus} +*/ + + +/*! + \property QSGItem::anchors + \internal +*/ + +/*! + \property QSGItem::left + \internal +*/ + +/*! + \property QSGItem::right + \internal +*/ + +/*! + \property QSGItem::horizontalCenter + \internal +*/ + +/*! + \property QSGItem::top + \internal +*/ + +/*! + \property QSGItem::bottom + \internal +*/ + +/*! + \property QSGItem::verticalCenter + \internal +*/ + +/*! + \property QSGItem::focus + \internal +*/ + +/*! + \property QSGItem::transform + \internal +*/ + +/*! + \property QSGItem::transformOrigin + \internal +*/ + +/*! + \property QSGItem::activeFocus + \internal +*/ + +/*! + \property QSGItem::baseline + \internal +*/ + +/*! + \property QSGItem::data + \internal +*/ + +/*! + \property QSGItem::resources + \internal +*/ + +/*! + \property QSGItem::state + \internal +*/ + +/*! + \property QSGItem::states + \internal +*/ + +/*! + \property QSGItem::transformOriginPoint + \internal +*/ + +/*! + \property QSGItem::transitions + \internal +*/ + bool QSGItem::event(QEvent *ev) { return QObject::event(ev); diff --git a/src/declarative/items/qsgitem.h b/src/declarative/items/qsgitem.h index f8e52cf848..02a7a33ed8 100644 --- a/src/declarative/items/qsgitem.h +++ b/src/declarative/items/qsgitem.h @@ -375,6 +375,7 @@ protected: virtual void dragExitEvent(QSGDragEvent *event); virtual void dragDropEvent(QSGDragEvent *event); virtual bool childMouseEventFilter(QSGItem *, QEvent *); + virtual void windowDeactivateEvent(); virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); diff --git a/src/declarative/items/qsgitemsmodule.cpp b/src/declarative/items/qsgitemsmodule.cpp index 62a4bd06cb..9359906f25 100644 --- a/src/declarative/items/qsgitemsmodule.cpp +++ b/src/declarative/items/qsgitemsmodule.cpp @@ -126,6 +126,7 @@ static void qt_sgitems_defineModule(const char *uri, int major, int minor) qmlRegisterType<QDeclarativePathPercent>(uri,major,minor,"PathPercent"); qmlRegisterType<QDeclarativePathQuad>(uri,major,minor,"PathQuad"); qmlRegisterType<QSGPathView>(uri,major,minor,"PathView"); + qmlRegisterUncreatableType<QSGBasePositioner>(uri,major,minor,"Positioner","Positioner is an abstract type that is only available as an attached property."); #ifndef QT_NO_VALIDATOR qmlRegisterType<QIntValidator>(uri,major,minor,"IntValidator"); qmlRegisterType<QDoubleValidator>(uri,major,minor,"DoubleValidator"); diff --git a/src/declarative/items/qsgitemview.cpp b/src/declarative/items/qsgitemview.cpp index bcb4a8a97c..5543f12f6d 100644 --- a/src/declarative/items/qsgitemview.cpp +++ b/src/declarative/items/qsgitemview.cpp @@ -7,28 +7,29 @@ ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying ** this package. -** ** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception +** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. ** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. ** ** ** @@ -723,7 +724,6 @@ void QSGItemView::trackedPositionChanged() qreal trackedPos = d->trackedItem->position(); qreal trackedSize = d->trackedItem->size(); if (d->trackedItem != d->currentItem) { - trackedPos -= d->currentItem->sectionSize(); trackedSize += d->currentItem->sectionSize(); } qreal viewPos; @@ -756,16 +756,32 @@ void QSGItemView::trackedPositionChanged() pos = d->startPosition(); } } else { - if (trackedPos < viewPos && d->currentItem->position() < viewPos) { - pos = qMax(trackedPos, d->currentItem->position()); - } else if (d->trackedItem->endPosition() >= viewPos + d->size() - && d->currentItem->endPosition() >= viewPos + d->size()) { - if (d->trackedItem->endPosition() <= d->currentItem->endPosition()) { - pos = d->trackedItem->endPosition() - d->size(); + qreal trackedEndPos = d->trackedItem->endPosition(); + qreal toItemPos = d->currentItem->position(); + qreal toItemEndPos = d->currentItem->endPosition(); + + if (d->header && d->showHeaderForIndex(d->currentIndex)) { + trackedPos -= d->headerSize(); + trackedEndPos -= d->headerSize(); + toItemPos -= d->headerSize(); + toItemEndPos -= d->headerSize(); + } else if (d->footer && d->showFooterForIndex(d->currentIndex)) { + trackedPos += d->footerSize(); + trackedEndPos += d->footerSize(); + toItemPos += d->footerSize(); + toItemEndPos += d->footerSize(); + } + + if (trackedPos < viewPos && toItemPos < viewPos) { + pos = qMax(trackedPos, toItemPos); + } else if (trackedEndPos >= viewPos + d->size() + && toItemEndPos >= viewPos + d->size()) { + if (trackedEndPos <= toItemEndPos) { + pos = trackedEndPos - d->size(); if (trackedSize > d->size()) pos = trackedPos; } else { - pos = d->currentItem->endPosition() - d->size(); + pos = toItemEndPos - d->size(); if (d->currentItem->size() > d->size()) pos = d->currentItem->position(); } @@ -1082,7 +1098,7 @@ int QSGItemViewPrivate::mapFromModel(int modelIndex) const for (int i = 0; i < visibleItems.count(); ++i) { FxViewItem *item = visibleItems.at(i); if (item->index == modelIndex) - return i + visibleIndex; + return i; if (item->index > modelIndex) return -1; } diff --git a/src/declarative/items/qsgitemview_p.h b/src/declarative/items/qsgitemview_p.h index 4c5b91fcde..b784015f66 100644 --- a/src/declarative/items/qsgitemview_p.h +++ b/src/declarative/items/qsgitemview_p.h @@ -7,29 +7,29 @@ ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** ** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception +** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. ** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. ** ** ** diff --git a/src/declarative/items/qsgitemview_p_p.h b/src/declarative/items/qsgitemview_p_p.h index 3113a8b754..f20f4cca3e 100644 --- a/src/declarative/items/qsgitemview_p_p.h +++ b/src/declarative/items/qsgitemview_p_p.h @@ -7,29 +7,29 @@ ** This file is part of the QtDeclarative module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** ** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception +** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. ** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. ** ** ** @@ -95,10 +95,10 @@ public: void adjustMoveParameters(int *from, int *to, int *count) const; virtual void init(); - virtual void updateCurrent(int modelIndex); virtual void clear(); - virtual void regenerate(); virtual void updateViewport(); + + void regenerate(); void layout(); void refill(); void refill(qreal from, qreal to, bool doBuffer = false); @@ -111,6 +111,7 @@ public: QSGItem *createHighlightItem(); QSGItem *createComponentItem(QDeclarativeComponent *component, bool receiveItemGeometryChanges, bool createDefault = false); + void updateCurrent(int modelIndex); void updateTrackedItem(); void updateUnrequestedIndexes(); void updateUnrequestedPositions(); @@ -176,6 +177,8 @@ protected: virtual qreal headerSize() const = 0; virtual qreal footerSize() const = 0; + virtual bool showHeaderForIndex(int index) const = 0; + virtual bool showFooterForIndex(int index) const = 0; virtual void updateHeader() = 0; virtual void updateFooter() = 0; @@ -191,14 +194,14 @@ protected: virtual void visibleItemsChanged() = 0; virtual FxViewItem *newViewItem(int index, QSGItem *item) = 0; - virtual void initializeViewItem(FxViewItem *) {} virtual void repositionPackageItemAt(QSGItem *item, int index) = 0; virtual void layoutVisibleItems() = 0; - - virtual void updateSections() {} virtual void changedVisibleIndex(int newIndex) = 0; + + virtual void initializeViewItem(FxViewItem *) {} virtual void initializeCurrentItem() {} + virtual void updateSections() {} virtual void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry); }; diff --git a/src/declarative/items/qsglistview.cpp b/src/declarative/items/qsglistview.cpp index 641dd35104..3be4a4bd58 100644 --- a/src/declarative/items/qsglistview.cpp +++ b/src/declarative/items/qsglistview.cpp @@ -223,6 +223,8 @@ public: virtual qreal headerSize() const; virtual qreal footerSize() const; + virtual bool showHeaderForIndex(int index) const; + virtual bool showFooterForIndex(int index) const; virtual void updateHeader(); virtual void updateFooter(); @@ -434,6 +436,7 @@ qreal QSGListViewPrivate::snapPosAt(qreal pos) FxViewItem *QSGListViewPrivate::snapItemAt(qreal pos) { FxViewItem *snapItem = 0; + qreal prevItemSize = 0; for (int i = 0; i < visibleItems.count(); ++i) { FxViewItem *item = visibleItems.at(i); if (item->index == -1) @@ -441,8 +444,9 @@ FxViewItem *QSGListViewPrivate::snapItemAt(qreal pos) qreal itemTop = item->position(); if (highlight && itemTop >= pos && item->endPosition() <= pos + highlight->size()) return item; - if (itemTop+item->size()/2 >= pos && itemTop-item->size()/2 < pos) + if (itemTop+item->size()/2 >= pos && itemTop-prevItemSize/2 < pos) snapItem = item; + prevItemSize = item->size(); } return snapItem; } @@ -455,6 +459,7 @@ void QSGListViewPrivate::changedVisibleIndex(int newIndex) void QSGListViewPrivate::init() { + QSGItemViewPrivate::init(); ::memset(sectionCache, 0, sizeof(QSGItem*) * sectionCacheSize); } @@ -498,6 +503,8 @@ FxViewItem *QSGListViewPrivate::newViewItem(int modelIndex, QSGItem *item) void QSGListViewPrivate::initializeViewItem(FxViewItem *item) { + QSGItemViewPrivate::initializeViewItem(item); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item->item); itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); @@ -600,6 +607,8 @@ bool QSGListViewPrivate::removeNonVisibleItems(int bufferFrom, int bufferTo) while (visibleItems.count() > 1 && (item = visibleItems.first()) && item->endPosition() <= bufferFrom) { if (item->attached->delayRemove()) break; + if (item->size() == 0) + break; // qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition(); if (item->index != -1) visibleIndex++; @@ -726,7 +735,8 @@ void QSGListViewPrivate::updateHighlight() { if ((!currentItem && highlight) || (currentItem && !highlight)) createHighlight(); - if (currentItem && autoHighlight && highlight && !movingHorizontally && !movingVertically) { + bool strictHighlight = haveHighlightRange && highlightRange == QSGListView::StrictlyEnforceRange; + if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) { // auto-update highlight FxListItemSG *listItem = static_cast<FxListItemSG*>(currentItem); highlightPosAnimator->to = isRightToLeft() @@ -814,6 +824,8 @@ void QSGListViewPrivate::createSection(FxListItemSG *listItem) void QSGListViewPrivate::updateSections() { + QSGItemViewPrivate::updateSections(); + if (sectionCriteria && !visibleItems.isEmpty()) { QString prevSection; if (visibleIndex > 0) @@ -870,6 +882,8 @@ void QSGListViewPrivate::updateCurrentSection() void QSGListViewPrivate::initializeCurrentItem() { + QSGItemViewPrivate::initializeCurrentItem(); + if (currentItem) { FxListItemSG *listItem = static_cast<FxListItemSG *>(currentItem); @@ -913,6 +927,16 @@ qreal QSGListViewPrivate::footerSize() const return footer ? footer->size() : 0.0; } +bool QSGListViewPrivate::showHeaderForIndex(int index) const +{ + return index == 0; +} + +bool QSGListViewPrivate::showFooterForIndex(int index) const +{ + return index == model->count()-1; +} + void QSGListViewPrivate::updateFooter() { Q_Q(QSGListView); @@ -1241,6 +1265,68 @@ void QSGListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, //---------------------------------------------------------------------------- +/*! + \qmlclass ListView QSGListView + \inqmlmodule QtQuick 2 + \ingroup qml-view-elements + \inherits Flickable + \brief The ListView item provides a list view of items provided by a model. + + A ListView displays data from models created from built-in QML elements like ListModel + and XmlListModel, or custom model classes defined in C++ that inherit from + QAbstractListModel. + + A ListView has a \l model, which defines the data to be displayed, and + a \l delegate, which defines how the data should be displayed. Items in a + ListView are laid out horizontally or vertically. List views are inherently + flickable because ListView inherits from \l Flickable. + + \section1 Example Usage + + The following example shows the definition of a simple list model defined + in a file called \c ContactModel.qml: + + \snippet doc/src/snippets/declarative/listview/ContactModel.qml 0 + + Another component can display this model data in a ListView, like this: + + \snippet doc/src/snippets/declarative/listview/listview.qml import + \codeline + \snippet doc/src/snippets/declarative/listview/listview.qml classdocs simple + + \image listview-simple.png + + Here, the ListView creates a \c ContactModel component for its model, and a \l Text element + for its delegate. The view will create a new \l Text component for each item in the model. Notice + the delegate is able to access the model's \c name and \c number data directly. + + An improved list view is shown below. The delegate is visually improved and is moved + into a separate \c contactDelegate component. + + \snippet doc/src/snippets/declarative/listview/listview.qml classdocs advanced + \image listview-highlight.png + + The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property, + and \c focus is set to \c true to enable keyboard navigation for the list view. + The list view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details). + + Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. + + ListView attaches a number of properties to the root item of the delegate, for example + \c {ListView.isCurrentItem}. In the following example, the root delegate item can access + this attached property directly as \c ListView.isCurrentItem, while the child + \c contactInfo object must refer to this property as \c wrapper.ListView.isCurrentItem. + + \snippet doc/src/snippets/declarative/listview/listview.qml isCurrentItem + + \note Views do not enable \e clip automatically. If the view + is not clipped by another item or the screen, it will be necessary + to set \e {clip: true} in order to have the out of view items clipped + nicely. + + \sa {QML Data Models}, GridView, {declarative/modelviews/listview}{ListView examples} +*/ QSGListView::QSGListView(QSGItem *parent) : QSGItemView(*(new QSGListViewPrivate), parent) { @@ -1250,6 +1336,203 @@ QSGListView::~QSGListView() { } +/*! + \qmlattachedproperty bool QtQuick2::ListView::isCurrentItem + This attached property is true if this delegate is the current item; otherwise false. + + It is attached to each instance of the delegate. + + This property may be used to adjust the appearance of the current item, for example: + + \snippet doc/src/snippets/declarative/listview/listview.qml isCurrentItem +*/ + +/*! + \qmlattachedproperty ListView QtQuick2::ListView::view + This attached property holds the view that manages this delegate instance. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty string QtQuick2::ListView::previousSection + This attached property holds the section of the previous element. + + It is attached to each instance of the delegate. + + The section is evaluated using the \l {ListView::section.property}{section} properties. +*/ + +/*! + \qmlattachedproperty string QtQuick2::ListView::nextSection + This attached property holds the section of the next element. + + It is attached to each instance of the delegate. + + The section is evaluated using the \l {ListView::section.property}{section} properties. +*/ + +/*! + \qmlattachedproperty string QtQuick2::ListView::section + This attached property holds the section of this element. + + It is attached to each instance of the delegate. + + The section is evaluated using the \l {ListView::section.property}{section} properties. +*/ + +/*! + \qmlattachedproperty bool QtQuick2::ListView::delayRemove + This attached property holds whether the delegate may be destroyed. + + It is attached to each instance of the delegate. + + It is sometimes necessary to delay the destruction of an item + until an animation completes. + + The example delegate below ensures that the animation completes before + the item is removed from the list. + + \snippet doc/src/snippets/declarative/listview/listview.qml delayRemove +*/ + +/*! + \qmlattachedsignal QtQuick2::ListView::onAdd() + This attached handler is called immediately after an item is added to the view. +*/ + +/*! + \qmlattachedsignal QtQuick2::ListView::onRemove() + This attached handler is called immediately before an item is removed from the view. +*/ + +/*! + \qmlproperty model QtQuick2::ListView::model + This property holds the model providing data for the list. + + The model provides the set of data that is used to create the items + in the view. Models can be created directly in QML using \l ListModel, \l XmlListModel + or \l VisualItemModel, or provided by C++ model classes. If a C++ model class is + used, it must be a subclass of \l QAbstractItemModel or a simple list. + + \sa {qmlmodels}{Data Models} +*/ + +/*! + \qmlproperty Component QtQuick2::ListView::delegate + + The delegate provides a template defining each item instantiated by the view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + + The number of elements in the delegate has a direct effect on the + flicking performance of the view. If at all possible, place functionality + that is not needed for the normal display of the delegate in a \l Loader which + can load additional elements when needed. + + The ListView will lay out the items based on the size of the root item + in the delegate. + + It is recommended that the delagate's size be a whole number to avoid sub-pixel + alignment of items. + + \note Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. +*/ +/*! + \qmlproperty int QtQuick2::ListView::currentIndex + \qmlproperty Item QtQuick2::ListView::currentItem + + The \c currentIndex property holds the index of the current item, and + \c currentItem holds the current item. Setting the currentIndex to -1 + will clear the highlight and set currentItem to null. + + If highlightFollowsCurrentItem is \c true, setting either of these + properties will smoothly scroll the ListView so that the current + item becomes visible. + + Note that the position of the current item + may only be approximate until it becomes visible in the view. +*/ + +/*! + \qmlproperty Item QtQuick2::ListView::highlightItem + + This holds the highlight item created from the \l highlight component. + + The \c highlightItem is managed by the view unless + \l highlightFollowsCurrentItem is set to false. + + \sa highlight, highlightFollowsCurrentItem +*/ + +/*! + \qmlproperty int QtQuick2::ListView::count + This property holds the number of items in the view. +*/ + +/*! + \qmlproperty Component QtQuick2::ListView::highlight + This property holds the component to use as the highlight. + + An instance of the highlight component is created for each list. + The geometry of the resulting component instance is managed by the list + so as to stay with the current item, unless the highlightFollowsCurrentItem + property is false. + + \sa highlightItem, highlightFollowsCurrentItem, {declarative/modelviews/listview}{ListView examples} +*/ + +/*! + \qmlproperty bool QtQuick2::ListView::highlightFollowsCurrentItem + This property holds whether the highlight is managed by the view. + + If this property is true (the default value), the highlight is moved smoothly + to follow the current item. Otherwise, the + highlight is not moved by the view, and any movement must be implemented + by the highlight. + + Here is a highlight with its motion defined by a \l {SpringAnimation} item: + + \snippet doc/src/snippets/declarative/listview/listview.qml highlightFollowsCurrentItem + + Note that the highlight animation also affects the way that the view + is scrolled. This is because the view moves to maintain the + highlight within the preferred highlight range (or visible viewport). + + \sa highlight, highlightMoveSpeed +*/ +//###Possibly rename these properties, since they are very useful even without a highlight? +/*! + \qmlproperty real QtQuick2::ListView::preferredHighlightBegin + \qmlproperty real QtQuick2::ListView::preferredHighlightEnd + \qmlproperty enumeration QtQuick2::ListView::highlightRangeMode + + These properties define the preferred range of the highlight (for the current item) + within the view. The \c preferredHighlightBegin value must be less than the + \c preferredHighlightEnd value. + + These properties affect the position of the current item when the list is scrolled. + For example, if the currently selected item should stay in the middle of the + list when the view is scrolled, set the \c preferredHighlightBegin and + \c preferredHighlightEnd values to the top and bottom coordinates of where the middle + item would be. If the \c currentItem is changed programmatically, the list will + automatically scroll so that the current item is in the middle of the view. + Furthermore, the behavior of the current item index will occur whether or not a + highlight exists. + + Valid values for \c highlightRangeMode are: + + \list + \o ListView.ApplyRange - the view attempts to maintain the highlight within the range. + However, the highlight can move outside of the range at the ends of the list or due + to mouse interaction. + \o ListView.StrictlyEnforceRange - the highlight never moves outside of the range. + The current item changes if a keyboard or mouse action would cause the highlight to move + outside of the range. + \o ListView.NoHighlightRange - this is the default value. + \endlist +*/ void QSGListView::setHighlightFollowsCurrentItem(bool autoHighlight) { Q_D(QSGListView); @@ -1264,6 +1547,13 @@ void QSGListView::setHighlightFollowsCurrentItem(bool autoHighlight) } } +/*! + \qmlproperty real QtQuick2::ListView::spacing + + This property holds the spacing between items. + + The default value is 0. +*/ qreal QSGListView::spacing() const { Q_D(const QSGListView); @@ -1280,6 +1570,27 @@ void QSGListView::setSpacing(qreal spacing) } } +/*! + \qmlproperty enumeration QtQuick2::ListView::orientation + This property holds the orientation of the list. + + Possible values: + + \list + \o ListView.Horizontal - Items are laid out horizontally + \o ListView.Vertical (default) - Items are laid out vertically + \endlist + + \table + \row + \o Horizontal orientation: + \image ListViewHorizontal.png + + \row + \o Vertical orientation: + \image listview-highlight.png + \endtable +*/ QSGListView::Orientation QSGListView::orientation() const { Q_D(const QSGListView); @@ -1305,6 +1616,116 @@ void QSGListView::setOrientation(QSGListView::Orientation orientation) } } +/*! + \qmlproperty enumeration QtQuick2::ListView::layoutDirection + This property holds the layout direction of the horizontal list. + + Possible values: + + \list + \o Qt.LeftToRight (default) - Items will be laid out from left to right. + \o Qt.RightToLeft - Items will be laid out from right to let. + \endlist + + \sa ListView::effectiveLayoutDirection +*/ + + +/*! + \qmlproperty enumeration QtQuick2::ListView::effectiveLayoutDirection + This property holds the effective layout direction of the horizontal list. + + When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, + the visual layout direction of the horizontal list will be mirrored. However, the + property \l {ListView::layoutDirection}{layoutDirection} will remain unchanged. + + \sa ListView::layoutDirection, {LayoutMirroring}{LayoutMirroring} +*/ + +/*! + \qmlproperty bool QtQuick2::ListView::keyNavigationWraps + This property holds whether the list wraps key navigation. + + If this is true, key navigation that would move the current item selection + past the end of the list instead wraps around and moves the selection to + the start of the list, and vice-versa. + + By default, key navigation is not wrapped. +*/ + + +/*! + \qmlproperty int QtQuick2::ListView::cacheBuffer + This property determines whether delegates are retained outside the + visible area of the view. + + If this value is non-zero, the view keeps as many delegates + instantiated as it can fit within the buffer specified. For example, + if in a vertical view the delegate is 20 pixels high and \c cacheBuffer is + set to 40, then up to 2 delegates above and 2 delegates below the visible + area may be retained. + + Note that cacheBuffer is not a pixel buffer - it only maintains additional + instantiated delegates. + + Setting this value can improve the smoothness of scrolling behavior at the expense + of additional memory usage. It is not a substitute for creating efficient + delegates; the fewer elements in a delegate, the faster a view can be + scrolled. +*/ + + +/*! + \qmlproperty string QtQuick2::ListView::section.property + \qmlproperty enumeration QtQuick2::ListView::section.criteria + \qmlproperty Component QtQuick2::ListView::section.delegate + + These properties hold the expression to be evaluated for the \l section attached property. + + The \l section attached property enables a ListView to be visually + separated into different parts. These properties determine how sections + are created. + + \c section.property holds the name of the property that is the basis + of each section. + + \c section.criteria holds the criteria for forming each section based on + \c section.property. This value can be one of: + + \list + \o ViewSection.FullString (default) - sections are created based on the + \c section.property value. + \o ViewSection.FirstCharacter - sections are created based on the first + character of the \c section.property value (for example, 'A', 'B', 'C' + sections, etc. for an address book) + \endlist + + \c section.delegate holds the delegate component for each section. + + Each item in the list has attached properties named \c ListView.section, + \c ListView.previousSection and \c ListView.nextSection. These may be + used to place a section header for related items. + + For example, here is a ListView that displays a list of animals, separated + into sections. Each item in the ListView is placed in a different section + depending on the "size" property of the model item. The \c sectionHeading + delegate component provides the light blue bar that marks the beginning of + each section. + + + \snippet examples/declarative/modelviews/listview/sections.qml 0 + + \image qml-listview-sections-example.png + + \note Adding sections to a ListView does not automatically re-order the + list items by the section criteria. + If the model is not ordered by section, then it is possible that + the sections created will not be unique; each boundary between + differing sections will result in a section header being created + even if that section exists elsewhere. + + \sa {declarative/modelviews/listview}{ListView examples} +*/ QSGViewSection *QSGListView::sectionCriteria() { Q_D(QSGListView); @@ -1315,12 +1736,35 @@ QSGViewSection *QSGListView::sectionCriteria() return d->sectionCriteria; } +/*! + \qmlproperty string QtQuick2::ListView::currentSection + This property holds the section that is currently at the beginning of the view. +*/ QString QSGListView::currentSection() const { Q_D(const QSGListView); return d->currentSection; } +/*! + \qmlproperty real QtQuick2::ListView::highlightMoveSpeed + \qmlproperty int QtQuick2::ListView::highlightMoveDuration + \qmlproperty real QtQuick2::ListView::highlightResizeSpeed + \qmlproperty int QtQuick2::ListView::highlightResizeDuration + + These properties hold the move and resize animation speed of the highlight delegate. + + \l highlightFollowsCurrentItem must be true for these properties + to have effect. + + The default value for the speed properties is 400 pixels/second. + The default value for the duration properties is -1, i.e. the + highlight will take as much time as necessary to move at the set speed. + + These properties have the same characteristics as a SmoothedAnimation. + + \sa highlightFollowsCurrentItem +*/ qreal QSGListView::highlightMoveSpeed() const { Q_D(const QSGListView); @@ -1382,6 +1826,27 @@ void QSGListView::setHighlightResizeDuration(int duration) } } +/*! + \qmlproperty enumeration QtQuick2::ListView::snapMode + + This property determines how the view scrolling will settle following a drag or flick. + The possible values are: + + \list + \o ListView.NoSnap (default) - the view stops anywhere within the visible area. + \o ListView.SnapToItem - the view settles with an item aligned with the start of + the view. + \o ListView.SnapOneItem - the view settles no more than one item away from the first + visible item at the time the mouse button is released. This mode is particularly + useful for moving one page at a time. + \endlist + + \c snapMode does not affect the \l currentIndex. To update the + \l currentIndex as the list is moved, set \l highlightRangeMode + to \c ListView.StrictlyEnforceRange. + + \sa highlightRangeMode +*/ QSGListView::SnapMode QSGListView::snapMode() const { Q_D(const QSGListView); @@ -1397,6 +1862,28 @@ void QSGListView::setSnapMode(SnapMode mode) } } + +/*! + \qmlproperty Component QtQuick2::ListView::footer + This property holds the component to use as the footer. + + An instance of the footer component is created for each view. The + footer is positioned at the end of the view, after any items. + + \sa header +*/ + + +/*! + \qmlproperty Component QtQuick2::ListView::header + This property holds the component to use as the header. + + An instance of the header component is created for each view. The + header is positioned at the beginning of the view, before any items. + + \sa footer +*/ + void QSGListView::viewportMoved() { Q_D(QSGListView); @@ -1432,8 +1919,12 @@ void QSGListView::viewportMoved() pos = viewPos + highlightEnd - d->highlight->size(); if (pos < viewPos + highlightStart) pos = viewPos + highlightStart; - d->highlightPosAnimator->stop(); - static_cast<FxListItemSG*>(d->highlight)->setPosition(qRound(pos)); + if (pos != d->highlight->position()) { + d->highlightPosAnimator->stop(); + static_cast<FxListItemSG*>(d->highlight)->setPosition(pos); + } else { + d->updateHighlight(); + } // update current index if (FxViewItem *snapItem = d->snapItemAt(d->highlight->position())) { @@ -1527,6 +2018,15 @@ void QSGListView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGe } +/*! + \qmlmethod QtQuick2::ListView::incrementCurrentIndex() + + Increments the current index. The current index will wrap + if keyNavigationWraps is true and it is currently at the end. + This method has no effect if the \l count is zero. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGListView::incrementCurrentIndex() { Q_D(QSGListView); @@ -1538,6 +2038,15 @@ void QSGListView::incrementCurrentIndex() } } +/*! + \qmlmethod QtQuick2::ListView::decrementCurrentIndex() + + Decrements the current index. The current index will wrap + if keyNavigationWraps is true and it is currently at the beginning. + This method has no effect if the \l count is zero. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGListView::decrementCurrentIndex() { Q_D(QSGListView); @@ -1566,7 +2075,7 @@ void QSGListView::updateSections() void QSGListView::itemsInserted(int modelIndex, int count) { Q_D(QSGListView); - if (!isComponentComplete()) + if (!isComponentComplete() || !d->model || !d->model->isValid()) return; d->updateUnrequestedIndexes(); d->moveReason = QSGListViewPrivate::Other; @@ -1706,7 +2215,7 @@ void QSGListView::itemsInserted(int modelIndex, int count) void QSGListView::itemsRemoved(int modelIndex, int count) { Q_D(QSGListView); - if (!isComponentComplete()) + if (!isComponentComplete() || !d->model || !d->model->isValid()) return; d->moveReason = QSGListViewPrivate::Other; d->updateUnrequestedIndexes(); @@ -1803,7 +2312,7 @@ void QSGListView::itemsRemoved(int modelIndex, int count) void QSGListView::itemsMoved(int from, int to, int count) { Q_D(QSGListView); - if (!isComponentComplete()) + if (!isComponentComplete() || !d->isValid()) return; d->updateUnrequestedIndexes(); @@ -1926,6 +2435,72 @@ void QSGListView::itemsMoved(int from, int to, int count) d->layout(); } +/*! + \qmlmethod QtQuick2::ListView::positionViewAtIndex(int index, PositionMode mode) + + Positions the view such that the \a index is at the position specified by + \a mode: + + \list + \o ListView.Beginning - position item at the top (or left for horizontal orientation) of the view. + \o ListView.Center - position item in the center of the view. + \o ListView.End - position item at bottom (or right for horizontal orientation) of the view. + \o ListView.Visible - if any part of the item is visible then take no action, otherwise + bring the item into view. + \o ListView.Contain - ensure the entire item is visible. If the item is larger than + the view the item is positioned at the top (or left for horizontal orientation) of the view. + \endlist + + If positioning the view at \a index would cause empty space to be displayed at + the beginning or end of the view, the view will be positioned at the boundary. + + It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view + at a particular index. This is unreliable since removing items from the start + of the list does not cause all other items to be repositioned, and because + the actual start of the view can vary based on the size of the delegates. + The correct way to bring an item into view is with \c positionViewAtIndex. + + \bold Note: methods should only be called after the Component has completed. To position + the view at startup, this method should be called by Component.onCompleted. For + example, to position the view at the end: + + \code + Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning) + \endcode +*/ + +/*! + \qmlmethod QtQuick2::ListView::positionViewAtBeginning() + \qmlmethod QtQuick2::ListView::positionViewAtEnd() + + Positions the view at the beginning or end, taking into account any header or footer. + + It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view + at a particular index. This is unreliable since removing items from the start + of the list does not cause all other items to be repositioned, and because + the actual start of the view can vary based on the size of the delegates. + + \bold Note: methods should only be called after the Component has completed. To position + the view at startup, this method should be called by Component.onCompleted. For + example, to position the view at the end on startup: + + \code + Component.onCompleted: positionViewAtEnd() + \endcode +*/ + +/*! + \qmlmethod int QtQuick2::ListView::indexAt(int x, int y) + + Returns the index of the visible item containing the point \a x, \a y in content + coordinates. If there is no item at the point specified, or the item is + not visible -1 is returned. + + If the item is outside the visible area, -1 is returned, regardless of + whether an item will exist at that point when scrolled into view. + + \bold Note: methods should only be called after the Component has completed. +*/ QSGListViewAttached *QSGListView::qmlAttachedProperties(QObject *obj) { diff --git a/src/declarative/items/qsgloader.cpp b/src/declarative/items/qsgloader.cpp index ccf16896fb..999c174ee9 100644 --- a/src/declarative/items/qsgloader.cpp +++ b/src/declarative/items/qsgloader.cpp @@ -103,6 +103,115 @@ void QSGLoaderPrivate::initResize() _q_updateSize(); } +/*! + \qmlclass Loader QSGLoader + \inqmlmodule QtQuick 2 + \ingroup qml-utility-elements + \inherits Item + + \brief The Loader item allows dynamically loading an Item-based + subtree from a URL or Component. + + Loader is used to dynamically load visual QML components. It can load a + QML file (using the \l source property) or a \l Component object (using + the \l sourceComponent property). It is useful for delaying the creation + of a component until it is required: for example, when a component should + be created on demand, or when a component should not be created + unnecessarily for performance reasons. + + Here is a Loader that loads "Page1.qml" as a component when the + \l MouseArea is clicked: + + \snippet doc/src/snippets/declarative/loader/simple.qml 0 + + The loaded item can be accessed using the \l item property. + + If the \l source or \l sourceComponent changes, any previously instantiated + items are destroyed. Setting \l source to an empty string or setting + \l sourceComponent to \c undefined destroys the currently loaded item, + freeing resources and leaving the Loader empty. + + \section2 Loader sizing behavior + + Loader is like any other visual item and must be positioned and sized + accordingly to become visible. + + \list + \o If an explicit size is not specified for the Loader, the Loader + is automatically resized to the size of the loaded item once the + component is loaded. + \o If the size of the Loader is specified explicitly by setting + the width, height or by anchoring, the loaded item will be resized + to the size of the Loader. + \endlist + + In both scenarios the size of the item and the Loader are identical. + This ensures that anchoring to the Loader is equivalent to anchoring + to the loaded item. + + \table + \row + \o sizeloader.qml + \o sizeitem.qml + \row + \o \snippet doc/src/snippets/declarative/loader/sizeloader.qml 0 + \o \snippet doc/src/snippets/declarative/loader/sizeitem.qml 0 + \row + \o The red rectangle will be sized to the size of the root item. + \o The red rectangle will be 50x50, centered in the root item. + \endtable + + + \section2 Receiving signals from loaded items + + Any signals emitted from the loaded item can be received using the + \l Connections element. For example, the following \c application.qml + loads \c MyItem.qml, and is able to receive the \c message signal from + the loaded item through a \l Connections object: + + \table + \row + \o application.qml + \o MyItem.qml + \row + \o \snippet doc/src/snippets/declarative/loader/connections.qml 0 + \o \snippet doc/src/snippets/declarative/loader/MyItem.qml 0 + \endtable + + Alternatively, since \c MyItem.qml is loaded within the scope of the + Loader, it could also directly call any function defined in the Loader or + its parent \l Item. + + + \section2 Focus and key events + + Loader is a focus scope. Its \l {Item::}{focus} property must be set to + \c true for any of its children to get the \e {active focus}. (See + \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} + for more details.) Any key events received in the loaded item should likely + also be \l {KeyEvent::}{accepted} so they are not propagated to the Loader. + + For example, the following \c application.qml loads \c KeyReader.qml when + the \l MouseArea is clicked. Notice the \l {Item::}{focus} property is + set to \c true for the Loader as well as the \l Item in the dynamically + loaded object: + + \table + \row + \o application.qml + \o KeyReader.qml + \row + \o \snippet doc/src/snippets/declarative/loader/focus.qml 0 + \o \snippet doc/src/snippets/declarative/loader/KeyReader.qml 0 + \endtable + + Once \c KeyReader.qml is loaded, it accepts key events and sets + \c event.accepted to \c true so that the event is not propagated to the + parent \l Rectangle. + + \sa {dynamic-object-creation}{Dynamic Object Creation} +*/ + QSGLoader::QSGLoader(QSGItem *parent) : QSGImplicitSizeItem(*(new QSGLoaderPrivate), parent) { @@ -118,6 +227,19 @@ QSGLoader::~QSGLoader() } } +/*! + \qmlproperty url QtQuick2::Loader::source + This property holds the URL of the QML component to instantiate. + + Note the QML component must be an \l{Item}-based component. The loader + cannot load non-visual components. + + To unload the currently loaded item, set this property to an empty string, + or set \l sourceComponent to \c undefined. Setting \c source to a + new URL will also cause the item created by the previous URL to be unloaded. + + \sa sourceComponent, status, progress +*/ QUrl QSGLoader::source() const { Q_D(const QSGLoader); @@ -148,6 +270,28 @@ void QSGLoader::setSource(const QUrl &url) d->load(); } +/*! + \qmlproperty Component QtQuick2::Loader::sourceComponent + This property holds the \l{Component} to instantiate. + + \qml + Item { + Component { + id: redSquare + Rectangle { color: "red"; width: 10; height: 10 } + } + + Loader { sourceComponent: redSquare } + Loader { sourceComponent: redSquare; x: 10 } + } + \endqml + + To unload the currently loaded item, set this property to an empty string + or \c undefined. + + \sa source, progress +*/ + QDeclarativeComponent *QSGLoader::sourceComponent() const { Q_D(const QSGLoader); @@ -270,6 +414,46 @@ void QSGLoaderPrivate::_q_sourceLoaded() } } +/*! + \qmlproperty enumeration QtQuick2::Loader::status + + This property holds the status of QML loading. It can be one of: + \list + \o Loader.Null - no QML source has been set + \o Loader.Ready - the QML source has been loaded + \o Loader.Loading - the QML source is currently being loaded + \o Loader.Error - an error occurred while loading the QML source + \endlist + + Use this status to provide an update or respond to the status change in some way. + For example, you could: + + \list + \o Trigger a state change: + \qml + State { name: 'loaded'; when: loader.status == Loader.Ready } + \endqml + + \o Implement an \c onStatusChanged signal handler: + \qml + Loader { + id: loader + onStatusChanged: if (loader.status == Loader.Ready) console.log('Loaded') + } + \endqml + + \o Bind to the status value: + \qml + Text { text: loader.status == Loader.Ready ? 'Loaded' : 'Not loaded' } + \endqml + \endlist + + Note that if the source is a local file, the status will initially be Ready (or Error). While + there will be no onStatusChanged signal in that case, the onLoaded will still be invoked. + + \sa progress +*/ + QSGLoader::Status QSGLoader::status() const { Q_D(const QSGLoader); @@ -290,6 +474,23 @@ void QSGLoader::componentComplete() d->load(); } +/*! + \qmlsignal QtQuick2::Loader::onLoaded() + + This handler is called when the \l status becomes \c Loader.Ready, or on successful + initial load. +*/ + + +/*! +\qmlproperty real QtQuick2::Loader::progress + +This property holds the progress of loading QML data from the network, from +0.0 (nothing loaded) to 1.0 (finished). Most QML files are quite small, so +this value will rapidly change from 0 to 1. + +\sa status +*/ qreal QSGLoader::progress() const { Q_D(const QSGLoader); @@ -328,6 +529,10 @@ void QSGLoaderPrivate::_q_updateSize(bool loaderGeometryChanged) updatingSize = false; } +/*! + \qmlproperty Item QtQuick2::Loader::item + This property holds the top-level item that is currently loaded. +*/ QSGItem *QSGLoader::item() const { Q_D(const QSGLoader); diff --git a/src/declarative/items/qsgmousearea.cpp b/src/declarative/items/qsgmousearea.cpp index b35c14624a..7d86d2e248 100644 --- a/src/declarative/items/qsgmousearea.cpp +++ b/src/declarative/items/qsgmousearea.cpp @@ -85,7 +85,7 @@ void QSGDrag::resetTarget() } /*! - \qmlproperty Item MouseArea::drag.dropItem + \qmlproperty Item QtQuick2::MouseArea::drag.dropItem This property holds the item an active drag will be dropped on if released at the current position. @@ -115,7 +115,7 @@ void QSGDrag::setGrabItem(QSGItem *item) } /*! - \qmlproperty variant MouseArea::drag.data + \qmlproperty variant QtQuick2::MouseArea::drag.data This property holds the data sent to recipients of drag events generated by a MouseArea. @@ -234,7 +234,7 @@ void QSGDrag::setFilterChildren(bool filter) } /*! - \qmlproperty stringlist MouseArea::drag.keys + \qmlproperty stringlist QtQuick2::MouseArea::drag.keys This property holds a list of keys drag recipients can use to identify the source or data type of a drag event. @@ -272,7 +272,7 @@ void QSGMouseAreaPrivate::init() q->setFiltersChildMouseEvents(true); } -void QSGMouseAreaPrivate::saveEvent(QGraphicsSceneMouseEvent *event) +void QSGMouseAreaPrivate::saveEvent(QGraphicsSceneMouseEvent *event) { lastPos = event->pos(); lastScenePos = event->scenePos(); @@ -287,14 +287,14 @@ void QSGMouseAreaPrivate::forwardEvent(QGraphicsSceneMouseEvent* event) event->setPos(q->mapFromScene(event->scenePos())); } -bool QSGMouseAreaPrivate::isPressAndHoldConnected() +bool QSGMouseAreaPrivate::isPressAndHoldConnected() { Q_Q(QSGMouseArea); static int idx = QObjectPrivate::get(q)->signalIndex("pressAndHold(QSGMouseEvent*)"); return QObjectPrivate::get(q)->isSignalConnected(idx); } -bool QSGMouseAreaPrivate::isDoubleClickConnected() +bool QSGMouseAreaPrivate::isDoubleClickConnected() { Q_Q(QSGMouseArea); static int idx = QObjectPrivate::get(q)->signalIndex("doubleClicked(QSGMouseEvent*)"); @@ -373,7 +373,65 @@ bool QSGMouseAreaPrivate::propagateHelper(QSGMouseEvent *ev, QSGItem *item,const } -/* +/*! + \qmlclass MouseArea QSGMouseArea + \inqmlmodule QtQuick 2 + \ingroup qml-basic-interaction-elements + \brief The MouseArea item enables simple mouse handling. + \inherits Item + + A MouseArea is an invisible item that is typically used in conjunction with + a visible item in order to provide mouse handling for that item. + By effectively acting as a proxy, the logic for mouse handling can be + contained within a MouseArea item. + + For basic key handling, see the \l{Keys}{Keys attached property}. + + The \l enabled property is used to enable and disable mouse handling for + the proxied item. When disabled, the mouse area becomes transparent to + mouse events. + + The \l pressed read-only property indicates whether or not the user is + holding down a mouse button over the mouse area. This property is often + used in bindings between properties in a user interface. The containsMouse + read-only property indicates the presence of the mouse cursor over the + mouse area but, by default, only when a mouse button is held down; see below + for further details. + + Information about the mouse position and button clicks are provided via + signals for which event handler properties are defined. The most commonly + used involved handling mouse presses and clicks: onClicked, onDoubleClicked, + onPressed, onReleased and onPressAndHold. + + By default, MouseArea items only report mouse clicks and not changes to the + position of the mouse cursor. Setting the hoverEnabled property ensures that + handlers defined for onPositionChanged, onEntered and onExited are used and + that the containsMouse property is updated even when no mouse buttons are + pressed. + + \section1 Example Usage + + \div {class="float-right"} + \inlineimage qml-mousearea-snippet.png + \enddiv + + The following example uses a MouseArea in a \l Rectangle that changes + the \l Rectangle color to red when clicked: + + \snippet doc/src/snippets/declarative/mousearea/mousearea.qml import + \codeline + \snippet doc/src/snippets/declarative/mousearea/mousearea.qml intro + + \clearfloat + Many MouseArea signals pass a \l{MouseEvent}{mouse} parameter that contains + additional information about the mouse event, such as the position, button, + and any key modifiers. + + Here is an extension of the previous example that produces a different + color when the area is right clicked: + + \snippet doc/src/snippets/declarative/mousearea/mousearea.qml intro-extended + Behavioral Change in QtQuick 2.0 From QtQuick 2.0, the signals clicked, doubleClicked and pressAndHold have a different interaction @@ -385,7 +443,149 @@ bool QSGMouseAreaPrivate::propagateHelper(QSGMouseEvent *ev, QSGItem *item,const Note that to get the same behavior as a QtQuick 1.0 MouseArea{} with regard to absorbing all mouse events, you will now need to add empty signal handlers for these three signals. - */ + + \sa MouseEvent, {declarative/touchinteraction/mousearea}{MouseArea example} +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onEntered() + + This handler is called when the mouse enters the mouse area. + + By default the onEntered handler is only called while a button is + pressed. Setting hoverEnabled to true enables handling of + onEntered when no mouse button is pressed. + + \sa hoverEnabled +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onExited() + + This handler is called when the mouse exits the mouse area. + + By default the onExited handler is only called while a button is + pressed. Setting hoverEnabled to true enables handling of + onExited when no mouse button is pressed. + + The example below shows a fairly typical relationship between + two MouseAreas, with \c mouseArea2 on top of \c mouseArea1. Moving the + mouse into \c mouseArea2 from \c mouseArea1 will cause \c onExited + to be called for \c mouseArea1. + \qml + Rectangle { + width: 400; height: 400 + MouseArea { + id: mouseArea1 + anchors.fill: parent + hoverEnabled: true + } + MouseArea { + id: mouseArea2 + width: 100; height: 100 + anchors.centerIn: parent + hoverEnabled: true + } + } + \endqml + + If instead you give the two mouseAreas a parent-child relationship, + moving the mouse into \c mouseArea2 from \c mouseArea1 will \b not + cause \c onExited to be called for \c mouseArea1. Instead, they will + both be considered to be simultaneously hovered. + + \sa hoverEnabled +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onPositionChanged(MouseEvent mouse) + + This handler is called when the mouse position changes. + + The \l {MouseEvent}{mouse} parameter provides information about the mouse, including the x and y + position, and any buttons currently pressed. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. + + By default the onPositionChanged handler is only called while a button is + pressed. Setting hoverEnabled to true enables handling of + onPositionChanged when no mouse button is pressed. +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onClicked(MouseEvent mouse) + + This handler is called when there is a click. A click is defined as a press followed by a release, + both inside the MouseArea (pressing, moving outside the MouseArea, and then moving back inside and + releasing is also considered a click). + + The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y + position of the release of the click, and whether the click was held. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onPressed(MouseEvent mouse) + + This handler is called when there is a press. + The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y + position and which button was pressed. + + The \e accepted property of the MouseEvent parameter determines whether this MouseArea + will handle the press and all future mouse events until release. The default is to accept + the event and not allow other MouseArea beneath this one to handle the event. If \e accepted + is set to false, no further events will be sent to this MouseArea until the button is next + pressed. +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onReleased(MouseEvent mouse) + + This handler is called when there is a release. + The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y + position of the release of the click, and whether the click was held. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. + + \sa onCanceled +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onPressAndHold(MouseEvent mouse) + + This handler is called when there is a long press (currently 800ms). + The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y + position of the press, and which button is pressed. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onDoubleClicked(MouseEvent mouse) + + This handler is called when there is a double-click (a press followed by a release followed by a press). + The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y + position of the release of the click, and whether the click was held. + + If the \e accepted property of the \l {MouseEvent}{mouse} parameter is set to false + in the handler, the onPressed/onReleased/onClicked handlers will be called for the second + click; otherwise they are suppressed. The accepted property defaults to true. +*/ + +/*! + \qmlsignal QtQuick2::MouseArea::onCanceled() + + This handler is called when mouse events have been canceled, either because an event was not accepted, or + because another element stole the mouse event handling. + + This signal is for advanced use: it is useful when there is more than one MouseArea + that is handling input, or when there is a MouseArea inside a \l Flickable. In the latter + case, if you execute some logic on the pressed signal and then start dragging, the + \l Flickable will steal the mouse handling from the MouseArea. In these cases, to reset + the logic when the MouseArea has lost the mouse handling to the \l Flickable, + \c onCanceled should be used in addition to onReleased. +*/ QSGMouseArea::QSGMouseArea(QSGItem *parent) : QSGItem(*(new QSGMouseAreaPrivate), parent) { @@ -397,6 +597,25 @@ QSGMouseArea::~QSGMouseArea() { } +/*! + \qmlproperty real QtQuick2::MouseArea::mouseX + \qmlproperty real QtQuick2::MouseArea::mouseY + These properties hold the coordinates of the mouse cursor. + + If the hoverEnabled property is false then these properties will only be valid + while a button is pressed, and will remain valid as long as the button is held + down even if the mouse is moved outside the area. + + By default, this property is false. + + If hoverEnabled is true then these properties will be valid when: + \list + \i no button is pressed, but the mouse is within the MouseArea (containsMouse is true). + \i a button is pressed and held, even if it has since moved out of the area. + \endlist + + The coordinates are relative to the MouseArea. +*/ qreal QSGMouseArea::mouseX() const { Q_D(const QSGMouseArea); @@ -409,6 +628,12 @@ qreal QSGMouseArea::mouseY() const return d->lastPos.y(); } +/*! + \qmlproperty bool QtQuick2::MouseArea::enabled + This property holds whether the item accepts mouse events. + + By default, this property is true. +*/ bool QSGMouseArea::isEnabled() const { Q_D(const QSGMouseArea); @@ -424,6 +649,22 @@ void QSGMouseArea::setEnabled(bool a) } } +/*! + \qmlproperty bool QtQuick2::MouseArea::preventStealing + This property holds whether the mouse events may be stolen from this + MouseArea. + + If a MouseArea is placed within an item that filters child mouse + events, such as Flickable, the mouse + events may be stolen from the MouseArea if a gesture is recognized + by the parent element, e.g. a flick gesture. If preventStealing is + set to true, no element will steal the mouse events. + + Note that setting preventStealing to true once an element has started + stealing events will have no effect until the next press event. + + By default this property is false. +*/ bool QSGMouseArea::preventStealing() const { Q_D(const QSGMouseArea); @@ -440,6 +681,23 @@ void QSGMouseArea::setPreventStealing(bool prevent) } } +/*! + \qmlproperty MouseButtons QtQuick2::MouseArea::pressedButtons + This property holds the mouse buttons currently pressed. + + It contains a bitwise combination of: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MiddleButton + \endlist + + The code below displays "right" when the right mouse buttons is pressed: + + \snippet doc/src/snippets/declarative/mousearea/mousearea.qml mousebuttons + + \sa acceptedButtons +*/ Qt::MouseButtons QSGMouseArea::pressedButtons() const { Q_D(const QSGMouseArea); @@ -682,7 +940,7 @@ void QSGMouseArea::hoverLeaveEvent(QHoverEvent *event) setHovered(false); } -void QSGMouseArea::mouseUngrabEvent() +void QSGMouseArea::ungrabMouse() { Q_D(QSGMouseArea); if (d->pressed) { @@ -700,6 +958,11 @@ void QSGMouseArea::mouseUngrabEvent() } } +void QSGMouseArea::mouseUngrabEvent() +{ + ungrabMouse(); +} + bool QSGMouseArea::sendMouseEvent(QGraphicsSceneMouseEvent *event) { Q_D(QSGMouseArea); @@ -794,6 +1057,12 @@ void QSGMouseArea::timerEvent(QTimerEvent *event) } } +void QSGMouseArea::windowDeactivateEvent() +{ + ungrabMouse(); + QSGItem::windowDeactivateEvent(); +} + void QSGMouseArea::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { @@ -821,6 +1090,17 @@ void QSGMouseArea::itemChange(ItemChange change, const ItemChangeData &value) QSGItem::itemChange(change, value); } +/*! + \qmlproperty bool QtQuick2::MouseArea::hoverEnabled + This property holds whether hover events are handled. + + By default, mouse events are only handled in response to a button event, or when a button is + pressed. Hover enables handling of all mouse events even when no mouse button is + pressed. + + This property affects the containsMouse property and the onEntered, onExited and + onPositionChanged signals. +*/ bool QSGMouseArea::hoverEnabled() const { return acceptHoverEvents(); @@ -834,16 +1114,26 @@ void QSGMouseArea::setHoverEnabled(bool h) setAcceptHoverEvents(h); emit hoverEnabledChanged(); - if (d->hovered != isUnderMouse()) - setHovered(!d->hovered); } + +/*! + \qmlproperty bool QtQuick2::MouseArea::containsMouse + This property holds whether the mouse is currently inside the mouse area. + + \warning This property is not updated if the area moves under the mouse: \e containsMouse will not change. + In addition, if hoverEnabled is false, containsMouse will only be valid when the mouse is pressed. +*/ bool QSGMouseArea::hovered() const { Q_D(const QSGMouseArea); return d->hovered; } +/*! + \qmlproperty bool QtQuick2::MouseArea::pressed + This property holds whether the mouse area is currently pressed. +*/ bool QSGMouseArea::pressed() const { Q_D(const QSGMouseArea); @@ -859,7 +1149,26 @@ void QSGMouseArea::setHovered(bool h) d->hovered ? emit entered() : emit exited(); } } +/*! + \qmlproperty QtQuick2::Qt::MouseButtons MouseArea::acceptedButtons + This property holds the mouse buttons that the mouse area reacts to. + The available buttons are: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MiddleButton + \endlist + + To accept more than one button the flags can be combined with the + "|" (or) operator: + + \code + MouseArea { acceptedButtons: Qt.LeftButton | Qt.RightButton } + \endcode + + The default value is \c Qt.LeftButton. +*/ Qt::MouseButtons QSGMouseArea::acceptedButtons() const { return acceptedMouseButtons(); @@ -907,6 +1216,43 @@ bool QSGMouseArea::setPressed(bool p) return false; } +/*! + \qmlproperty Item QtQuick2::MouseArea::drag.target + \qmlproperty bool QtQuick2::MouseArea::drag.active + \qmlproperty enumeration QtQuick2::MouseArea::drag.axis + \qmlproperty real QtQuick2::MouseArea::drag.minimumX + \qmlproperty real QtQuick2::MouseArea::drag.maximumX + \qmlproperty real QtQuick2::MouseArea::drag.minimumY + \qmlproperty real QtQuick2::MouseArea::drag.maximumY + \qmlproperty bool QtQuick2::MouseArea::drag.filterChildren + + \c drag provides a convenient way to make an item draggable. + + \list + \i \c drag.target specifies the id of the item to drag. + \i \c drag.active specifies if the target item is currently being dragged. + \i \c drag.axis specifies whether dragging can be done horizontally (\c Drag.XAxis), vertically (\c Drag.YAxis), or both (\c Drag.XandYAxis) + \i \c drag.minimum and \c drag.maximum limit how far the target can be dragged along the corresponding axes. + \endlist + + The following example displays a \l Rectangle that can be dragged along the X-axis. The opacity + of the rectangle is reduced when it is dragged to the right. + + \snippet doc/src/snippets/declarative/mousearea/mousearea.qml drag + + \note Items cannot be dragged if they are anchored for the requested + \c drag.axis. For example, if \c anchors.left or \c anchors.right was set + for \c rect in the above example, it cannot be dragged along the X-axis. + This can be avoided by settng the anchor value to \c undefined in + an \l onPressed handler. + + If \c drag.filterChildren is set to true, a drag can override descendant MouseAreas. This + enables a parent MouseArea to handle drags, for example, while descendants handle clicks: + + \snippet doc/src/snippets/declarative/mousearea/mouseareadragfilter.qml dragfilter + +*/ + QSGDrag *QSGMouseArea::drag() { Q_D(QSGMouseArea); diff --git a/src/declarative/items/qsgmousearea_p.h b/src/declarative/items/qsgmousearea_p.h index aac829f6a9..ff3863c3bb 100644 --- a/src/declarative/items/qsgmousearea_p.h +++ b/src/declarative/items/qsgmousearea_p.h @@ -223,6 +223,7 @@ protected: virtual void hoverLeaveEvent(QHoverEvent *event); virtual bool childMouseEventFilter(QSGItem *i, QEvent *e); virtual void timerEvent(QTimerEvent *event); + virtual void windowDeactivateEvent(); virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); @@ -231,6 +232,7 @@ protected: private: void handlePress(); void handleRelease(); + void ungrabMouse(); private: Q_DISABLE_COPY(QSGMouseArea) diff --git a/src/declarative/items/qsgpainteditem.cpp b/src/declarative/items/qsgpainteditem.cpp index d4543c94f4..95aa2b4fb2 100644 --- a/src/declarative/items/qsgpainteditem.cpp +++ b/src/declarative/items/qsgpainteditem.cpp @@ -150,15 +150,10 @@ void QSGPaintedItem::update(const QRect &rect) Q_D(QSGPaintedItem); d->contentsDirty = true; - QRect srect(qCeil(rect.x()*d->contentsScale), - qCeil(rect.y()*d->contentsScale), - qCeil(rect.width()*d->contentsScale), - qCeil(rect.height()*d->contentsScale)); - - if (srect.isNull() && !d->dirtyRect.isNull()) + if (rect.isNull() && !d->dirtyRect.isNull()) d->dirtyRect = contentsBoundingRect().toAlignedRect(); else - d->dirtyRect |= (contentsBoundingRect() & srect).toAlignedRect(); + d->dirtyRect |= (contentsBoundingRect() & rect).toAlignedRect(); QSGItem::update(); } diff --git a/src/declarative/items/qsgpathview.cpp b/src/declarative/items/qsgpathview.cpp index 0bc45fd037..2c3e3cee71 100644 --- a/src/declarative/items/qsgpathview.cpp +++ b/src/declarative/items/qsgpathview.cpp @@ -111,7 +111,7 @@ void QSGPathViewPrivate::init() q, movementEndingIdx, Qt::DirectConnection); } -QSGItem *QSGPathViewPrivate::getItem(int modelIndex) +QSGItem *QSGPathViewPrivate::getItem(int modelIndex, bool onPath) { Q_Q(QSGPathView); requestedIndex = modelIndex; @@ -128,7 +128,7 @@ QSGItem *QSGPathViewPrivate::getItem(int modelIndex) qPathViewAttachedType = 0; if (att) { att->m_view = q; - att->setOnPath(true); + att->setOnPath(onPath); } item->setParentItem(q); QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); @@ -158,6 +158,10 @@ QSGPathViewAttached *QSGPathViewPrivate::attached(QSGItem *item) void QSGPathViewPrivate::clear() { + if (currentItem) { + releaseItem(currentItem); + currentItem = 0; + } for (int i=0; i<items.count(); i++){ QSGItem *p = items[i]; releaseItem(p); @@ -345,6 +349,71 @@ void QSGPathViewPrivate::regenerate() q->refill(); } +/*! + \qmlclass PathView QSGPathView + \inqmlmodule QtQuick 2 + \ingroup qml-view-elements + \brief The PathView element lays out model-provided items on a path. + \inherits Item + + A PathView displays data from models created from built-in QML elements like ListModel + and XmlListModel, or custom model classes defined in C++ that inherit from + QAbstractListModel. + + The view has a \l model, which defines the data to be displayed, and + a \l delegate, which defines how the data should be displayed. + The \l delegate is instantiated for each item on the \l path. + The items may be flicked to move them along the path. + + For example, if there is a simple list model defined in a file \c ContactModel.qml like this: + + \snippet doc/src/snippets/declarative/pathview/ContactModel.qml 0 + + This data can be represented as a PathView, like this: + + \snippet doc/src/snippets/declarative/pathview/pathview.qml 0 + + \image pathview.gif + + (Note the above example uses PathAttribute to scale and modify the + opacity of the items as they rotate. This additional code can be seen in the + PathAttribute documentation.) + + PathView does not automatically handle keyboard navigation. This is because + the keys to use for navigation will depend upon the shape of the path. Navigation + can be added quite simply by setting \c focus to \c true and calling + \l decrementCurrentIndex() or \l incrementCurrentIndex(), for example to navigate + using the left and right arrow keys: + + \qml + PathView { + // ... + focus: true + Keys.onLeftPressed: decrementCurrentIndex() + Keys.onRightPressed: incrementCurrentIndex() + } + \endqml + + The path view itself is a focus scope (see \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} for more details). + + Delegates are instantiated as needed and may be destroyed at any time. + State should \e never be stored in a delegate. + + PathView attaches a number of properties to the root item of the delegate, for example + \c {PathView.isCurrentItem}. In the following example, the root delegate item can access + this attached property directly as \c PathView.isCurrentItem, while the child + \c nameText object must refer to this property as \c wrapper.PathView.isCurrentItem. + + \snippet doc/src/snippets/declarative/pathview/pathview.qml 1 + + \bold Note that views do not enable \e clip automatically. If the view + is not clipped by another item or the screen, it will be necessary + to set \e {clip: true} in order to have the out of view items clipped + nicely. + + \sa Path, {declarative/modelviews/pathview}{PathView example} +*/ + QSGPathView::QSGPathView(QSGItem *parent) : QSGItem(*(new QSGPathViewPrivate), parent) { @@ -362,6 +431,54 @@ QSGPathView::~QSGPathView() delete d->model; } +/*! + \qmlattachedproperty PathView QtQuick2::PathView::view + This attached property holds the view that manages this delegate instance. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty bool QtQuick2::PathView::onPath + This attached property holds whether the item is currently on the path. + + If a pathItemCount has been set, it is possible that some items may + be instantiated, but not considered to be currently on the path. + Usually, these items would be set invisible, for example: + + \qml + Component { + Rectangle { + visible: PathView.onPath + // ... + } + } + \endqml + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty bool QtQuick2::PathView::isCurrentItem + This attached property is true if this delegate is the current item; otherwise false. + + It is attached to each instance of the delegate. + + This property may be used to adjust the appearance of the current item. + + \snippet doc/src/snippets/declarative/pathview/pathview.qml 1 +*/ + +/*! + \qmlproperty model QtQuick2::PathView::model + This property holds the model providing data for the view. + + The model provides a set of data that is used to create the items for the view. + For large or dynamic datasets the model is usually provided by a C++ model object. + Models can also be created directly in QML, using the ListModel element. + + \sa {qmlmodels}{Data Models} +*/ QVariant QSGPathView::model() const { Q_D(const QSGPathView); @@ -426,12 +543,21 @@ void QSGPathView::setModel(const QVariant &model) emit modelChanged(); } +/*! + \qmlproperty int QtQuick2::PathView::count + This property holds the number of items in the model. +*/ int QSGPathView::count() const { Q_D(const QSGPathView); return d->model ? d->modelCount : 0; } +/*! + \qmlproperty Path QtQuick2::PathView::path + This property holds the path used to lay out the items. + For more information see the \l Path documentation. +*/ QDeclarativePath *QSGPathView::path() const { Q_D(const QSGPathView); @@ -458,6 +584,10 @@ void QSGPathView::setPath(QDeclarativePath *path) emit pathChanged(); } +/*! + \qmlproperty int QtQuick2::PathView::currentIndex + This property holds the index of the current item. +*/ int QSGPathView::currentIndex() const { Q_D(const QSGPathView); @@ -470,28 +600,31 @@ void QSGPathView::setCurrentIndex(int idx) if (d->model && d->modelCount) idx = qAbs(idx % d->modelCount); if (d->model && idx != d->currentIndex) { - if (d->modelCount) { - int itemIndex = (d->currentIndex - d->firstIndex + d->modelCount) % d->modelCount; - if (itemIndex < d->items.count()) { - if (QSGItem *item = d->items.at(itemIndex)) { - if (QSGPathViewAttached *att = d->attached(item)) - att->setIsCurrentItem(false); - } - } + if (d->currentItem) { + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setIsCurrentItem(false); + d->releaseItem(d->currentItem); } d->currentItem = 0; d->moveReason = QSGPathViewPrivate::SetIndex; d->currentIndex = idx; if (d->modelCount) { - if (d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange) - d->snapToCurrent(); int itemIndex = (idx - d->firstIndex + d->modelCount) % d->modelCount; if (itemIndex < d->items.count()) { - d->currentItem = d->items.at(itemIndex); + d->currentItem = d->model->item(d->currentIndex, true); d->currentItem->setFocus(true); if (QSGPathViewAttached *att = d->attached(d->currentItem)) att->setIsCurrentItem(true); + } else { + d->currentItem = d->getItem(d->currentIndex, false); + d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0); + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setIsCurrentItem(true); + if (d->model->completePending()) + d->model->completeItem(); } + if (d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange) + d->snapToCurrent(); d->currentItemOffset = d->positionOfIndex(d->currentIndex); d->updateHighlight(); } @@ -499,6 +632,19 @@ void QSGPathView::setCurrentIndex(int idx) } } +QSGItem *QSGPathView::currentItem() const +{ + Q_D(const QSGPathView); + return d->currentItem; +} + +/*! + \qmlmethod QtQuick2::PathView::incrementCurrentIndex() + + Increments the current index. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGPathView::incrementCurrentIndex() { Q_D(QSGPathView); @@ -506,6 +652,13 @@ void QSGPathView::incrementCurrentIndex() setCurrentIndex(currentIndex()+1); } +/*! + \qmlmethod QtQuick2::PathView::decrementCurrentIndex() + + Decrements the current index. + + \bold Note: methods should only be called after the Component has completed. +*/ void QSGPathView::decrementCurrentIndex() { Q_D(QSGPathView); @@ -518,6 +671,12 @@ void QSGPathView::decrementCurrentIndex() } } +/*! + \qmlproperty real QtQuick2::PathView::offset + + The offset specifies how far along the path the items are from their initial positions. + This is a real number that ranges from 0.0 to the count of items in the model. +*/ qreal QSGPathView::offset() const { Q_D(const QSGPathView); @@ -552,6 +711,30 @@ void QSGPathViewPrivate::setAdjustedOffset(qreal o) setOffset(o+offsetAdj); } +/*! + \qmlproperty Component QtQuick2::PathView::highlight + This property holds the component to use as the highlight. + + An instance of the highlight component will be created for each view. + The geometry of the resultant component instance will be managed by the view + so as to stay with the current item. + + The below example demonstrates how to make a simple highlight. Note the use + of the \l{PathView::onPath}{PathView.onPath} attached property to ensure that + the highlight is hidden when flicked away from the path. + + \qml + Component { + Rectangle { + visible: PathView.onPath + // ... + } + } + \endqml + + \sa highlightItem, highlightRangeMode +*/ + QDeclarativeComponent *QSGPathView::highlight() const { Q_D(const QSGPathView); @@ -569,12 +752,54 @@ void QSGPathView::setHighlight(QDeclarativeComponent *highlight) } } +/*! + \qmlproperty Item QtQuick2::PathView::highlightItem + + \c highlightItem holds the highlight item, which was created + from the \l highlight component. + + \sa highlight +*/ QSGItem *QSGPathView::highlightItem() { Q_D(const QSGPathView); return d->highlightItem; } - +/*! + \qmlproperty real QtQuick2::PathView::preferredHighlightBegin + \qmlproperty real QtQuick2::PathView::preferredHighlightEnd + \qmlproperty enumeration QtQuick2::PathView::highlightRangeMode + + These properties set the preferred range of the highlight (current item) + within the view. The preferred values must be in the range 0.0-1.0. + + If highlightRangeMode is set to \e PathView.NoHighlightRange + + If highlightRangeMode is set to \e PathView.ApplyRange the view will + attempt to maintain the highlight within the range, however + the highlight can move outside of the range at the ends of the path + or due to a mouse interaction. + + If highlightRangeMode is set to \e PathView.StrictlyEnforceRange the highlight will never + move outside of the range. This means that the current item will change + if a keyboard or mouse action would cause the highlight to move + outside of the range. + + Note that this is the correct way to influence where the + current item ends up when the view moves. For example, if you want the + currently selected item to be in the middle of the path, then set the + highlight range to be 0.5,0.5 and highlightRangeMode to PathView.StrictlyEnforceRange. + Then, when the path scrolls, + the currently selected item will be the item at that position. This also applies to + when the currently selected item changes - it will scroll to within the preferred + highlight range. Furthermore, the behaviour of the current item index will occur + whether or not a highlight exists. + + The default value is \e PathView.StrictlyEnforceRange. + + Note that a valid range requires preferredHighlightEnd to be greater + than or equal to preferredHighlightBegin. +*/ qreal QSGPathView::preferredHighlightBegin() const { Q_D(const QSGPathView); @@ -625,6 +850,15 @@ void QSGPathView::setHighlightRangeMode(HighlightRangeMode mode) emit highlightRangeModeChanged(); } +/*! + \qmlproperty int QtQuick2::PathView::highlightMoveDuration + This property holds the move animation duration of the highlight delegate. + + If the highlightRangeMode is StrictlyEnforceRange then this property + determines the speed that the items move along the path. + + The default value for the duration is 300ms. +*/ int QSGPathView::highlightMoveDuration() const { Q_D(const QSGPathView); @@ -640,6 +874,14 @@ void QSGPathView::setHighlightMoveDuration(int duration) emit highlightMoveDurationChanged(); } +/*! + \qmlproperty real QtQuick2::PathView::dragMargin + This property holds the maximum distance from the path that initiate mouse dragging. + + By default the path can only be dragged by clicking on an item. If + dragMargin is greater than zero, a drag can be initiated by clicking + within dragMargin pixels of the path. +*/ qreal QSGPathView::dragMargin() const { Q_D(const QSGPathView); @@ -655,6 +897,12 @@ void QSGPathView::setDragMargin(qreal dragMargin) emit dragMarginChanged(); } +/*! + \qmlproperty real QtQuick2::PathView::flickDeceleration + This property holds the rate at which a flick will decelerate. + + The default is 100. +*/ qreal QSGPathView::flickDeceleration() const { Q_D(const QSGPathView); @@ -670,6 +918,14 @@ void QSGPathView::setFlickDeceleration(qreal dec) emit flickDecelerationChanged(); } +/*! + \qmlproperty bool QtQuick2::PathView::interactive + + A user cannot drag or flick a PathView that is not interactive. + + This property is useful for temporarily disabling flicking. This allows + special interaction with PathView's children. +*/ bool QSGPathView::isInteractive() const { Q_D(const QSGPathView); @@ -687,18 +943,79 @@ void QSGPathView::setInteractive(bool interactive) } } +/*! + \qmlproperty bool QtQuick2::PathView::moving + + This property holds whether the view is currently moving + due to the user either dragging or flicking the view. +*/ bool QSGPathView::isMoving() const { Q_D(const QSGPathView); return d->moving; } +/*! + \qmlproperty bool QtQuick2::PathView::flicking + + This property holds whether the view is currently moving + due to the user flicking the view. +*/ bool QSGPathView::isFlicking() const { Q_D(const QSGPathView); return d->flicking; } +/*! + \qmlsignal QtQuick2::PathView::onMovementStarted() + + This handler is called when the view begins moving due to user + interaction. +*/ + +/*! + \qmlsignal QtQuick2::PathView::onMovementEnded() + + This handler is called when the view stops moving due to user + interaction. If a flick was generated, this handler will + be triggered once the flick stops. If a flick was not + generated, the handler will be triggered when the + user stops dragging - i.e. a mouse or touch release. +*/ + +/*! + \qmlsignal QtQuick2::PathView::onFlickStarted() + + This handler is called when the view is flicked. A flick + starts from the point that the mouse or touch is released, + while still in motion. +*/ + +/*! + \qmlsignal QtQuick2::PathView::onFlickEnded() + + This handler is called when the view stops moving due to a flick. +*/ + +/*! + \qmlproperty Component QtQuick2::PathView::delegate + + The delegate provides a template defining each item instantiated by the view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + + The number of elements in the delegate has a direct effect on the + flicking performance of the view when pathItemCount is specified. If at all possible, place functionality + that is not needed for the normal display of the delegate in a \l Loader which + can load additional elements when needed. + + Note that the PathView will layout the items based on the size of the root + item in the delegate. + + Here is an example delegate: + \snippet doc/src/snippets/declarative/pathview/pathview.qml 1 +*/ QDeclarativeComponent *QSGPathView::delegate() const { Q_D(const QSGPathView); @@ -730,6 +1047,10 @@ void QSGPathView::setDelegate(QDeclarativeComponent *delegate) } } +/*! + \qmlproperty int QtQuick2::PathView::pathItemCount + This property holds the number of items visible on the path at any one time. +*/ int QSGPathView::pathItemCount() const { Q_D(const QSGPathView); @@ -976,6 +1297,7 @@ bool QSGPathView::sendMouseEvent(QGraphicsSceneMouseEvent *event) return d->stealMouse; } else if (d->lastPosTime.isValid()) { d->lastPosTime.invalidate(); + d->fixOffset(); } if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) d->stealMouse = false; @@ -1000,6 +1322,18 @@ bool QSGPathView::childMouseEventFilter(QSGItem *i, QEvent *e) return QSGItem::childMouseEventFilter(i, e); } +void QSGPathView::mouseUngrabEvent() +{ + Q_D(QSGPathView); + if (d->stealMouse) { + // if our mouse grab has been removed (probably by a Flickable), + // fix our state + d->stealMouse = false; + setKeepMouseGrab(false); + d->lastPosTime.invalidate(); + } +} + void QSGPathView::updatePolish() { QSGItem::updatePolish(); @@ -1079,12 +1413,8 @@ void QSGPathView::refill() if (d->model->completePending()) item->setZ(idx+1); if (d->currentIndex == idx) { - item->setFocus(true); - if (QSGPathViewAttached *att = d->attached(item)) - att->setIsCurrentItem(true); currentVisible = true; d->currentItemOffset = pos; - d->currentItem = item; } if (d->items.count() == 0) d->firstIndex = idx; @@ -1108,12 +1438,8 @@ void QSGPathView::refill() if (d->model->completePending()) item->setZ(idx+1); if (d->currentIndex == idx) { - item->setFocus(true); - if (QSGPathViewAttached *att = d->attached(item)) - att->setIsCurrentItem(true); currentVisible = true; d->currentItemOffset = pos; - d->currentItem = item; } d->items.prepend(item); d->updateItem(item, pos); @@ -1128,8 +1454,25 @@ void QSGPathView::refill() } } - if (!currentVisible) + if (!currentVisible) { d->currentItemOffset = 1.0; + if (d->currentItem) { + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setOnPath(false); + } else if (d->currentIndex >= 0 && d->currentIndex < d->modelCount) { + d->currentItem = d->getItem(d->currentIndex, false); + d->updateItem(d->currentItem, d->currentIndex < d->firstIndex ? 0.0 : 1.0); + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setIsCurrentItem(true); + if (d->model->completePending()) + d->model->completeItem(); + } + } else if (!d->currentItem) { + d->currentItem = d->model->item(d->currentIndex, true); + d->currentItem->setFocus(true); + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setIsCurrentItem(true); + } if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange) { d->updateItem(d->highlightItem, d->highlightRangeStart); @@ -1193,6 +1536,8 @@ void QSGPathView::itemsRemoved(int modelIndex, int count) if (d->currentItem) { if (QSGPathViewAttached *att = d->attached(d->currentItem)) att->setIsCurrentItem(true); + d->releaseItem(d->currentItem); + d->currentItem = 0; } currentChanged = true; } @@ -1332,21 +1677,26 @@ void QSGPathViewPrivate::updateCurrent() int idx = calcCurrentIndex(); if (model && idx != currentIndex) { - int itemIndex = (currentIndex - firstIndex + modelCount) % modelCount; - if (itemIndex < items.count()) { - if (QSGItem *item = items.at(itemIndex)) { - if (QSGPathViewAttached *att = attached(item)) - att->setIsCurrentItem(false); - } + if (currentItem) { + if (QSGPathViewAttached *att = attached(currentItem)) + att->setIsCurrentItem(false); + releaseItem(currentItem); } currentIndex = idx; currentItem = 0; - itemIndex = (idx - firstIndex + modelCount) % modelCount; + int itemIndex = (idx - firstIndex + modelCount) % modelCount; if (itemIndex < items.count()) { - currentItem = items.at(itemIndex); + currentItem = model->item(currentIndex, true); currentItem->setFocus(true); if (QSGPathViewAttached *att = attached(currentItem)) att->setIsCurrentItem(true); + } else if (currentIndex >= 0 && currentIndex < modelCount) { + currentItem = getItem(currentIndex, false); + updateItem(currentItem, currentIndex < firstIndex ? 0.0 : 1.0); + if (QSGPathViewAttached *att = attached(currentItem)) + att->setIsCurrentItem(true); + if (model->completePending()) + model->completeItem(); } emit q->currentIndexChanged(); } diff --git a/src/declarative/items/qsgpathview_p.h b/src/declarative/items/qsgpathview_p.h index 31f2f6a113..a271e323cd 100644 --- a/src/declarative/items/qsgpathview_p.h +++ b/src/declarative/items/qsgpathview_p.h @@ -62,6 +62,7 @@ class Q_AUTOTEST_EXPORT QSGPathView : public QSGItem Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(QDeclarativePath *path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QSGItem *currentItem READ currentItem NOTIFY currentIndexChanged) Q_PROPERTY(qreal offset READ offset WRITE setOffset NOTIFY offsetChanged) Q_PROPERTY(QDeclarativeComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged) @@ -98,6 +99,8 @@ public: int currentIndex() const; void setCurrentIndex(int idx); + QSGItem *currentItem() const; + qreal offset() const; void setOffset(qreal offset); @@ -176,6 +179,7 @@ protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *); bool sendMouseEvent(QGraphicsSceneMouseEvent *event); bool childMouseEventFilter(QSGItem *, QEvent *); + void mouseUngrabEvent(); void componentComplete(); private Q_SLOTS: diff --git a/src/declarative/items/qsgpathview_p_p.h b/src/declarative/items/qsgpathview_p_p.h index a133e80abc..1575c6c1ca 100644 --- a/src/declarative/items/qsgpathview_p_p.h +++ b/src/declarative/items/qsgpathview_p_p.h @@ -111,7 +111,7 @@ public: } } - QSGItem *getItem(int modelIndex); + QSGItem *getItem(int modelIndex, bool onPath = true); void releaseItem(QSGItem *item); QSGPathViewAttached *attached(QSGItem *item); void clear(); diff --git a/src/declarative/items/qsgpincharea.cpp b/src/declarative/items/qsgpincharea.cpp index f86c18dfcf..54c32e5025 100644 --- a/src/declarative/items/qsgpincharea.cpp +++ b/src/declarative/items/qsgpincharea.cpp @@ -50,6 +50,102 @@ QT_BEGIN_NAMESPACE +/*! + \qmlclass PinchEvent QSGPinchEvent + \inqmlmodule QtQuick 2 + \ingroup qml-event-elements + \brief The PinchEvent object provides information about a pinch event. + + \bold {The PinchEvent element was added in QtQuick 1.1} + + The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points. + + The \c scale and \c previousScale properties provide the scale factor. + + The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation. + + The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points. + + The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not + be handled. + + \sa PinchArea +*/ + +/*! + \qmlproperty QPointF QtQuick2::PinchEvent::center + \qmlproperty QPointF QtQuick2::PinchEvent::startCenter + \qmlproperty QPointF QtQuick2::PinchEvent::previousCenter + + These properties hold the position of the center point between the two touch points. + + \list + \o \c center is the current center point + \o \c previousCenter is the center point of the previous event. + \o \c startCenter is the center point when the gesture began + \endlist +*/ + +/*! + \qmlproperty real QtQuick2::PinchEvent::scale + \qmlproperty real QtQuick2::PinchEvent::previousScale + + These properties hold the scale factor determined by the change in distance between the two touch points. + + \list + \o \c scale is the current scale factor. + \o \c previousScale is the scale factor of the previous event. + \endlist + + When a pinch gesture is started, the scale is 1.0. +*/ + +/*! + \qmlproperty real QtQuick2::PinchEvent::angle + \qmlproperty real QtQuick2::PinchEvent::previousAngle + \qmlproperty real QtQuick2::PinchEvent::rotation + + These properties hold the angle between the two touch points. + + \list + \o \c angle is the current angle between the two points in the range -180 to 180. + \o \c previousAngle is the angle of the previous event. + \o \c rotation is the total rotation since the pinch gesture started. + \endlist + + When a pinch gesture is started, the rotation is 0.0. +*/ + +/*! + \qmlproperty QPointF QtQuick2::PinchEvent::point1 + \qmlproperty QPointF QtQuick2::PinchEvent::startPoint1 + \qmlproperty QPointF QtQuick2::PinchEvent::point2 + \qmlproperty QPointF QtQuick2::PinchEvent::startPoint2 + + These properties provide the actual touch points generating the pinch. + + \list + \o \c point1 and \c point2 hold the current positions of the points. + \o \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched. + \endlist +*/ + +/*! + \qmlproperty bool QtQuick2::PinchEvent::accepted + + Setting this property to false in the \c PinchArea::onPinchStarted handler + will result in no further pinch events being generated, and the gesture + ignored. +*/ + +/*! + \qmlproperty int QtQuick2::PinchEvent::pointCount + + Holds the number of points currently touched. The PinchArea will not react + until two touch points have initited a gesture, but will remain active until + all touch points have been released. +*/ + QSGPinch::QSGPinch() : m_target(0), m_minScale(1.0), m_maxScale(1.0) , m_minRotation(0.0), m_maxRotation(0.0) @@ -63,6 +159,87 @@ QSGPinchAreaPrivate::~QSGPinchAreaPrivate() delete pinch; } +/*! + \qmlclass PinchArea QSGPinchArea + \inqmlmodule QtQuick 2 + \brief The PinchArea item enables simple pinch gesture handling. + \inherits Item + + \bold {The PinchArea element was added in QtQuick 1.1} + + A PinchArea is an invisible item that is typically used in conjunction with + a visible item in order to provide pinch gesture handling for that item. + + The \l enabled property is used to enable and disable pinch handling for + the proxied item. When disabled, the pinch area becomes transparent to + mouse/touch events. + + PinchArea can be used in two ways: + + \list + \o setting a \c pinch.target to provide automatic interaction with an element + \o using the onPinchStarted, onPinchUpdated and onPinchFinished handlers + \endlist + + \sa PinchEvent +*/ + +/*! + \qmlsignal QtQuick2::PinchArea::onPinchStarted() + + This handler is called when the pinch area detects that a pinch gesture has started. + + The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture, + including the scale, center and angle of the pinch. + + To ignore this gesture set the \c pinch.accepted property to false. The gesture + will be cancelled and no further events will be sent. +*/ + +/*! + \qmlsignal QtQuick2::PinchArea::onPinchUpdated() + + This handler is called when the pinch area detects that a pinch gesture has changed. + + The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture, + including the scale, center and angle of the pinch. +*/ + +/*! + \qmlsignal QtQuick2::PinchArea::onPinchFinished() + + This handler is called when the pinch area detects that a pinch gesture has finished. + + The \l {PinchEvent}{pinch} parameter provides information about the pinch gesture, + including the scale, center and angle of the pinch. +*/ + + +/*! + \qmlproperty Item QtQuick2::PinchArea::pinch.target + \qmlproperty bool QtQuick2::PinchArea::pinch.active + \qmlproperty real QtQuick2::PinchArea::pinch.minimumScale + \qmlproperty real QtQuick2::PinchArea::pinch.maximumScale + \qmlproperty real QtQuick2::PinchArea::pinch.minimumRotation + \qmlproperty real QtQuick2::PinchArea::pinch.maximumRotation + \qmlproperty enumeration QtQuick2::PinchArea::pinch.dragAxis + \qmlproperty real QtQuick2::PinchArea::pinch.minimumX + \qmlproperty real QtQuick2::PinchArea::pinch.maximumX + \qmlproperty real QtQuick2::PinchArea::pinch.minimumY + \qmlproperty real QtQuick2::PinchArea::pinch.maximumY + + \c pinch provides a convenient way to make an item react to pinch gestures. + + \list + \i \c pinch.target specifies the id of the item to drag. + \i \c pinch.active specifies if the target item is currently being dragged. + \i \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item::scale property. + \i \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item::rotation property. + \i \c pinch.dragAxis specifies whether dragging in not allowed (\c Pinch.NoDrag), can be done horizontally (\c Pinch.XAxis), vertically (\c Pinch.YAxis), or both (\c Pinch.XandYAxis) + \i \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes. + \endlist +*/ + QSGPinchArea::QSGPinchArea(QSGItem *parent) : QSGItem(*(new QSGPinchAreaPrivate), parent) { @@ -73,7 +250,12 @@ QSGPinchArea::QSGPinchArea(QSGItem *parent) QSGPinchArea::~QSGPinchArea() { } +/*! + \qmlproperty bool QtQuick2::PinchArea::enabled + This property holds whether the item accepts pinch gestures. + This property defaults to true. +*/ bool QSGPinchArea::isEnabled() const { Q_D(const QSGPinchArea); diff --git a/src/declarative/items/qsgpositioners.cpp b/src/declarative/items/qsgpositioners.cpp index da3c7fea04..fca0aa1877 100644 --- a/src/declarative/items/qsgpositioners.cpp +++ b/src/declarative/items/qsgpositioners.cpp @@ -50,6 +50,7 @@ #include <private/qdeclarativestate_p.h> #include <private/qdeclarativestategroup_p.h> #include <private/qdeclarativestateoperations_p.h> +#include <private/qdeclarativetransition_p.h> QT_BEGIN_NAMESPACE @@ -77,6 +78,22 @@ QSGBasePositioner::QSGBasePositioner(PositionerType at, QSGItem *parent) Q_D(QSGBasePositioner); d->init(at); } +/*! + \internal + \class QSGBasePositioner + \brief The QSGBasePositioner class provides a base for QSGGraphics layouts. + + To create a QSGGraphics Positioner, simply subclass QSGBasePositioner and implement + doLayout(), which is automatically called when the layout might need + updating. In doLayout() use the setX and setY functions from QSGBasePositioner, and the + base class will apply the positions along with the appropriate transitions. The items to + position are provided in order as the protected member positionedItems. + + You also need to set a PositionerType, to declare whether you are positioning the x, y or both + for the child items. Depending on the chosen type, only x or y changes will be applied. + + Note that the subclass is responsible for adding the spacing in between items. +*/ QSGBasePositioner::QSGBasePositioner(QSGBasePositionerPrivate &dd, PositionerType at, QSGItem *parent) : QSGImplicitSizeItem(dd, parent) @@ -213,7 +230,8 @@ void QSGBasePositioner::prePositioning() } QSizeF contentSize(0,0); doPositioning(&contentSize); - if(d->addTransition || d->moveTransition) + updateAttachedProperties(); + if (!d->addActions.isEmpty() || !d->moveActions.isEmpty()) finishApplyTransitions(); d->doingPositioning = false; //Set implicit size to the size of its children @@ -226,12 +244,12 @@ void QSGBasePositioner::positionX(int x, const PositionedItem &target) Q_D(QSGBasePositioner); if(d->type == Horizontal || d->type == Both){ if (target.isNew) { - if (!d->addTransition) + if (!d->addTransition || !d->addTransition->enabled()) target.item->setX(x); else d->addActions << QDeclarativeAction(target.item, QLatin1String("x"), QVariant(x)); } else if (x != target.item->x()) { - if (!d->moveTransition) + if (!d->moveTransition || !d->moveTransition->enabled()) target.item->setX(x); else d->moveActions << QDeclarativeAction(target.item, QLatin1String("x"), QVariant(x)); @@ -244,12 +262,12 @@ void QSGBasePositioner::positionY(int y, const PositionedItem &target) Q_D(QSGBasePositioner); if(d->type == Vertical || d->type == Both){ if (target.isNew) { - if (!d->addTransition) + if (!d->addTransition || !d->addTransition->enabled()) target.item->setY(y); else d->addActions << QDeclarativeAction(target.item, QLatin1String("y"), QVariant(y)); } else if (y != target.item->y()) { - if (!d->moveTransition) + if (!d->moveTransition || !d->moveTransition->enabled()) target.item->setY(y); else d->moveActions << QDeclarativeAction(target.item, QLatin1String("y"), QVariant(y)); @@ -268,6 +286,233 @@ void QSGBasePositioner::finishApplyTransitions() d->moveActions.clear(); } +QSGPositionerAttached *QSGBasePositioner::qmlAttachedProperties(QObject *obj) +{ + return new QSGPositionerAttached(obj); +} + +void QSGBasePositioner::updateAttachedProperties(QSGPositionerAttached *specificProperty, QSGItem *specificPropertyOwner) const +{ + // If this function is deemed too expensive or shows up in profiles, it could + // be changed to run only when there are attached properties present. This + // could be a flag in the positioner that is set by the attached property + // constructor. + QSGPositionerAttached *prevLastProperty = 0; + QSGPositionerAttached *lastProperty = 0; + + int visibleItemIndex = 0; + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (!child.item) + continue; + + QSGPositionerAttached *property = 0; + + if (specificProperty) { + if (specificPropertyOwner == child.item) { + property = specificProperty; + } + } else { + property = static_cast<QSGPositionerAttached *>(qmlAttachedPropertiesObject<QSGBasePositioner>(child.item, false)); + } + + if (child.isVisible) { + if (property) { + property->setIndex(visibleItemIndex); + property->setIsFirstItem(visibleItemIndex == 0); + + if (property->isLastItem()) + prevLastProperty = property; + } + + lastProperty = property; + ++visibleItemIndex; + } else if (property) { + property->setIndex(-1); + property->setIsFirstItem(false); + property->setIsLastItem(false); + } + } + + if (prevLastProperty && prevLastProperty != lastProperty) + prevLastProperty->setIsLastItem(false); + if (lastProperty) + lastProperty->setIsLastItem(true); +} + +/*! + \qmlclass Positioner QSGPositionerAttached + \inqmlmodule QtQuick 2 + \ingroup qml-positioning-elements + \brief The Positioner type provides attached properties that contain details on where an item exists in a positioner. + + Positioner items (such as Column, Row, Flow and Grid) provide automatic layout + for child items. Attaching this property allows a child item to determine + where it exists within the positioner. +*/ + +QSGPositionerAttached::QSGPositionerAttached(QObject *parent) : QObject(parent), m_index(-1), m_isFirstItem(false), m_isLastItem(false) +{ + QSGItem *attachedItem = qobject_cast<QSGItem *>(parent); + if (attachedItem) { + QSGBasePositioner *positioner = qobject_cast<QSGBasePositioner *>(attachedItem->parent()); + if (positioner) { + positioner->updateAttachedProperties(this, attachedItem); + } + } +} + +/*! + \qmlattachedproperty Item QtQuick2::Positioner::index + + This property allows the item to determine + its index within the positioner. +*/ +void QSGPositionerAttached::setIndex(int index) +{ + if (m_index == index) + return; + m_index = index; + emit indexChanged(); +} + +/*! + \qmlattachedproperty Item QtQuick2::Positioner::isFirstItem + \qmlattachedproperty Item QtQuick2::Positioner::isLastItem + + These properties allow the item to determine if it + is the first or last item in the positioner, respectively. +*/ +void QSGPositionerAttached::setIsFirstItem(bool isFirstItem) +{ + if (m_isFirstItem == isFirstItem) + return; + m_isFirstItem = isFirstItem; + emit isFirstItemChanged(); +} + +void QSGPositionerAttached::setIsLastItem(bool isLastItem) +{ + if (m_isLastItem == isLastItem) + return; + m_isLastItem = isLastItem; + emit isLastItemChanged(); +} + +/*! + \qmlclass Column QSGColumn + \inqmlmodule QtQuick 2 + \ingroup qml-positioning-elements + \brief The Column item arranges its children vertically. + \inherits Item + + The Column item positions its child items so that they are vertically + aligned and not overlapping. + + Spacing between items can be added using the \l spacing property. + Transitions can be used for cases where items managed by a Column are + added or moved. These are stored in the \l add and \l move properties + respectively. + + See \l{Using QML Positioner and Repeater Items} for more details about this item and other + related items. + + \section1 Example Usage + + The following example positions differently shaped rectangles using a Column + item. + + \image verticalpositioner_example.png + + \snippet doc/src/snippets/declarative/column/vertical-positioner.qml document + + \section1 Using Transitions + + Transitions can be used to animate items that are added to, moved within, + or removed from a Column item. The \l add and \l move properties can be set to + the transitions that will be applied when items are added to, removed from, + or re-positioned within a Column item. + + The use of transitions with positioners is described in more detail in the + \l{Using QML Positioner and Repeater Items#Using Transitions}{Using QML + Positioner and Repeater Items} document. + + \image verticalpositioner_transition.gif + + \qml + Column { + spacing: 2 + add: Transition { + // Define an animation for adding a new item... + } + move: Transition { + // Define an animation for moving items within the column... + } + // ... + } + \endqml + + \section1 Limitations + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, use anchors on a child of a positioner, or have the + height of a child depend on the position of a child, then the + positioner may exhibit strange behavior. If you need to perform any of these + actions, consider positioning the items without the use of a Column. + + Items with a width or height of 0 will not be positioned. + + \sa Row, Grid, Flow, Positioner, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty Transition QtQuick2::Column::add + + This property holds the transition to be applied when adding an + item to the positioner. The transition will only be applied to the + added item(s). Positioner transitions will only affect the + position (x, y) of items. + + For a positioner, adding an item can mean that either the object + has been created or reparented, and thus is now a child or the + positioner, or that the object has had its opacity increased from + zero, and thus is now visible. + + \sa move +*/ +/*! + \qmlproperty Transition QtQuick2::Column::move + + This property holds the transition to apply when moving an item + within the positioner. Positioner transitions will only affect + the position (x, y) of items. + + This transition can be performed when other items are added or removed + from the positioner, or when items resize themselves. + + \image positioner-move.gif + + \qml + Column { + move: Transition { + NumberAnimation { + properties: "y" + duration: 1000 + } + } + } + \endqml + + \sa add, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty int QtQuick2::Column::spacing + + The spacing is the amount in pixels left empty between adjacent + items. The default spacing is 0. + + \sa Grid::spacing +*/ QSGColumn::QSGColumn(QSGItem *parent) : QSGBasePositioner(Vertical, parent) { @@ -288,10 +533,11 @@ void QSGColumn::doPositioning(QSizeF *contentSize) contentSize->setWidth(qMax(contentSize->width(), child.item->width())); voffset += child.item->height(); - if (ii != positionedItems.count() - 1) - voffset += spacing(); + voffset += spacing(); } + if (voffset != 0)//If we positioned any items, undo the spacing from the last item + voffset -= spacing(); contentSize->setHeight(voffset); } @@ -318,11 +564,119 @@ void QSGColumn::reportConflictingAnchors() qmlInfo(this) << "Cannot specify top, bottom, verticalCenter, fill or centerIn anchors for items inside Column"; } } +/*! + \qmlclass Row QSGRow + \inqmlmodule QtQuick 2 + \ingroup qml-positioning-elements + \brief The Row item arranges its children horizontally. + \inherits Item + + The Row item positions its child items so that they are horizontally + aligned and not overlapping. + + Use \l spacing to set the spacing between items in a Row, and use the + \l add and \l move properties to set the transitions that should be applied + when items are added to, removed from, or re-positioned within the Row. + + See \l{Using QML Positioner and Repeater Items} for more details about this item and other + related items. + + \section1 Example Usage + + The following example lays out differently shaped rectangles using a Row. + + \image horizontalpositioner_example.png + + \snippet doc/src/snippets/declarative/row/row.qml document + + \section1 Using Transitions + + Transitions can be used to animate items that are added to, moved within, + or removed from a Grid item. The \l add and \l move properties can be set to + the transitions that will be applied when items are added to, removed from, + or re-positioned within a Row item. + + \section1 Limitations + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, use anchors on a child of a positioner, or have the + width of a child depend on the position of a child, then the + positioner may exhibit strange behaviour. If you need to perform any of these + actions, consider positioning the items without the use of a Row. + + Items with a width or height of 0 will not be positioned. + + \sa Column, Grid, Flow, Positioner, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty Transition QtQuick2::Row::add + + This property holds the transition to be applied when adding an + item to the positioner. The transition will only be applied to the + added item(s). Positioner transitions will only affect the + position (x, y) of items. + + For a positioner, adding an item can mean that either the object + has been created or reparented, and thus is now a child or the + positioner, or that the object has had its opacity increased from + zero, and thus is now visible. + + \sa move +*/ +/*! + \qmlproperty Transition QtQuick2::Row::move + + This property holds the transition to be applied when moving an + item within the positioner. Positioner transitions will only affect + the position (x, y) of items. + + This transition can be performed when other items are added or removed + from the positioner, or when items resize themselves. + + \qml + Row { + id: positioner + move: Transition { + NumberAnimation { + properties: "x" + duration: 1000 + } + } + } + \endqml + + \sa add, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty int QtQuick2::Row::spacing + + The spacing is the amount in pixels left empty between adjacent + items. The default spacing is 0. + + \sa Grid::spacing +*/ QSGRow::QSGRow(QSGItem *parent) : QSGBasePositioner(Horizontal, parent) { } +/*! + \qmlproperty enumeration QtQuick2::Row::layoutDirection + + This property holds the layoutDirection of the row. + + Possible values: + + \list + \o Qt.LeftToRight (default) - Items are laid out from left to right. If the width of the row is explicitly set, + the left anchor remains to the left of the row. + \o Qt.RightToLeft - Items are laid out from right to left. If the width of the row is explicitly set, + the right anchor remains to the right of the row. + \endlist + + \sa Grid::layoutDirection, Flow::layoutDirection, {declarative/righttoleft/layoutdirection}{Layout directions example} +*/ Qt::LayoutDirection QSGRow::layoutDirection() const { @@ -344,6 +698,16 @@ void QSGRow::setLayoutDirection(Qt::LayoutDirection layoutDirection) emit effectiveLayoutDirectionChanged(); } } +/*! + \qmlproperty enumeration QtQuick2::Row::effectiveLayoutDirection + This property holds the effective layout direction of the row positioner. + + When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, + the visual layout direction of the row positioner will be mirrored. However, the + property \l {Row::layoutDirection}{layoutDirection} will remain unchanged. + + \sa Row::layoutDirection, {LayoutMirroring}{LayoutMirroring} +*/ Qt::LayoutDirection QSGRow::effectiveLayoutDirection() const { @@ -371,10 +735,11 @@ void QSGRow::doPositioning(QSizeF *contentSize) contentSize->setHeight(qMax(contentSize->height(), child.item->height())); hoffset += child.item->width(); - if (ii != positionedItems.count() - 1) - hoffset += spacing(); + hoffset += spacing(); } + if (hoffset != 0)//If we positioned any items, undo the extra spacing from the last item + hoffset -= spacing(); contentSize->setWidth(hoffset); if (d->isLeftToRight()) @@ -421,11 +786,138 @@ void QSGRow::reportConflictingAnchors() qmlInfo(this) << "Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside Row"; } +/*! + \qmlclass Grid QSGGrid + \inqmlmodule QtQuick 2 + \ingroup qml-positioning-elements + \brief The Grid item positions its children in a grid. + \inherits Item + + The Grid item positions its child items so that they are + aligned in a grid and are not overlapping. + + The grid positioner calculates a grid of rectangular cells of sufficient + size to hold all items, placing the items in the cells, from left to right + and top to bottom. Each item is positioned in the top-left corner of its + cell with position (0, 0). + + A Grid defaults to four columns, and as many rows as are necessary to + fit all child items. The number of rows and columns can be constrained + by setting the \l rows and \l columns properties. + + Spacing can be added between child items by setting the \l spacing + property. The amount of spacing applied will be the same in the + horizontal and vertical directions. + + See \l{Using QML Positioner and Repeater Items} for more details about this item and other + related items. + + \section1 Example Usage + + The following example demonstrates this. + + \image gridLayout_example.png + + \snippet doc/src/snippets/declarative/grid/grid.qml document + + \section1 Using Transitions + + Transitions can be used to animate items that are added to, moved within, + or removed from a Grid item. The \l add and \l move properties can be set to + the transitions that will be applied when items are added to, removed from, + or re-positioned within a Grid item. + + \section1 Limitations + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, use anchors on a child of a positioner, or have the + width or height of a child depend on the position of a child, then the + positioner may exhibit strange behaviour. If you need to perform any of these + actions, consider positioning the items without the use of a Grid. + + Items with a width or height of 0 will not be positioned. + + \sa Flow, Row, Column, Positioner, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty Transition QtQuick2::Grid::add + + This property holds the transition to be applied when adding an + item to the positioner. The transition will only be applied to the + added item(s). Positioner transitions will only affect the + position (x, y) of items. + + For a positioner, adding an item can mean that either the object + has been created or reparented, and thus is now a child or the + positioner, or that the object has had its opacity increased from + zero, and thus is now visible. + + \sa move +*/ +/*! + \qmlproperty Transition QtQuick2::Grid::move + + This property holds the transition to be applied when moving an + item within the positioner. Positioner transitions will only affect + the position (x, y) of items. + + This transition can be performed when other items are added or removed + from the positioner, or when items resize themselves. + + \qml + Grid { + move: Transition { + NumberAnimation { + properties: "x,y" + duration: 1000 + } + } + } + \endqml + + \sa add, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty int QtQuick2::Grid::spacing + + The spacing is the amount in pixels left empty between adjacent + items. The default spacing is 0. + + The below example places a Grid containing a red, a blue and a + green rectangle on a gray background. The area the grid positioner + occupies is colored white. The positioner on the left has the + no spacing (the default), and the positioner on the right has + a spacing of 6. + + \inlineimage qml-grid-no-spacing.png + \inlineimage qml-grid-spacing.png + + \sa rows, columns +*/ QSGGrid::QSGGrid(QSGItem *parent) : - QSGBasePositioner(Both, parent), m_rows(-1), m_columns(-1), m_flow(LeftToRight) + QSGBasePositioner(Both, parent), m_rows(-1), m_columns(-1), m_rowSpacing(-1), m_columnSpacing(-1), m_flow(LeftToRight) { } +/*! + \qmlproperty int QtQuick2::Grid::columns + + This property holds the number of columns in the grid. The default + number of columns is 4. + + If the grid does not have enough items to fill the specified + number of columns, some columns will be of zero width. +*/ + +/*! + \qmlproperty int QtQuick2::Grid::rows + This property holds the number of rows in the grid. + + If the grid does not have enough items to fill the specified + number of rows, some rows will be of zero width. +*/ + void QSGGrid::setColumns(const int columns) { if (columns == m_columns) @@ -444,6 +936,19 @@ void QSGGrid::setRows(const int rows) emit rowsChanged(); } +/*! + \qmlproperty enumeration QtQuick2::Grid::flow + This property holds the flow of the layout. + + Possible values are: + + \list + \o Grid.LeftToRight (default) - Items are positioned next to + each other in the \l layoutDirection, then wrapped to the next line. + \o Grid.TopToBottom - Items are positioned next to each + other from top to bottom, then wrapped to the next column. + \endlist +*/ QSGGrid::Flow QSGGrid::flow() const { return m_flow; @@ -458,6 +963,58 @@ void QSGGrid::setFlow(Flow flow) } } +/*! + \qmlproperty int QtQuick2::Grid::rowSpacing + + This property holds the spacing in pixels between rows. + + \sa columnSpacing + \since QtQuick2.0 +*/ +void QSGGrid::setRowSpacing(const int rowSpacing) +{ + if (rowSpacing == m_rowSpacing) + return; + m_rowSpacing = rowSpacing; + prePositioning(); + emit rowSpacingChanged(); +} + +/*! + \qmlproperty int QtQuick2::Grid::columnSpacing + + This property holds the spacing in pixels between columns. + + \sa rowSpacing + \since QtQuick2.0 +*/ +void QSGGrid::setColumnSpacing(const int columnSpacing) +{ + if (columnSpacing == m_columnSpacing) + return; + m_columnSpacing = columnSpacing; + prePositioning(); + emit columnSpacingChanged(); +} + +/*! + \qmlproperty enumeration QtQuick2::Grid::layoutDirection + + This property holds the layout direction of the layout. + + Possible values are: + + \list + \o Qt.LeftToRight (default) - Items are positioned from the top to bottom, + and left to right. The flow direction is dependent on the + \l Grid::flow property. + \o Qt.RightToLeft - Items are positioned from the top to bottom, + and right to left. The flow direction is dependent on the + \l Grid::flow property. + \endlist + + \sa Flow::layoutDirection, Row::layoutDirection, {declarative/righttoleft/layoutdirection}{Layout directions example} +*/ Qt::LayoutDirection QSGGrid::layoutDirection() const { return QSGBasePositionerPrivate::getLayoutDirection(this); @@ -479,6 +1036,16 @@ void QSGGrid::setLayoutDirection(Qt::LayoutDirection layoutDirection) } } +/*! + \qmlproperty enumeration QtQuick2::Grid::effectiveLayoutDirection + This property holds the effective layout direction of the grid positioner. + + When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, + the visual layout direction of the grid positioner will be mirrored. However, the + property \l {Grid::layoutDirection}{layoutDirection} will remain unchanged. + + \sa Grid::layoutDirection, {LayoutMirroring}{LayoutMirroring} +*/ Qt::LayoutDirection QSGGrid::effectiveLayoutDirection() const { return QSGBasePositionerPrivate::getEffectiveLayoutDirection(this); @@ -550,17 +1117,25 @@ void QSGGrid::doPositioning(QSizeF *contentSize) } } + int columnSpacing = m_columnSpacing; + if (columnSpacing == -1) + columnSpacing = spacing(); + + int rowSpacing = m_rowSpacing; + if (rowSpacing == -1) + rowSpacing = spacing(); + int widthSum = 0; for (int j=0; j < maxColWidth.size(); j++){ if (j) - widthSum += spacing(); + widthSum += columnSpacing; widthSum += maxColWidth[j]; } int heightSum = 0; for (int i=0; i < maxRowHeight.size(); i++){ if (i) - heightSum += spacing(); + heightSum += rowSpacing; heightSum += maxRowHeight[i]; } @@ -591,13 +1166,13 @@ void QSGGrid::doPositioning(QSizeF *contentSize) if (m_flow == LeftToRight) { if (d->isLeftToRight()) - xoffset += maxColWidth[curCol]+spacing(); + xoffset += maxColWidth[curCol]+columnSpacing; else - xoffset -= maxColWidth[curCol]+spacing(); + xoffset -= maxColWidth[curCol]+columnSpacing; curCol++; curCol%=c; if (!curCol){ - yoffset += maxRowHeight[curRow]+spacing(); + yoffset += maxRowHeight[curRow]+rowSpacing; if (d->isLeftToRight()) xoffset = 0; else @@ -607,14 +1182,14 @@ void QSGGrid::doPositioning(QSizeF *contentSize) break; } } else { - yoffset+=maxRowHeight[curRow]+spacing(); + yoffset+=maxRowHeight[curRow]+rowSpacing; curRow++; curRow%=r; if (!curRow){ if (d->isLeftToRight()) - xoffset += maxColWidth[curCol]+spacing(); + xoffset += maxColWidth[curCol]+columnSpacing; else - xoffset -= maxColWidth[curCol]+spacing(); + xoffset -= maxColWidth[curCol]+columnSpacing; yoffset=0; curCol++; if (curCol>=c) @@ -641,6 +1216,105 @@ void QSGGrid::reportConflictingAnchors() qmlInfo(this) << "Cannot specify anchors for items inside Grid"; } +/*! + \qmlclass Flow QSGFlow + \inqmlmodule QtQuick 2 + \ingroup qml-positioning-elements + \brief The Flow item arranges its children side by side, wrapping as necessary. + \inherits Item + + The Flow item positions its child items like words on a page, wrapping them + to create rows or columns of items that do not overlap. + + Spacing between items can be added using the \l spacing property. + Transitions can be used for cases where items managed by a Column are + added or moved. These are stored in the \l add and \l move properties + respectively. + + See \l{Using QML Positioner and Repeater Items} for more details about this item and other + related items. + + \section1 Example Usage + + The following example positions \l Text items within a parent item using + a Flow item. + + \image qml-flow-snippet.png + + \snippet doc/src/snippets/declarative/flow.qml flow item + + \section1 Using Transitions + + Transitions can be used to animate items that are added to, moved within, + or removed from a Flow item. The \l add and \l move properties can be set to + the transitions that will be applied when items are added to, removed from, + or re-positioned within a Flow item. + + The use of transitions with positioners is described in more detail in the + \l{Using QML Positioner and Repeater Items#Using Transitions}{Using QML + Positioner and Repeater Items} document. + + \section1 Limitations + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, use anchors on a child of a positioner, or have the + width or height of a child depend on the position of a child, then the + positioner may exhibit strange behaviour. If you need to perform any of these + actions, consider positioning the items without the use of a Flow. + + Items with a width or height of 0 will not be positioned. + + \sa Column, Row, Grid, Positioner, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty Transition QtQuick2::Flow::add + + This property holds the transition to be applied when adding an + item to the positioner. The transition will only be applied to the + added item(s). Positioner transitions will only affect the + position (x, y) of items. + + For a positioner, adding an item can mean that either the object + has been created or reparented, and thus is now a child or the + positioner, or that the object has had its opacity increased from + zero, and thus is now visible. + + \sa move +*/ +/*! + \qmlproperty Transition QtQuick2::Flow::move + + This property holds the transition to be applied when moving an + item within the positioner. Positioner transitions will only affect + the position (x, y) of items. + + This transition can be performed when other items are added or removed + from the positioner, or when items resize themselves. + + \qml + Flow { + id: positioner + move: Transition { + NumberAnimation { + properties: "x,y" + ease: "easeOutBounce" + } + } + } + \endqml + + \sa add, {declarative/positioners}{Positioners example} +*/ +/*! + \qmlproperty int QtQuick2::Flow::spacing + + spacing is the amount in pixels left empty between each adjacent + item, and defaults to 0. + + \sa Grid::spacing +*/ + class QSGFlowPrivate : public QSGBasePositionerPrivate { Q_DECLARE_PUBLIC(QSGFlow) @@ -661,6 +1335,21 @@ QSGFlow::QSGFlow(QSGItem *parent) d->addItemChangeListener(d, QSGItemPrivate::Geometry); } +/*! + \qmlproperty enumeration QtQuick2::Flow::flow + This property holds the flow of the layout. + + Possible values are: + + \list + \o Flow.LeftToRight (default) - Items are positioned next to + to each other according to the \l layoutDirection until the width of the Flow + is exceeded, then wrapped to the next line. + \o Flow.TopToBottom - Items are positioned next to each + other from top to bottom until the height of the Flow is exceeded, + then wrapped to the next column. + \endlist +*/ QSGFlow::Flow QSGFlow::flow() const { Q_D(const QSGFlow); @@ -677,6 +1366,25 @@ void QSGFlow::setFlow(Flow flow) } } +/*! + \qmlproperty enumeration QtQuick2::Flow::layoutDirection + + This property holds the layout direction of the layout. + + Possible values are: + + \list + \o Qt.LeftToRight (default) - Items are positioned from the top to bottom, + and left to right. The flow direction is dependent on the + \l Flow::flow property. + \o Qt.RightToLeft - Items are positioned from the top to bottom, + and right to left. The flow direction is dependent on the + \l Flow::flow property. + \endlist + + \sa Grid::layoutDirection, Row::layoutDirection, {declarative/righttoleft/layoutdirection}{Layout directions example} +*/ + Qt::LayoutDirection QSGFlow::layoutDirection() const { Q_D(const QSGFlow); @@ -694,6 +1402,17 @@ void QSGFlow::setLayoutDirection(Qt::LayoutDirection layoutDirection) } } +/*! + \qmlproperty enumeration QtQuick2::Flow::effectiveLayoutDirection + This property holds the effective layout direction of the flow positioner. + + When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts, + the visual layout direction of the grid positioner will be mirrored. However, the + property \l {Flow::layoutDirection}{layoutDirection} will remain unchanged. + + \sa Flow::layoutDirection, {LayoutMirroring}{LayoutMirroring} +*/ + Qt::LayoutDirection QSGFlow::effectiveLayoutDirection() const { return QSGBasePositionerPrivate::getEffectiveLayoutDirection(this); diff --git a/src/declarative/items/qsgpositioners_p.h b/src/declarative/items/qsgpositioners_p.h index a23c9d446c..7200b6da8d 100644 --- a/src/declarative/items/qsgpositioners_p.h +++ b/src/declarative/items/qsgpositioners_p.h @@ -59,6 +59,37 @@ QT_MODULE(Declarative) class QSGBasePositionerPrivate; +class QSGPositionerAttached : public QObject +{ + Q_OBJECT + +public: + QSGPositionerAttached(QObject *parent); + + Q_PROPERTY(int index READ index NOTIFY indexChanged) + Q_PROPERTY(bool isFirstItem READ isFirstItem NOTIFY isFirstItemChanged) + Q_PROPERTY(bool isLastItem READ isLastItem NOTIFY isLastItemChanged) + + int index() const { return m_index; } + void setIndex(int index); + + bool isFirstItem() const { return m_isFirstItem; } + void setIsFirstItem(bool isFirstItem); + + bool isLastItem() const { return m_isLastItem; } + void setIsLastItem(bool isLastItem); + +Q_SIGNALS: + void indexChanged(); + void isFirstItemChanged(); + void isLastItemChanged(); + +private: + int m_index; + bool m_isFirstItem; + bool m_isLastItem; +}; + class Q_DECLARATIVE_PRIVATE_EXPORT QSGBasePositioner : public QSGImplicitSizeItem { Q_OBJECT @@ -80,6 +111,10 @@ public: QDeclarativeTransition *add() const; void setAdd(QDeclarativeTransition *); + static QSGPositionerAttached *qmlAttachedProperties(QObject *obj); + + void updateAttachedProperties(QSGPositionerAttached *specificProperty = 0, QSGItem *specificPropertyOwner = 0) const; + protected: QSGBasePositioner(QSGBasePositionerPrivate &dd, PositionerType at, QSGItem *parent); virtual void componentComplete(); @@ -120,6 +155,7 @@ class Q_AUTOTEST_EXPORT QSGColumn : public QSGBasePositioner Q_OBJECT public: QSGColumn(QSGItem *parent=0); + protected: virtual void doPositioning(QSizeF *contentSize); virtual void reportConflictingAnchors(); @@ -155,6 +191,8 @@ class Q_AUTOTEST_EXPORT QSGGrid : public QSGBasePositioner Q_OBJECT Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged) + Q_PROPERTY(int rowSpacing READ rowSpacing WRITE setRowSpacing NOTIFY rowSpacingChanged) + Q_PROPERTY(int columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged) Q_PROPERTY(Flow flow READ flow WRITE setFlow NOTIFY flowChanged) Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged) Q_PROPERTY(Qt::LayoutDirection effectiveLayoutDirection READ effectiveLayoutDirection NOTIFY effectiveLayoutDirectionChanged) @@ -168,6 +206,12 @@ public: int columns() const {return m_columns;} void setColumns(const int columns); + int rowSpacing() const { return m_rowSpacing; } + void setRowSpacing(int); + + int columnSpacing() const { return m_columnSpacing; } + void setColumnSpacing(int); + Q_ENUMS(Flow) enum Flow { LeftToRight, TopToBottom }; Flow flow() const; @@ -183,6 +227,8 @@ Q_SIGNALS: void flowChanged(); void layoutDirectionChanged(); void effectiveLayoutDirectionChanged(); + void rowSpacingChanged(); + void columnSpacingChanged(); protected: virtual void doPositioning(QSizeF *contentSize); @@ -191,6 +237,8 @@ protected: private: int m_rows; int m_columns; + int m_rowSpacing; + int m_columnSpacing; Flow m_flow; Q_DISABLE_COPY(QSGGrid) }; @@ -237,6 +285,9 @@ QML_DECLARE_TYPE(QSGRow) QML_DECLARE_TYPE(QSGGrid) QML_DECLARE_TYPE(QSGFlow) +QML_DECLARE_TYPE(QSGBasePositioner) +QML_DECLARE_TYPEINFO(QSGBasePositioner, QML_HAS_ATTACHED_PROPERTIES) + QT_END_HEADER #endif // QSGPOSITIONERS_P_H diff --git a/src/declarative/items/qsgrectangle.cpp b/src/declarative/items/qsgrectangle.cpp index cb648e25d7..25f0eda5d5 100644 --- a/src/declarative/items/qsgrectangle.cpp +++ b/src/declarative/items/qsgrectangle.cpp @@ -52,6 +52,24 @@ QT_BEGIN_NAMESPACE // XXX todo - should we change rectangle to draw entirely within its width/height? +/*! + \internal + \class QSGPen + \brief The QSGPen class provides a pen used for drawing rectangle borders on a QSGView. + + By default, the pen is invalid and nothing is drawn. You must either set a color (then the default + width is 1) or a width (then the default color is black). + + A width of 1 indicates is a single-pixel line on the border of the item being painted. + + Example: + \qml + Rectangle { + border.width: 2 + border.color: "red" + } + \endqml +*/ QSGPen::QSGPen(QObject *parent) : QObject(parent) @@ -63,7 +81,7 @@ QSGPen::QSGPen(QObject *parent) } qreal QSGPen::width() const -{ +{ return m_width; } @@ -77,9 +95,9 @@ void QSGPen::setWidth(qreal w) emit penChanged(); } -QColor QSGPen::color() const -{ - return m_color; +QColor QSGPen::color() const +{ + return m_color; } void QSGPen::setColor(const QColor &c) @@ -104,33 +122,53 @@ void QSGPen::setAligned(bool aligned) } bool QSGPen::isValid() const -{ +{ return m_valid; } -QSGGradientStop::QSGGradientStop(QObject *parent) +/*! + \qmlclass GradientStop QSGGradientStop + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The GradientStop item defines the color at a position in a Gradient. + + \sa Gradient +*/ + +/*! + \qmlproperty real QtQuick2::GradientStop::position + \qmlproperty color QtQuick2::GradientStop::color + + The position and color properties describe the color used at a given + position in a gradient, as represented by a gradient stop. + + The default position is 0.0; the default color is black. + + \sa Gradient +*/ +QSGGradientStop::QSGGradientStop(QObject *parent) : QObject(parent) { } -qreal QSGGradientStop::position() const +qreal QSGGradientStop::position() const { - return m_position; + return m_position; } -void QSGGradientStop::setPosition(qreal position) +void QSGGradientStop::setPosition(qreal position) { - m_position = position; updateGradient(); + m_position = position; updateGradient(); } -QColor QSGGradientStop::color() const +QColor QSGGradientStop::color() const { - return m_color; + return m_color; } -void QSGGradientStop::setColor(const QColor &color) +void QSGGradientStop::setColor(const QColor &color) { - m_color = color; updateGradient(); + m_color = color; updateGradient(); } void QSGGradientStop::updateGradient() @@ -139,19 +177,80 @@ void QSGGradientStop::updateGradient() grad->doUpdate(); } -QSGGradient::QSGGradient(QObject *parent) -: QObject(parent), m_gradient(0) +/*! + \qmlclass Gradient QSGGradient + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The Gradient item defines a gradient fill. + + A gradient is defined by two or more colors, which will be blended seamlessly. + + The colors are specified as a set of GradientStop child items, each of + which defines a position on the gradient from 0.0 to 1.0 and a color. + The position of each GradientStop is defined by setting its + \l{GradientStop::}{position} property; its color is defined using its + \l{GradientStop::}{color} property. + + A gradient without any gradient stops is rendered as a solid white fill. + + Note that this item is not a visual representation of a gradient. To display a + gradient, use a visual element (like \l Rectangle) which supports the use + of gradients. + + \section1 Example Usage + + \div {class="float-right"} + \inlineimage qml-gradient.png + \enddiv + + The following example declares a \l Rectangle item with a gradient starting + with red, blending to yellow at one third of the height of the rectangle, + and ending with green: + + \snippet doc/src/snippets/declarative/gradient.qml code + + \clearfloat + \section1 Performance and Limitations + + Calculating gradients can be computationally expensive compared to the use + of solid color fills or images. Consider using gradients for static items + in a user interface. + + In Qt 4.7, only vertical, linear gradients can be applied to items. If you + need to apply different orientations of gradients, a combination of rotation + and clipping will need to be applied to the relevant items. This can + introduce additional performance requirements for your application. + + The use of animations involving gradient stops may not give the desired + result. An alternative way to animate gradients is to use pre-generated + images or SVG drawings containing gradients. + + \sa GradientStop +*/ + +/*! + \qmlproperty list<GradientStop> QtQuick2::Gradient::stops + \default + + This property holds the gradient stops describing the gradient. + + By default, this property contains an empty list. + + To set the gradient stops, define them as children of the Gradient element. +*/ +QSGGradient::QSGGradient(QObject *parent) +: QObject(parent), m_gradient(0) { } -QSGGradient::~QSGGradient() +QSGGradient::~QSGGradient() { - delete m_gradient; + delete m_gradient; } -QDeclarativeListProperty<QSGGradientStop> QSGGradient::stops() +QDeclarativeListProperty<QSGGradientStop> QSGGradient::stops() { - return QDeclarativeListProperty<QSGGradientStop>(this, m_stops); + return QDeclarativeListProperty<QSGGradientStop>(this, m_stops); } const QGradient *QSGGradient::gradient() const @@ -177,6 +276,51 @@ void QSGGradient::doUpdate() int QSGRectanglePrivate::doUpdateSlotIdx = -1; +/*! + \qmlclass Rectangle QSGRectangle + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The Rectangle item provides a filled rectangle with an optional border. + \inherits Item + + Rectangle items are used to fill areas with solid color or gradients, and are + often used to hold other items. + + \section1 Appearance + + Each Rectangle item is painted using either a solid fill color, specified using + the \l color property, or a gradient, defined using a Gradient element and set + using the \l gradient property. If both a color and a gradient are specified, + the gradient is used. + + You can add an optional border to a rectangle with its own color and thickness + by settting the \l border.color and \l border.width properties. + + You can also create rounded rectangles using the \l radius property. Since this + introduces curved edges to the corners of a rectangle, it may be appropriate to + set the \l smooth property to improve its appearance. + + \section1 Example Usage + + \div {class="float-right"} + \inlineimage declarative-rect.png + \enddiv + + The following example shows the effects of some of the common properties on a + Rectangle item, which in this case is used to create a square: + + \snippet doc/src/snippets/declarative/rectangle/rectangle.qml document + + \clearfloat + \section1 Performance + + Using the \l smooth property improves the appearance of a rounded rectangle at + the cost of rendering performance. You should consider unsetting this property + for rectangles in motion, and only set it when they are stationary. + + \sa Image +*/ + QSGRectangle::QSGRectangle(QSGItem *parent) : QSGItem(*(new QSGRectanglePrivate), parent) { @@ -186,17 +330,77 @@ QSGRectangle::QSGRectangle(QSGItem *parent) void QSGRectangle::doUpdate() { Q_D(QSGRectangle); - const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0; - d->setPaintMargin((pw+1)/2); + qreal penMargin = 0; + qreal penOffset = 0; + if (d->pen && d->pen->isValid()) { + if (d->pen->aligned()) { + const int pw = qRound(d->pen->width()); + penMargin = qreal(0.5) * pw; + penOffset = (pw & 1) * qreal(0.5); + } else { + penMargin = qreal(0.5) * d->pen->width(); + } + } + if (penMargin != d->penMargin || penOffset != d->penOffset) { + d->penMargin = penMargin; + d->penOffset = penOffset; + d->dirty(QSGItemPrivate::Size); // update clip + } update(); } +/*! + \qmlproperty int QtQuick2::Rectangle::border.width + \qmlproperty color QtQuick2::Rectangle::border.color + + The width and color used to draw the border of the rectangle. + + A width of 1 creates a thin line. For no line, use a width of 0 or a transparent color. + + \note The width of the rectangle's border does not affect the geometry of the + rectangle itself or its position relative to other items if anchors are used. + + If \c border.width is an odd number, the rectangle is painted at a half-pixel offset to retain + border smoothness. Also, the border is rendered evenly on either side of the + rectangle's boundaries, and the spare pixel is rendered to the right and below the + rectangle (as documented for QRect rendering). This can cause unintended effects if + \c border.width is 1 and the rectangle is \l{Item::clip}{clipped} by a parent item: + + \div {class="float-right"} + \inlineimage rect-border-width.png + \enddiv + + \snippet doc/src/snippets/declarative/rectangle/rect-border-width.qml 0 + + \clearfloat + Here, the innermost rectangle's border is clipped on the bottom and right edges by its + parent. To avoid this, the border width can be set to two instead of one. +*/ QSGPen *QSGRectangle::border() { Q_D(QSGRectangle); return d->getPen(); } +/*! + \qmlproperty Gradient QtQuick2::Rectangle::gradient + + The gradient to use to fill the rectangle. + + This property allows for the construction of simple vertical gradients. + Other gradients may by formed by adding rotation to the rectangle. + + \div {class="float-left"} + \inlineimage declarative-rect_gradient.png + \enddiv + + \snippet doc/src/snippets/declarative/rectangle/rectangle-gradient.qml rectangles + \clearfloat + + If both a gradient and a color are specified, the gradient will be used. + + \sa Gradient, color +*/ QSGGradient *QSGRectangle::gradient() const { Q_D(const QSGRectangle); @@ -221,6 +425,14 @@ void QSGRectangle::setGradient(QSGGradient *gradient) update(); } +/*! + \qmlproperty real QtQuick2::Rectangle::radius + This property holds the corner radius used to draw a rounded rectangle. + + If radius is non-zero, the rectangle will be painted as a rounded rectangle, otherwise it will be + painted as a normal rectangle. The same radius is used by all 4 corners; there is currently + no way to specify different radii for different corners. +*/ qreal QSGRectangle::radius() const { Q_D(const QSGRectangle); @@ -238,6 +450,26 @@ void QSGRectangle::setRadius(qreal radius) emit radiusChanged(); } +/*! + \qmlproperty color QtQuick2::Rectangle::color + This property holds the color used to fill the rectangle. + + The default color is white. + + \div {class="float-right"} + \inlineimage rect-color.png + \enddiv + + The following example shows rectangles with colors specified + using hexadecimal and named color notation: + + \snippet doc/src/snippets/declarative/rectangle/rectangle-colors.qml rectangles + + \clearfloat + If both a gradient and a color are specified, the gradient will be used. + + \sa gradient +*/ QColor QSGRectangle::color() const { Q_D(const QSGRectangle); @@ -297,11 +529,27 @@ QSGNode *QSGRectangle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *da return rectangle; } +/*! + \qmlproperty bool QtQuick2::Rectangle::smooth + + Set this property if you want the item to be smoothly scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. + + \image rect-smooth.png + On this image, smooth is turned off on the top half and on on the bottom half. +*/ QRectF QSGRectangle::boundingRect() const { Q_D(const QSGRectangle); - return QRectF(-d->paintmargin, -d->paintmargin, width()+d->paintmargin*2, height()+d->paintmargin*2); + return QRectF(d->penOffset - d->penMargin, d->penOffset - d->penMargin, + d->width + 2 * d->penMargin, d->height + 2 * d->penMargin); } QT_END_NAMESPACE diff --git a/src/declarative/items/qsgrectangle_p_p.h b/src/declarative/items/qsgrectangle_p_p.h index 6e1beb7bdb..234a029aaf 100644 --- a/src/declarative/items/qsgrectangle_p_p.h +++ b/src/declarative/items/qsgrectangle_p_p.h @@ -66,7 +66,7 @@ class QSGRectanglePrivate : public QSGItemPrivate public: QSGRectanglePrivate() : - color(Qt::white), gradient(0), pen(0), radius(0), paintmargin(0) + color(Qt::white), gradient(0), pen(0), radius(0), penMargin(0), penOffset(0) { } @@ -79,7 +79,8 @@ public: QSGGradient *gradient; QSGPen *pen; qreal radius; - qreal paintmargin; + qreal penMargin; + qreal penOffset; static int doUpdateSlotIdx; QSGPen *getPen() { @@ -95,13 +96,6 @@ public: } return pen; } - - void setPaintMargin(qreal margin) - { - if (margin == paintmargin) - return; - paintmargin = margin; - } }; QT_END_NAMESPACE diff --git a/src/declarative/items/qsgrepeater.cpp b/src/declarative/items/qsgrepeater.cpp index cdada2d59d..bdb3a3d98a 100644 --- a/src/declarative/items/qsgrepeater.cpp +++ b/src/declarative/items/qsgrepeater.cpp @@ -60,6 +60,90 @@ QSGRepeaterPrivate::~QSGRepeaterPrivate() delete model; } +/*! + \qmlclass Repeater QSGRepeater + \inqmlmodule QtQuick 2 + \ingroup qml-utility-elements + \inherits Item + + \brief The Repeater element allows you to repeat an Item-based component using a model. + + The Repeater element is used to create a large number of + similar items. Like other view elements, a Repeater has a \l model and a \l delegate: + for each entry in the model, the delegate is instantiated + in a context seeded with data from the model. A Repeater item is usually + enclosed in a positioner element such as \l Row or \l Column to visually + position the multiple delegate items created by the Repeater. + + The following Repeater creates three instances of a \l Rectangle item within + a \l Row: + + \snippet doc/src/snippets/declarative/repeaters/repeater.qml import + \codeline + \snippet doc/src/snippets/declarative/repeaters/repeater.qml simple + + \image repeater-simple.png + + A Repeater's \l model can be any of the supported \l {qmlmodels}{data models}. + Additionally, like delegates for other views, a Repeater delegate can access + its index within the repeater, as well as the model data relevant to the + delegate. See the \l delegate property documentation for details. + + Items instantiated by the Repeater are inserted, in order, as + children of the Repeater's parent. The insertion starts immediately after + the repeater's position in its parent stacking list. This allows + a Repeater to be used inside a layout. For example, the following Repeater's + items are stacked between a red rectangle and a blue rectangle: + + \snippet doc/src/snippets/declarative/repeaters/repeater.qml layout + + \image repeater.png + + + \note A Repeater item owns all items it instantiates. Removing or dynamically destroying + an item created by a Repeater results in unpredictable behavior. + + + \section2 Considerations when using Repeater + + The Repeater element creates all of its delegate items when the repeater is first + created. This can be inefficient if there are a large number of delegate items and + not all of the items are required to be visible at the same time. If this is the case, + consider using other view elements like ListView (which only creates delegate items + when they are scrolled into view) or use the \l {Dynamic Object Creation} methods to + create items as they are required. + + Also, note that Repeater is \l {Item}-based, and can only repeat \l {Item}-derived objects. + For example, it cannot be used to repeat QtObjects: + \badcode + Item { + //XXX does not work! Can't repeat QtObject as it doesn't derive from Item. + Repeater { + model: 10 + QtObject {} + } + } + \endcode + */ + +/*! + \qmlsignal QtQuick2::Repeater::onItemAdded(int index, Item item) + + This handler is called when an item is added to the repeater. The \a index + parameter holds the index at which the item has been inserted within the + repeater, and the \a item parameter holds the \l Item that has been added. +*/ + +/*! + \qmlsignal QtQuick2::Repeater::onItemRemoved(int index, Item item) + + This handler is called when an item is removed from the repeater. The \a index + parameter holds the index at which the item was removed from the repeater, + and the \a item parameter holds the \l Item that was removed. + + Do not keep a reference to \a item if it was created by this repeater, as + in these cases it will be deleted shortly after the handler is called. +*/ QSGRepeater::QSGRepeater(QSGItem *parent) : QSGItem(*(new QSGRepeaterPrivate), parent) { @@ -69,6 +153,24 @@ QSGRepeater::~QSGRepeater() { } +/*! + \qmlproperty any QtQuick2::Repeater::model + + The model providing data for the repeater. + + This property can be set to any of the supported \l {qmlmodels}{data models}: + + \list + \o A number that indicates the number of delegates to be created by the repeater + \o A model (e.g. a ListModel item, or a QAbstractItemModel subclass) + \o A string list + \o An object list + \endlist + + The type of model affects the properties that are exposed to the \l delegate. + + \sa {qmlmodels}{Data Models} +*/ QVariant QSGRepeater::model() const { Q_D(const QSGRepeater); @@ -124,6 +226,39 @@ void QSGRepeater::setModel(const QVariant &model) emit countChanged(); } +/*! + \qmlproperty Component QtQuick2::Repeater::delegate + \default + + The delegate provides a template defining each item instantiated by the repeater. + + Delegates are exposed to a read-only \c index property that indicates the index + of the delegate within the repeater. For example, the following \l Text delegate + displays the index of each repeated item: + + \table + \row + \o \snippet doc/src/snippets/declarative/repeaters/repeater.qml index + \o \image repeater-index.png + \endtable + + If the \l model is a \l{QStringList-based model}{string list} or + \l{QObjectList-based model}{object list}, the delegate is also exposed to + a read-only \c modelData property that holds the string or object data. For + example: + + \table + \row + \o \snippet doc/src/snippets/declarative/repeaters/repeater.qml modeldata + \o \image repeater-modeldata.png + \endtable + + If the \l model is a model object (such as a \l ListModel) the delegate + can access all model roles as named properties, in the same way that delegates + do for view classes like ListView. + + \sa {QML Data Models} + */ QDeclarativeComponent *QSGRepeater::delegate() const { Q_D(const QSGRepeater); @@ -153,6 +288,11 @@ void QSGRepeater::setDelegate(QDeclarativeComponent *delegate) } } +/*! + \qmlproperty int QtQuick2::Repeater::count + + This property holds the number of items in the repeater. +*/ int QSGRepeater::count() const { Q_D(const QSGRepeater); @@ -161,6 +301,12 @@ int QSGRepeater::count() const return 0; } +/*! + \qmlmethod Item QtQuick2::Repeater::itemAt(index) + + Returns the \l Item that has been created at the given \a index, or \c null + if no item exists at \a index. +*/ QSGItem *QSGRepeater::itemAt(int index) const { Q_D(const QSGRepeater); diff --git a/src/declarative/items/qsgscalegrid.cpp b/src/declarative/items/qsgscalegrid.cpp index a9eb080d86..e1b76c4158 100644 --- a/src/declarative/items/qsgscalegrid.cpp +++ b/src/declarative/items/qsgscalegrid.cpp @@ -164,6 +164,8 @@ QSGGridScaledImage::QSGGridScaledImage(QIODevice *data) _l = l; _r = r; _t = t; _b = b; _pix = imgFile; + if (_pix.startsWith(QLatin1Char('"')) && _pix.endsWith(QLatin1Char('"'))) + _pix = _pix.mid(1, _pix.size() - 2); // remove leading/trailing quotes. } QSGBorderImage::TileMode QSGGridScaledImage::stringToRule(const QString &s) diff --git a/src/declarative/items/qsgshadereffectsource.cpp b/src/declarative/items/qsgshadereffectsource.cpp index f9dbfe58ec..ef6dd64532 100644 --- a/src/declarative/items/qsgshadereffectsource.cpp +++ b/src/declarative/items/qsgshadereffectsource.cpp @@ -619,8 +619,12 @@ void QSGShaderEffectSource::setSourceRect(const QRectF &rect) /*! \qmlproperty size ShaderEffectSource::textureSize - This property holds the size of the texture. If it is empty, which is the - default, the size of the source rectangle is used. + This property holds the requested size of the texture. If it is empty, + which is the default, the size of the source rectangle is used. + + \note Some platforms have a limit on how small framebuffer objects can be, + which means the actual texture size might be larger than the requested + size. */ QSize QSGShaderEffectSource::textureSize() const @@ -812,7 +816,7 @@ static void get_wrap_mode(QSGShaderEffectSource::WrapMode mode, QSGTexture::Wrap QSGNode *QSGShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { - if (!m_sourceItem) { + if (!m_sourceItem || m_sourceItem->width() == 0 || m_sourceItem->height() == 0) { delete oldNode; return 0; } @@ -820,13 +824,22 @@ QSGNode *QSGShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNod QSGShaderEffectTexture *tex = qobject_cast<QSGShaderEffectTexture *>(m_texture); tex->setLive(m_live); tex->setItem(QSGItemPrivate::get(m_sourceItem)->itemNode()); - QRectF sourceRect = m_sourceRect.isNull() + QRectF sourceRect = m_sourceRect.width() == 0 || m_sourceRect.height() == 0 ? QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height()) : m_sourceRect; tex->setRect(sourceRect); QSize textureSize = m_textureSize.isEmpty() ? QSize(qCeil(qAbs(sourceRect.width())), qCeil(qAbs(sourceRect.height()))) : m_textureSize; + Q_ASSERT(!textureSize.isEmpty()); + QSGItemPrivate *d = static_cast<QSGItemPrivate *>(QObjectPrivate::get(this)); + const QSize minTextureSize = d->sceneGraphContext()->minimumFBOSize(); + // Keep power-of-two by doubling the size. + while (textureSize.width() < minTextureSize.width()) + textureSize.rwidth() *= 2; + while (textureSize.height() < minTextureSize.height()) + textureSize.rheight() *= 2; + tex->setSize(textureSize); tex->setRecursive(m_recursive); tex->setFormat(GLenum(m_format)); diff --git a/src/declarative/items/qsgtext.cpp b/src/declarative/items/qsgtext.cpp index cc6a4b406d..bac0be969b 100644 --- a/src/declarative/items/qsgtext.cpp +++ b/src/declarative/items/qsgtext.cpp @@ -91,6 +91,7 @@ private: static QSet<QUrl> errors; }; +DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) DEFINE_BOOL_CONFIG_OPTION(enableImageCache, QML_ENABLE_TEXT_IMAGE_CACHE); QString QSGTextPrivate::elideChar = QString(0x2026); @@ -106,7 +107,12 @@ QSGTextPrivate::QSGTextPrivate() richText(false), singleline(false), cacheAllTextAsImage(true), internalWidthUpdate(false), requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false), layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), naturalWidth(0), - doc(0), layoutThread(0), nodeType(NodeIsNull) + doc(0), nodeType(NodeIsNull) + +#if defined(Q_OS_MAC) + , layoutThread(0) +#endif + { cacheAllTextAsImage = enableImageCache(); } @@ -290,7 +296,9 @@ void QSGTextPrivate::updateSize() int dy = q->height(); QSize size(0, 0); +#if defined(Q_OS_MAC) layoutThread = QThread::currentThread(); +#endif //setup instance of QTextLayout for all cases other than richtext if (!richText) { @@ -312,6 +320,8 @@ void QSGTextPrivate::updateSize() QTextOption option; option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign)); option.setWrapMode(QTextOption::WrapMode(wrapMode)); + if (!cacheAllTextAsImage && !richTextAsImage && !qmlDisableDistanceField()) + option.setUseDesignMetrics(true); doc->setDefaultTextOption(option); if (requireImplicitWidth && q->widthValid()) { doc->setTextWidth(-1); @@ -375,6 +385,8 @@ QRect QSGTextPrivate::setupTextLayout() QTextOption textOption = layout.textOption(); textOption.setAlignment(Qt::Alignment(q->effectiveHAlign())); textOption.setWrapMode(QTextOption::WrapMode(wrapMode)); + if (!cacheAllTextAsImage && !richTextAsImage && !qmlDisableDistanceField()) + textOption.setUseDesignMetrics(true); layout.setTextOption(textOption); bool elideText = false; @@ -574,7 +586,7 @@ void QSGTextPrivate::invalidateImageCache() { Q_Q(QSGText); - if(richTextAsImage || cacheAllTextAsImage || (!QSGDistanceFieldGlyphCache::distanceFieldEnabled() && style != QSGText::Normal)){//If actually using the image cache + if(richTextAsImage || cacheAllTextAsImage || (qmlDisableDistanceField() && style != QSGText::Normal)){//If actually using the image cache if (imageCacheDirty) return; @@ -694,6 +706,49 @@ QPixmap QSGTextPrivate::drawOutline(const QPixmap &source, const QPixmap &styleS return img; } +/*! + \qmlclass Text QSGText + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The Text item allows you to add formatted text to a scene. + \inherits Item + + Text items can display both plain and rich text. For example, red text with + a specific font and size can be defined like this: + + \qml + Text { + text: "Hello World!" + font.family: "Helvetica" + font.pointSize: 24 + color: "red" + } + \endqml + + Rich text is defined using HTML-style markup: + + \qml + Text { + text: "<b>Hello</b> <i>World!</i>" + } + \endqml + + \image declarative-text.png + + If height and width are not explicitly set, Text will attempt to determine how + much room is needed and set it accordingly. Unless \l wrapMode is set, it will always + prefer width to height (all text will be placed on a single line). + + The \l elide property can alternatively be used to fit a single line of + plain text to a set width. + + Note that the \l{Supported HTML Subset} is limited. Also, if the text contains + HTML img tags that load remote images, the text is reloaded. + + Text provides read-only text. For editable text, see \l TextEdit. + + \sa {declarative/text/fonts}{Fonts example} +*/ QSGText::QSGText(QSGItem *parent) : QSGImplicitSizeItem(*(new QSGTextPrivate), parent) { @@ -705,6 +760,149 @@ QSGText::~QSGText() { } +/*! + \qmlproperty bool QtQuick2::Text::clip + This property holds whether the text is clipped. + + Note that if the text does not fit in the bounding rectangle it will be abruptly chopped. + + If you want to display potentially long text in a limited space, you probably want to use \c elide instead. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::smooth + + This property holds whether the text is smoothly scaled or transformed. + + Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +/*! + \qmlsignal QtQuick2::Text::onLinkActivated(string link) + + This handler is called when the user clicks on a link embedded in the text. + The link must be in rich text or HTML format and the + \a link string provides access to the particular link. + + \snippet doc/src/snippets/declarative/text/onLinkActivated.qml 0 + + The example code will display the text + "The main website is at \l{http://qt.nokia.com}{Nokia Qt DF}." + + Clicking on the highlighted link will output + \tt{http://qt.nokia.com link activated} to the console. +*/ + +/*! + \qmlproperty string QtQuick2::Text::font.family + + Sets the family name of the font. + + The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". + If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. + If the family isn't available a family will be set using the font matching algorithm. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.bold + + Sets whether the font weight is bold. +*/ + +/*! + \qmlproperty enumeration QtQuick2::Text::font.weight + + Sets the font's weight. + + The weight can be one of: + \list + \o Font.Light + \o Font.Normal - the default + \o Font.DemiBold + \o Font.Bold + \o Font.Black + \endlist + + \qml + Text { text: "Hello"; font.weight: Font.DemiBold } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.italic + + Sets whether the font has an italic style. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.underline + + Sets whether the text is underlined. +*/ + +/*! + \qmlproperty bool QtQuick2::Text::font.strikeout + + Sets whether the font has a strikeout style. +*/ + +/*! + \qmlproperty real QtQuick2::Text::font.pointSize + + Sets the font size in points. The point size must be greater than zero. +*/ + +/*! + \qmlproperty int QtQuick2::Text::font.pixelSize + + Sets the font size in pixels. + + Using this function makes the font device dependent. + Use \c pointSize to set the size of the font in a device independent manner. +*/ + +/*! + \qmlproperty real QtQuick2::Text::font.letterSpacing + + Sets the letter spacing for the font. + + Letter spacing changes the default spacing between individual letters in the font. + A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. +*/ + +/*! + \qmlproperty real QtQuick2::Text::font.wordSpacing + + Sets the word spacing for the font. + + Word spacing changes the default spacing between individual words. + A positive value increases the word spacing by a corresponding amount of pixels, + while a negative value decreases the inter-word spacing accordingly. +*/ + +/*! + \qmlproperty enumeration QtQuick2::Text::font.capitalization + + Sets the capitalization for the text. + + \list + \o Font.MixedCase - This is the normal text rendering option where no capitalization change is applied. + \o Font.AllUppercase - This alters the text to be rendered in all uppercase type. + \o Font.AllLowercase - This alters the text to be rendered in all lowercase type. + \o Font.SmallCaps - This alters the text to be rendered in small-caps type. + \o Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character. + \endlist + + \qml + Text { text: "Hello"; font.capitalization: Font.AllLowercase } + \endqml +*/ QFont QSGText::font() const { Q_D(const QSGText); @@ -720,8 +918,6 @@ void QSGText::setFont(const QFont &font) d->sourceFont = font; QFont oldFont = d->font; d->font = font; - if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) - d->font.setHintingPreference(QFont::PreferNoHinting); if (d->font.pointSizeF() != -1) { // 0.5pt resolution @@ -735,6 +931,14 @@ void QSGText::setFont(const QFont &font) emit fontChanged(d->sourceFont); } +/*! + \qmlproperty string QtQuick2::Text::text + + The text to display. Text supports both plain and rich text strings. + + The item will try to automatically determine whether the text should + be treated as rich text. This determination is made using Qt::mightBeRichText(). +*/ QString QSGText::text() const { Q_D(const QSGText); @@ -764,6 +968,27 @@ void QSGText::setText(const QString &n) emit textChanged(d->text); } +/*! + \qmlproperty color QtQuick2::Text::color + + The text color. + + An example of green text defined using hexadecimal notation: + \qml + Text { + color: "#00FF00" + text: "green text" + } + \endqml + + An example of steel blue text defined using an SVG color name: + \qml + Text { + color: "steelblue" + text: "blue text" + } + \endqml +*/ QColor QSGText::color() const { Q_D(const QSGText); @@ -780,7 +1005,30 @@ void QSGText::setColor(const QColor &color) d->invalidateImageCache(); emit colorChanged(d->color); } +/*! + \qmlproperty enumeration QtQuick2::Text::style + + Set an additional text style. + + Supported text styles are: + \list + \o Text.Normal - the default + \o Text.Outline + \o Text.Raised + \o Text.Sunken + \endlist + + \qml + Row { + Text { font.pointSize: 24; text: "Normal" } + Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" } + Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" } + Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" } + } + \endqml + \image declarative-textstyle.png +*/ QSGText::TextStyle QSGText::style() const { Q_D(const QSGText); @@ -801,6 +1049,21 @@ void QSGText::setStyle(QSGText::TextStyle style) emit styleChanged(d->style); } +/*! + \qmlproperty color QtQuick2::Text::styleColor + + Defines the secondary color used by text styles. + + \c styleColor is used as the outline color for outlined text, and as the + shadow color for raised or sunken text. If no style has been set, it is not + used at all. + + \qml + Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" } + \endqml + + \sa style + */ QColor QSGText::styleColor() const { Q_D(const QSGText); @@ -818,6 +1081,30 @@ void QSGText::setStyleColor(const QColor &color) emit styleColorChanged(d->styleColor); } +/*! + \qmlproperty enumeration QtQuick2::Text::horizontalAlignment + \qmlproperty enumeration QtQuick2::Text::verticalAlignment + \qmlproperty enumeration QtQuick2::Text::effectiveHorizontalAlignment + + Sets the horizontal and vertical alignment of the text within the Text items + width and height. By default, the text is vertically aligned to the top. Horizontal + alignment follows the natural alignment of the text, for example text that is read + from left to right will be aligned to the left. + + The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight, \c Text.AlignHCenter and + \c Text.AlignJustify. The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom + and \c Text.AlignVCenter. + + Note that for a single line of text, the size of the text is the area of the text. In this common case, + all alignments are equivalent. If you want the text to be, say, centered in its parent, then you will + need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to + that of the parent. + + When using the attached property LayoutMirroring::enabled to mirror application + layouts, the horizontal alignment of text will also be mirrored. However, the property + \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment + of Text, use the read-only property \c effectiveHorizontalAlignment. +*/ QSGText::HAlignment QSGText::hAlign() const { Q_D(const QSGText); @@ -917,6 +1204,19 @@ void QSGText::setVAlign(VAlignment align) emit verticalAlignmentChanged(align); } +/*! + \qmlproperty enumeration QtQuick2::Text::wrapMode + + Set this property to wrap the text to the Text item's width. The text will only + wrap if an explicit width has been set. wrapMode can be one of: + + \list + \o Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l paintedWidth will exceed a set width. + \o Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l paintedWidth will exceed a set width. + \o Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word. + \o Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word. + \endlist +*/ QSGText::WrapMode QSGText::wrapMode() const { Q_D(const QSGText); @@ -935,18 +1235,48 @@ void QSGText::setWrapMode(WrapMode mode) emit wrapModeChanged(); } +/*! + \qmlproperty int QtQuick2::Text::lineCount + + Returns the number of lines visible in the text item. + + This property is not supported for rich text. + + \sa maximumLineCount +*/ int QSGText::lineCount() const { Q_D(const QSGText); return d->lineCount; } +/*! + \qmlproperty bool QtQuick2::Text::truncated + + Returns true if the text has been truncated due to \l maximumLineCount + or \l elide. + + This property is not supported for rich text. + + \sa maximumLineCount, elide +*/ bool QSGText::truncated() const { Q_D(const QSGText); return d->truncated; } +/*! + \qmlproperty int QtQuick2::Text::maximumLineCount + + Set this property to limit the number of lines that the text item will show. + If elide is set to Text.ElideRight, the text will be elided appropriately. + By default, this is the value of the largest possible integer. + + This property is not supported for rich text. + + \sa lineCount, elide +*/ int QSGText::maximumLineCount() const { Q_D(const QSGText); @@ -976,6 +1306,61 @@ void QSGText::resetMaximumLineCount() } } +/*! + \qmlproperty enumeration QtQuick2::Text::textFormat + + The way the text property should be displayed. + + Supported text formats are: + + \list + \o Text.AutoText (default) + \o Text.PlainText + \o Text.RichText + \o Text.StyledText + \endlist + + If the text format is \c Text.AutoText the text element + will automatically determine whether the text should be treated as + rich text. This determination is made using Qt::mightBeRichText(). + + Text.StyledText is an optimized format supporting some basic text + styling markup, in the style of html 3.2: + + \code + <font size="4" color="#ff0000">font size and color</font> + <b>bold</b> + <i>italic</i> + <br> + > < & + \endcode + + \c Text.StyledText parser is strict, requiring tags to be correctly nested. + + \table + \row + \o + \qml +Column { + Text { + font.pointSize: 24 + text: "<b>Hello</b> <i>World!</i>" + } + Text { + font.pointSize: 24 + textFormat: Text.RichText + text: "<b>Hello</b> <i>World!</i>" + } + Text { + font.pointSize: 24 + textFormat: Text.PlainText + text: "<b>Hello</b> <i>World!</i>" + } +} + \endqml + \o \image declarative-textformat.png + \endtable +*/ QSGText::TextFormat QSGText::textFormat() const { Q_D(const QSGText); @@ -1002,6 +1387,31 @@ void QSGText::setTextFormat(TextFormat format) emit textFormatChanged(d->format); } +/*! + \qmlproperty enumeration QtQuick2::Text::elide + + Set this property to elide parts of the text fit to the Text item's width. + The text will only elide if an explicit width has been set. + + This property cannot be used with rich text. + + Eliding can be: + \list + \o Text.ElideNone - the default + \o Text.ElideLeft + \o Text.ElideMiddle + \o Text.ElideRight + \endlist + + If this property is set to Text.ElideRight, it can be used with multiline + text. The text will only elide if maximumLineCount has been set. + + If the text is a multi-length string, and the mode is not \c Text.ElideNone, + the first string that fits will be used, otherwise the last will be elided. + + Multi-length strings are ordered from longest to shortest, separated by the + Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}). +*/ QSGText::TextElideMode QSGText::elideMode() const { Q_D(const QSGText); @@ -1079,11 +1489,13 @@ QSGNode *QSGText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) QRectF bounds = boundingRect(); // We need to make sure the layout is done in the current thread +#if defined(Q_OS_MAC) if (d->layoutThread != QThread::currentThread()) d->updateLayout(); +#endif // XXX todo - some styled text can be done by the QSGTextNode - if (d->richTextAsImage || d->cacheAllTextAsImage || (!QSGDistanceFieldGlyphCache::distanceFieldEnabled() && d->style != Normal)) { + if (d->richTextAsImage || d->cacheAllTextAsImage || (qmlDisableDistanceField() && d->style != Normal)) { bool wasDirty = d->textureImageCacheDirty; d->textureImageCacheDirty = false; @@ -1156,18 +1568,39 @@ bool QSGText::event(QEvent *e) } } +/*! + \qmlproperty real QtQuick2::Text::paintedWidth + + Returns the width of the text, including width past the width + which is covered due to insufficient wrapping if WrapMode is set. +*/ qreal QSGText::paintedWidth() const { Q_D(const QSGText); return d->paintedSize.width(); } +/*! + \qmlproperty real QtQuick2::Text::paintedHeight + + Returns the height of the text, including height past the height + which is covered due to there being more text than fits in the set height. +*/ qreal QSGText::paintedHeight() const { Q_D(const QSGText); return d->paintedSize.height(); } +/*! + \qmlproperty real QtQuick2::Text::lineHeight + + Sets the line height for the text. + The value can be in pixels or a multiplier depending on lineHeightMode. + + The default value is a multiplier of 1.0. + The line height must be a positive value. +*/ qreal QSGText::lineHeight() const { Q_D(const QSGText); @@ -1186,6 +1619,18 @@ void QSGText::setLineHeight(qreal lineHeight) emit lineHeightChanged(lineHeight); } +/*! + \qmlproperty enumeration QtQuick2::Text::lineHeightMode + + This property determines how the line height is specified. + The possible values are: + + \list + \o Text.ProportionalHeight (default) - this sets the spacing proportional to the + line (as a multiplier). For example, set to 2 for double spacing. + \o Text.FixedHeight - this sets the line height to a fixed line height (in pixels). + \endlist +*/ QSGText::LineHeightMode QSGText::lineHeightMode() const { Q_D(const QSGText); diff --git a/src/declarative/items/qsgtext_p_p.h b/src/declarative/items/qsgtext_p_p.h index 050e3984ab..40c986142c 100644 --- a/src/declarative/items/qsgtext_p_p.h +++ b/src/declarative/items/qsgtext_p_p.h @@ -134,7 +134,6 @@ public: QPixmap textLayoutImage(bool drawStyle); void drawTextLayout(QPainter *p, const QPointF &pos, bool drawStyle); QTextLayout layout; - QThread *layoutThread; static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource); static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource, int yOffset); @@ -146,9 +145,13 @@ public: enum NodeType { NodeIsNull, NodeIsTexture, - NodeIsText, + NodeIsText }; NodeType nodeType; + +#if defined(Q_OS_MAC) + QThread *layoutThread; +#endif }; QT_END_NAMESPACE diff --git a/src/declarative/items/qsgtextedit.cpp b/src/declarative/items/qsgtextedit.cpp index 4ba5bec5e4..1c0ed62f0d 100644 --- a/src/declarative/items/qsgtextedit.cpp +++ b/src/declarative/items/qsgtextedit.cpp @@ -43,6 +43,8 @@ #include "qsgtextedit_p_p.h" #include "qsgevents_p_p.h" #include "qsgcanvas.h" +#include "qsgtextnode_p.h" +#include "qsgsimplerectnode.h" #include <QtDeclarative/qdeclarativeinfo.h> #include <QtWidgets/qapplication.h> @@ -55,13 +57,68 @@ #include <private/qtextcontrol_p.h> #include <private/qtextengine_p.h> #include <private/qwidget_p.h> +#include <private/qsgdistancefieldglyphcache_p.h> +#include <private/qsgtexture_p.h> +#include <private/qsgadaptationlayer_p.h> QT_BEGIN_NAMESPACE +DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) + QWidgetPrivate *qt_widget_private(QWidget *widget); +/*! + \qmlclass TextEdit QSGTextEdit + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The TextEdit item displays multiple lines of editable formatted text. + \inherits Item + + The TextEdit item displays a block of editable, formatted text. + + It can display both plain and rich text. For example: + + \qml +TextEdit { + width: 240 + text: "<b>Hello</b> <i>World!</i>" + font.family: "Helvetica" + font.pointSize: 20 + color: "blue" + focus: true +} + \endqml + + \image declarative-textedit.gif + + Setting \l {Item::focus}{focus} to \c true enables the TextEdit item to receive keyboard focus. + + Note that the TextEdit does not implement scrolling, following the cursor, or other behaviors specific + to a look-and-feel. For example, to add flickable scrolling that follows the cursor: + \snippet snippets/declarative/texteditor.qml 0 + + A particular look-and-feel might use smooth scrolling (eg. using SmoothedFollow), might have a visible + scrollbar, or a scrollbar that fades in to show location, etc. + + Clipboard support is provided by the cut(), copy(), and paste() functions, and the selection can + be handled in a traditional "mouse" mechanism by setting selectByMouse, or handled completely + from QML by manipulating selectionStart and selectionEnd, or using selectAll() or selectWord(). + + You can translate between cursor positions (characters from the start of the document) and pixel + points using positionAt() and positionToRectangle(). + + \sa Text, TextInput, {declarative/text/textselection}{Text Selection example} +*/ + +/*! + \qmlsignal QtQuick2::TextEdit::onLinkActivated(string link) + + This handler is called when the user clicks on a link embedded in the text. + The link must be in rich text or HTML format and the + \a link string provides access to the particular link. +*/ QSGTextEdit::QSGTextEdit(QSGItem *parent) -: QSGImplicitSizePaintedItem(*(new QSGTextEditPrivate), parent) +: QSGImplicitSizeItem(*(new QSGTextEditPrivate), parent) { Q_D(QSGTextEdit); d->init(); @@ -79,6 +136,119 @@ QString QSGTextEdit::text() const return d->document->toPlainText(); } +/*! + \qmlproperty string QtQuick2::TextEdit::font.family + + Sets the family name of the font. + + The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". + If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. + If the family isn't available a family will be set using the font matching algorithm. +*/ + +/*! + \qmlproperty bool QtQuick2::TextEdit::font.bold + + Sets whether the font weight is bold. +*/ + +/*! + \qmlproperty enumeration QtQuick2::TextEdit::font.weight + + Sets the font's weight. + + The weight can be one of: + \list + \o Font.Light + \o Font.Normal - the default + \o Font.DemiBold + \o Font.Bold + \o Font.Black + \endlist + + \qml + TextEdit { text: "Hello"; font.weight: Font.DemiBold } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick2::TextEdit::font.italic + + Sets whether the font has an italic style. +*/ + +/*! + \qmlproperty bool QtQuick2::TextEdit::font.underline + + Sets whether the text is underlined. +*/ + +/*! + \qmlproperty bool QtQuick2::TextEdit::font.strikeout + + Sets whether the font has a strikeout style. +*/ + +/*! + \qmlproperty real QtQuick2::TextEdit::font.pointSize + + Sets the font size in points. The point size must be greater than zero. +*/ + +/*! + \qmlproperty int QtQuick2::TextEdit::font.pixelSize + + Sets the font size in pixels. + + Using this function makes the font device dependent. Use + \l{TextEdit::font.pointSize} to set the size of the font in a + device independent manner. +*/ + +/*! + \qmlproperty real QtQuick2::TextEdit::font.letterSpacing + + Sets the letter spacing for the font. + + Letter spacing changes the default spacing between individual letters in the font. + A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. +*/ + +/*! + \qmlproperty real QtQuick2::TextEdit::font.wordSpacing + + Sets the word spacing for the font. + + Word spacing changes the default spacing between individual words. + A positive value increases the word spacing by a corresponding amount of pixels, + while a negative value decreases the inter-word spacing accordingly. +*/ + +/*! + \qmlproperty enumeration QtQuick2::TextEdit::font.capitalization + + Sets the capitalization for the text. + + \list + \o Font.MixedCase - This is the normal text rendering option where no capitalization change is applied. + \o Font.AllUppercase - This alters the text to be rendered in all uppercase type. + \o Font.AllLowercase - This alters the text to be rendered in all lowercase type. + \o Font.SmallCaps - This alters the text to be rendered in small-caps type. + \o Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character. + \endlist + + \qml + TextEdit { text: "Hello"; font.capitalization: Font.AllLowercase } + \endqml +*/ + +/*! + \qmlproperty string QtQuick2::TextEdit::text + + The text to display. If the text format is AutoText the text edit will + automatically determine whether the text should be treated as + rich text. This determination is made using Qt::mightBeRichText(). +*/ void QSGTextEdit::setText(const QString &text) { Q_D(QSGTextEdit); @@ -92,12 +262,52 @@ void QSGTextEdit::setText(const QString &text) #else d->control->setPlainText(text); #endif + d->isComplexRichText = QSGTextNode::isComplexRichText(d->document); } else { d->control->setPlainText(text); } q_textChanged(); } +/*! + \qmlproperty enumeration QtQuick2::TextEdit::textFormat + + The way the text property should be displayed. + + \list + \o TextEdit.AutoText + \o TextEdit.PlainText + \o TextEdit.RichText + \endlist + + The default is TextEdit.AutoText. If the text format is TextEdit.AutoText the text edit + will automatically determine whether the text should be treated as + rich text. This determination is made using Qt::mightBeRichText(). + + \table + \row + \o + \qml +Column { + TextEdit { + font.pointSize: 24 + text: "<b>Hello</b> <i>World!</i>" + } + TextEdit { + font.pointSize: 24 + textFormat: TextEdit.RichText + text: "<b>Hello</b> <i>World!</i>" + } + TextEdit { + font.pointSize: 24 + textFormat: TextEdit.PlainText + text: "<b>Hello</b> <i>World!</i>" + } +} + \endqml + \o \image declarative-textformat.png + \endtable +*/ QSGTextEdit::TextFormat QSGTextEdit::textFormat() const { Q_D(const QSGTextEdit); @@ -122,6 +332,7 @@ void QSGTextEdit::setTextFormat(TextFormat format) d->control->setPlainText(d->text); #endif updateSize(); + d->isComplexRichText = QSGTextNode::isComplexRichText(d->document); } d->format = format; d->control->setAcceptRichText(d->format != PlainText); @@ -156,11 +367,26 @@ void QSGTextEdit::setFont(const QFont &font) moveCursorDelegate(); } updateSize(); - update(); + updateDocument(); } emit fontChanged(d->sourceFont); } +/*! + \qmlproperty color QtQuick2::TextEdit::color + + The text color. + + \qml + // green text using hexadecimal notation + TextEdit { color: "#00FF00" } + \endqml + + \qml + // steelblue text using SVG color name + TextEdit { color: "steelblue" } + \endqml +*/ QColor QSGTextEdit::color() const { Q_D(const QSGTextEdit); @@ -177,10 +403,15 @@ void QSGTextEdit::setColor(const QColor &color) QPalette pal = d->control->palette(); pal.setColor(QPalette::Text, color); d->control->setPalette(pal); - update(); + updateDocument(); emit colorChanged(d->color); } +/*! + \qmlproperty color QtQuick2::TextEdit::selectionColor + + The text highlight color, used behind selections. +*/ QColor QSGTextEdit::selectionColor() const { Q_D(const QSGTextEdit); @@ -197,10 +428,15 @@ void QSGTextEdit::setSelectionColor(const QColor &color) QPalette pal = d->control->palette(); pal.setColor(QPalette::Highlight, color); d->control->setPalette(pal); - update(); + updateDocument(); emit selectionColorChanged(d->selectionColor); } +/*! + \qmlproperty color QtQuick2::TextEdit::selectedTextColor + + The selected text color, used in selections. +*/ QColor QSGTextEdit::selectedTextColor() const { Q_D(const QSGTextEdit); @@ -217,10 +453,40 @@ void QSGTextEdit::setSelectedTextColor(const QColor &color) QPalette pal = d->control->palette(); pal.setColor(QPalette::HighlightedText, color); d->control->setPalette(pal); - update(); + updateDocument(); emit selectedTextColorChanged(d->selectedTextColor); } +/*! + \qmlproperty enumeration QtQuick2::TextEdit::horizontalAlignment + \qmlproperty enumeration QtQuick2::TextEdit::verticalAlignment + \qmlproperty enumeration QtQuick2::TextEdit::effectiveHorizontalAlignment + + Sets the horizontal and vertical alignment of the text within the TextEdit item's + width and height. By default, the text alignment follows the natural alignment + of the text, for example text that is read from left to right will be aligned to + the left. + + Valid values for \c horizontalAlignment are: + \list + \o TextEdit.AlignLeft (default) + \o TextEdit.AlignRight + \o TextEdit.AlignHCenter + \o TextEdit.AlignJustify + \endlist + + Valid values for \c verticalAlignment are: + \list + \o TextEdit.AlignTop (default) + \o TextEdit.AlignBottom + \o TextEdit.AlignVCenter + \endlist + + When using the attached property LayoutMirroring::enabled to mirror application + layouts, the horizontal alignment of text will also be mirrored. However, the property + \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment + of TextEdit, use the read-only property \c effectiveHorizontalAlignment. +*/ QSGTextEdit::HAlignment QSGTextEdit::hAlign() const { Q_D(const QSGTextEdit); @@ -320,7 +586,21 @@ void QSGTextEdit::setVAlign(QSGTextEdit::VAlignment alignment) moveCursorDelegate(); emit verticalAlignmentChanged(d->vAlign); } +/*! + \qmlproperty enumeration QtQuick2::TextEdit::wrapMode + + Set this property to wrap the text to the TextEdit item's width. + The text will only wrap if an explicit width has been set. + + \list + \o TextEdit.NoWrap - no wrapping will be performed. If the text contains insufficient newlines, then implicitWidth will exceed a set width. + \o TextEdit.WordWrap - wrapping is done on word boundaries only. If a word is too long, implicitWidth will exceed a set width. + \o TextEdit.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word. + \o TextEdit.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word. + \endlist + The default is TextEdit.NoWrap. If you set a width, consider using TextEdit.Wrap. +*/ QSGTextEdit::WrapMode QSGTextEdit::wrapMode() const { Q_D(const QSGTextEdit); @@ -338,24 +618,48 @@ void QSGTextEdit::setWrapMode(WrapMode mode) emit wrapModeChanged(); } +/*! + \qmlproperty int QtQuick2::TextEdit::lineCount + + Returns the total number of lines in the textEdit item. +*/ int QSGTextEdit::lineCount() const { Q_D(const QSGTextEdit); return d->lineCount; } +/*! + \qmlproperty real QtQuick2::TextEdit::paintedWidth + + Returns the width of the text, including the width past the width + which is covered due to insufficient wrapping if \l wrapMode is set. +*/ qreal QSGTextEdit::paintedWidth() const { Q_D(const QSGTextEdit); return d->paintedSize.width(); } +/*! + \qmlproperty real QtQuick2::TextEdit::paintedHeight + + Returns the height of the text, including the height past the height + that is covered if the text does not fit within the set height. +*/ qreal QSGTextEdit::paintedHeight() const { Q_D(const QSGTextEdit); return d->paintedSize.height(); } +/*! + \qmlmethod rectangle QtQuick2::TextEdit::positionToRectangle(position) + + Returns the rectangle at the given \a position in the text. The x, y, + and height properties correspond to the cursor that would describe + that position. +*/ QRectF QSGTextEdit::positionToRectangle(int pos) const { Q_D(const QSGTextEdit); @@ -365,6 +669,14 @@ QRectF QSGTextEdit::positionToRectangle(int pos) const } +/*! + \qmlmethod int QtQuick2::TextEdit::positionAt(int x, int y) + + Returns the text position closest to pixel position (\a x, \a y). + + Position 0 is before the first character, position 1 is after the first character + but before the second, and so on until position \l {text}.length, which is after all characters. +*/ int QSGTextEdit::positionAt(int x, int y) const { Q_D(const QSGTextEdit); @@ -388,6 +700,43 @@ int QSGTextEdit::positionAt(int x, int y) const return r; } +/*! + \qmlmethod void QtQuick2::TextEdit::moveCursorSelection(int position, SelectionMode mode = TextEdit.SelectCharacters) + + Moves the cursor to \a position and updates the selection according to the optional \a mode + parameter. (To only move the cursor, set the \l cursorPosition property.) + + When this method is called it additionally sets either the + selectionStart or the selectionEnd (whichever was at the previous cursor position) + to the specified position. This allows you to easily extend and contract the selected + text range. + + The selection mode specifies whether the selection is updated on a per character or a per word + basis. If not specified the selection mode will default to TextEdit.SelectCharacters. + + \list + \o TextEdit.SelectCharacters - Sets either the selectionStart or selectionEnd (whichever was at + the previous cursor position) to the specified position. + \o TextEdit.SelectWords - Sets the selectionStart and selectionEnd to include all + words between the specified postion and the previous cursor position. Words partially in the + range are included. + \endlist + + For example, take this sequence of calls: + + \code + cursorPosition = 5 + moveCursorSelection(9, TextEdit.SelectCharacters) + moveCursorSelection(7, TextEdit.SelectCharacters) + \endcode + + This moves the cursor to position 5, extend the selection end from 5 to 9 + and then retract the selection end from 9 to 7, leaving the text from position 5 to 7 + selected (the 6th and 7th characters). + + The same sequence with TextEdit.SelectWords will extend the selection start to a word boundary + before or on position 5 and extend the selection end to a word boundary on or past position 9. +*/ void QSGTextEdit::moveCursorSelection(int pos) { //Note that this is the same as setCursorPosition but with the KeepAnchor flag set @@ -448,6 +797,13 @@ void QSGTextEdit::moveCursorSelection(int pos, SelectionMode mode) d->control->setTextCursor(cursor); } +/*! + \qmlproperty bool QtQuick2::TextEdit::cursorVisible + If true the text edit shows a cursor. + + This property is set and unset when the text edit gets active focus, but it can also + be set directly (useful, for example, if a KeyProxy might forward keys to it). +*/ bool QSGTextEdit::isCursorVisible() const { Q_D(const QSGTextEdit); @@ -467,6 +823,10 @@ void QSGTextEdit::setCursorVisible(bool on) emit cursorVisibleChanged(d->cursorVisible); } +/*! + \qmlproperty int QtQuick2::TextEdit::cursorPosition + The position of the cursor in the TextEdit. +*/ int QSGTextEdit::cursorPosition() const { Q_D(const QSGTextEdit); @@ -485,6 +845,19 @@ void QSGTextEdit::setCursorPosition(int pos) d->control->setTextCursor(cursor); } +/*! + \qmlproperty Component QtQuick2::TextEdit::cursorDelegate + 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 QDeclarativeItem or + QDeclarativeItem derived item. +*/ QDeclarativeComponent* QSGTextEdit::cursorDelegate() const { Q_D(const QSGTextEdit); @@ -497,7 +870,7 @@ void QSGTextEdit::setCursorDelegate(QDeclarativeComponent* c) if(d->cursorComponent){ if(d->cursor){ d->control->setCursorWidth(-1); - update(cursorRectangle()); + updateCursor(); delete d->cursor; d->cursor = 0; } @@ -522,7 +895,7 @@ void QSGTextEdit::loadCursorDelegate() d->cursor = qobject_cast<QSGItem*>(d->cursorComponent->create(qmlContext(this))); if(d->cursor){ d->control->setCursorWidth(0); - update(cursorRectangle()); + updateCursor(); QDeclarative_setParent_noEvent(d->cursor, this); d->cursor->setParentItem(this); d->cursor->setHeight(QFontMetrics(d->font).height()); @@ -532,24 +905,64 @@ void QSGTextEdit::loadCursorDelegate() } } +/*! + \qmlproperty int QtQuick2::TextEdit::selectionStart + + The cursor position before the first character in the current selection. + + This property is read-only. To change the selection, use select(start,end), + selectAll(), or selectWord(). + + \sa selectionEnd, cursorPosition, selectedText +*/ int QSGTextEdit::selectionStart() const { Q_D(const QSGTextEdit); return d->control->textCursor().selectionStart(); } +/*! + \qmlproperty int QtQuick2::TextEdit::selectionEnd + + The cursor position after the last character in the current selection. + + This property is read-only. To change the selection, use select(start,end), + selectAll(), or selectWord(). + + \sa selectionStart, cursorPosition, selectedText +*/ int QSGTextEdit::selectionEnd() const { Q_D(const QSGTextEdit); return d->control->textCursor().selectionEnd(); } +/*! + \qmlproperty string QtQuick2::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 QSGTextEdit::selectedText() const { Q_D(const QSGTextEdit); return d->control->textCursor().selectedText(); } +/*! + \qmlproperty bool QtQuick2::TextEdit::activeFocusOnPress + + Whether the TextEdit should gain active focus on a mouse press. By default this is + set to true. +*/ bool QSGTextEdit::focusOnPress() const { Q_D(const QSGTextEdit); @@ -565,6 +978,12 @@ void QSGTextEdit::setFocusOnPress(bool on) emit activeFocusOnPressChanged(d->focusOnPress); } +/*! + \qmlproperty bool QtQuick2::TextEdit::persistentSelection + + Whether the TextEdit should keep the selection visible when it loses active focus to another + item in the scene. By default this is set to true; +*/ bool QSGTextEdit::persistentSelection() const { Q_D(const QSGTextEdit); @@ -580,6 +999,11 @@ void QSGTextEdit::setPersistentSelection(bool on) emit persistentSelectionChanged(d->persistentSelection); } +/* + \qmlproperty real QtQuick2::TextEdit::textMargin + + The margin, in pixels, around the text in the TextEdit. +*/ qreal QSGTextEdit::textMargin() const { Q_D(const QSGTextEdit); @@ -601,21 +1025,40 @@ void QSGTextEdit::geometryChanged(const QRectF &newGeometry, { if (newGeometry.width() != oldGeometry.width()) updateSize(); - QSGPaintedItem::geometryChanged(newGeometry, oldGeometry); + QSGImplicitSizeItem::geometryChanged(newGeometry, oldGeometry); } +/*! + Ensures any delayed caching or data loading the class + needs to performed is complete. +*/ void QSGTextEdit::componentComplete() { Q_D(QSGTextEdit); - QSGPaintedItem::componentComplete(); + QSGImplicitSizeItem::componentComplete(); + + if (d->richText) { + d->isComplexRichText = QSGTextNode::isComplexRichText(d->document); + } + if (d->dirty) { d->determineHorizontalAlignment(); d->updateDefaultTextOption(); updateSize(); d->dirty = false; } + } +/*! + \qmlproperty bool QtQuick2::TextEdit::selectByMouse + Defaults to false. + + If true, the user can use the mouse to select text in some + platform-specific way. Note that for some platforms this may + not be an appropriate interaction (eg. may conflict with how + the text needs to behave inside a Flickable. +*/ bool QSGTextEdit::selectByMouse() const { Q_D(const QSGTextEdit); @@ -636,6 +1079,18 @@ void QSGTextEdit::setSelectByMouse(bool on) } } +/*! + \qmlproperty enum QtQuick2::TextEdit::mouseSelectionMode + + Specifies how text should be selected using a mouse. + + \list + \o TextEdit.SelectCharacters - The selection is updated with individual characters. (Default) + \o TextEdit.SelectWords - The selection is updated with whole words. + \endlist + + This property only applies when \l selectByMouse is true. +*/ QSGTextEdit::SelectionMode QSGTextEdit::mouseSelectionMode() const { Q_D(const QSGTextEdit); @@ -652,6 +1107,14 @@ void QSGTextEdit::setMouseSelectionMode(SelectionMode mode) } } +/*! + \qmlproperty bool QtQuick2::TextEdit::readOnly + + Whether the user can interact with the TextEdit item. If this + property is set to true the text cannot be edited by user interaction. + + By default this property is false. +*/ void QSGTextEdit::setReadOnly(bool r) { Q_D(QSGTextEdit); @@ -677,18 +1140,32 @@ bool QSGTextEdit::isReadOnly() const return !(d->control->textInteractionFlags() & Qt::TextEditable); } +/*! + Sets how the text edit should interact with user input to the given + \a flags. +*/ void QSGTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) { Q_D(QSGTextEdit); d->control->setTextInteractionFlags(flags); } +/*! + Returns the flags specifying how the text edit should interact + with user input. +*/ Qt::TextInteractionFlags QSGTextEdit::textInteractionFlags() const { Q_D(const QSGTextEdit); return d->control->textInteractionFlags(); } +/*! + \qmlproperty rectangle QtQuick2::TextEdit::cursorRectangle + + The rectangle where the text cursor is rendered + within the text edit. Read-only. +*/ QRect QSGTextEdit::cursorRectangle() const { Q_D(const QSGTextEdit); @@ -702,7 +1179,7 @@ bool QSGTextEdit::event(QEvent *event) d->control->processEvent(event, QPointF(0, -d->yoff)); return event->isAccepted(); } - return QSGPaintedItem::event(event); + return QSGImplicitSizeItem::event(event); } /*! @@ -714,7 +1191,7 @@ void QSGTextEdit::keyPressEvent(QKeyEvent *event) Q_D(QSGTextEdit); d->control->processEvent(event, QPointF(0, -d->yoff)); if (!event->isAccepted()) - QSGPaintedItem::keyPressEvent(event); + QSGImplicitSizeItem::keyPressEvent(event); } /*! @@ -726,9 +1203,14 @@ void QSGTextEdit::keyReleaseEvent(QKeyEvent *event) Q_D(QSGTextEdit); d->control->processEvent(event, QPointF(0, -d->yoff)); if (!event->isAccepted()) - QSGPaintedItem::keyReleaseEvent(event); + QSGImplicitSizeItem::keyReleaseEvent(event); } +/*! + \qmlmethod void QtQuick2::TextEdit::deselect() + + Removes active text selection. +*/ void QSGTextEdit::deselect() { Q_D(QSGTextEdit); @@ -737,12 +1219,22 @@ void QSGTextEdit::deselect() d->control->setTextCursor(c); } +/*! + \qmlmethod void QtQuick2::TextEdit::selectAll() + + Causes all text to be selected. +*/ void QSGTextEdit::selectAll() { Q_D(QSGTextEdit); d->control->selectAll(); } +/*! + \qmlmethod void QtQuick2::TextEdit::selectWord() + + Causes the word closest to the current cursor position to be selected. +*/ void QSGTextEdit::selectWord() { Q_D(QSGTextEdit); @@ -751,6 +1243,19 @@ void QSGTextEdit::selectWord() d->control->setTextCursor(c); } +/*! + \qmlmethod void QtQuick2::TextEdit::select(int start, int end) + + Causes the text from \a start to \a end to be selected. + + If either start or end is out of range, the selection is not changed. + + After calling this, selectionStart will become the lesser + and selectionEnd will become the greater (regardless of the order passed + to this method). + + \sa selectionStart, selectionEnd +*/ void QSGTextEdit::select(int start, int end) { Q_D(QSGTextEdit); @@ -767,6 +1272,12 @@ void QSGTextEdit::select(int start, int end) updateSelectionMarkers(); } +/*! + \qmlmethod void QtQuick2::TextEdit::isRightToLeft(int start, int end) + + Returns true if the natural reading direction of the editor text + found between positions \a start and \a end is right to left. +*/ bool QSGTextEdit::isRightToLeft(int start, int end) { Q_D(QSGTextEdit); @@ -779,18 +1290,33 @@ bool QSGTextEdit::isRightToLeft(int start, int end) } #ifndef QT_NO_CLIPBOARD +/*! + \qmlmethod QtQuick2::TextEdit::cut() + + Moves the currently selected text to the system clipboard. +*/ void QSGTextEdit::cut() { Q_D(QSGTextEdit); d->control->cut(); } +/*! + \qmlmethod QtQuick2::TextEdit::copy() + + Copies the currently selected text to the system clipboard. +*/ void QSGTextEdit::copy() { Q_D(QSGTextEdit); d->control->copy(); } +/*! + \qmlmethod QtQuick2::TextEdit::paste() + + Replaces the currently selected text by the contents of the system clipboard. +*/ void QSGTextEdit::paste() { Q_D(QSGTextEdit); @@ -821,7 +1347,7 @@ void QSGTextEdit::mousePressEvent(QGraphicsSceneMouseEvent *event) } d->control->processEvent(event, QPointF(0, -d->yoff)); if (!event->isAccepted()) - QSGPaintedItem::mousePressEvent(event); + QSGImplicitSizeItem::mousePressEvent(event); } /*! @@ -844,7 +1370,7 @@ void QSGTextEdit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) d->clickCausedFocus = false; if (!event->isAccepted()) - QSGPaintedItem::mouseReleaseEvent(event); + QSGImplicitSizeItem::mouseReleaseEvent(event); } /*! @@ -856,7 +1382,7 @@ void QSGTextEdit::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) Q_D(QSGTextEdit); d->control->processEvent(event, QPointF(0, -d->yoff)); if (!event->isAccepted()) - QSGPaintedItem::mouseDoubleClickEvent(event); + QSGImplicitSizeItem::mouseDoubleClickEvent(event); } /*! @@ -868,7 +1394,7 @@ void QSGTextEdit::mouseMoveEvent(QGraphicsSceneMouseEvent *event) Q_D(QSGTextEdit); d->control->processEvent(event, QPointF(0, -d->yoff)); if (!event->isAccepted()) - QSGPaintedItem::mouseMoveEvent(event); + QSGImplicitSizeItem::mouseMoveEvent(event); } /*! @@ -903,49 +1429,162 @@ QVariant QSGTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const return d->control->inputMethodQuery(property); } -/*! -Draws the contents of the text edit using the given \a painter within -the given \a bounds. -*/ -void QSGTextEdit::paint(QPainter *painter) +void QSGTextEdit::updateImageCache(const QRectF &) { - // XXX todo - QRect bounds(0, 0, width(), height()); Q_D(QSGTextEdit); - painter->setRenderHint(QPainter::TextAntialiasing, true); - painter->translate(0,d->yoff); + // Do we really need the image cache? + if (!d->richText || !d->isComplexRichText) { + if (!d->pixmapCache.isNull()) + d->pixmapCache = QPixmap(); + return; + } + + if (width() != d->pixmapCache.width() || height() != d->pixmapCache.height()) + d->pixmapCache = QPixmap(width(), height()); + + if (d->pixmapCache.isNull()) + return; + + // ### Use supplied rect, clear area and update only this part (for cursor updates) + QRectF bounds = QRectF(0, 0, width(), height()); + d->pixmapCache.fill(Qt::transparent); + { + QPainter painter(&d->pixmapCache); - d->control->drawContents(painter, bounds.translated(0,-d->yoff)); + painter.setRenderHint(QPainter::TextAntialiasing); + painter.translate(0, d->yoff); + + d->control->drawContents(&painter, bounds); + } - painter->translate(0,-d->yoff); } -void QSGTextEdit::updateImgCache(const QRectF &rf) +QSGNode *QSGTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) { - Q_D(const QSGTextEdit); - QRect r; - if (!rf.isValid()) { - r = QRect(0,0,INT_MAX,INT_MAX); - } else { - r = rf.toRect(); - if (r.height() > INT_MAX/2) { - // Take care of overflow when translating "everything" - r.setTop(r.y() + d->yoff); - r.setBottom(INT_MAX/2); + Q_UNUSED(updatePaintNodeData); + Q_D(QSGTextEdit); + + QSGNode *currentNode = oldNode; + if (d->richText && d->isComplexRichText) { + QSGImageNode *node = 0; + if (oldNode == 0 || d->nodeType != QSGTextEditPrivate::NodeIsTexture) { + delete oldNode; + node = QSGItemPrivate::get(this)->sceneGraphContext()->createImageNode(); + d->texture = new QSGPlainTexture(); + d->nodeType = QSGTextEditPrivate::NodeIsTexture; + currentNode = node; + } else { + node = static_cast<QSGImageNode *>(oldNode); + } + + qobject_cast<QSGPlainTexture *>(d->texture)->setImage(d->pixmapCache.toImage()); + node->setTexture(0); + node->setTexture(d->texture); + + node->setTargetRect(QRectF(0, 0, d->pixmapCache.width(), d->pixmapCache.height())); + node->setSourceRect(QRectF(0, 0, 1, 1)); + node->setHorizontalWrapMode(QSGTexture::ClampToEdge); + node->setVerticalWrapMode(QSGTexture::ClampToEdge); + node->setFiltering(QSGTexture::Linear); // Nonsmooth text just ugly, so don't do that.. + node->update(); + + } else if (oldNode == 0 || d->documentDirty) { + d->documentDirty = false; + +#if defined(Q_WS_MAC) + // Make sure document is relayouted in the paint node on Mac + // to avoid crashes due to the font engines created in the + // shaping process + d->document->markContentsDirty(0, d->document->characterCount()); +#endif + + QSGTextNode *node = 0; + if (oldNode == 0 || d->nodeType != QSGTextEditPrivate::NodeIsText) { + delete oldNode; + node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext()); + d->nodeType = QSGTextEditPrivate::NodeIsText; + currentNode = node; } else { - r = r.translated(0,d->yoff); + node = static_cast<QSGTextNode *>(oldNode); } + + node->deleteContent(); + node->setMatrix(QMatrix4x4()); + + QRectF bounds = boundingRect(); + + QColor selectionColor = d->control->palette().color(QPalette::Highlight); + QColor selectedTextColor = d->control->palette().color(QPalette::HighlightedText); + node->addTextDocument(bounds.topLeft(), d->document, d->color, QSGText::Normal, QColor(), + selectionColor, selectedTextColor, selectionStart(), + selectionEnd()); + +#if defined(Q_WS_MAC) + // We also need to make sure the document layout is redone when + // control is returned to the main thread, as all the font engines + // are now owned by the rendering thread + d->document->markContentsDirty(0, d->document->characterCount()); +#endif } - update(r); + + if (d->nodeType == QSGTextEditPrivate::NodeIsText && d->cursorComponent == 0 && !isReadOnly()) { + QSGTextNode *node = static_cast<QSGTextNode *>(currentNode); + + QColor color = (!d->cursorVisible || !d->control->cursorOn()) + ? QColor(0, 0, 0, 0) + : d->color; + + if (node->cursorNode() == 0) { + node->setCursor(cursorRectangle(), color); + } else { + node->cursorNode()->setRect(cursorRectangle()); + node->cursorNode()->setColor(color); + } + + } + + return currentNode; } +/*! + \qmlproperty bool QtQuick2::TextEdit::smooth + + This property holds whether the text is smoothly scaled or transformed. + + Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +/*! + \qmlproperty bool QtQuick2::TextEdit::canPaste + + Returns true if the TextEdit is writable and the content of the clipboard is + suitable for pasting into the TextEdit. +*/ bool QSGTextEdit::canPaste() const { Q_D(const QSGTextEdit); return d->canPaste; } +/*! + \qmlproperty bool QtQuick2::TextEdit::inputMethodComposing + + + This property holds whether the TextEdit has partial text input from an + input method. + + While it is composing an input method may rely on mouse or key events from + the TextEdit to edit or commit the partial text. This property can be used + to determine when to disable events handlers that may interfere with the + correct operation of an input method. +*/ bool QSGTextEdit::isInputMethodComposing() const { Q_D(const QSGTextEdit); @@ -961,12 +1600,22 @@ void QSGTextEditPrivate::init() q->setSmooth(smooth); q->setAcceptedMouseButtons(Qt::LeftButton); q->setFlag(QSGItem::ItemAcceptsInputMethod); + q->setFlag(QSGItem::ItemHasContents); control = new QTextControl(q); control->setIgnoreUnusedNavigationEvents(true); control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable); control->setDragEnabled(false); + // By default, QTextControl will issue both a updateCursorRequest() and an updateRequest() + // when the cursor needs to be repainted. We need the signals to be separate to be able to + // distinguish the cursor updates so that we can avoid updating the whole subtree when the + // cursor blinks. + if (!QObject::disconnect(control, SIGNAL(updateCursorRequest(QRectF)), + control, SIGNAL(updateRequest(QRectF)))) { + qWarning("QSGTextEditPrivate::init: Failed to disconnect updateCursorRequest and updateRequest"); + } + // QTextControl follows the default text color // defined by the platform, declarative text // should be black by default @@ -976,8 +1625,8 @@ void QSGTextEditPrivate::init() control->setPalette(pal); } - QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateImgCache(QRectF))); - + QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateDocument())); + QObject::connect(control, SIGNAL(updateCursorRequest()), q, SLOT(updateCursor())); 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())); @@ -1056,7 +1705,7 @@ void QSGTextEdit::updateSelectionMarkers() QRectF QSGTextEdit::boundingRect() const { Q_D(const QSGTextEdit); - QRectF r = QSGPaintedItem::boundingRect(); + QRectF r = QSGImplicitSizeItem::boundingRect(); int cursorWidth = 1; if(d->cursor) cursorWidth = d->cursor->width(); @@ -1121,7 +1770,7 @@ void QSGTextEdit::updateSize() } else { nyoff = 0; } - if (nyoff != d->yoff) + if (nyoff != d->yoff) d->yoff = nyoff; setBaselineOffset(fm.ascent() + d->yoff + d->textMargin); @@ -1138,12 +1787,31 @@ void QSGTextEdit::updateSize() setImplicitHeight(newHeight); d->paintedSize = QSize(newWidth, newHeight); - setContentsSize(d->paintedSize); emit paintedSizeChanged(); } else { d->dirty = true; } - update(); + updateDocument(); +} + +void QSGTextEdit::updateDocument() +{ + Q_D(QSGTextEdit); + d->documentDirty = true; + + if (isComponentComplete()) { + updateImageCache(); + update(); + } +} + +void QSGTextEdit::updateCursor() +{ + Q_D(QSGTextEdit); + if (isComponentComplete()) { + updateImageCache(d->control->cursorRect()); + update(); + } } void QSGTextEdit::updateTotalLines() @@ -1184,12 +1852,59 @@ void QSGTextEditPrivate::updateDefaultTextOption() QTextOption::WrapMode oldWrapMode = opt.wrapMode(); opt.setWrapMode(QTextOption::WrapMode(wrapMode)); - if (oldWrapMode == opt.wrapMode() && oldAlignment == opt.alignment()) + bool oldUseDesignMetrics = opt.useDesignMetrics(); + bool useDesignMetrics = !qmlDisableDistanceField(); + opt.setUseDesignMetrics(useDesignMetrics); + + if (oldWrapMode == opt.wrapMode() + && oldAlignment == opt.alignment() + && oldUseDesignMetrics == useDesignMetrics) { return; + } document->setDefaultTextOption(opt); } + +/*! + \qmlmethod void QtQuick2::TextEdit::openSoftwareInputPanel() + + Opens software input panels like virtual keyboards for typing, useful for + customizing when you want the input keyboard to be shown and hidden in + your application. + + By default the opening of input panels follows the platform style. On Symbian^1 and + Symbian^3 -based devices the panels are opened by clicking TextEdit. On other platforms + the panels are automatically opened when TextEdit element gains active focus. Input panels are + always closed if no editor has active focus. + + You can disable the automatic behavior by setting the property \c activeFocusOnPress to false + and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement + the behavior you want. + + Only relevant on platforms, which provide virtual keyboards. + + \code + import QtQuick 1.0 + TextEdit { + id: textEdit + text: "Hello world!" + activeFocusOnPress: false + MouseArea { + anchors.fill: parent + onClicked: { + if (!textEdit.activeFocus) { + textEdit.forceActiveFocus(); + textEdit.openSoftwareInputPanel(); + } else { + textEdit.focus = false; + } + } + onPressAndHold: textEdit.closeSoftwareInputPanel(); + } + } + \endcode +*/ void QSGTextEdit::openSoftwareInputPanel() { if (qApp) { @@ -1200,6 +1915,45 @@ void QSGTextEdit::openSoftwareInputPanel() } } +/*! + \qmlmethod void QtQuick2::TextEdit::closeSoftwareInputPanel() + + Closes a software input panel like a virtual keyboard shown on the screen, useful + for customizing when you want the input keyboard to be shown and hidden in + your application. + + By default the opening of input panels follows the platform style. On Symbian^1 and + Symbian^3 -based devices the panels are opened by clicking TextEdit. On other platforms + the panels are automatically opened when TextEdit element gains active focus. Input panels are + always closed if no editor has active focus. + + You can disable the automatic behavior by setting the property \c activeFocusOnPress to false + and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement + the behavior you want. + + Only relevant on platforms, which provide virtual keyboards. + + \code + import QtQuick 1.0 + TextEdit { + id: textEdit + text: "Hello world!" + activeFocusOnPress: false + MouseArea { + anchors.fill: parent + onClicked: { + if (!textEdit.activeFocus) { + textEdit.forceActiveFocus(); + textEdit.openSoftwareInputPanel(); + } else { + textEdit.focus = false; + } + } + onPressAndHold: textEdit.closeSoftwareInputPanel(); + } + } + \endcode +*/ void QSGTextEdit::closeSoftwareInputPanel() { if (qApp) { @@ -1218,7 +1972,7 @@ void QSGTextEdit::focusInEvent(QFocusEvent *event) openSoftwareInputPanel(); } } - QSGPaintedItem::focusInEvent(event); + QSGImplicitSizeItem::focusInEvent(event); } void QSGTextEdit::q_canPasteChanged() diff --git a/src/declarative/items/qsgtextedit_p.h b/src/declarative/items/qsgtextedit_p.h index 115b25040d..fa61f03f7f 100644 --- a/src/declarative/items/qsgtextedit_p.h +++ b/src/declarative/items/qsgtextedit_p.h @@ -54,7 +54,7 @@ QT_BEGIN_NAMESPACE QT_MODULE(Declarative) class QSGTextEditPrivate; -class Q_AUTOTEST_EXPORT QSGTextEdit : public QSGImplicitSizePaintedItem +class Q_AUTOTEST_EXPORT QSGTextEdit : public QSGImplicitSizeItem { Q_OBJECT Q_ENUMS(VAlignment) @@ -260,16 +260,18 @@ public Q_SLOTS: #endif private Q_SLOTS: - void updateImgCache(const QRectF &rect); void q_textChanged(); void updateSelectionMarkers(); void moveCursorDelegate(); void loadCursorDelegate(); void q_canPasteChanged(); + void updateDocument(); + void updateCursor(); private: void updateSize(); void updateTotalLines(); + void updateImageCache(const QRectF &rect = QRectF()); protected: virtual void geometryChanged(const QRectF &newGeometry, @@ -288,7 +290,8 @@ protected: void inputMethodEvent(QInputMethodEvent *e); virtual void itemChange(ItemChange, const ItemChangeData &); - void paint(QPainter *); + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData); + private: Q_DISABLE_COPY(QSGTextEdit) Q_DECLARE_PRIVATE(QSGTextEdit) diff --git a/src/declarative/items/qsgtextedit_p_p.h b/src/declarative/items/qsgtextedit_p_p.h index c080857e46..e4f74c0ef5 100644 --- a/src/declarative/items/qsgtextedit_p_p.h +++ b/src/declarative/items/qsgtextedit_p_p.h @@ -63,21 +63,21 @@ QT_BEGIN_NAMESPACE class QTextLayout; class QTextDocument; class QTextControl; -class QSGTextEditPrivate : public QSGImplicitSizePaintedItemPrivate +class QSGTextEditPrivate : public QSGImplicitSizeItemPrivate { Q_DECLARE_PUBLIC(QSGTextEdit) public: QSGTextEditPrivate() : color("black"), hAlign(QSGTextEdit::AlignLeft), vAlign(QSGTextEdit::AlignTop), - imgDirty(true), dirty(false), richText(false), cursorVisible(false), focusOnPress(true), + documentDirty(true), dirty(false), richText(false), cursorVisible(false), focusOnPress(true), showInputPanelOnFocus(true), clickCausedFocus(false), persistentSelection(true), requireImplicitWidth(false), selectByMouse(false), canPaste(false), - hAlignImplicit(true), rightToLeftText(false), + hAlignImplicit(true), rightToLeftText(false), isComplexRichText(false), textMargin(0.0), lastSelectionStart(0), lastSelectionEnd(0), cursorComponent(0), cursor(0), format(QSGTextEdit::AutoText), document(0), wrapMode(QSGTextEdit::NoWrap), mouseSelectionMode(QSGTextEdit::SelectCharacters), - yoff(0) + yoff(0), nodeType(NodeIsNull), texture(0) { #ifdef Q_OS_SYMBIAN if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) { @@ -104,12 +104,10 @@ public: QColor selectedTextColor; QString style; QColor styleColor; - QPixmap imgCache; - QPixmap imgStyleCache; QSGTextEdit::HAlignment hAlign; QSGTextEdit::VAlignment vAlign; - bool imgDirty : 1; + bool documentDirty : 1; bool dirty : 1; bool richText : 1; bool cursorVisible : 1; @@ -122,6 +120,7 @@ public: bool canPaste:1; bool hAlignImplicit:1; bool rightToLeftText:1; + bool isComplexRichText:1; qreal textMargin; int lastSelectionStart; @@ -136,6 +135,15 @@ public: int lineCount; int yoff; QSize paintedSize; + + enum NodeType { + NodeIsNull, + NodeIsTexture, + NodeIsText + }; + NodeType nodeType; + QSGTexture *texture; + QPixmap pixmapCache; }; QT_END_NAMESPACE diff --git a/src/declarative/items/qsgtextinput.cpp b/src/declarative/items/qsgtextinput.cpp index 405540d419..d30faadb34 100644 --- a/src/declarative/items/qsgtextinput.cpp +++ b/src/declarative/items/qsgtextinput.cpp @@ -45,19 +45,43 @@ #include <private/qdeclarativeglobal_p.h> #include <private/qwidget_p.h> +#include <private/qsgdistancefieldglyphcache_p.h> #include <QtDeclarative/qdeclarativeinfo.h> #include <QtWidgets/qgraphicssceneevent.h> #include <QtWidgets/qinputcontext.h> #include <QTextBoundaryFinder> #include <qstyle.h> +#include <qsgtextnode_p.h> +#include <qsgsimplerectnode.h> QT_BEGIN_NAMESPACE +DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) + QWidgetPrivate *qt_widget_private(QWidget *widget); +/*! + \qmlclass TextInput QSGTextInput + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The TextInput item displays an editable line of text. + \inherits Item + + The TextInput element displays a single line of editable plain text. + + TextInput is used to accept a line of text input. Input constraints + can be placed on a TextInput item (for example, through a \l validator or \l inputMask), + and setting \l echoMode to an appropriate value enables TextInput to be used for + a password input field. + + On Mac OS X, the Up/Down key bindings for Home/End are explicitly disabled. + If you want such bindings (on any platform), you will need to construct them in QML. + + \sa TextEdit, Text, {declarative/text/textselection}{Text Selection example} +*/ QSGTextInput::QSGTextInput(QSGItem* parent) -: QSGImplicitSizePaintedItem(*(new QSGTextInputPrivate), parent) +: QSGImplicitSizeItem(*(new QSGTextInputPrivate), parent) { Q_D(QSGTextInput); d->init(); @@ -67,6 +91,11 @@ QSGTextInput::~QSGTextInput() { } +/*! + \qmlproperty string QtQuick2::TextInput::text + + The text in the TextInput. +*/ QString QSGTextInput::text() const { Q_D(const QSGTextInput); @@ -81,6 +110,111 @@ void QSGTextInput::setText(const QString &s) d->control->setText(s); } +/*! + \qmlproperty string QtQuick2::TextInput::font.family + + Sets the family name of the font. + + The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". + If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. + If the family isn't available a family will be set using the font matching algorithm. +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.bold + + Sets whether the font weight is bold. +*/ + +/*! + \qmlproperty enumeration QtQuick2::TextInput::font.weight + + Sets the font's weight. + + The weight can be one of: + \list + \o Font.Light + \o Font.Normal - the default + \o Font.DemiBold + \o Font.Bold + \o Font.Black + \endlist + + \qml + TextInput { text: "Hello"; font.weight: Font.DemiBold } + \endqml +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.italic + + Sets whether the font has an italic style. +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.underline + + Sets whether the text is underlined. +*/ + +/*! + \qmlproperty bool QtQuick2::TextInput::font.strikeout + + Sets whether the font has a strikeout style. +*/ + +/*! + \qmlproperty real QtQuick2::TextInput::font.pointSize + + Sets the font size in points. The point size must be greater than zero. +*/ + +/*! + \qmlproperty int QtQuick2::TextInput::font.pixelSize + + Sets the font size in pixels. + + Using this function makes the font device dependent. + Use \c pointSize to set the size of the font in a device independent manner. +*/ + +/*! + \qmlproperty real QtQuick2::TextInput::font.letterSpacing + + Sets the letter spacing for the font. + + Letter spacing changes the default spacing between individual letters in the font. + A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. +*/ + +/*! + \qmlproperty real QtQuick2::TextInput::font.wordSpacing + + Sets the word spacing for the font. + + Word spacing changes the default spacing between individual words. + A positive value increases the word spacing by a corresponding amount of pixels, + while a negative value decreases the inter-word spacing accordingly. +*/ + +/*! + \qmlproperty enumeration QtQuick2::TextInput::font.capitalization + + Sets the capitalization for the text. + + \list + \o Font.MixedCase - This is the normal text rendering option where no capitalization change is applied. + \o Font.AllUppercase - This alters the text to be rendered in all uppercase type. + \o Font.AllLowercase - This alters the text to be rendered in all lowercase type. + \o Font.SmallCaps - This alters the text to be rendered in small-caps type. + \o Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character. + \endlist + + \qml + TextInput { text: "Hello"; font.capitalization: Font.AllLowercase } + \endqml +*/ + QFont QSGTextInput::font() const { Q_D(const QSGTextInput); @@ -112,6 +246,11 @@ void QSGTextInput::setFont(const QFont &font) emit fontChanged(d->sourceFont); } +/*! + \qmlproperty color QtQuick2::TextInput::color + + The text color. +*/ QColor QSGTextInput::color() const { Q_D(const QSGTextInput); @@ -128,6 +267,12 @@ void QSGTextInput::setColor(const QColor &c) } } + +/*! + \qmlproperty color QtQuick2::TextInput::selectionColor + + The text highlight color, used behind selections. +*/ QColor QSGTextInput::selectionColor() const { Q_D(const QSGTextInput); @@ -144,11 +289,15 @@ void QSGTextInput::setSelectionColor(const QColor &color) QPalette p = d->control->palette(); p.setColor(QPalette::Highlight, d->selectionColor); d->control->setPalette(p); - if (d->control->hasSelectedText()) + if (d->control->hasSelectedText()) update(); emit selectionColorChanged(color); } +/*! + \qmlproperty color QtQuick2::TextInput::selectedTextColor + The highlighted text color, used in selections. +*/ QColor QSGTextInput::selectedTextColor() const { Q_D(const QSGTextInput); @@ -165,11 +314,34 @@ void QSGTextInput::setSelectedTextColor(const QColor &color) QPalette p = d->control->palette(); p.setColor(QPalette::HighlightedText, d->selectedTextColor); d->control->setPalette(p); - if (d->control->hasSelectedText()) + if (d->control->hasSelectedText()) update(); emit selectedTextColorChanged(color); } +/*! + \qmlproperty enumeration QtQuick2::TextInput::horizontalAlignment + \qmlproperty enumeration QtQuick2::TextInput::effectiveHorizontalAlignment + + Sets the horizontal alignment of the text within the TextInput item's + width and height. By default, the text alignment follows the natural alignment + of the text, for example text that is read from left to right will be aligned to + the left. + + TextInput does not have vertical alignment, as the natural height is + exactly the height of the single line of text. If you set the height + manually to something larger, TextInput will always be top aligned + vertically. You can use anchors to align it however you want within + another item. + + The valid values for \c horizontalAlignment are \c TextInput.AlignLeft, \c TextInput.AlignRight and + \c TextInput.AlignHCenter. + + When using the attached property LayoutMirroring::enabled to mirror application + layouts, the horizontal alignment of text will also be mirrored. However, the property + \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment + of TextInput, use the read-only property \c effectiveHorizontalAlignment. +*/ QSGTextInput::HAlignment QSGTextInput::hAlign() const { Q_D(const QSGTextInput); @@ -250,6 +422,15 @@ void QSGTextInputPrivate::mirrorChange() } } +/*! + \qmlproperty bool QtQuick2::TextInput::readOnly + + Sets whether user input can modify the contents of the TextInput. + + If readOnly is set to true, then user input will not affect the text + property. Any bindings or attempts to set the text property will still + work. +*/ bool QSGTextInput::isReadOnly() const { Q_D(const QSGTextInput); @@ -270,6 +451,14 @@ void QSGTextInput::setReadOnly(bool ro) emit readOnlyChanged(ro); } +/*! + \qmlproperty int QtQuick2::TextInput::maximumLength + The maximum permitted length of the text in the TextInput. + + If the text is too long, it is truncated at the limit. + + By default, this property contains a value of 32767. +*/ int QSGTextInput::maxLength() const { Q_D(const QSGTextInput); @@ -287,6 +476,32 @@ void QSGTextInput::setMaxLength(int ml) emit maximumLengthChanged(ml); } +/*! + \qmlproperty bool QtQuick2::TextInput::cursorVisible + Set to true when the TextInput shows a cursor. + + This property is set and unset when the TextInput gets active focus, so that other + properties can be bound to whether the cursor is currently showing. As it + gets set and unset automatically, when you set the value yourself you must + keep in mind that your value may be overwritten. + + It can be set directly in script, for example if a KeyProxy might + forward keys to it and you desire it to look active when this happens + (but without actually giving it active focus). + + It should not be set directly on the element, like in the below QML, + as the specified value will be overridden an lost on focus changes. + + \code + TextInput { + text: "Text" + cursorVisible: false + } + \endcode + + In the above snippet the cursor will still become visible when the + TextInput gains active focus. +*/ bool QSGTextInput::isCursorVisible() const { Q_D(const QSGTextInput); @@ -308,6 +523,10 @@ void QSGTextInput::setCursorVisible(bool on) emit cursorVisibleChanged(d->cursorVisible); } +/*! + \qmlproperty int QtQuick2::TextInput::cursorPosition + The position of the cursor in the TextInput. +*/ int QSGTextInput::cursorPosition() const { Q_D(const QSGTextInput); @@ -321,6 +540,10 @@ void QSGTextInput::setCursorPosition(int cp) d->control->moveCursor(cp); } +/*! + Returns a Rect which encompasses the cursor, but which may be larger than is + required. Ignores custom cursor delegates. +*/ QRect QSGTextInput::cursorRectangle() const { Q_D(const QSGTextInput); @@ -331,19 +554,49 @@ QRect QSGTextInput::cursorRectangle() const r.adjust(5 - d->hscroll, 0, -4 - d->hscroll, -1); return r; } +/*! + \qmlproperty int QtQuick2::TextInput::selectionStart + + The cursor position before the first character in the current selection. + + This property is read-only. To change the selection, use select(start,end), + selectAll(), or selectWord(). + \sa selectionEnd, cursorPosition, selectedText +*/ int QSGTextInput::selectionStart() const { Q_D(const QSGTextInput); return d->lastSelectionStart; } +/*! + \qmlproperty int QtQuick2::TextInput::selectionEnd + The cursor position after the last character in the current selection. + + This property is read-only. To change the selection, use select(start,end), + selectAll(), or selectWord(). + + \sa selectionStart, cursorPosition, selectedText +*/ int QSGTextInput::selectionEnd() const { Q_D(const QSGTextInput); return d->lastSelectionEnd; } +/*! + \qmlmethod void QtQuick2::TextInput::select(int start, int end) + + Causes the text from \a start to \a end to be selected. + + If either start or end is out of range, the selection is not changed. + + After calling this, selectionStart will become the lesser + and selectionEnd will become the greater (regardless of the order passed + to this method). + \sa selectionStart, selectionEnd +*/ void QSGTextInput::select(int start, int end) { Q_D(QSGTextInput); @@ -352,12 +605,32 @@ void QSGTextInput::select(int start, int end) d->control->setSelection(start, end-start); } +/*! + \qmlproperty string QtQuick2::TextInput::selectedText + + This read-only property provides the text currently selected in the + text input. + + It is equivalent to the following snippet, but is faster and easier + to use. + + \js + myTextInput.text.toString().substring(myTextInput.selectionStart, + myTextInput.selectionEnd); + \endjs +*/ QString QSGTextInput::selectedText() const { Q_D(const QSGTextInput); return d->control->selectedText(); } +/*! + \qmlproperty bool QtQuick2::TextInput::activeFocusOnPress + + Whether the TextInput should gain active focus on a mouse press. By default this is + set to true. +*/ bool QSGTextInput::focusOnPress() const { Q_D(const QSGTextInput); @@ -374,7 +647,12 @@ void QSGTextInput::setFocusOnPress(bool b) emit activeFocusOnPressChanged(d->focusOnPress); } +/*! + \qmlproperty bool QtQuick2::TextInput::autoScroll + Whether the TextInput should scroll when the text is longer than the width. By default this is + set to true. +*/ bool QSGTextInput::autoScroll() const { Q_D(const QSGTextInput); @@ -395,6 +673,114 @@ void QSGTextInput::setAutoScroll(bool b) } #ifndef QT_NO_VALIDATOR + +/*! + \qmlclass IntValidator QIntValidator + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + + This element provides a validator for integer values. + + IntValidator uses the \l {QLocale::setDefault()}{default locale} to interpret the number and + will accept locale specific digits, group separators, and positive and negative signs. In + addition, IntValidator is always guaranteed to accept a number formatted according to the "C" + locale. +*/ +/*! + \qmlproperty int QtQuick2::IntValidator::top + + This property holds the validator's highest acceptable value. + By default, this property's value is derived from the highest signed integer available (typically 2147483647). +*/ +/*! + \qmlproperty int QtQuick2::IntValidator::bottom + + This property holds the validator's lowest acceptable value. + By default, this property's value is derived from the lowest signed integer available (typically -2147483647). +*/ + +/*! + \qmlclass DoubleValidator QDoubleValidator + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + + This element provides a validator for non-integer numbers. +*/ + +/*! + \qmlproperty real QtQuick2::DoubleValidator::top + + This property holds the validator's maximum acceptable value. + By default, this property contains a value of infinity. +*/ +/*! + \qmlproperty real QtQuick2::DoubleValidator::bottom + + This property holds the validator's minimum acceptable value. + By default, this property contains a value of -infinity. +*/ +/*! + \qmlproperty int QtQuick2::DoubleValidator::decimals + + This property holds the validator's maximum number of digits after the decimal point. + By default, this property contains a value of 1000. +*/ +/*! + \qmlproperty enumeration QtQuick2::DoubleValidator::notation + This property holds the notation of how a string can describe a number. + + The possible values for this property are: + + \list + \o DoubleValidator.StandardNotation + \o DoubleValidator.ScientificNotation (default) + \endlist + + If this property is set to DoubleValidator.ScientificNotation, the written number may have an exponent part (e.g. 1.5E-2). +*/ + +/*! + \qmlclass RegExpValidator QRegExpValidator + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + + This element provides a validator, which counts as valid any string which + matches a specified regular expression. +*/ +/*! + \qmlproperty regExp QtQuick2::RegExpValidator::regExp + + This property holds the regular expression used for validation. + + Note that this property should be a regular expression in JS syntax, e.g /a/ for the regular expression + matching "a". + + By default, this property contains a regular expression with the pattern .* that matches any string. +*/ + +/*! + \qmlproperty Validator QtQuick2::TextInput::validator + + Allows you to set a validator on the TextInput. When a validator is set + the TextInput will only accept input which leaves the text property in + an acceptable or intermediate state. The accepted signal will only be sent + if the text is in an acceptable state when enter is pressed. + + Currently supported validators are IntValidator, DoubleValidator and + RegExpValidator. An example of using validators is shown below, which allows + input of integers between 11 and 31 into the text input: + + \code + import QtQuick 1.0 + TextInput{ + validator: IntValidator{bottom: 11; top: 31;} + focus: true + } + \endcode + + \sa acceptableInput, inputMask +*/ + QValidator* QSGTextInput::validator() const { Q_D(const QSGTextInput); @@ -418,6 +804,15 @@ void QSGTextInput::setValidator(QValidator* v) } #endif // QT_NO_VALIDATOR +/*! + \qmlproperty string QtQuick2::TextInput::inputMask + + Allows you to set an input mask on the TextInput, restricting the allowable + text inputs. See QLineEdit::inputMask for further details, as the exact + same mask strings are used by TextInput. + + \sa acceptableInput, validator +*/ QString QSGTextInput::inputMask() const { Q_D(const QSGTextInput); @@ -434,12 +829,29 @@ void QSGTextInput::setInputMask(const QString &im) emit inputMaskChanged(d->control->inputMask()); } +/*! + \qmlproperty bool QtQuick2::TextInput::acceptableInput + + This property is always true unless a validator or input mask has been set. + If a validator or input mask has been set, this property will only be true + if the current text is acceptable to the validator or input mask as a final + string (not as an intermediate string). +*/ bool QSGTextInput::hasAcceptableInput() const { Q_D(const QSGTextInput); return d->control->hasAcceptableInput(); } +/*! + \qmlsignal QtQuick2::TextInput::onAccepted() + + This handler is called when the Return or Enter key is pressed. + Note that if there is a \l validator or \l inputMask set on the text + input, the handler will only be emitted if the input is in an acceptable + state. +*/ + void QSGTextInputPrivate::updateInputMethodHints() { Q_Q(QSGTextInput); @@ -453,7 +865,18 @@ void QSGTextInputPrivate::updateInputMethodHints() hints |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText); q->setInputMethodHints(hints); } - +/*! + \qmlproperty enumeration QtQuick2::TextInput::echoMode + + Specifies how the text should be displayed in the TextInput. + \list + \o TextInput.Normal - Displays the text as it is. (Default) + \o TextInput.Password - Displays asterixes instead of characters. + \o TextInput.NoEcho - Displays nothing. + \o TextInput.PasswordEchoOnEdit - Displays characters as they are entered + while editing, otherwise displays asterisks. + \endlist +*/ QSGTextInput::EchoMode QSGTextInput::echoMode() const { Q_D(const QSGTextInput); @@ -486,6 +909,19 @@ void QSGTextInput::setIMHints(Qt::InputMethodHints hints) d->updateInputMethodHints(); } +/*! + \qmlproperty Component QtQuick2::TextInput::cursorDelegate + The delegate for the cursor in the TextInput. + + If you set a cursorDelegate for a TextInput, 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 TextInput when a cursor is + needed, and the x property 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 QDeclarativeItem or + QDeclarativeItem derived item. +*/ QDeclarativeComponent* QSGTextInput::cursorDelegate() const { Q_D(const QSGTextInput); @@ -547,6 +983,15 @@ void QSGTextInput::createCursor() d->cursorItem->setHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. } +/*! + \qmlmethod rect QtQuick2::TextInput::positionToRectangle(int pos) + + This function takes a character position and returns the rectangle that the + cursor would occupy, if it was placed at that character position. + + This is similar to setting the cursorPosition, and then querying the cursor + rectangle, but the cursorPosition is not changed. +*/ QRectF QSGTextInput::positionToRectangle(int pos) const { Q_D(const QSGTextInput); @@ -558,6 +1003,24 @@ QRectF QSGTextInput::positionToRectangle(int pos) const cursorRectangle().height()); } +/*! + \qmlmethod int QtQuick2::TextInput::positionAt(int x, CursorPosition position = CursorBetweenCharacters) + + This function returns the character position at + x pixels from the left of the textInput. Position 0 is before the + first character, position 1 is after the first character but before the second, + and so on until position text.length, which is after all characters. + + This means that for all x values before the first character this function returns 0, + and for all x values after the last character this function returns text.length. + + The cursor position type specifies how the cursor position should be resolved. + + \list + \o TextInput.CursorBetweenCharacters - Returns the position between characters that is nearest x. + \o TextInput.CursorOnCharacter - Returns the position before the character that is nearest x. + \endlist +*/ int QSGTextInput::positionAt(int x) const { return positionAt(x, CursorBetweenCharacters); @@ -597,7 +1060,7 @@ void QSGTextInput::keyPressEvent(QKeyEvent* ev) d->control->processKeyEvent(ev); } if (!ev->isAccepted()) - QSGPaintedItem::keyPressEvent(ev); + QSGImplicitSizeItem::keyPressEvent(ev); } void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev) @@ -610,7 +1073,7 @@ void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev) d->control->processInputMethodEvent(ev); } if (!ev->isAccepted()) - QSGPaintedItem::inputMethodEvent(ev); + QSGImplicitSizeItem::inputMethodEvent(ev); if (wasComposing != (d->control->preeditAreaText().length() > 0)) emit inputMethodComposingChanged(); @@ -626,7 +1089,7 @@ void QSGTextInput::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) d->control->selectWordAtPos(cursor); event->setAccepted(true); } else { - QSGPaintedItem::mouseDoubleClickEvent(event); + QSGImplicitSizeItem::mouseDoubleClickEvent(event); } } @@ -671,7 +1134,7 @@ void QSGTextInput::mouseMoveEvent(QGraphicsSceneMouseEvent *event) moveCursorSelection(d->xToPos(event->pos().x()), d->mouseSelectionMode); event->setAccepted(true); } else { - QSGPaintedItem::mouseMoveEvent(event); + QSGImplicitSizeItem::mouseMoveEvent(event); } } @@ -696,7 +1159,7 @@ void QSGTextInput::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) d->clickCausedFocus = false; d->control->processEvent(event); if (!event->isAccepted()) - QSGPaintedItem::mouseReleaseEvent(event); + QSGImplicitSizeItem::mouseReleaseEvent(event); } bool QSGTextInputPrivate::sendMouseEventToInputContext( @@ -762,7 +1225,7 @@ bool QSGTextInput::event(QEvent* ev) handled = d->control->processEvent(ev); } if(!handled) - handled = QSGPaintedItem::event(ev); + handled = QSGImplicitSizeItem::event(ev); return handled; } @@ -773,7 +1236,7 @@ void QSGTextInput::geometryChanged(const QRectF &newGeometry, updateSize(); updateCursorRectangle(); } - QSGPaintedItem::geometryChanged(newGeometry, oldGeometry); + QSGImplicitSizeItem::geometryChanged(newGeometry, oldGeometry); } int QSGTextInputPrivate::calculateTextWidth() @@ -827,25 +1290,73 @@ void QSGTextInputPrivate::updateHorizontalScroll() } } -void QSGTextInput::paint(QPainter *p) +QSGNode *QSGTextInput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { - // XXX todo - QRect r = boundingRect().toRect(); - + Q_UNUSED(data); Q_D(QSGTextInput); - p->setRenderHint(QPainter::TextAntialiasing, true); - p->save(); - p->setPen(QPen(d->color)); - int flags = QLineControl::DrawText; - if(!isReadOnly() && d->cursorVisible && !d->cursorItem) - flags |= QLineControl::DrawCursor; - if (d->control->hasSelectedText()) - flags |= QLineControl::DrawSelections; - QFontMetrics fm = QFontMetrics(d->font); - // the y offset is there to keep the baseline constant in case we have script changes in the text. - QPoint offset(-d->hscroll, fm.ascent() - d->control->ascent()); - d->control->draw(p, offset, r, flags); - p->restore(); + + QSGTextNode *node = static_cast<QSGTextNode *>(oldNode); + if (node == 0) + node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext()); + d->textNode = node; + + if (!d->textLayoutDirty) { + QSGSimpleRectNode *cursorNode = node->cursorNode(); + if (cursorNode != 0 && !isReadOnly()) { + QFontMetrics fm = QFontMetrics(d->font); + // the y offset is there to keep the baseline constant in case we have script changes in the text. + QPoint offset(-d->hscroll, fm.ascent() - d->control->ascent()); + offset.rx() += d->control->cursorToX(); + + QRect br(boundingRect().toRect()); + cursorNode->setRect(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height()))); + + if (!d->cursorVisible + || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) { + d->hideCursor(); + } else { + d->showCursor(); + } + } + } else { + node->deleteContent(); + node->setMatrix(QMatrix4x4()); + + QPoint offset = QPoint(0,0); + QFontMetrics fm = QFontMetrics(d->font); + QRect br(boundingRect().toRect()); + if (d->autoScroll) { + // the y offset is there to keep the baseline constant in case we have script changes in the text. + offset = br.topLeft() - QPoint(d->hscroll, d->control->ascent() - fm.ascent()); + } else { + offset = QPoint(d->hscroll, 0); + } + + QTextLayout *textLayout = d->control->textLayout(); + if (!textLayout->text().isEmpty()) { + node->addTextLayout(offset, textLayout, d->color, + QSGText::Normal, QColor(), + d->selectionColor, d->selectedTextColor, + d->control->selectionStart(), + d->control->selectionEnd() - 1); // selectionEnd() returns first char after + // selection + } + + if (!isReadOnly() && d->cursorItem == 0) { + offset.rx() += d->control->cursorToX(); + node->setCursor(QRectF(offset, QSizeF(d->control->cursorWidth(), br.height())), d->color); + if (!d->cursorVisible + || (!d->control->cursorBlinkStatus() && d->control->cursorBlinkPeriod() > 0)) { + d->hideCursor(); + } else { + d->showCursor(); + } + } + + d->textLayoutDirty = false; + } + + return node; } QVariant QSGTextInput::inputMethodQuery(Qt::InputMethodQuery property) const @@ -879,18 +1390,34 @@ QVariant QSGTextInput::inputMethodQuery(Qt::InputMethodQuery property) const } } +/*! + \qmlmethod void QtQuick2::TextInput::deselect() + + Removes active text selection. +*/ void QSGTextInput::deselect() { Q_D(QSGTextInput); d->control->deselect(); } +/*! + \qmlmethod void QtQuick2::TextInput::selectAll() + + Causes all text to be selected. +*/ void QSGTextInput::selectAll() { Q_D(QSGTextInput); d->control->setSelection(0, d->control->text().length()); } +/*! + \qmlmethod void QtQuick2::TextInput::isRightToLeft(int start, int end) + + Returns true if the natural reading direction of the editor text + found between positions \a start and \a end is right to left. +*/ bool QSGTextInput::isRightToLeft(int start, int end) { Q_D(QSGTextInput); @@ -903,6 +1430,11 @@ bool QSGTextInput::isRightToLeft(int start, int end) } #ifndef QT_NO_CLIPBOARD +/*! + \qmlmethod QtQuick2::TextInput::cut() + + Moves the currently selected text to the system clipboard. +*/ void QSGTextInput::cut() { Q_D(QSGTextInput); @@ -910,12 +1442,22 @@ void QSGTextInput::cut() d->control->del(); } +/*! + \qmlmethod QtQuick2::TextInput::copy() + + Copies the currently selected text to the system clipboard. +*/ void QSGTextInput::copy() { Q_D(QSGTextInput); d->control->copy(); } +/*! + \qmlmethod QtQuick2::TextInput::paste() + + Replaces the currently selected text by the contents of the system clipboard. +*/ void QSGTextInput::paste() { Q_D(QSGTextInput); @@ -924,12 +1466,41 @@ void QSGTextInput::paste() } #endif // QT_NO_CLIPBOARD +/*! + \qmlmethod void QtQuick2::TextInput::selectWord() + + Causes the word closest to the current cursor position to be selected. +*/ void QSGTextInput::selectWord() { Q_D(QSGTextInput); d->control->selectWordAtPos(d->control->cursor()); } +/*! + \qmlproperty bool QtQuick2::TextInput::smooth + + This property holds whether the text is smoothly scaled or transformed. + + Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +/*! + \qmlproperty string QtQuick2::TextInput::passwordCharacter + + This is the character displayed when echoMode is set to Password or + PasswordEchoOnEdit. By default it is an asterisk. + + If this property is set to a string with more than one character, + the first character is used. If the string is empty, the value + is ignored and the property is not set. +*/ QString QSGTextInput::passwordCharacter() const { Q_D(const QSGTextInput); @@ -949,12 +1520,32 @@ void QSGTextInput::setPasswordCharacter(const QString &str) emit passwordCharacterChanged(); } +/*! + \qmlproperty string QtQuick2::TextInput::displayText + + This is the text displayed in the TextInput. + + If \l echoMode is set to TextInput::Normal, this holds the + same value as the TextInput::text property. Otherwise, + this property holds the text visible to the user, while + the \l text property holds the actual entered text. +*/ QString QSGTextInput::displayText() const { Q_D(const QSGTextInput); return d->control->displayText(); } +/*! + \qmlproperty bool QtQuick2::TextInput::selectByMouse + + Defaults to false. + + If true, the user can use the mouse to select text in some + platform-specific way. Note that for some platforms this may + not be an appropriate interaction (eg. may conflict with how + the text needs to behave inside a Flickable. +*/ bool QSGTextInput::selectByMouse() const { Q_D(const QSGTextInput); @@ -970,6 +1561,19 @@ void QSGTextInput::setSelectByMouse(bool on) } } +/*! + \qmlproperty enum QtQuick2::TextInput::mouseSelectionMode + + Specifies how text should be selected using a mouse. + + \list + \o TextInput.SelectCharacters - The selection is updated with individual characters. (Default) + \o TextInput.SelectWords - The selection is updated with whole words. + \endlist + + This property only applies when \l selectByMouse is true. +*/ + QSGTextInput::SelectionMode QSGTextInput::mouseSelectionMode() const { Q_D(const QSGTextInput); @@ -985,6 +1589,12 @@ void QSGTextInput::setMouseSelectionMode(SelectionMode mode) } } +/*! + \qmlproperty bool QtQuick2::TextInput::canPaste + + Returns true if the TextInput is writable and the content of the clipboard is + suitable for pasting into the TextEdit. +*/ bool QSGTextInput::canPaste() const { Q_D(const QSGTextInput); @@ -997,6 +1607,43 @@ void QSGTextInput::moveCursorSelection(int position) d->control->moveCursor(position, true); } +/*! + \qmlmethod void QtQuick2::TextInput::moveCursorSelection(int position, SelectionMode mode = TextInput.SelectCharacters) + + Moves the cursor to \a position and updates the selection according to the optional \a mode + parameter. (To only move the cursor, set the \l cursorPosition property.) + + When this method is called it additionally sets either the + selectionStart or the selectionEnd (whichever was at the previous cursor position) + to the specified position. This allows you to easily extend and contract the selected + text range. + + The selection mode specifies whether the selection is updated on a per character or a per word + basis. If not specified the selection mode will default to TextInput.SelectCharacters. + + \list + \o TextEdit.SelectCharacters - Sets either the selectionStart or selectionEnd (whichever was at + the previous cursor position) to the specified position. + \o TextEdit.SelectWords - Sets the selectionStart and selectionEnd to include all + words between the specified postion and the previous cursor position. Words partially in the + range are included. + \endlist + + For example, take this sequence of calls: + + \code + cursorPosition = 5 + moveCursorSelection(9, TextInput.SelectCharacters) + moveCursorSelection(7, TextInput.SelectCharacters) + \endcode + + This moves the cursor to position 5, extend the selection end from 5 to 9 + and then retract the selection end from 9 to 7, leaving the text from position 5 to 7 + selected (the 6th and 7th characters). + + The same sequence with TextInput.SelectWords will extend the selection start to a word boundary + before or on position 5 and extend the selection end to a word boundary on or past position 9. +*/ void QSGTextInput::moveCursorSelection(int pos, SelectionMode mode) { Q_D(QSGTextInput); @@ -1054,6 +1701,45 @@ void QSGTextInput::moveCursorSelection(int pos, SelectionMode mode) } } +/*! + \qmlmethod void QtQuick2::TextInput::openSoftwareInputPanel() + + Opens software input panels like virtual keyboards for typing, useful for + customizing when you want the input keyboard to be shown and hidden in + your application. + + By default the opening of input panels follows the platform style. On Symbian^1 and + Symbian^3 -based devices the panels are opened by clicking TextInput. On other platforms + the panels are automatically opened when TextInput element gains active focus. Input panels are + always closed if no editor has active focus. + + . You can disable the automatic behavior by setting the property \c activeFocusOnPress to false + and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement + the behavior you want. + + Only relevant on platforms, which provide virtual keyboards. + + \qml + import QtQuick 1.0 + TextInput { + id: textInput + text: "Hello world!" + activeFocusOnPress: false + MouseArea { + anchors.fill: parent + onClicked: { + if (!textInput.activeFocus) { + textInput.forceActiveFocus() + textInput.openSoftwareInputPanel(); + } else { + textInput.focus = false; + } + } + onPressAndHold: textInput.closeSoftwareInputPanel(); + } + } + \endqml +*/ void QSGTextInput::openSoftwareInputPanel() { if (qApp) { @@ -1064,6 +1750,45 @@ void QSGTextInput::openSoftwareInputPanel() } } +/*! + \qmlmethod void QtQuick2::TextInput::closeSoftwareInputPanel() + + Closes a software input panel like a virtual keyboard shown on the screen, useful + for customizing when you want the input keyboard to be shown and hidden in + your application. + + By default the opening of input panels follows the platform style. On Symbian^1 and + Symbian^3 -based devices the panels are opened by clicking TextInput. On other platforms + the panels are automatically opened when TextInput element gains active focus. Input panels are + always closed if no editor has active focus. + + . You can disable the automatic behavior by setting the property \c activeFocusOnPress to false + and use functions openSoftwareInputPanel() and closeSoftwareInputPanel() to implement + the behavior you want. + + Only relevant on platforms, which provide virtual keyboards. + + \qml + import QtQuick 1.0 + TextInput { + id: textInput + text: "Hello world!" + activeFocusOnPress: false + MouseArea { + anchors.fill: parent + onClicked: { + if (!textInput.activeFocus) { + textInput.forceActiveFocus(); + textInput.openSoftwareInputPanel(); + } else { + textInput.focus = false; + } + } + onPressAndHold: textInput.closeSoftwareInputPanel(); + } + } + \endqml +*/ void QSGTextInput::closeSoftwareInputPanel() { if (qApp) { @@ -1082,7 +1807,7 @@ void QSGTextInput::focusInEvent(QFocusEvent *event) openSoftwareInputPanel(); } } - QSGPaintedItem::focusInEvent(event); + QSGImplicitSizeItem::focusInEvent(event); } void QSGTextInput::itemChange(ItemChange change, const ItemChangeData &value) @@ -1100,6 +1825,18 @@ void QSGTextInput::itemChange(ItemChange change, const ItemChangeData &value) QSGItem::itemChange(change, value); } +/*! + \qmlproperty bool QtQuick2::TextInput::inputMethodComposing + + + This property holds whether the TextInput has partial text input from an + input method. + + While it is composing an input method may rely on mouse or key events from + the TextInput to edit or commit the partial text. This property can be + used to determine when to disable events handlers that may interfere with + the correct operation of an input method. +*/ bool QSGTextInput::isInputMethodComposing() const { Q_D(const QSGTextInput); @@ -1109,12 +1846,16 @@ bool QSGTextInput::isInputMethodComposing() const void QSGTextInputPrivate::init() { Q_Q(QSGTextInput); +#if defined(Q_WS_MAC) + control->setThreadChecks(true); +#endif control->setParent(q);//Now mandatory due to accessibility changes control->setCursorWidth(1); control->setPasswordCharacter(QLatin1Char('*')); q->setSmooth(smooth); q->setAcceptedMouseButtons(Qt::LeftButton); q->setFlag(QSGItem::ItemAcceptsInputMethod); + q->setFlag(QSGItem::ItemHasContents); q->connect(control, SIGNAL(cursorPositionChanged(int,int)), q, SLOT(cursorPosChanged())); q->connect(control, SIGNAL(selectionChanged()), @@ -1144,6 +1885,12 @@ void QSGTextInputPrivate::init() selectedTextColor = p.color(QPalette::HighlightedText); selectionColor = p.color(QPalette::Highlight); determineHorizontalAlignment(); + + if (!qmlDisableDistanceField()) { + QTextOption option = control->textLayout()->textOption(); + option.setUseDesignMetrics(true); + control->textLayout()->setTextOption(option); + } } void QSGTextInput::cursorPosChanged() @@ -1214,19 +1961,35 @@ void QSGTextInput::q_textChanged() } } +void QSGTextInputPrivate::showCursor() +{ + if (textNode != 0 && textNode->cursorNode() != 0) + textNode->cursorNode()->setColor(color); +} + +void QSGTextInputPrivate::hideCursor() +{ + if (textNode != 0 && textNode->cursorNode() != 0) + textNode->cursorNode()->setColor(QColor(0, 0, 0, 0)); +} + void QSGTextInput::updateRect(const QRect &r) { Q_D(QSGTextInput); - if(r == QRect()) - update(); - else - update(QRect(r.x() - d->hscroll, r.y(), r.width(), r.height())); + if (!isComponentComplete()) + return; + + if (r.isEmpty()) { + d->textLayoutDirty = true; + } + + update(); } QRectF QSGTextInput::boundingRect() const { Q_D(const QSGTextInput); - QRectF r = QSGPaintedItem::boundingRect(); + QRectF r = QSGImplicitSizeItem::boundingRect(); int cursorWidth = d->cursorItem ? d->cursorItem->width() : d->control->cursorWidth(); @@ -1243,7 +2006,6 @@ void QSGTextInput::updateSize(bool needsRedraw) int h = height(); setImplicitHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. setImplicitWidth(d->calculateTextWidth()); - setContentsSize(boundingRect().size().toSize()); if(w==width() && h==height() && needsRedraw) update(); } diff --git a/src/declarative/items/qsgtextinput_p.h b/src/declarative/items/qsgtextinput_p.h index 770afa8283..f5ac50e8e1 100644 --- a/src/declarative/items/qsgtextinput_p.h +++ b/src/declarative/items/qsgtextinput_p.h @@ -54,7 +54,7 @@ QT_MODULE(Declarative) class QSGTextInputPrivate; class QValidator; -class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizePaintedItem +class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizeItem { Q_OBJECT Q_ENUMS(HAlignment) @@ -204,7 +204,6 @@ public: bool hasAcceptableInput() const; - void paint(QPainter *p); QVariant inputMethodQuery(Qt::InputMethodQuery property) const; QRectF boundingRect() const; @@ -261,6 +260,7 @@ protected: bool event(QEvent *e); void focusInEvent(QFocusEvent *event); virtual void itemChange(ItemChange, const ItemChangeData &); + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data); public Q_SLOTS: void selectAll(); diff --git a/src/declarative/items/qsgtextinput_p_p.h b/src/declarative/items/qsgtextinput_p_p.h index f25722972b..0d1f21b54c 100644 --- a/src/declarative/items/qsgtextinput_p_p.h +++ b/src/declarative/items/qsgtextinput_p_p.h @@ -65,18 +65,35 @@ QT_BEGIN_NAMESPACE -class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizePaintedItemPrivate +class QSGTextNode; + +class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizeItemPrivate { Q_DECLARE_PUBLIC(QSGTextInput) public: - QSGTextInputPrivate() : control(new QLineControl(QString())), - color((QRgb)0), style(QSGText::Normal), - styleColor((QRgb)0), hAlign(QSGTextInput::AlignLeft), - mouseSelectionMode(QSGTextInput::SelectCharacters), inputMethodHints(Qt::ImhNone), - hscroll(0), oldScroll(0), oldValidity(false), focused(false), focusOnPress(true), - showInputPanelOnFocus(true), clickCausedFocus(false), cursorVisible(false), - autoScroll(true), selectByMouse(false), canPaste(false), hAlignImplicit(true), - selectPressed(false) + QSGTextInputPrivate() + : control(new QLineControl(QString())) + , color((QRgb)0) + , style(QSGText::Normal) + , styleColor((QRgb)0) + , hAlign(QSGTextInput::AlignLeft) + , mouseSelectionMode(QSGTextInput::SelectCharacters) + , inputMethodHints(Qt::ImhNone) + , textNode(0) + , hscroll(0) + , oldScroll(0) + , oldValidity(false) + , focused(false) + , focusOnPress(true) + , showInputPanelOnFocus(true) + , clickCausedFocus(false) + , cursorVisible(false) + , autoScroll(true) + , selectByMouse(false) + , canPaste(false) + , hAlignImplicit(true) + , selectPressed(false) + , textLayoutDirty(true) { #ifdef Q_OS_SYMBIAN if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) { @@ -106,6 +123,8 @@ public: int calculateTextWidth(); bool sendMouseEventToInputContext(QGraphicsSceneMouseEvent *event, QEvent::Type eventType); void updateInputMethodHints(); + void hideCursor(); + void showCursor(); QLineControl* control; @@ -122,6 +141,7 @@ public: QPointer<QDeclarativeComponent> cursorComponent; QPointer<QSGItem> cursorItem; QPointF pressPos; + QSGTextNode *textNode; int lastSelectionStart; int lastSelectionEnd; @@ -141,6 +161,7 @@ public: bool canPaste:1; bool hAlignImplicit:1; bool selectPressed:1; + bool textLayoutDirty:1; static inline QSGTextInputPrivate *get(QSGTextInput *t) { return t->d_func(); diff --git a/src/declarative/items/qsgtextnode.cpp b/src/declarative/items/qsgtextnode.cpp index 03a558fe3d..1ae457b6fa 100644 --- a/src/declarative/items/qsgtextnode.cpp +++ b/src/declarative/items/qsgtextnode.cpp @@ -47,6 +47,7 @@ #include <private/qsgcontext_p.h> +#include <QtCore/qpoint.h> #include <qmath.h> #include <qtextdocument.h> #include <qtextlayout.h> @@ -57,6 +58,7 @@ #include <private/qfont_p.h> #include <private/qfontengine_p.h> #include <private/qrawfont_p.h> +#include <qhash.h> QT_BEGIN_NAMESPACE @@ -64,7 +66,7 @@ QT_BEGIN_NAMESPACE Creates an empty QSGTextNode */ QSGTextNode::QSGTextNode(QSGContext *context) -: m_context(context) + : m_context(context), m_cursorNode(0) { #if defined(QML_RUNTIME_TESTING) description = QLatin1String("text"); @@ -119,131 +121,682 @@ void QSGTextNode::setStyleColor(const QColor &styleColor) } #endif -void QSGTextNode::addTextDecorations(Decoration decorations, const QPointF &position, - const QColor &color, qreal width, qreal lineThickness, - qreal underlinePos, qreal ascent) +QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color, + QSGText::TextStyle style, const QColor &styleColor, + QSGNode *parentNode) { - QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness); + QSGGlyphNode *node = m_context->createGlyphNode(); + node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs); + node->setStyle(style); + node->setStyleColor(styleColor); + node->setColor(color); + node->update(); - if (decorations & Underline) { - int underlinePosition = qCeil(underlinePos); - QRectF underline(line); - underline.translate(0.0, underlinePosition); - appendChildNode(new QSGSimpleRectNode(underline, color)); - } + /* We flag the geometry as static, but we never call markVertexDataDirty + or markIndexDataDirty on them. This is because all text nodes are + discarded when a change occurs. If we start appending/removing from + existing geometry, then we also need to start marking the geometry as + dirty. + */ + node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern); + node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern); - if (decorations & Overline) { - QRectF overline(line); - overline.translate(0.0, -ascent); - appendChildNode(new QSGSimpleRectNode(overline, color)); - } + if (parentNode == 0) + parentNode = this; + parentNode->appendChildNode(node); - if (decorations & StrikeOut) { - QRectF strikeOut(line); - strikeOut.translate(0.0, ascent / -3.0); - appendChildNode(new QSGSimpleRectNode(strikeOut, color)); - } + return node; } -QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color, - QSGText::TextStyle style, const QColor &styleColor, QSGGlyphNode *prevNode) +void QSGTextNode::setCursor(const QRectF &rect, const QColor &color) { - QSGGlyphNode *node = prevNode; + if (m_cursorNode != 0) + delete m_cursorNode; + + m_cursorNode = new QSGSimpleRectNode(rect, color); + appendChildNode(m_cursorNode); +} + +namespace { + + struct BinaryTreeNode { + enum SelectionState { + Unselected, + Selected + }; + + BinaryTreeNode() + : selectionState(Unselected) + , clipNode(0) + , decorations(QSGTextNode::NoDecoration) + , leftChildIndex(-1) + , rightChildIndex(-1) + { + + } + + BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect, + const QSGTextNode::Decorations &decs, const QColor &c, + const QPointF &pos) + : glyphRun(g) + , boundingRect(brect) + , selectionState(selState) + , clipNode(0) + , decorations(decs) + , color(c) + , position(pos) + , leftChildIndex(-1) + , rightChildIndex(-1) + { + } + + QGlyphRun glyphRun; + QRectF boundingRect; + SelectionState selectionState; + QSGClipNode *clipNode; + QSGTextNode::Decorations decorations; + QColor color; + QPointF position; + + int leftChildIndex; + int rightChildIndex; + + static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree, + const QGlyphRun &glyphRun, + SelectionState selectionState, + const QColor &textColor, + const QPointF &position) + { + int newIndex = binaryTree->size(); + QRectF searchRect = glyphRun.boundingRect(); + + if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height())) + return; + + QSGTextNode::Decorations decorations = QSGTextNode::NoDecoration; + decorations |= (glyphRun.underline() ? QSGTextNode::Underline : QSGTextNode::NoDecoration); + decorations |= (glyphRun.overline() ? QSGTextNode::Overline : QSGTextNode::NoDecoration); + decorations |= (glyphRun.strikeOut() ? QSGTextNode::StrikeOut : QSGTextNode::NoDecoration); + + binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect, decorations, + textColor, position)); + if (newIndex == 0) + return; + + int searchIndex = 0; + forever { + BinaryTreeNode *node = binaryTree->data() + searchIndex; + if (searchRect.left() < node->boundingRect.left()) { + if (node->leftChildIndex < 0) { + node->leftChildIndex = newIndex; + break; + } else { + searchIndex = node->leftChildIndex; + } + } else { + if (node->rightChildIndex < 0) { + node->rightChildIndex = newIndex; + break; + } else { + searchIndex = node->rightChildIndex; + } + } + } + } + + static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree, + QVarLengthArray<int> *sortedIndexes, + int currentIndex = 0) + { + Q_ASSERT(currentIndex < binaryTree.size()); - if (!node) - node = m_context->createGlyphNode(); + const BinaryTreeNode *node = binaryTree.data() + currentIndex; + if (node->leftChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->leftChildIndex); - node->setGlyphs(position, glyphs); + sortedIndexes->append(currentIndex); - if (node != prevNode) { - if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) { - QSGDistanceFieldGlyphNode *dfNode = static_cast<QSGDistanceFieldGlyphNode *>(node); - dfNode->setStyle(style); - dfNode->setStyleColor(styleColor); + if (node->rightChildIndex >= 0) + inOrder(binaryTree, sortedIndexes, node->rightChildIndex); + } + }; + + // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes, + // and rectangle nodes to represent the text, decorations and selection. Will try to minimize + // number of nodes, and join decorations in neighbouring items + class SelectionEngine + { + public: + SelectionEngine() : m_hasSelection(false) {} + + QTextLine currentLine() const { return m_currentLine; } + + void setCurrentLine(const QTextLine ¤tLine) + { + if (m_currentLine.isValid()) + processCurrentLine(); + + m_currentLine = currentLine; + } + + void addSelectedGlyphs(const QGlyphRun &glyphRun); + void addUnselectedGlyphs(const QGlyphRun &glyphRun); + + void addToSceneGraph(QSGTextNode *parent, + QSGText::TextStyle style = QSGText::Normal, + const QColor &styleColor = QColor()); + + void setSelectionColor(const QColor &selectionColor) + { + m_selectionColor = selectionColor; + } + + void setSelectedTextColor(const QColor &selectedTextColor) + { + m_selectedTextColor = selectedTextColor; + } + + void setTextColor(const QColor &textColor) + { + m_textColor = textColor; + } + + void setPosition(const QPointF &position) + { + m_position = position; + } + + private: + struct TextDecoration + { + TextDecoration() : selectionState(BinaryTreeNode::Unselected) {} + TextDecoration(const BinaryTreeNode::SelectionState &s, + const QRectF &r, + const QColor &c) + : selectionState(s) + , rect(r) + , color(c) + { + } + + BinaryTreeNode::SelectionState selectionState; + QRectF rect; + QColor color; + }; + + void processCurrentLine(); + void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations, + qreal offset, qreal thickness); + + QColor m_selectionColor; + QColor m_textColor; + QColor m_selectedTextColor; + QPointF m_position; + + QTextLine m_currentLine; + bool m_hasSelection; + + QList<QRectF> m_selectionRects; + QVarLengthArray<BinaryTreeNode> m_currentLineTree; + + QList<TextDecoration> m_lines; + QVector<BinaryTreeNode> m_processedNodes; + }; + + void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations, + qreal offset, qreal thickness) + { + for (int i=0; i<textDecorations.size(); ++i) { + TextDecoration textDecoration = textDecorations.at(i); + + { + QRectF &rect = textDecoration.rect; + rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset)); + rect.setHeight(thickness); + } + + m_lines.append(textDecoration); } - node->setColor(color); } - node->update(); + void SelectionEngine::processCurrentLine() + { + // No glyphs, do nothing + if (m_currentLineTree.isEmpty()) + return; + + // 1. Go through current line and get correct decoration position for each node based on + // neighbouring decorations. Add decoration to global list + // 2. Create clip nodes for all selected text. Try to merge as many as possible within + // the line. + // 3. Add QRects to a list of selection rects. + // 4. Add all nodes to a global processed list + QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position + BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes); + + Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size()); + + BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected; + QRectF currentRect; + + QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration; + qreal underlineOffset = 0.0; + qreal underlineThickness = 0.0; + + qreal overlineOffset = 0.0; + qreal overlineThickness = 0.0; + + qreal strikeOutOffset = 0.0; + qreal strikeOutThickness = 0.0; + + QRectF decorationRect = currentRect; + + QColor lastColor; + + QVarLengthArray<TextDecoration> pendingUnderlines; + QVarLengthArray<TextDecoration> pendingOverlines; + QVarLengthArray<TextDecoration> pendingStrikeOuts; + if (!sortedIndexes.isEmpty()) { + QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0; + bool currentClipNodeUsed = false; + for (int i=0; i<=sortedIndexes.size(); ++i) { + BinaryTreeNode *node = 0; + if (i < sortedIndexes.size()) { + int sortedIndex = sortedIndexes.at(i); + Q_ASSERT(sortedIndex < m_currentLineTree.size()); + + node = m_currentLineTree.data() + sortedIndex; + } + + if (i == 0) + currentSelectionState = node->selectionState; + + // Update decorations + if (currentDecorations != QSGTextNode::NoDecoration) { + decorationRect.setY(m_position.y() + m_currentLine.y()); + decorationRect.setHeight(m_currentLine.height()); + + if (node != 0) + decorationRect.setRight(node->boundingRect.left()); + + TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor); + if (currentDecorations & QSGTextNode::Underline) + pendingUnderlines.append(textDecoration); + + if (currentDecorations & QSGTextNode::Overline) + pendingOverlines.append(textDecoration); + + if (currentDecorations & QSGTextNode::StrikeOut) + pendingStrikeOuts.append(textDecoration); + } + + // If we've reached an unselected node from a selected node, we add the + // selection rect to the graph, and we add decoration every time the + // selection state changes, because that means the text color changes + if (node == 0 || node->selectionState != currentSelectionState) { + if (node != 0) + currentRect.setRight(node->boundingRect.left()); + currentRect.setY(m_position.y() + m_currentLine.y()); + currentRect.setHeight(m_currentLine.height()); + + // Draw selection all the way up to the left edge of the unselected item + if (currentSelectionState == BinaryTreeNode::Selected) + m_selectionRects.append(currentRect); + + if (currentClipNode != 0) { + if (!currentClipNodeUsed) { + delete currentClipNode; + } else { + currentClipNode->setIsRectangular(true); + currentClipNode->setClipRect(currentRect); + } + } + + if (node != 0 && m_hasSelection) + currentClipNode = new QSGClipNode; + else + currentClipNode = 0; + currentClipNodeUsed = false; + + if (node != 0) { + currentSelectionState = node->selectionState; + currentRect = node->boundingRect; + + // Make sure currentRect is valid, otherwise the unite won't work + if (currentRect.isNull()) + currentRect.setSize(QSizeF(1, 1)); + } + } else { + if (currentRect.isNull()) + currentRect = node->boundingRect; + else + currentRect = currentRect.united(node->boundingRect); + } + + if (node != 0) { + node->clipNode = currentClipNode; + currentClipNodeUsed = true; + + decorationRect = node->boundingRect; + + // If previous item(s) had underline and current does not, then we add the + // pending lines to the lists and likewise for overlines and strikeouts + if (!pendingUnderlines.isEmpty() + && !(node->decorations & QSGTextNode::Underline)) { + addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); + + pendingUnderlines.clear(); + + underlineOffset = 0.0; + underlineThickness = 0.0; + } + + // ### Add pending when overlineOffset/thickness changes to minimize number of + // nodes + if (!pendingOverlines.isEmpty()) { + addTextDecorations(pendingOverlines, overlineOffset, overlineThickness); + + pendingOverlines.clear(); + + overlineOffset = 0.0; + overlineThickness = 0.0; + } + + // ### Add pending when overlineOffset/thickness changes to minimize number of + // nodes + if (!pendingStrikeOuts.isEmpty()) { + addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness); + + pendingStrikeOuts.clear(); + + strikeOutOffset = 0.0; + strikeOutThickness = 0.0; + } + + // Merge current values with previous. Prefer greatest thickness + QRawFont rawFont = node->glyphRun.rawFont(); + if (node->decorations & QSGTextNode::Underline) { + if (rawFont.lineThickness() > underlineThickness) { + underlineThickness = rawFont.lineThickness(); + underlineOffset = rawFont.underlinePosition(); + } + } - // A new node, add it to the graph. - if (node != prevNode) { - appendChildNode(node); - - /* We flag the geometry as static, but we never call markVertexDataDirty - or markIndexDataDirty on them. This is because all text nodes are - discarded when a change occurs. If we start appending/removing from - existing geometry, then we also need to start marking the geometry as - dirty. - */ - node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern); - node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern); + if (node->decorations & QSGTextNode::Overline) { + overlineOffset = -rawFont.ascent(); + overlineThickness = rawFont.lineThickness(); + } + + if (node->decorations & QSGTextNode::StrikeOut) { + strikeOutThickness = rawFont.lineThickness(); + strikeOutOffset = rawFont.ascent() / -3.0; + } + + currentDecorations = node->decorations; + lastColor = node->color; + m_processedNodes.append(*node); + } + } + + // If there are pending decorations, we need to add them + if (!pendingUnderlines.isEmpty()) + addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); + + if (!pendingOverlines.isEmpty()) + addTextDecorations(pendingOverlines, overlineOffset, overlineThickness); + + if (!pendingStrikeOuts.isEmpty()) + addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness); + } + + m_currentLineTree.clear(); + m_currentLine = QTextLine(); + m_hasSelection = false; } - return node; + void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun) + { + BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected, + m_textColor, m_position); + } + + void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun) + { + int currentSize = m_currentLineTree.size(); + BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected, + m_textColor, m_position); + m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize; + } + + void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode, + QSGText::TextStyle style, + const QColor &styleColor) + { + if (m_currentLine.isValid()) + processCurrentLine(); + + // First, prepend all selection rectangles to the tree + for (int i=0; i<m_selectionRects.size(); ++i) { + const QRectF &rect = m_selectionRects.at(i); + + parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor)); + } + + // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common + // font, selection state and clip node. + typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType; + QHash<KeyType, BinaryTreeNode *> map; + for (int i=0; i<m_processedNodes.size(); ++i) { + BinaryTreeNode *node = m_processedNodes.data() + i; + + QGlyphRun glyphRun = node->glyphRun; + QRawFont rawFont = glyphRun.rawFont(); + QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont); + + QFontEngine *fontEngine = rawFontD->fontEngine; + + KeyType key(qMakePair(fontEngine, + qMakePair(node->clipNode, + qMakePair(node->color.rgba(), int(node->selectionState))))); + + BinaryTreeNode *otherNode = map.value(key, 0); + if (otherNode != 0) { + QGlyphRun &otherGlyphRun = otherNode->glyphRun; + + QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes(); + QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions(); + + otherGlyphIndexes += glyphRun.glyphIndexes(); + + QVector<QPointF> glyphPositions = glyphRun.positions(); + for (int j=0; j<glyphPositions.size(); ++j) { + otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position); + } + + otherGlyphRun.setGlyphIndexes(otherGlyphIndexes); + otherGlyphRun.setPositions(otherGlyphPositions); + + } else { + map.insert(key, node); + } + } + + // ...and add clip nodes and glyphs to tree. + QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin(); + while (it != map.constEnd()) { + + BinaryTreeNode *node = it.value(); + + QSGClipNode *clipNode = node->clipNode; + if (clipNode != 0 && clipNode->parent() == 0 ) + parentNode->appendChildNode(clipNode); + + QColor color = node->selectionState == BinaryTreeNode::Selected + ? m_selectedTextColor + : node->color; + + parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode); + + ++it; + } + + // Finally, add decorations for each node to the tree. + for (int i=0; i<m_lines.size(); ++i) { + const TextDecoration &textDecoration = m_lines.at(i); + + QColor color = textDecoration.selectionState == BinaryTreeNode::Selected + ? m_selectedTextColor + : textDecoration.color; + + parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color)); + } + } } -void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color, - QSGText::TextStyle style, const QColor &styleColor) +void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument, + const QColor &textColor, + QSGText::TextStyle style, const QColor &styleColor, + const QColor &selectionColor, const QColor &selectedTextColor, + int selectionStart, int selectionEnd) { - Q_UNUSED(position) QTextFrame *textFrame = textDocument->rootFrame(); - QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft(); + QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft(); + + SelectionEngine engine; + engine.setTextColor(textColor); + engine.setSelectedTextColor(selectedTextColor); + engine.setSelectionColor(selectionColor); + + bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd; QTextFrame::iterator it = textFrame->begin(); while (!it.atEnd()) { - addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor); + Q_ASSERT(!engine.currentLine().isValid()); + + QTextBlock block = it.currentBlock(); + + QTextBlock::iterator blockIterator = block.begin(); + while (!blockIterator.atEnd()) { + QTextFragment fragment = blockIterator.fragment(); + if (fragment.text().isEmpty()) + continue; + + QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft(); + engine.setPosition(position + blockPosition); + + QTextCharFormat charFormat = fragment.charFormat(); + if (!textColor.isValid()) + engine.setTextColor(charFormat.foreground().color()); + + int fragmentEnd = fragment.position() + fragment.length(); + int textPos = fragment.position(); + while (textPos < fragmentEnd) { + int blockRelativePosition = textPos - block.position(); + QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition); + Q_ASSERT(line.textLength() > 0); + if (!engine.currentLine().isValid() || line.lineNumber() != engine.currentLine().lineNumber()) + engine.setCurrentLine(line); + + int lineEnd = line.textStart() + block.position() + line.textLength(); + + int len = qMin(lineEnd - textPos, fragmentEnd - textPos); + Q_ASSERT(len > 0); + + int currentStepEnd = textPos + len; + + if (!hasSelection || selectionStart > currentStepEnd || selectionEnd < textPos) { + QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition, len); + for (int j=0; j<glyphRuns.size(); ++j) + engine.addUnselectedGlyphs(glyphRuns.at(j)); + } else { + if (textPos < selectionStart) { + int unselectedPartLength = qMin(selectionStart - textPos, len); + QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition, + unselectedPartLength); + for (int j=0; j<glyphRuns.size(); ++j) + engine.addUnselectedGlyphs(glyphRuns.at(j)); + } + + if (selectionStart < currentStepEnd && selectionEnd > textPos) { + int currentSelectionStart = qMax(selectionStart, textPos); + int currentSelectionLength = qMin(selectionEnd - currentSelectionStart, + currentStepEnd - currentSelectionStart); + int selectionRelativeBlockPosition = currentSelectionStart - block.position(); + + QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition, + currentSelectionLength); + for (int j=0; j<glyphRuns.size(); ++j) + engine.addSelectedGlyphs(glyphRuns.at(j)); + } + + if (selectionEnd >= textPos && selectionEnd < currentStepEnd) { + QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionEnd - block.position(), + currentStepEnd - selectionEnd); + for (int j=0; j<glyphRuns.size(); ++j) + engine.addUnselectedGlyphs(glyphRuns.at(j)); + } + } + + textPos = currentStepEnd; + } + + ++blockIterator; + } + + engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed ++it; } + + engine.addToSceneGraph(this, style, styleColor); } void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color, - QSGText::TextStyle style, const QColor &styleColor) + QSGText::TextStyle style, const QColor &styleColor, + const QColor &selectionColor, const QColor &selectedTextColor, + int selectionStart, int selectionEnd) { - QList<QGlyphRun> glyphsList(textLayout->glyphRuns()); - - QSGGlyphNode *prevNode = 0; - - QFont font = textLayout->font(); - qreal underlinePosition, ascent, lineThickness; - int decorations = NoDecoration; - decorations |= (font.underline() ? Underline : 0); - decorations |= (font.overline() ? Overline : 0); - decorations |= (font.strikeOut() ? StrikeOut : 0); - - underlinePosition = ascent = lineThickness = 0; - for (int i=0; i<glyphsList.size(); ++i) { - QGlyphRun glyphs = glyphsList.at(i); - QRawFont rawfont = glyphs.rawFont(); - prevNode = addGlyphs(position + QPointF(0, rawfont.ascent()), glyphs, color, style, styleColor); - - if (decorations) { - qreal rawAscent = rawfont.ascent(); - if (decorations & Underline) { - ascent = qMax(ascent, rawAscent); - qreal pos = rawfont.underlinePosition(); - if (pos > underlinePosition) { - underlinePosition = pos; - // take line thickness from the rawfont with maximum underline - // position in this case - lineThickness = rawfont.lineThickness(); - } - } else { - // otherwise it's strike out or overline, we take line thickness - // from the rawfont with maximum ascent - if (rawAscent > ascent) { - ascent = rawAscent; - lineThickness = rawfont.lineThickness(); - } + SelectionEngine engine; + engine.setTextColor(color); + engine.setSelectedTextColor(selectedTextColor); + engine.setSelectionColor(selectionColor); + engine.setPosition(position); + + for (int i=0; i<textLayout->lineCount(); ++i) { + QTextLine line = textLayout->lineAt(i); + + engine.setCurrentLine(line); + + int lineEnd = line.textStart() + line.textLength(); + if (selectionStart > lineEnd || selectionEnd < line.textStart()) { + QList<QGlyphRun> glyphRuns = line.glyphRuns(); + for (int j=0; j<glyphRuns.size(); ++j) + engine.addUnselectedGlyphs(glyphRuns.at(j)); + } else { + if (line.textStart() < selectionStart) { + QList<QGlyphRun> glyphRuns = line.glyphRuns(line.textStart(), + qMin(selectionStart - line.textStart(), + line.textLength())); + + for (int j=0; j<glyphRuns.size(); ++j) + engine.addUnselectedGlyphs(glyphRuns.at(j)); + } + + if (lineEnd >= selectionStart && selectionStart >= line.textStart()) { + QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1); + + for (int j=0; j<glyphRuns.size(); ++j) + engine.addSelectedGlyphs(glyphRuns.at(j)); + } + + if (selectionEnd >= line.textStart() && selectionEnd < lineEnd) { + QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, lineEnd - selectionEnd); + for (int j=0; j<glyphRuns.size(); ++j) + engine.addUnselectedGlyphs(glyphRuns.at(j)); } } } - if (decorations) { - addTextDecorations(Decoration(decorations), position + QPointF(0, ascent), color, - textLayout->boundingRect().width(), - lineThickness, underlinePosition, ascent); - } + engine.addToSceneGraph(this, style, styleColor); } @@ -383,49 +936,11 @@ bool QSGTextNode::isComplexRichText(QTextDocument *doc) return reader.hasError(); } -void QSGTextNode::addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block, - const QColor &overrideColor, QSGText::TextStyle style, const QColor &styleColor) -{ - if (!block.isValid()) - return; - - QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft(); - - QTextBlock::iterator it = block.begin(); - while (!it.atEnd()) { - QTextFragment fragment = it.fragment(); - if (!fragment.text().isEmpty()) { - QTextCharFormat charFormat = fragment.charFormat(); - QColor color = overrideColor.isValid() - ? overrideColor - : charFormat.foreground().color(); - - QList<QGlyphRun> glyphsList = fragment.glyphRuns(); - for (int i=0; i<glyphsList.size(); ++i) { - QGlyphRun glyphs = glyphsList.at(i); - QRawFont font = glyphs.rawFont(); - QSGGlyphNode *glyphNode = addGlyphs(position + blockPosition + QPointF(0, font.ascent()), - glyphs, color, style, styleColor); - int decorations = (glyphs.overline() ? Overline : 0) | - (glyphs.strikeOut() ? StrikeOut : 0) | - (glyphs.underline() ? Underline : 0); - if (decorations) { - QPointF baseLine = glyphNode->baseLine(); - qreal width = glyphNode->boundingRect().width(); - addTextDecorations(Decoration(decorations), baseLine, color, width, - font.lineThickness(), font.underlinePosition(), font.ascent()); - } - } - } - - ++it; - } -} - void QSGTextNode::deleteContent() { - while (firstChild() > 0) + while (firstChild() != 0) delete firstChild(); + m_cursorNode = 0; } #if 0 diff --git a/src/declarative/items/qsgtextnode_p.h b/src/declarative/items/qsgtextnode_p.h index 7ff31998c6..4c5199aefa 100644 --- a/src/declarative/items/qsgtextnode_p.h +++ b/src/declarative/items/qsgtextnode_p.h @@ -54,10 +54,20 @@ class QColor; class QTextDocument; class QSGContext; class QRawFont; +class QSGSimpleRectNode; +class QSGClipNode; class QSGTextNode : public QSGTransformNode { public: + enum Decoration { + NoDecoration = 0x0, + Underline = 0x1, + Overline = 0x2, + StrikeOut = 0x4 + }; + Q_DECLARE_FLAGS(Decorations, Decoration) + QSGTextNode(QSGContext *); ~QSGTextNode(); @@ -65,26 +75,24 @@ public: void deleteContent(); void addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color = QColor(), - QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(), + const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(), + int selectionStart = -1, int selectionEnd = -1); void addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color = QColor(), - QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(), + const QColor &selectionColor = QColor(), const QColor &selectedTextColor = QColor(), + int selectionStart = -1, int selectionEnd = -1); -private: - enum Decoration { - NoDecoration = 0x0, - Underline = 0x1, - Overline = 0x2, - StrikeOut = 0x4 - }; + void setCursor(const QRectF &rect, const QColor &color); + QSGSimpleRectNode *cursorNode() const { return m_cursorNode; } - void addTextBlock(const QPointF &position, QTextDocument *textDocument, const QTextBlock &block, - const QColor &overrideColor, QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); QSGGlyphNode *addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color, - QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(), - QSGGlyphNode *node = 0); - void addTextDecorations(Decoration decorations, const QPointF &position, const QColor &color, - qreal width, qreal lineThickness, qreal underlinePosition, qreal ascent); + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor(), + QSGNode *parentNode = 0); + +private: QSGContext *m_context; + QSGSimpleRectNode *m_cursorNode; }; QT_END_NAMESPACE diff --git a/src/declarative/items/qsgtranslate.cpp b/src/declarative/items/qsgtranslate.cpp index fc2e7b3cc2..1de5c6c3ea 100644 --- a/src/declarative/items/qsgtranslate.cpp +++ b/src/declarative/items/qsgtranslate.cpp @@ -56,15 +56,29 @@ public: qreal y; }; +/*! + Constructs an empty QSGTranslate object with the given \a parent. +*/ QSGTranslate::QSGTranslate(QObject *parent) : QSGTransform(*new QSGTranslatePrivate, parent) { } +/*! + Destroys the graphics scale. +*/ QSGTranslate::~QSGTranslate() { } +/*! + \property QSGTranslate::x + \brief the horizontal translation. + + The translation can be any real number; the default value is 0.0. + + \sa y +*/ qreal QSGTranslate::x() const { Q_D(const QSGTranslate); @@ -81,6 +95,14 @@ void QSGTranslate::setX(qreal x) emit xChanged(); } +/*! + \property QSGTranslate::y + \brief the vertical translation. + + The translation can be any real number; the default value is 0.0. + + \sa x +*/ qreal QSGTranslate::y() const { Q_D(const QSGTranslate); diff --git a/src/declarative/items/qsgvisualitemmodel.cpp b/src/declarative/items/qsgvisualitemmodel.cpp index 7e45aeb629..5dfe01dd2b 100644 --- a/src/declarative/items/qsgvisualitemmodel.cpp +++ b/src/declarative/items/qsgvisualitemmodel.cpp @@ -57,6 +57,7 @@ #include <private/qdeclarativeglobal_p.h> #include <private/qlistmodelinterface_p.h> #include <private/qmetaobjectbuilder_p.h> +#include <private/qdeclarativeproperty_p.h> #include <private/qobject_p.h> #include <QtCore/qhash.h> @@ -122,11 +123,56 @@ public: QList<Item> children; }; + +/*! + \qmlclass VisualItemModel QSGVisualItemModel + \inqmlmodule QtQuick 2 + \ingroup qml-working-with-data + \brief The VisualItemModel allows items to be provided to a view. + + A VisualItemModel contains the visual items to be used in a view. + When a VisualItemModel is used in a view, the view does not require + a delegate since the VisualItemModel already contains the visual + delegate (items). + + An item can determine its index within the + model via the \l{VisualItemModel::index}{index} attached property. + + The example below places three colored rectangles in a ListView. + \code + import QtQuick 1.0 + + Rectangle { + VisualItemModel { + id: itemModel + Rectangle { height: 30; width: 80; color: "red" } + Rectangle { height: 30; width: 80; color: "green" } + Rectangle { height: 30; width: 80; color: "blue" } + } + + ListView { + anchors.fill: parent + model: itemModel + } + } + \endcode + + \image visualitemmodel.png + + \sa {declarative/modelviews/visualitemmodel}{VisualItemModel example} +*/ QSGVisualItemModel::QSGVisualItemModel(QObject *parent) : QSGVisualModel(*(new QSGVisualItemModelPrivate), parent) { } +/*! + \qmlattachedproperty int QtQuick2::VisualItemModel::index + This attached property holds the index of this delegate's item within the model. + + It is attached to each instance of the delegate. +*/ + QDeclarativeListProperty<QSGItem> QSGVisualItemModel::children() { Q_D(QSGVisualItemModel); @@ -134,6 +180,11 @@ QDeclarativeListProperty<QSGItem> QSGVisualItemModel::children() d->children_count, d->children_at); } +/*! + \qmlproperty int QtQuick2::VisualItemModel::count + + The number of items in the model. This property is readonly. +*/ int QSGVisualItemModel::count() const { Q_D(const QSGVisualItemModel); @@ -198,20 +249,53 @@ QSGVisualItemModelAttached *QSGVisualItemModel::qmlAttachedProperties(QObject *o //============================================================================ -class VDMDelegateDataType : public QDeclarativeOpenMetaObjectType +class VDMDelegateDataType : public QDeclarativeRefCount { public: - VDMDelegateDataType(const QMetaObject *base, QDeclarativeEngine *engine) : QDeclarativeOpenMetaObjectType(base, engine) {} + VDMDelegateDataType() + : metaObject(0) + , propertyCache(0) + , propertyOffset(0) + , signalOffset(0) + , shared(true) + { + } + + VDMDelegateDataType(const VDMDelegateDataType &type) + : metaObject(0) + , propertyCache(0) + , propertyOffset(type.propertyOffset) + , signalOffset(type.signalOffset) + , shared(false) + , builder(type.metaObject, QMetaObjectBuilder::Properties + | QMetaObjectBuilder::Signals + | QMetaObjectBuilder::SuperClass + | QMetaObjectBuilder::ClassName) + { + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + } - void propertyCreated(int, QMetaPropertyBuilder &prop) { - prop.setWritable(false); + ~VDMDelegateDataType() + { + if (propertyCache) + propertyCache->release(); + qFree(metaObject); } + + QMetaObject *metaObject; + QDeclarativePropertyCache *propertyCache; + int propertyOffset; + int signalOffset; + bool shared : 1; + QMetaObjectBuilder builder; }; class QSGVisualDataModelParts; class QSGVisualDataModelData; +class QSGVisualDataModelDataMetaObject; class QSGVisualDataModelPrivate : public QObjectPrivate { + Q_DECLARE_PUBLIC(QSGVisualDataModel) public: QSGVisualDataModelPrivate(QDeclarativeContext *); @@ -228,55 +312,30 @@ public: QDeclarativeGuard<QDeclarativeContext> m_context; QList<int> m_roles; QHash<QByteArray,int> m_roleNames; - void ensureRoles() { - if (m_roleNames.isEmpty()) { - if (m_listModelInterface) { - m_roles = m_listModelInterface->roles(); - for (int ii = 0; ii < m_roles.count(); ++ii) - m_roleNames.insert(m_listModelInterface->toString(m_roles.at(ii)).toUtf8(), m_roles.at(ii)); - } else if (m_abstractItemModel) { - for (QHash<int,QByteArray>::const_iterator it = m_abstractItemModel->roleNames().begin(); - it != m_abstractItemModel->roleNames().end(); ++it) { - m_roles.append(it.key()); - m_roleNames.insert(*it, it.key()); - } - if (m_roles.count()) - m_roleNames.insert("hasModelChildren", -1); - } else if (m_listAccessor) { - m_roleNames.insert("modelData", 0); - if (m_listAccessor->type() == QDeclarativeListAccessor::Instance) { - if (QObject *object = m_listAccessor->at(0).value<QObject*>()) { - int count = object->metaObject()->propertyCount(); - for (int ii = 1; ii < count; ++ii) { - const QMetaProperty &prop = object->metaObject()->property(ii); - m_roleNames.insert(prop.name(), 0); - } - } - } - } - } + + void addProperty(int role, int propertyId, const char *propertyName, const char *propertyType, bool isModelData = false); + template <typename T> void setModelDataType() + { + createModelData = &T::create; + m_delegateDataType->builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + m_delegateDataType->builder.setClassName(T::staticMetaObject.className()); + m_delegateDataType->builder.setSuperClass(&T::staticMetaObject); + m_delegateDataType->propertyOffset = T::staticMetaObject.propertyCount(); + m_delegateDataType->signalOffset = T::staticMetaObject.methodCount(); } + QSGVisualDataModelData *createMetaObject(int index, QSGVisualDataModel *model); - QHash<int,int> m_roleToPropId; - int m_modelDataPropId; - void createMetaData() { - if (!m_metaDataCreated) { - ensureRoles(); - if (m_roleNames.count()) { - QHash<QByteArray, int>::const_iterator it = m_roleNames.begin(); - while (it != m_roleNames.end()) { - int propId = m_delegateDataType->createProperty(it.key()) - m_delegateDataType->propertyOffset(); - m_roleToPropId.insert(*it, propId); - ++it; - } - // Add modelData property - if (m_roles.count() == 1 && !m_roleNames.contains("modelData")) - m_modelDataPropId = m_delegateDataType->createProperty("modelData") - m_delegateDataType->propertyOffset(); - m_metaDataCreated = true; - } - } + static QSGVisualDataModelData *initializeModelData(int index, QSGVisualDataModel *model) { + return get(model)->createMetaObject(index, model); } + typedef QSGVisualDataModelData *(*CreateModelData)(int index, QSGVisualDataModel *model); + + struct PropertyData { + int role; + bool isModelData : 1; + }; + struct ObjectRef { ObjectRef(QObject *object=0) : obj(object), ref(1) {} QObject *obj; @@ -338,11 +397,12 @@ public: friend class QSGVisualItemParts; VDMDelegateDataType *m_delegateDataType; + CreateModelData createModelData; + friend class QSGVisualDataModelData; - bool m_metaDataCreated : 1; - bool m_metaDataCacheable : 1; bool m_delegateValidated : 1; bool m_completePending : 1; + bool m_objectList : 1; QSGVisualDataModelData *data(QObject *item); @@ -352,164 +412,315 @@ public: QModelIndex m_root; QList<QByteArray> watchedRoles; QList<int> watchedRoleIds; -}; - -class QSGVisualDataModelDataMetaObject : public QDeclarativeOpenMetaObject -{ -public: - QSGVisualDataModelDataMetaObject(QObject *parent, QDeclarativeOpenMetaObjectType *type) - : QDeclarativeOpenMetaObject(parent, type) {} - virtual QVariant initialValue(int); - virtual int createProperty(const char *, const char *); - -private: - friend class QSGVisualDataModelData; + QVector<PropertyData> m_propertyData; }; class QSGVisualDataModelData : public QObject { -Q_OBJECT + Q_OBJECT + Q_PROPERTY(int index READ index NOTIFY indexChanged) public: QSGVisualDataModelData(int index, QSGVisualDataModel *model); ~QSGVisualDataModelData(); - Q_PROPERTY(int index READ index NOTIFY indexChanged) int index() const; void setIndex(int index); - int propForRole(int) const; - int modelDataPropertyId() const { - QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(m_model); - return model->m_modelDataPropId; - } - - void setValue(int, const QVariant &); - bool hasValue(int id) const { - return m_meta->hasValue(id); - } - - void ensureProperties(); - Q_SIGNALS: void indexChanged(); -private: - friend class QSGVisualDataModelDataMetaObject; +public: int m_index; QDeclarativeGuard<QSGVisualDataModel> m_model; - QSGVisualDataModelDataMetaObject *m_meta; }; -int QSGVisualDataModelData::propForRole(int id) const +class QSGVisualDataModelDataMetaObject : public QAbstractDynamicMetaObject { - QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(m_model); - QHash<int,int>::const_iterator it = model->m_roleToPropId.find(id); - if (it != model->m_roleToPropId.end()) - return *it; +public: + QSGVisualDataModelDataMetaObject(QSGVisualDataModelData *data, VDMDelegateDataType *type) + : m_data(data) + , m_type(type) + { + QObjectPrivate *op = QObjectPrivate::get(m_data); + *static_cast<QMetaObject *>(this) = *type->metaObject; + op->metaObject = this; + m_type->addref(); + } - return -1; -} + ~QSGVisualDataModelDataMetaObject() { m_type->release(); } -void QSGVisualDataModelData::setValue(int id, const QVariant &val) -{ - m_meta->setValue(id, val); -} + QSGVisualDataModelData *m_data; + VDMDelegateDataType *m_type; +}; -int QSGVisualDataModelDataMetaObject::createProperty(const char *name, const char *type) +class QSGVDMAbstractItemModelDataMetaObject : public QSGVisualDataModelDataMetaObject { - QSGVisualDataModelData *data = - static_cast<QSGVisualDataModelData *>(object()); +public: + QSGVDMAbstractItemModelDataMetaObject(QSGVisualDataModelData *object, VDMDelegateDataType *type) + : QSGVisualDataModelDataMetaObject(object, type) {} + + int metaCall(QMetaObject::Call call, int id, void **arguments) + { + if (call == QMetaObject::ReadProperty && id >= m_type->propertyOffset) { + QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(m_data->m_model); + if (m_data->m_index == -1 || !model->m_abstractItemModel) + return -1; + *static_cast<QVariant *>(arguments[0]) = model->m_abstractItemModel->index( + m_data->m_index, 0, model->m_root).data(model->m_propertyData.at(id - m_type->propertyOffset).role); + return -1; + } else { + return m_data->qt_metacall(call, id, arguments); + } + } +}; - if (!data->m_model) - return -1; +class QSGVDMAbstractItemModelData : public QSGVisualDataModelData +{ + Q_OBJECT + Q_PROPERTY(bool hasModelChildren READ hasModelChildren CONSTANT) +public: + bool hasModelChildren() const + { + QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(m_model); + return model->m_abstractItemModel->hasChildren(model->m_abstractItemModel->index(m_index, 0, model->m_root)); + } - QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(data->m_model); - if (data->m_index < 0 || data->m_index >= model->modelCount()) - return -1; + static QSGVisualDataModelData *create(int index, QSGVisualDataModel *model) { + return new QSGVDMAbstractItemModelData(index, model); } +private: + QSGVDMAbstractItemModelData(int index, QSGVisualDataModel *model) + : QSGVisualDataModelData(index, model) + { + new QSGVDMAbstractItemModelDataMetaObject( + this, QSGVisualDataModelPrivate::get(m_model)->m_delegateDataType); + } +}; - if ((!model->m_listModelInterface || !model->m_abstractItemModel) && model->m_listAccessor) { - if (model->m_listAccessor->type() == QDeclarativeListAccessor::ListProperty) { - model->ensureRoles(); - if (qstrcmp(name,"modelData") == 0) - return QDeclarativeOpenMetaObject::createProperty(name, type); +class QSGVDMListModelInterfaceDataMetaObject : public QSGVisualDataModelDataMetaObject +{ +public: + QSGVDMListModelInterfaceDataMetaObject(QSGVisualDataModelData *object, VDMDelegateDataType *type) + : QSGVisualDataModelDataMetaObject(object, type) {} + + int metaCall(QMetaObject::Call call, int id, void **arguments) + { + if (call == QMetaObject::ReadProperty && id >= m_type->propertyOffset) { + QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(m_data->m_model); + if (m_data->m_index == -1 || !model->m_listModelInterface) + return -1; + *static_cast<QVariant *>(arguments[0]) = model->m_listModelInterface->data( + m_data->m_index, model->m_propertyData.at(id - m_type->propertyOffset).role); + return -1; + } else { + return m_data->qt_metacall(call, id, arguments); } } - return -1; -} +}; -QVariant QSGVisualDataModelDataMetaObject::initialValue(int propId) +class QSGVDMListModelInterfaceData : public QSGVisualDataModelData { - QSGVisualDataModelData *data = - static_cast<QSGVisualDataModelData *>(object()); +public: + static QSGVisualDataModelData *create(int index, QSGVisualDataModel *model) { + return new QSGVDMListModelInterfaceData(index, model); } +private: + QSGVDMListModelInterfaceData(int index, QSGVisualDataModel *model) + : QSGVisualDataModelData(index, model) + { + new QSGVDMListModelInterfaceDataMetaObject( + this, QSGVisualDataModelPrivate::get(m_model)->m_delegateDataType); + } +}; - Q_ASSERT(data->m_model); - QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(data->m_model); +class QSGVDMListAccessorData : public QSGVisualDataModelData +{ + Q_OBJECT + Q_PROPERTY(QVariant modelData READ modelData CONSTANT) +public: + QVariant modelData() const { + return QSGVisualDataModelPrivate::get(m_model)->m_listAccessor->at(m_index); } - QByteArray propName = name(propId); - if ((!model->m_listModelInterface || !model->m_abstractItemModel) && model->m_listAccessor) { - if (propName == "modelData") { - if (model->m_listAccessor->type() == QDeclarativeListAccessor::Instance) { - QObject *object = model->m_listAccessor->at(0).value<QObject*>(); - return object->metaObject()->property(1).read(object); // the first property after objectName - } - return model->m_listAccessor->at(data->m_index); + static QSGVisualDataModelData *create(int index, QSGVisualDataModel *model) { + return new QSGVDMListAccessorData(index, model); } +private: + QSGVDMListAccessorData(int index, QSGVisualDataModel *model) + : QSGVisualDataModelData(index, model) + { + } +}; + +class QSGVDMObjectDataMetaObject : public QSGVisualDataModelDataMetaObject +{ +public: + QSGVDMObjectDataMetaObject(QSGVisualDataModelData *data, VDMDelegateDataType *type) + : QSGVisualDataModelDataMetaObject(data, type) + , m_object(QSGVisualDataModelPrivate::get(data->m_model)->m_listAccessor->at(data->m_index).value<QObject *>()) + {} + + int metaCall(QMetaObject::Call call, int id, void **arguments) + { + if (id >= m_type->propertyOffset + && (call == QMetaObject::ReadProperty + || call == QMetaObject::WriteProperty + || call == QMetaObject::ResetProperty)) { + if (m_object) + QMetaObject::metacall(m_object, call, id - m_type->propertyOffset + 1, arguments); + return -1; + } else if (id >= m_type->signalOffset && call == QMetaObject::InvokeMetaMethod) { + QMetaObject::activate(m_data, this, id, 0); + return -1; } else { - // return any property of a single object instance. - QObject *object = model->m_listAccessor->at(data->m_index).value<QObject*>(); - return object->property(propName); + return m_data->qt_metacall(call, id, arguments); } - } else if (model->m_listModelInterface) { - model->ensureRoles(); - QHash<QByteArray,int>::const_iterator it = model->m_roleNames.find(propName); - if (it != model->m_roleNames.end()) { - QVariant value = model->m_listModelInterface->data(data->m_index, *it); - return value; - } else if (model->m_roles.count() == 1 && propName == "modelData") { - //for compatibility with other lists, assign modelData if there is only a single role - QVariant value = model->m_listModelInterface->data(data->m_index, model->m_roles.first()); - return value; + } + + int createProperty(const char *name, const char *) + { + if (!m_object) + return -1; + const QMetaObject *metaObject = m_object->metaObject(); + + const int previousPropertyCount = propertyCount() - propertyOffset(); + int propertyIndex = metaObject->indexOfProperty(name); + if (propertyIndex == -1) + return -1; + if (previousPropertyCount + 1 == metaObject->propertyCount()) + return propertyIndex + m_type->propertyOffset - 1; + + if (m_type->shared) { + VDMDelegateDataType *type = m_type; + m_type = new VDMDelegateDataType(*m_type); + type->release(); } - } else if (model->m_abstractItemModel) { - model->ensureRoles(); - QModelIndex index = model->m_abstractItemModel->index(data->m_index, 0, model->m_root); - if (propName == "hasModelChildren") { - return model->m_abstractItemModel->hasChildren(index); - } else { - QHash<QByteArray,int>::const_iterator it = model->m_roleNames.find(propName); - if (it != model->m_roleNames.end()) { - return model->m_abstractItemModel->data(index, *it); - } else if (model->m_roles.count() == 1 && propName == "modelData") { - //for compatibility with other lists, assign modelData if there is only a single role - return model->m_abstractItemModel->data(index, model->m_roles.first()); + + const int previousMethodCount = methodCount(); + int notifierId = previousMethodCount; + for (int propertyId = previousPropertyCount; propertyId < metaObject->propertyCount() - 1; ++propertyId) { + QMetaProperty property = metaObject->property(propertyId + 1); + QMetaPropertyBuilder propertyBuilder; + if (property.hasNotifySignal()) { + m_type->builder.addSignal("__" + QByteArray::number(propertyId) + "()"); + propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName(), notifierId); + ++notifierId; + } else { + propertyBuilder = m_type->builder.addProperty(property.name(), property.typeName()); + } + propertyBuilder.setWritable(property.isWritable()); + propertyBuilder.setResettable(property.isResettable()); + propertyBuilder.setConstant(property.isConstant()); + } + + if (m_type->metaObject) + qFree(m_type->metaObject); + m_type->metaObject = m_type->builder.toMetaObject(); + *static_cast<QMetaObject *>(this) = *m_type->metaObject; + + notifierId = previousMethodCount; + for (int i = previousPropertyCount; i < metaObject->propertyCount(); ++i) { + QMetaProperty property = metaObject->property(i); + if (property.hasNotifySignal()) { + QDeclarativePropertyPrivate::connect( + m_object, property.notifySignalIndex(), m_data, notifierId); + ++notifierId; } } + return propertyIndex + m_type->propertyOffset - 1; + } + + QDeclarativeGuard<QObject> m_object; +}; + +class QSGVDMObjectData : public QSGVisualDataModelData +{ + Q_OBJECT + Q_PROPERTY(QObject *modelData READ modelData CONSTANT) +public: + QObject *modelData() const { return m_metaObject->m_object; } + + static QSGVisualDataModelData *create(int index, QSGVisualDataModel *model) { + return new QSGVDMObjectData(index, model); } + +private: + QSGVDMObjectData(int index, QSGVisualDataModel *model) + : QSGVisualDataModelData(index, model) + , m_metaObject(new QSGVDMObjectDataMetaObject(this, QSGVisualDataModelPrivate::get(m_model)->m_delegateDataType)) + { } - Q_ASSERT(!"Can never be reached"); - return QVariant(); + + QSGVDMObjectDataMetaObject *m_metaObject; +}; + +void QSGVisualDataModelPrivate::addProperty( + int role, int propertyId, const char *propertyName, const char *propertyType, bool isModelData) +{ + PropertyData propertyData; + propertyData.role = role; + propertyData.isModelData = isModelData; + m_delegateDataType->builder.addSignal("__" + QByteArray::number(propertyId) + "()"); + QMetaPropertyBuilder property = m_delegateDataType->builder.addProperty( + propertyName, propertyType, propertyId); + property.setWritable(false); + + m_propertyData.append(propertyData); } -QSGVisualDataModelData::QSGVisualDataModelData(int index, - QSGVisualDataModel *model) -: m_index(index), m_model(model), -m_meta(new QSGVisualDataModelDataMetaObject(this, QSGVisualDataModelPrivate::get(model)->m_delegateDataType)) +QSGVisualDataModelData *QSGVisualDataModelPrivate::createMetaObject(int index, QSGVisualDataModel *model) { - ensureProperties(); + Q_ASSERT(!m_delegateDataType); + + m_objectList = false; + m_propertyData.clear(); + if (m_listAccessor + && m_listAccessor->type() != QDeclarativeListAccessor::ListProperty + && m_listAccessor->type() != QDeclarativeListAccessor::Instance) { + createModelData = &QSGVDMListAccessorData::create; + return QSGVDMListAccessorData::create(index, model); + } + + m_delegateDataType = new VDMDelegateDataType; + if (m_listModelInterface) { + setModelDataType<QSGVDMListModelInterfaceData>(); + QList<int> roles = m_listModelInterface->roles(); + for (int propertyId = 0; propertyId < roles.count(); ++propertyId) { + const int role = roles.at(propertyId); + const QByteArray propertyName = m_listModelInterface->toString(role).toUtf8(); + addProperty(role, propertyId, propertyName, "QVariant"); + m_roleNames.insert(propertyName, role); + } + if (m_propertyData.count() == 1) + addProperty(roles.first(), 1, "modelData", "QVariant", true); + } else if (m_abstractItemModel) { + setModelDataType<QSGVDMAbstractItemModelData>(); + QHash<int, QByteArray> roleNames = m_abstractItemModel->roleNames(); + for (QHash<int, QByteArray>::const_iterator it = roleNames.begin(); it != roleNames.end(); ++it) { + addProperty(it.key(), m_propertyData.count(), it.value(), "QVariant"); + m_roleNames.insert(it.value(), it.key()); + } + if (m_propertyData.count() == 1) + addProperty(roleNames.begin().key(), 1, "modelData", "QVariant", true); + } else if (m_listAccessor) { + setModelDataType<QSGVDMObjectData>(); + m_objectList = true; + } else { + Q_ASSERT(!"No model set on VisualDataModel"); + return 0; + } + m_delegateDataType->metaObject = m_delegateDataType->builder.toMetaObject(); + if (!m_objectList) { + m_delegateDataType->propertyCache = new QDeclarativePropertyCache( + m_context ? m_context->engine() : qmlEngine(q_func()), m_delegateDataType->metaObject); + } + return createModelData(index, model); } -QSGVisualDataModelData::~QSGVisualDataModelData() +QSGVisualDataModelData::QSGVisualDataModelData(int index, QSGVisualDataModel *model) + : m_index(index) + , m_model(model) { } -void QSGVisualDataModelData::ensureProperties() +QSGVisualDataModelData::~QSGVisualDataModelData() { - QSGVisualDataModelPrivate *modelPriv = QSGVisualDataModelPrivate::get(m_model); - if (modelPriv->m_metaDataCacheable) { - if (!modelPriv->m_metaDataCreated) - modelPriv->createMetaData(); - if (modelPriv->m_metaDataCreated) - m_meta->setCached(true); - } } int QSGVisualDataModelData::index() const @@ -571,8 +782,8 @@ QSGVisualDataModelParts::QSGVisualDataModelParts(QSGVisualDataModel *parent) QSGVisualDataModelPrivate::QSGVisualDataModelPrivate(QDeclarativeContext *ctxt) : m_listModelInterface(0), m_abstractItemModel(0), m_visualItemModel(0), m_delegate(0) -, m_context(ctxt), m_modelDataPropId(-1), m_parts(0), m_delegateDataType(0), m_metaDataCreated(false) -, m_metaDataCacheable(false), m_delegateValidated(false), m_completePending(false), m_listAccessor(0) +, m_context(ctxt), m_parts(0), m_delegateDataType(0), createModelData(&initializeModelData) +, m_delegateValidated(false), m_completePending(false), m_objectList(false), m_listAccessor(0) { } @@ -586,6 +797,26 @@ QSGVisualDataModelData *QSGVisualDataModelPrivate::data(QObject *item) //--------------------------------------------------------------------------- +/*! + \qmlclass VisualDataModel QSGVisualDataModel + \inqmlmodule QtQuick 2 + \ingroup qml-working-with-data + \brief The VisualDataModel encapsulates a model and delegate + + A VisualDataModel encapsulates a model and the delegate that will + be instantiated for items in the model. + + It is usually not necessary to create VisualDataModel elements. + However, it can be useful for manipulating and accessing the \l modelIndex + when a QAbstractItemModel subclass is used as the + model. Also, VisualDataModel is used together with \l Package to + provide delegates to multiple views. + + The example below illustrates using a VisualDataModel with a ListView. + + \snippet doc/src/snippets/declarative/visualdatamodel.qml 0 +*/ + QSGVisualDataModel::QSGVisualDataModel() : QSGVisualModel(*(new QSGVisualDataModelPrivate(0))) { @@ -605,6 +836,20 @@ QSGVisualDataModel::~QSGVisualDataModel() d->m_delegateDataType->release(); } +/*! + \qmlproperty model QtQuick2::VisualDataModel::model + This property holds the model providing data for the VisualDataModel. + + The model provides a set of data that is used to create the items + for a view. For large or dynamic datasets the model is usually + provided by a C++ model object. The C++ model object must be a \l + {QAbstractItemModel} subclass or a simple list. + + Models can also be created directly in QML, using a \l{ListModel} or + \l{XmlListModel}. + + \sa {qmlmodels}{Data Models} +*/ QVariant QSGVisualDataModel::model() const { Q_D(const QSGVisualDataModel); @@ -658,9 +903,8 @@ void QSGVisualDataModel::setModel(const QVariant &model) d->m_roleNames.clear(); if (d->m_delegateDataType) d->m_delegateDataType->release(); - d->m_metaDataCreated = 0; - d->m_metaDataCacheable = false; - d->m_delegateDataType = new VDMDelegateDataType(&QSGVisualDataModelData::staticMetaObject, d->m_context?d->m_context->engine():qmlEngine(this)); + d->m_delegateDataType = 0; + d->createModelData = &QSGVisualDataModelPrivate::initializeModelData; QObject *object = qvariant_cast<QObject *>(model); if (object && (d->m_listModelInterface = qobject_cast<QListModelInterface *>(object))) { @@ -672,7 +916,6 @@ void QSGVisualDataModel::setModel(const QVariant &model) this, SLOT(_q_itemsRemoved(int,int))); QObject::connect(d->m_listModelInterface, SIGNAL(itemsMoved(int,int,int)), this, SLOT(_q_itemsMoved(int,int,int))); - d->m_metaDataCacheable = true; if (d->m_delegate && d->m_listModelInterface->count()) emit itemsInserted(0, d->m_listModelInterface->count()); return; @@ -687,7 +930,6 @@ void QSGVisualDataModel::setModel(const QVariant &model) this, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); QObject::connect(d->m_abstractItemModel, SIGNAL(modelReset()), this, SLOT(_q_modelReset())); QObject::connect(d->m_abstractItemModel, SIGNAL(layoutChanged()), this, SLOT(_q_layoutChanged())); - d->m_metaDataCacheable = true; if (d->m_abstractItemModel->canFetchMore(d->m_root)) d->m_abstractItemModel->fetchMore(d->m_root); return; @@ -710,13 +952,19 @@ void QSGVisualDataModel::setModel(const QVariant &model) d->m_listAccessor = new QDeclarativeListAccessor; d->m_listAccessor->setList(model, d->m_context?d->m_context->engine():qmlEngine(this)); if (d->m_listAccessor->type() != QDeclarativeListAccessor::ListProperty) - d->m_metaDataCacheable = true; if (d->m_delegate && d->modelCount()) { emit itemsInserted(0, d->modelCount()); emit countChanged(); } } +/*! + \qmlproperty Component QtQuick2::VisualDataModel::delegate + + The delegate provides a template defining each item instantiated by a view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. +*/ QDeclarativeComponent *QSGVisualDataModel::delegate() const { Q_D(const QSGVisualDataModel); @@ -741,6 +989,35 @@ void QSGVisualDataModel::setDelegate(QDeclarativeComponent *delegate) } } +/*! + \qmlproperty QModelIndex QtQuick2::VisualDataModel::rootIndex + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. \c rootIndex allows the children of + any node in a QAbstractItemModel to be provided by this model. + + This property only affects models of type QAbstractItemModel that + are hierarchical (e.g, a tree model). + + For example, here is a simple interactive file system browser. + When a directory name is clicked, the view's \c rootIndex is set to the + QModelIndex node of the clicked directory, thus updating the view to show + the new directory's contents. + + \c main.cpp: + \snippet doc/src/snippets/declarative/visualdatamodel_rootindex/main.cpp 0 + + \c view.qml: + \snippet doc/src/snippets/declarative/visualdatamodel_rootindex/view.qml 0 + + If the \l model is a QAbstractItemModel subclass, the delegate can also + reference a \c hasModelChildren property (optionally qualified by a + \e model. prefix) that indicates whether the delegate's model item has + any child nodes. + + + \sa modelIndex(), parentModelIndex() +*/ QVariant QSGVisualDataModel::rootIndex() const { Q_D(const QSGVisualDataModel); @@ -767,6 +1044,18 @@ void QSGVisualDataModel::setRootIndex(const QVariant &root) } } +/*! + \qmlmethod QModelIndex QtQuick2::VisualDataModel::modelIndex(int index) + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. This function assists in using + tree models in QML. + + Returns a QModelIndex for the specified index. + This value can be assigned to rootIndex. + + \sa rootIndex +*/ QVariant QSGVisualDataModel::modelIndex(int idx) const { Q_D(const QSGVisualDataModel); @@ -775,6 +1064,18 @@ QVariant QSGVisualDataModel::modelIndex(int idx) const return QVariant::fromValue(QModelIndex()); } +/*! + \qmlmethod QModelIndex QtQuick2::VisualDataModel::parentModelIndex() + + QAbstractItemModel provides a hierarchical tree of data, whereas + QML only operates on list data. This function assists in using + tree models in QML. + + Returns a QModelIndex for the parent of the current rootIndex. + This value can be assigned to rootIndex. + + \sa rootIndex +*/ QVariant QSGVisualDataModel::parentModelIndex() const { Q_D(const QSGVisualDataModel); @@ -783,6 +1084,33 @@ QVariant QSGVisualDataModel::parentModelIndex() const return QVariant::fromValue(QModelIndex()); } +/*! + \qmlproperty object QtQuick2::VisualDataModel::parts + + The \a parts property selects a VisualDataModel which creates + delegates from the part named. This is used in conjunction with + the \l Package element. + + For example, the code below selects a model which creates + delegates named \e list from a \l Package: + + \code + VisualDataModel { + id: visualModel + delegate: Package { + Item { Package.name: "list" } + } + model: myModel + } + + ListView { + width: 200; height:200 + model: visualModel.parts.list + } + \endcode + + \sa Package +*/ QString QSGVisualDataModel::part() const { Q_D(const QSGVisualDataModel); @@ -881,12 +1209,11 @@ QSGItem *QSGVisualDataModel::item(int index, const QByteArray &viewId, bool comp QDeclarativeContext *ccontext = d->m_context; if (!ccontext) ccontext = qmlContext(this); QDeclarativeContext *ctxt = new QDeclarativeContext(ccontext); - QSGVisualDataModelData *data = new QSGVisualDataModelData(index, this); - if ((!d->m_listModelInterface || !d->m_abstractItemModel) && d->m_listAccessor - && d->m_listAccessor->type() == QDeclarativeListAccessor::ListProperty) { - ctxt->setContextObject(d->m_listAccessor->at(index).value<QObject*>()); - ctxt = new QDeclarativeContext(ctxt, ctxt); + if (d->m_objectList) { + ctxt->setContextObject(d->m_listAccessor->at(index).value<QObject *>()); + ctxt = new QDeclarativeContext(ctxt); } + QSGVisualDataModelData *data = d->createModelData(index, this); ctxt->setContextProperty(QLatin1String("model"), data); ctxt->setContextObject(data); d->m_completePending = false; @@ -975,7 +1302,7 @@ QString QSGVisualDataModel::stringValue(int index, const QString &name) if (QObject *nobj = d->m_cache.item(index)) data = d->data(nobj); if (!data) { - data = new QSGVisualDataModelData(index, this); + data = d->createModelData(index, this); tempData = true; } @@ -1031,6 +1358,21 @@ void QSGVisualDataModel::_q_itemsChanged(int index, int count, } } + QVector<int> signalIndexes; + for (int i = 0; i < roles.count(); ++i) { + const int role = roles.at(i); + if (!changed && d->watchedRoleIds.contains(role)) + changed = true; + for (int propertyId = 0; propertyId < d->m_propertyData.count(); ++propertyId) { + if (d->m_propertyData.at(propertyId).role == role) + signalIndexes.append(propertyId + d->m_delegateDataType->signalOffset); + } + } + if (roles.isEmpty()) { + for (int propertyId = 0; propertyId < d->m_propertyData.count(); ++propertyId) + signalIndexes.append(propertyId + d->m_delegateDataType->signalOffset); + } + for (QHash<int,QSGVisualDataModelPrivate::ObjectRef>::ConstIterator iter = d->m_cache.begin(); iter != d->m_cache.end(); ++iter) { const int idx = iter.key(); @@ -1038,41 +1380,8 @@ void QSGVisualDataModel::_q_itemsChanged(int index, int count, if (idx >= index && idx < index+count) { QSGVisualDataModelPrivate::ObjectRef objRef = *iter; QSGVisualDataModelData *data = d->data(objRef.obj); - for (int roleIdx = 0; roleIdx < roles.count(); ++roleIdx) { - int role = roles.at(roleIdx); - if (!changed && !d->watchedRoleIds.isEmpty() && d->watchedRoleIds.contains(role)) - changed = true; - int propId = data->propForRole(role); - if (propId != -1) { - if (data->hasValue(propId)) { - if (d->m_listModelInterface) { - data->setValue(propId, d->m_listModelInterface->data(idx, role)); - } else if (d->m_abstractItemModel) { - QModelIndex index = d->m_abstractItemModel->index(idx, 0, d->m_root); - data->setValue(propId, d->m_abstractItemModel->data(index, role)); - } - } - } else { - QString roleName; - if (d->m_listModelInterface) - roleName = d->m_listModelInterface->toString(role); - else if (d->m_abstractItemModel) - roleName = QString::fromUtf8(d->m_abstractItemModel->roleNames().value(role)); - qmlInfo(this) << "Changing role not present in item: " << roleName; - } - } - if (d->m_modelDataPropId != -1) { - // Handle the modelData role we add if there is just one role. - if (data->hasValue(d->m_modelDataPropId)) { - int role = d->m_roles.at(0); - if (d->m_listModelInterface) { - data->setValue(d->m_modelDataPropId, d->m_listModelInterface->data(idx, role)); - } else if (d->m_abstractItemModel) { - QModelIndex index = d->m_abstractItemModel->index(idx, 0, d->m_root); - data->setValue(d->m_modelDataPropId, d->m_abstractItemModel->data(index, role)); - } - } - } + for (int i = 0; i < signalIndexes.count(); ++i) + QMetaObject::activate(data, signalIndexes.at(i), 0); } } if (changed) |