diff options
26 files changed, 762 insertions, 45 deletions
diff --git a/examples/quick/demos/rssnews/doc/src/rssnews.qdoc b/examples/quick/demos/rssnews/doc/src/rssnews.qdoc index 12c7c0d19b..b1a2d03cc7 100644 --- a/examples/quick/demos/rssnews/doc/src/rssnews.qdoc +++ b/examples/quick/demos/rssnews/doc/src/rssnews.qdoc @@ -29,13 +29,344 @@ \title Qt Quick Demo - RSS News \ingroup qtquickdemos \example demos/rssnews - \brief A QML RSS news reader. + \brief A QML RSS news reader that uses XmlListModel and XmlRole to download + XML data, ListModel and ListElement to create a category list, and ListView + to display the data. + \image qtquick-demo-rssnews-small.png - \e{RSS News} demonstrates various QML and \l{Qt Quick} features such as - loading XML data and displaying custom components. + \e{RSS News} demonstrates the following \l{Qt Quick} features: + + \list + \li Using custom types to create screens and screen controls. + \li Using list models and list elements to represent data. + \li Using XML list models to download XML data. + \li Using list views to display data. + \li Using the \l Component type to create a footer for the news item + list view. + \li Using the \l Image type to create a button for closing the app. + \endlist \include examples-run.qdocinc + \section1 Using Custom Types + + In the RSS News app, we use the following custom types that are each defined + in a separate .qml file: + + \list + \li \c BusyIndicator.qml + \li \c CategoryDelegate.qml + \li \c NewsDelegate.qml + \li \c RssFeeds.qml + \li \c ScrollBar.qml + \endlist + + To use the custom types, we add an import statement to the main QML file, + rssnews.qml that imports the folder called \c content where the types are + located: + + \quotefromfile demos/rssnews/rssnews.qml + \skipto content + \printuntil " + + \section1 Creating the Main Window + + In rssnews.qml, we use a \l{Rectangle} type with custom properties to create + the app main window: + + \printuntil isPortrait + + We will use the custom properties later for loading XML data and for + adjusting the screen layout depending on its orientation. + + \section1 Creating a Category List + + In rssnews.qml, we use the RssFeeds custom type that we specify in + RssFeeds.qml to create a list of feed categories: + + \skipto RssFeeds + \printuntil } + + In RssFeeds.qml, we use a ListModel type with a ListElement type to + create a category list where list elements represent feed categories: + + \quotefromfile demos/rssnews/content/RssFeeds.qml + \skipto ListModel + \printuntil /^\}/ + + List elements are defined like other QML types except that they contain a + collection of \e role definitions instead of properties. Roles both define + how the data is accessed and include the data itself. + + For each list element, we use the \c name role to specify the category name, + the \c feed role to specify the URL to load the data from, and the \c image + role to display an image for the category. + + In rssnews.qml, we use a ListView type to display the category list: + + \quotefromfile demos/rssnews/rssnews.qml + \skipto ListView + \printuntil } + \printuntil } + + To lay out the category list horizontally at the top of the window in + portrait orientation and vertically on the left side in landscape + orientation, we use the \c orientation property. Based on the orientation, + we bind either the width or the height of the list to a fixed value + (\c itemWidth). + + We use the \c anchors.top property to position the list view at the top of + the screen in both orientations. + + We use the \c model property to load XML data from the \c rssFeeds model, + and \c CategoryDelegate as the delegate to instantiate each item in the + list. + + \section1 Creating List Elements + + In CategoryDelegate.qml, we use the \l Rectangle type with custom properties + to create list elements: + + \quotefromfile demos/rssnews/content/CategoryDelegate.qml + \skipto Rectangle + \printuntil selected + + We set the \c selected property to the \c ListView.isCurrentItem attached + property to specify that \c selected is \c true if \c delegate is the + current item. + + We use the \l Image type \c source property to display the image, centered + in the delegate, specified for the list element by the \c image role in the + \c rssFeeds list model: + + \skipto Image + \printuntil } + + We use a \l Text type to add titles to list elements: + + \printuntil Behavior + \printuntil } + + We use the \c anchors property to position the title at the top of the list + element, with a 20-pixel margin. We use \c font properties to adjust font + size and text formatting. + + We use the \c color property to brighten the text and to scale it slightly + larger when the list item is the current item. By applying a \l Behavior to + the property, we animate the actions of selecting and deselecting list + items. + + We use a MouseArea type to download XML data when users tap a category list + element: + + \skipto MouseArea + \printuntil } + \printuntil } + + The \c anchors.fill property is set to \c delegate to enable users to tap + anywhere within the list element. + + We use the \c onClicked signal handler to load the XML data for the category + list. If the tapped category is already current, the \c reload() function + is called to reload the data. + + \section1 Downloading XML Data + + In rssnews.qml, we use an XmlListModel type as a data source for ListView + elements to display news items in the selected category: + + \quotefromfile demos/rssnews/rssnews.qml + \skipto XmlListModel { + \printuntil namespaceDeclarations + + We use the \c source property and the \c window.currentFeed custom property + to fetch news items for the selected category. + + The \c query property specifies that the XmlListModel generates a model item + for each \c <item> in the XML document. + + We use the XmlRole type to specify the model item attributes. Each model + item has the \c title, \c description, \c image, \c link, and \c pubDate + attributes that match the values of the corresponding \c <item> in the XML + document: + + \printuntil pubDate + \printuntil } + + We use the \c feedModel model in a ListView type to display the data: + + \skipuntil ScrollBar + \skipto ListView + \printuntil } + \printuntil } + + To list the news items below the category list in portrait orientation and + to its right in landscape orientation, we use the \c isPortrait custom + property to anchor the top of the news items list to the left of \c window + and bottom of \c categories in portrait orientation and to the right of + \c categories and bottom of \c window in landscape orientation. + + We use the \c anchors.bottom property to anchor the bottom of the list view + to the bottom of the window in both orientations. + + In portrait orientation, we clip the painting of the news items to the + bounding rectangle of the list view to avoid graphical artifacts when news + items are scrolled over other items. In landscape, this is not required, + because the list spans the entire screen vertically. + + We use the \c model property to load XML data from the \c feedModel model, + and use \c NewsDelegate as the delegate to instantiate each item in the + list. + + In NewsDelegate.qml, we use a \l Column type to lay out the XML data: + + \quotefromfile demos/rssnews/content/NewsDelegate.qml + \skipto Column + \printuntil spacing + + Within the column, we use a \l Row and another column to position images and + title text: + + \skipto Row + \printuntil font.bold + \printuntil } + \printuntil } + + We generate a textual representation of how long ago the item was posted + using the \c timeSinceEvent() JavaScript function: + + \printuntil } + \printuntil } + + We use the \c onLinkActivated signal handler to open the URL in an external + browser when users select the link. + + \section1 Providing Feedback to Users + + In CategoryDelegate.qml, we use the \c BusyIndicator custom type to indicate + activity while the XML data is being loaded: + + \quotefromfile demos/rssnews/content/CategoryDelegate.qml + \skipto BusyIndicator + \printuntil } + + We use the \c scale property to reduce the indicator size to \c 0.8. We bind + the \c visible property to the \c isCurrentItem attached property of the + \c delegate list view and \c loading property of the main window to display + the indicator image when a category list item is the current item and XML + data is being loaded. + + We define the \c BusyIndicator type in \c BusyIndicator.qml. We use an + \l Image type to display an image and apply a NumberAnimation to its + \c rotation property to rotate the image in an infinite loop: + + \quotefromfile demos/rssnews/content/BusyIndicator.qml + \skipto Image + \printuntil } + \printuntil } + + In your apps, you can also use the BusyIndicator type from the + \l {Qt Quick Controls} module. + + \section1 Creating Scroll Bars + + In rssnews.qml, we use our own custom \c ScrollBar type to create scroll + bars in the category and news item list views. In your apps, you can also + use the ScrollView type from the \l {Qt Quick Controls} module. + + First, we create a scroll bar in the category list view. We bind the + \c orientation property to the \c isPortrait property and to the + \c Horizontal value of the \c Qt::Orientation enum type to display a + horizontal scroll bar in portrait orientation and to the \c Vertical value + to display a vertical scroll bar in landscape orientation: + + \quotefromfile demos/rssnews/rssnews.qml + \skipto ScrollBar + \printuntil } + + Same as with the \c categories list view, we adjust the width and height of + the scroll bar based on the \c isPortrait property. + + We use the \c scrollArea property to display the scroll bar in the + \c categories list view. + + We use the \c anchors.right property to anchor the scroll bar to the right + side of the category list. + + \skipto ScrollBar + \printuntil } + + Second, we create another scroll bar in the news item list view. We want a + vertical scroll bar to appear on the right side of the view regardless of + screen orientation, so we can set the \c width property to \c 8 and bind the + \c anchors.right property to the \c window.right property. We use the + \c anchors.top property to anchor the scroll bar top to the bottom of the + category list in portrait orientation and to the top of the news item list + in landscape orientation. We use the \c anchors.bottom property to anchor + the scroll bar bottom to the list view bottom in both orientations. + + We define the \c ScrollBar type in \c ScrollBar.qml. We use an \l Item type + with custom properties to create a container for the scroll bar: + + \quotefromfile demos/rssnews/content/ScrollBar.qml + \skipto Item + \printuntil opacity + + We use a BorderImage type to display the scroll bar thumb at the x and y + position that we calculate by using the \c position() function: + + \skipto BorderImage + \printuntil height + \printuntil } + + We use the \c size function to calculate the thumb width and height + depending on the screen orientation. + + We use \c states to make the scroll bar visible when the users move the + scroll area: + + \printuntil } + \printuntil } + + We use \c transitions to apply a NumberAnimation to the \c "opacity" + property when the state changes from "visible" to the default state: + + \printuntil /^\}/ + + \section1 Creating Footers + + In rssnews.qml, we use a \l Component type with a \l Rectangle type to + create a footer for the news list view: + + \quotefromfile demos/rssnews/rssnews.qml + \skipto Component + \printuntil } + \printuntil } + \printuntil } + + We bind the \c width of the footer to the width of the component and the + \c height to the of close button to align them when no news items are + displayed. + + \section1 Creating Buttons + + In rssnews.qml, we use an \l Image type to create a simple push button that + users can tap to close the app: + + \printuntil Qt.quit() + \printuntil } + \printuntil } + \printuntil } + + We use \c anchors to position the close button in the top right corner of + the news list view, with 4-pixel margins. Because the close button overlaps + the category list in portrait orientation, we animate the \c opacity + property to make the button almost fully transparent when users are + scrolling the category list. + + We use the \c onClicked signal handler within a MouseArea to call the + \c quit() function when users select the close button. + \sa {QML Applications} */ diff --git a/src/qml/compiler/qv4ssa.cpp b/src/qml/compiler/qv4ssa.cpp index 673d4e5db0..8488d6eb2b 100644 --- a/src/qml/compiler/qv4ssa.cpp +++ b/src/qml/compiler/qv4ssa.cpp @@ -141,13 +141,31 @@ public: if (set.blockNumbers) numberIt = set.blockNumbers->begin(); else - flagIt = std::distance(set.blockFlags->begin(), - std::find(set.blockFlags->begin(), - set.blockFlags->end(), - true)); + findNextWithFlags(0); } } + void findNextWithFlags(size_t start) + { + flagIt = std::distance(set.blockFlags->begin(), + std::find(set.blockFlags->begin() + start, + set.blockFlags->end(), + true)); + + // The ++operator of std::vector<bool>::iterator in libc++ has a bug when using it on an + // iterator pointing to the last element. It will not be set to ::end(), but beyond + // that. (It will be set to the first multiple of the native word size that is bigger + // than size().) + // + // See http://llvm.org/bugs/show_bug.cgi?id=19663 + // + // As we use the size to for our end() iterator, take the minimum of the size and the + // distance for the flagIt: + flagIt = qMin(flagIt, set.blockFlags->size()); + + Q_ASSERT(flagIt <= set.blockFlags->size()); + } + public: BasicBlock *operator*() const { @@ -175,17 +193,10 @@ public: const_iterator &operator++() { - if (set.blockNumbers) { - if (numberIt != set.blockNumbers->end()) - ++numberIt; - } else if (flagIt < set.blockFlags->size()) { - flagIt = std::distance(set.blockFlags->begin(), - std::find(set.blockFlags->begin() + flagIt + 1, - set.blockFlags->end(), - true)); - if (flagIt > set.blockFlags->size()) - flagIt = set.blockFlags->size(); - } + if (set.blockNumbers) + ++numberIt; + else + findNextWithFlags(flagIt + 1); return *this; } diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp index 838c541900..abe8a44065 100644 --- a/src/qml/jsruntime/qv4arrayobject.cpp +++ b/src/qml/jsruntime/qv4arrayobject.cpp @@ -366,7 +366,7 @@ ReturnedValue ArrayPrototype::method_shift(CallContext *ctx) ScopedValue result(scope); - if (!instance->protoHasArray() && !instance->arrayData()->hasAttributes() && instance->arrayData()->length() <= len) { + if (!instance->protoHasArray() && !instance->arrayData()->hasAttributes() && instance->arrayData()->length() <= len && instance->arrayData()->type() != ArrayData::Custom) { result = instance->arrayData()->vtable()->pop_front(instance.getPointer()); } else { result = instance->getIndexed(0); @@ -545,7 +545,7 @@ ReturnedValue ArrayPrototype::method_unshift(CallContext *ctx) uint len = instance->getLength(); - if (!instance->protoHasArray() && !instance->arrayData()->hasAttributes() && instance->arrayData()->length() <= len) { + if (!instance->protoHasArray() && !instance->arrayData()->hasAttributes() && instance->arrayData()->length() <= len && instance->arrayData()->type() != ArrayData::Custom) { instance->arrayData()->vtable()->push_front(instance.getPointer(), ctx->d()->callData->args, ctx->d()->callData->argc); } else { ScopedValue v(scope); diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index f2cfc3efd2..7be518916d 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -851,7 +851,11 @@ void ExecutionEngine::markObjects() ExecutionContext *c = currentContext(); while (c) { - c->mark(this); + Q_ASSERT(c->inUse()); + if (!c->markBit()) { + c->d()->markBit = 1; + c->markObjects(c, this); + } c = c->d()->parent; } diff --git a/src/qml/qml/qqmlcomponent.cpp b/src/qml/qml/qqmlcomponent.cpp index 40b6a34f07..616f54d174 100644 --- a/src/qml/qml/qqmlcomponent.cpp +++ b/src/qml/qml/qqmlcomponent.cpp @@ -780,6 +780,11 @@ QQmlComponent::QQmlComponent(QQmlComponentPrivate &dd, QObject *parent) The ownership of the returned object instance is transferred to the caller. + If the object being created from this component is a visual item, it must + have a visual parent, which can be set by calling + QQuickItem::setParentItem(). See \l {Concepts - Visual Parent in Qt Quick} + for more details. + \sa QQmlEngine::ObjectOwnership */ QObject *QQmlComponent::create(QQmlContext *context) diff --git a/src/qmltest/quicktestresult.cpp b/src/qmltest/quicktestresult.cpp index ab37be3c95..3329fd72e6 100644 --- a/src/qmltest/quicktestresult.cpp +++ b/src/qmltest/quicktestresult.cpp @@ -685,7 +685,7 @@ void QuickTestResult::stopBenchmark() QObject *QuickTestResult::grabImage(QQuickItem *item) { - if (item) { + if (item && item->window()) { QQuickWindow *window = item->window(); QImage grabbed = window->grabWindow(); QRectF rf(item->x(), item->y(), item->width(), item->height()); diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 45566f2e89..ee71ea8a76 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -327,7 +327,7 @@ bool QQuickFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExt maxDistance = qAbs(maxExtent - data.move.value()); data.flickTarget = maxExtent; } - if (maxDistance > 0) { + if (maxDistance > 0 || boundsBehavior == QQuickFlickable::DragAndOvershootBounds) { qreal v = velocity; if (maxVelocity != -1 && maxVelocity < qAbs(v)) { if (v < 0) @@ -1541,6 +1541,10 @@ void QQuickFlickable::geometryChanged(const QRectF &newGeometry, void QQuickFlickable::flick(qreal xVelocity, qreal yVelocity) { Q_D(QQuickFlickable); + d->hData.reset(); + d->vData.reset(); + d->hData.velocity = xVelocity; + d->vData.velocity = yVelocity; bool flickedX = d->flickX(xVelocity); bool flickedY = d->flickY(yVelocity); d->flickingStarted(flickedX, flickedY); diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp index 77ff275619..5b928310cd 100644 --- a/src/quick/items/qquickgridview.cpp +++ b/src/quick/items/qquickgridview.cpp @@ -1616,7 +1616,7 @@ void QQuickGridView::setFlow(Flow flow) } setContentX(0); setContentY(0); - d->regenerate(); + d->regenerate(true); emit flowChanged(); } } diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index ef696dae96..2a686b4342 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -1783,18 +1783,20 @@ void QQuickItemViewPrivate::refill(qreal from, qreal to) emit q->countChanged(); } -void QQuickItemViewPrivate::regenerate() +void QQuickItemViewPrivate::regenerate(bool orientationChanged) { Q_Q(QQuickItemView); if (q->isComponentComplete()) { currentChanges.reset(); - delete header; - header = 0; - delete footer; - footer = 0; + if (orientationChanged) { + delete header; + header = 0; + delete footer; + footer = 0; + } + clear(); updateHeader(); updateFooter(); - clear(); updateViewport(); setPosition(contentStartOffset()); refill(); diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h index 3f0f3b3646..7c8656cced 100644 --- a/src/quick/items/qquickitemview_p_p.h +++ b/src/quick/items/qquickitemview_p_p.h @@ -184,7 +184,7 @@ public: virtual void clear(); virtual void updateViewport(); - void regenerate(); + void regenerate(bool orientationChanged=false); void layout(); virtual void animationFinished(QAbstractAnimationJob *); void refill(); diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index a1388e3512..e8043804fb 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -2072,7 +2072,7 @@ void QQuickListView::setOrientation(QQuickListView::Orientation orientation) setFlickableDirection(HorizontalFlick); setContentY(0); } - d->regenerate(); + d->regenerate(true); emit orientationChanged(); } } diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp index de5ed99640..efce244f7d 100644 --- a/src/quick/items/qquickpathview.cpp +++ b/src/quick/items/qquickpathview.cpp @@ -104,6 +104,7 @@ QQuickPathViewPrivate::QQuickPathViewPrivate() , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true) , autoHighlight(true), highlightUp(false), layoutScheduled(false) , moving(false), flicking(false), dragging(false), inRequest(false), delegateValidated(false) + , inRefill(false) , dragMargin(0), deceleration(100), maximumFlickVelocity(QML_FLICK_DEFAULTMAXVELOCITY) , moveOffset(this, &QQuickPathViewPrivate::setAdjustedOffset), flickDuration(0) , firstIndex(-1), pathItems(-1), requestedIndex(-1), cacheSize(0), requestedZ(0) @@ -1873,11 +1874,18 @@ void QQuickPathView::refill() { Q_D(QQuickPathView); + if (d->inRefill) { + d->scheduleLayout(); + return; + } + d->layoutScheduled = false; if (!d->isValid() || !isComponentComplete()) return; + d->inRefill = true; + bool currentVisible = false; int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount); @@ -2010,6 +2018,8 @@ void QQuickPathView::refill() } while (d->itemCache.count()) d->releaseItem(d->itemCache.takeLast()); + + d->inRefill = false; } void QQuickPathView::modelUpdated(const QQmlChangeSet &changeSet, bool reset) diff --git a/src/quick/items/qquickpathview_p_p.h b/src/quick/items/qquickpathview_p_p.h index 813f472072..e21f3757e6 100644 --- a/src/quick/items/qquickpathview_p_p.h +++ b/src/quick/items/qquickpathview_p_p.h @@ -152,6 +152,7 @@ public: bool requestedOnPath : 1; bool inRequest : 1; bool delegateValidated : 1; + bool inRefill : 1; QElapsedTimer timer; qint64 lastPosTime; QPointF lastPos; diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index 7a4027890c..71265689e9 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -711,7 +711,8 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) } bool shouldUseDesignMetrics = renderType != QQuickText::NativeRendering; - + if (!visibleImgTags.isEmpty()) + visibleImgTags.clear(); layout.setCacheEnabled(true); QTextOption textOption = layout.textOption(); if (textOption.alignment() != q->effectiveHAlign() @@ -940,9 +941,10 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) maxHeight = q->heightValid() ? q->height() : FLT_MAX; // If the width of the item has changed and it's possible the result of wrapping, - // eliding, or scaling has changed do another layout. + // eliding, scaling has changed, or the text is not left aligned do another layout. if ((lineWidth < qMin(oldWidth, naturalWidth) || (widthExceeded && lineWidth > oldWidth)) - && (singlelineElide || multilineElide || canWrap || horizontalFit)) { + && (singlelineElide || multilineElide || canWrap || horizontalFit + || q->effectiveHAlign() != QQuickText::AlignLeft)) { widthExceeded = false; heightExceeded = false; continue; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index b042e30483..fdaef6df8e 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -1777,8 +1777,12 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * d->updateType = QQuickTextEditPrivate::UpdateNone; - if (!oldNode) // If we had any text node references, they were deleted along with the root node + if (!oldNode) { + // If we had any QQuickTextNode node references, they were deleted along with the root node + // But here we must delete the Node structures in textNodeMap + qDeleteAll(d->textNodeMap); d->textNodeMap.clear(); + } RootNode *rootNode = static_cast<RootNode *>(oldNode); TextNodeIterator nodeIterator = d->textNodeMap.begin(); diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index 25681c89f6..0cf7ee2850 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -111,6 +111,11 @@ public: { } + ~QQuickTextEditPrivate() + { + qDeleteAll(textNodeMap); + } + static QQuickTextEditPrivate *get(QQuickTextEdit *item) { return static_cast<QQuickTextEditPrivate *>(QObjectPrivate::get(item)); } diff --git a/src/quick/items/qquicktextnode.cpp b/src/quick/items/qquicktextnode.cpp index da0d9cd714..02e321dfba 100644 --- a/src/quick/items/qquicktextnode.cpp +++ b/src/quick/items/qquicktextnode.cpp @@ -303,6 +303,8 @@ void QQuickTextNode::deleteContent() while (firstChild() != 0) delete firstChild(); m_cursorNode = 0; + qDeleteAll(m_textures); + m_textures.clear(); } #if 0 diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index a7659e4871..37525e6151 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -3335,7 +3335,7 @@ QSGTexture *QQuickWindow::createTextureFromImage(const QImage &image) const \warning The returned texture is not memory managed by the scene graph and must be explicitly deleted by the caller on the rendering thread. - This is acheived by deleting the texture from a QSGNode destructor + This is achieved by deleting the texture from a QSGNode destructor or by using deleteLater() in the case where the texture already has affinity to the rendering thread. diff --git a/src/quick/scenegraph/util/qsgatlastexture.cpp b/src/quick/scenegraph/util/qsgatlastexture.cpp index ceeaa977d7..c92d79dc2a 100644 --- a/src/quick/scenegraph/util/qsgatlastexture.cpp +++ b/src/quick/scenegraph/util/qsgatlastexture.cpp @@ -270,6 +270,9 @@ void Atlas::uploadBgra(Texture *texture) const QRect &r = texture->atlasSubRect(); QImage image = texture->image(); + if (image.isNull()) + return; + if (image.format() != QImage::Format_ARGB32_Premultiplied && image.format() != QImage::Format_RGB32) { image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); diff --git a/tests/auto/qml/qqmlecmascript/data/sequenceConversion.array.qml b/tests/auto/qml/qqmlecmascript/data/sequenceConversion.array.qml index 8847055a70..5103168fd3 100644 --- a/tests/auto/qml/qqmlecmascript/data/sequenceConversion.array.qml +++ b/tests/auto/qml/qqmlecmascript/data/sequenceConversion.array.qml @@ -165,6 +165,43 @@ Item { } } } + + // unshift + msco.stringListProperty = [ "one", "two" ] + var unshiftedVal = msco.stringListProperty.unshift("zero") + expected = [ "zero", "one", "two" ] + if (msco.stringListProperty.toString() != expected.toString()) success = false; + expected = 3 + if (msco.stringListProperty.length != expected) success = false + msco.stringListProperty = [ ] + msco.stringListProperty.unshift("zero", "one") + expected = [ "zero", "one" ] + if (msco.stringListProperty.toString() != expected.toString()) success = false; + expected = 2 + if (msco.stringListProperty.length != expected) success = false + + // shift + msco.stringListProperty = [ "one", "two", "three" ] + var shiftVal = msco.stringListProperty.shift() + expected = [ "two", "three" ] + if (msco.stringListProperty.toString() != expected.toString()) success = false; + expected = "one" + if (shiftVal != expected) success = false + shiftVal = msco.stringListProperty.shift() + expected = [ "three" ] + if (msco.stringListProperty.toString() != expected.toString()) success = false; + expected = "two" + if (shiftVal != expected) success = false + shiftVal = msco.stringListProperty.shift() + expected = 0 + if (msco.stringListProperty.length != expected) success = false; + expected = "three" + if (shiftVal != expected) success = false + shiftVal = msco.stringListProperty.shift() + expected = 0 + if (msco.stringListProperty.length != expected) success = false; + expected = undefined + if (shiftVal != expected) success = false } property variant variantList: [ 1, 2, 3, 4, 5 ]; diff --git a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index 863fb69b84..a350074b42 100644 --- a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -3613,12 +3613,13 @@ void tst_QQuickGridView::resetModel_headerFooter() model.reset(); - header = findItem<QQuickItem>(contentItem, "header"); - QVERIFY(header); + // A reset should not force a new header or footer to be created. + QQuickItem *newHeader = findItem<QQuickItem>(contentItem, "header"); + QVERIFY(newHeader == header); QCOMPARE(header->y(), -header->height()); - footer = findItem<QQuickItem>(contentItem, "footer"); - QVERIFY(footer); + QQuickItem *newFooter = findItem<QQuickItem>(contentItem, "footer"); + QVERIFY(newFooter == footer); QCOMPARE(footer->y(), 60.*2); delete window; diff --git a/tests/auto/quick/qquicklistview/data/simplelistview.qml b/tests/auto/quick/qquicklistview/data/simplelistview.qml new file mode 100644 index 0000000000..56a96150c5 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/simplelistview.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 + +ListView { + width: 400 + height: 400 + model: 100 + delegate: Rectangle { + height: 40; width: 400 + color: index % 2 ? "lightsteelblue" : "lightgray" + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index b909d14301..c8ab62cbbc 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -231,6 +231,9 @@ private slots: void roundingErrors(); void roundingErrors_data(); + void QTBUG_38209(); + void programmaticFlickAtBounds(); + private: template <class T> void items(const QUrl &source); template <class T> void changed(const QUrl &source); @@ -4081,12 +4084,13 @@ void tst_QQuickListView::resetModel_headerFooter() model.reset(); - header = findItem<QQuickItem>(contentItem, "header"); - QVERIFY(header); + // A reset should not force a new header or footer to be created. + QQuickItem *newHeader = findItem<QQuickItem>(contentItem, "header"); + QVERIFY(newHeader == header); QCOMPARE(header->y(), -header->height()); - footer = findItem<QQuickItem>(contentItem, "footer"); - QVERIFY(footer); + QQuickItem *newFooter = findItem<QQuickItem>(contentItem, "footer"); + QVERIFY(newFooter == footer); QCOMPARE(footer->y(), 30.*4); delete window; @@ -7362,6 +7366,58 @@ void tst_QQuickListView::roundingErrors_data() QTest::newRow("pixelAligned=false") << false; } +void tst_QQuickListView::QTBUG_38209() +{ + QScopedPointer<QQuickView> window(createView()); + window->setSource(testFileUrl("simplelistview.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject()); + QVERIFY(listview); + + // simulate mouse flick + flick(window.data(), QPoint(200, 200), QPoint(200, 50), 100); + QTRY_VERIFY(listview->isMoving() == false); + qreal contentY = listview->contentY(); + + // flick down + listview->flick(0, 1000); + + // ensure we move more than just a couple pixels + QTRY_VERIFY(contentY - listview->contentY() > qreal(100.0)); +} + +void tst_QQuickListView::programmaticFlickAtBounds() +{ + QScopedPointer<QQuickView> window(createView()); + window->setSource(testFileUrl("simplelistview.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject()); + QVERIFY(listview); + QSignalSpy spy(listview, SIGNAL(contentYChanged())); + + // flick down + listview->flick(0, 1000); + + // verify that there is movement beyond bounds + QVERIFY(spy.wait(100)); + + // reset, and test with StopAtBounds + listview->cancelFlick(); + listview->returnToBounds(); + QTRY_COMPARE(listview->contentY(), qreal(0.0)); + listview->setBoundsBehavior(QQuickFlickable::StopAtBounds); + + // flick down + listview->flick(0, 1000); + + // verify that there is no movement beyond bounds + QVERIFY(!spy.wait(100)); +} + QTEST_MAIN(tst_QQuickListView) #include "tst_qquicklistview.moc" diff --git a/tests/auto/quick/qquickpathview/data/changePathDuringRefill.qml b/tests/auto/quick/qquickpathview/data/changePathDuringRefill.qml new file mode 100644 index 0000000000..f02ab35faf --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/changePathDuringRefill.qml @@ -0,0 +1,45 @@ +import QtQuick 2.3 + +PathView { + id: view + objectName: "pathView" + width: 100 + height: delegateHeight * pathItemCount + model: ["A", "B", "C"] + pathItemCount: 3 + anchors.centerIn: parent + + property int delegateHeight: 0 + + activeFocusOnTab: true + Keys.onDownPressed: view.incrementCurrentIndex() + Keys.onUpPressed: view.decrementCurrentIndex() + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + + delegate: Rectangle { + objectName: "delegate" + modelData + width: view.width + height: textItem.height + border.color: "red" + + onHeightChanged: { + if (index == 0) + view.delegateHeight = textItem.height + } + + Text { + id: textItem + text: modelData + } + } + + path: Path { + startX: view.width / 2 + startY: 0 + PathLine { + x: view.width / 2 + y: view.pathItemCount * view.delegateHeight + } + } +} diff --git a/tests/auto/quick/qquickpathview/data/incorrectSteal.qml b/tests/auto/quick/qquickpathview/data/incorrectSteal.qml new file mode 100644 index 0000000000..bcd6923b73 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/incorrectSteal.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Flickable { + objectName: "flickable" + width: 400; height: 400 + + contentHeight: height + contentWidth: width * 3 + contentX: 400 + + Row { + Rectangle { width: 400; height: 400; color: "green" } + Rectangle { + width: 400; height: 400; color: "blue" + clip: true + + PathView { + id: pathView + objectName: "pathView" + width: parent.width + height: 200 + anchors.verticalCenter: parent.verticalCenter + + dragMargin: 400 + pathItemCount: 6 + + model: 10 + path: Path { + startX: -pathView.width / 2 + startY: pathView.height / 2 + PathLine { x: pathView.width + pathView.width / 2; y: pathView.height / 2 } + } + + delegate: Rectangle { + width: 100; height: 200 + color: "purple" + MouseArea { + anchors.fill: parent + } + } + } + } + Rectangle { width: 400; height: 400; color: "yellow" } + } +} diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 579cb954aa..1960775ad3 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -47,6 +47,7 @@ #include <QtQml/qqmlexpression.h> #include <QtQml/qqmlincubator.h> #include <QtQuick/private/qquickpathview_p.h> +#include <QtQuick/private/qquickflickable_p.h> #include <QtQuick/private/qquickpath_p.h> #include <QtQuick/private/qquicktext_p.h> #include <QtQuick/private/qquickrectangle_p.h> @@ -142,6 +143,8 @@ private slots: void indexAt_itemAt(); void indexAt_itemAt_data(); void cacheItemCount(); + void incorrectSteal(); + void changePathDuringRefill(); }; class TestObject : public QObject @@ -2120,7 +2123,102 @@ void tst_QQuickPathView::cacheItemCount() bool b = true; controller.incubateWhile(&b); } +} + +static void testCurrentIndexChange(QQuickPathView *pathView, const QStringList &objectNamesInOrder) +{ + for (int visualIndex = 0; visualIndex < objectNamesInOrder.size() - 1; ++visualIndex) { + QQuickRectangle *delegate = findItem<QQuickRectangle>(pathView, objectNamesInOrder.at(visualIndex)); + QVERIFY(delegate); + + QQuickRectangle *nextDelegate = findItem<QQuickRectangle>(pathView, objectNamesInOrder.at(visualIndex + 1)); + QVERIFY(nextDelegate); + + QVERIFY(delegate->y() < nextDelegate->y()); + } +} + +void tst_QQuickPathView::changePathDuringRefill() +{ + QScopedPointer<QQuickView> window(createView()); + + window->setSource(testFileUrl("changePathDuringRefill.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QCOMPARE(window.data(), qGuiApp->focusWindow()); + + QQuickPathView *pathView = qobject_cast<QQuickPathView*>(window->rootObject()); + QVERIFY(pathView != 0); + + testCurrentIndexChange(pathView, QStringList() << "delegateC" << "delegateA" << "delegateB"); + + pathView->incrementCurrentIndex(); + /* + Decrementing moves delegateA down, resulting in an offset of 1, + so incrementing will move it up, resulting in an offset of 2: + + delegateC delegateA + delegateA => delegateB + delegateB delegateC + */ + QTRY_COMPARE(pathView->offset(), 2.0); + testCurrentIndexChange(pathView, QStringList() << "delegateA" << "delegateB" << "delegateC"); +} + +void tst_QQuickPathView::incorrectSteal() +{ + QScopedPointer<QQuickView> window(createView()); + QQuickViewTestUtil::moveMouseAway(window.data()); + window->setSource(testFileUrl("incorrectSteal.qml")); + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + QCOMPARE(window.data(), qGuiApp->focusWindow()); + + QQuickPathView *pathview = findItem<QQuickPathView>(window->rootObject(), "pathView"); + QVERIFY(pathview != 0); + + QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(window->rootObject()); + QVERIFY(flickable != 0); + + QSignalSpy movingSpy(pathview, SIGNAL(movingChanged())); + QSignalSpy moveStartedSpy(pathview, SIGNAL(movementStarted())); + QSignalSpy moveEndedSpy(pathview, SIGNAL(movementEnded())); + + QSignalSpy fflickingSpy(flickable, SIGNAL(flickingChanged())); + QSignalSpy fflickStartedSpy(flickable, SIGNAL(flickStarted())); + QSignalSpy fflickEndedSpy(flickable, SIGNAL(flickEnded())); + + int waitInterval = 5; + QTest::mousePress(window.data(), Qt::LeftButton, 0, QPoint(23,218)); + + QTest::mouseMove(window.data(), QPoint(25,218), waitInterval); + QTest::mouseMove(window.data(), QPoint(26,218), waitInterval); + QTest::mouseMove(window.data(), QPoint(28,219), waitInterval); + QTest::mouseMove(window.data(), QPoint(31,219), waitInterval); + QTest::mouseMove(window.data(), QPoint(39,219), waitInterval); + + // first move beyond threshold does not trigger drag + QVERIFY(!pathview->isMoving()); + QVERIFY(!pathview->isDragging()); + QCOMPARE(movingSpy.count(), 0); + QCOMPARE(moveStartedSpy.count(), 0); + QCOMPARE(moveEndedSpy.count(), 0); + QCOMPARE(fflickingSpy.count(), 0); + QCOMPARE(fflickStartedSpy.count(), 0); + QCOMPARE(fflickEndedSpy.count(), 0); + + // no further moves after the initial move beyond threshold + QTest::mouseRelease(window.data(), Qt::LeftButton, 0, QPoint(53,219)); + QTRY_COMPARE(movingSpy.count(), 2); + QTRY_COMPARE(moveEndedSpy.count(), 1); + QCOMPARE(moveStartedSpy.count(), 1); + // Flickable should not handle this + QEXPECT_FAIL("", "QTBUG-37859", Abort); + QCOMPARE(fflickingSpy.count(), 0); + QCOMPARE(fflickStartedSpy.count(), 0); + QCOMPARE(fflickEndedSpy.count(), 0); } QTEST_MAIN(tst_QQuickPathView) |