diff options
Diffstat (limited to 'src/declarative/items')
103 files changed, 40535 insertions, 0 deletions
diff --git a/src/declarative/items/checksync.pl b/src/declarative/items/checksync.pl new file mode 100755 index 0000000000..26288bf1f4 --- /dev/null +++ b/src/declarative/items/checksync.pl @@ -0,0 +1,108 @@ +#!/usr/bin/perl +############################################################################# +## +## Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +## All rights reserved. +## Contact: Nokia Corporation (qt-info@nokia.com) +## +## This file is part of the Declarative 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. +## +## In addition, as a special exception, Nokia gives you certain additional +## 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. +## +## +## +## +## +## +## +## +## $QT_END_LICENSE$ +## +############################################################################# + +use strict; +use warnings; + +die "Usage: $0 <QML directory>" if (@ARGV != 1); + +my @excludes; +open (SYNCEXCLUDES, "<", "syncexcludes"); +while (<SYNCEXCLUDES>) { + if (/^([a-zA-Z0-9\._]+)/) { + my $exclude = $1; + push (@excludes, $exclude); + } +} + +my $portdir = "."; +my $qmldir = $ARGV[0]; + +opendir (PORTDIR, $portdir) or die "Cannot open port directory"; +opendir (QMLDIR, $qmldir) or die "Cannot open QML directory"; + +my @portfiles = readdir(PORTDIR); +my @qmlfiles = readdir(QMLDIR); + +closedir(PORTDIR); +closedir(QMLDIR); + +foreach my $qmlfile (@qmlfiles) { + if ($qmlfile =~ /^qdeclarative.*\.cpp$/ or $qmlfile =~ /qdeclarative.*\.h$/) { + + if (grep { $_ eq $qmlfile} @excludes) { + next; + } + + my $portfile = $qmlfile; + $portfile =~ s/^qdeclarative/qsg/; + + if (grep { $_ eq $portfile} @portfiles) { + + open (PORTFILE, "<", "$portdir/$portfile") or die("Cannot open $portdir/$portfile for reading"); + + my $firstline = <PORTFILE>; + + close (PORTFILE); + + if ($firstline and $firstline =~ /^\/\/ Commit: ([a-z0-9]+)/) { + my $sha1 = $1; + my $commitSha1 = ""; + + my $output = `cd $qmldir; git log $qmlfile | head -n 1`; + if ($output =~ /commit ([a-z0-9]+)/) { + $commitSha1 = $1; + } + + if ($commitSha1 eq $sha1) { + print ("$portfile: OK\n"); + } else { + print ("$portfile: OUT OF DATE\n"); + } + } else { + print ("$portfile: OUT OF DATE\n"); + } + } else { + print ("$portfile: MISSING\n"); + } + } +} diff --git a/src/declarative/items/items.pri b/src/declarative/items/items.pri new file mode 100644 index 0000000000..3dbb4fa910 --- /dev/null +++ b/src/declarative/items/items.pri @@ -0,0 +1,109 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qsgevents_p_p.h \ + $$PWD/qsgitemchangelistener_p.h \ + $$PWD/qsganchors_p.h \ + $$PWD/qsganchors_p_p.h \ + $$PWD/qsgitem.h \ + $$PWD/qsgitem_p.h \ + $$PWD/qsgrectangle_p.h \ + $$PWD/qsgrectangle_p_p.h \ + $$PWD/qsgcanvas.h \ + $$PWD/qsgcanvas_p.h \ + $$PWD/qsgfocusscope_p.h \ + $$PWD/qsgitemsmodule_p.h \ + $$PWD/qsgpainteditem.h \ + $$PWD/qsgpainteditem_p.h \ + $$PWD/qsgtext_p.h \ + $$PWD/qsgtext_p_p.h \ + $$PWD/qsgtextnode_p.h \ + $$PWD/qsgtextinput_p.h \ + $$PWD/qsgtextinput_p_p.h \ + $$PWD/qsgtextedit_p.h \ + $$PWD/qsgtextedit_p_p.h \ + $$PWD/qsgimagebase_p.h \ + $$PWD/qsgimagebase_p_p.h \ + $$PWD/qsgimage_p.h \ + $$PWD/qsgimage_p_p.h \ + $$PWD/qsgborderimage_p.h \ + $$PWD/qsgborderimage_p_p.h \ + $$PWD/qsgninepatchnode_p.h \ + $$PWD/qsgscalegrid_p_p.h \ + $$PWD/qsgmousearea_p.h \ + $$PWD/qsgmousearea_p_p.h \ + $$PWD/qsgpincharea_p.h \ + $$PWD/qsgpincharea_p_p.h \ + $$PWD/qsgflickable_p.h \ + $$PWD/qsgflickable_p_p.h \ + $$PWD/qsglistview_p.h \ + $$PWD/qsgvisualitemmodel_p.h \ + $$PWD/qsgrepeater_p.h \ + $$PWD/qsgrepeater_p_p.h \ + $$PWD/qsggridview_p.h \ + $$PWD/qsgpathview_p.h \ + $$PWD/qsgpathview_p_p.h \ + $$PWD/qsgpositioners_p.h \ + $$PWD/qsgpositioners_p_p.h \ + $$PWD/qsgloader_p.h \ + $$PWD/qsgloader_p_p.h \ + $$PWD/qsganimatedimage_p.h \ + $$PWD/qsganimatedimage_p_p.h \ + $$PWD/qsgflipable_p.h \ + $$PWD/qsgtranslate_p.h \ + $$PWD/qsgclipnode_p.h \ + $$PWD/qsgview.h \ + $$PWD/qsganimation_p.h \ + $$PWD/qsganimation_p_p.h \ + $$PWD/qsgstateoperations_p.h \ + $$PWD/qsgimplicitsizeitem_p.h \ + $$PWD/qsgimplicitsizeitem_p_p.h \ + +SOURCES += \ + $$PWD/qsgevents.cpp \ + $$PWD/qsganchors.cpp \ + $$PWD/qsgitem.cpp \ + $$PWD/qsgrectangle.cpp \ + $$PWD/qsgcanvas.cpp \ + $$PWD/qsgfocusscope.cpp \ + $$PWD/qsgitemsmodule.cpp \ + $$PWD/qsgpainteditem.cpp \ + $$PWD/qsgtext.cpp \ + $$PWD/qsgtextnode.cpp \ + $$PWD/qsgtextinput.cpp \ + $$PWD/qsgtextedit.cpp \ + $$PWD/qsgimagebase.cpp \ + $$PWD/qsgimage.cpp \ + $$PWD/qsgborderimage.cpp \ + $$PWD/qsgninepatchnode.cpp \ + $$PWD/qsgscalegrid.cpp \ + $$PWD/qsgmousearea.cpp \ + $$PWD/qsgpincharea.cpp \ + $$PWD/qsgflickable.cpp \ + $$PWD/qsglistview.cpp \ + $$PWD/qsgvisualitemmodel.cpp \ + $$PWD/qsgrepeater.cpp \ + $$PWD/qsggridview.cpp \ + $$PWD/qsgpathview.cpp \ + $$PWD/qsgpositioners.cpp \ + $$PWD/qsgloader.cpp \ + $$PWD/qsganimatedimage.cpp \ + $$PWD/qsgflipable.cpp \ + $$PWD/qsgtranslate.cpp \ + $$PWD/qsgclipnode.cpp \ + $$PWD/qsgview.cpp \ + $$PWD/qsganimation.cpp \ + $$PWD/qsgstateoperations.cpp \ + $$PWD/qsgimplicitsizeitem.cpp \ + +SOURCES += \ + $$PWD/qsgshadereffectitem.cpp \ + $$PWD/qsgshadereffectmesh.cpp \ + $$PWD/qsgshadereffectnode.cpp \ + $$PWD/qsgshadereffectsource.cpp \ + +HEADERS += \ + $$PWD/qsgshadereffectitem_p.h \ + $$PWD/qsgshadereffectmesh_p.h \ + $$PWD/qsgshadereffectnode_p.h \ + $$PWD/qsgshadereffectsource_p.h \ diff --git a/src/declarative/items/qsganchors.cpp b/src/declarative/items/qsganchors.cpp new file mode 100644 index 0000000000..ff9351edbc --- /dev/null +++ b/src/declarative/items/qsganchors.cpp @@ -0,0 +1,1111 @@ +// Commit: 2c7cab4172f1acc86fd49345a2847417e162f2c3 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsganchors_p_p.h" + +#include "qsgitem.h" +#include "qsgitem_p.h" + +#include <qdeclarativeinfo.h> + +QT_BEGIN_NAMESPACE + +//TODO: should we cache relationships, so we don't have to check each time (parent-child or sibling)? +//TODO: support non-parent, non-sibling (need to find lowest common ancestor) + +static qreal hcenter(QSGItem *item) +{ + qreal width = item->width(); + int iw = width; + if (iw % 2) + return (width + 1) / 2; + else + return width / 2; +} + +static qreal vcenter(QSGItem *item) +{ + qreal height = item->height(); + int ih = height; + if (ih % 2) + return (height + 1) / 2; + else + return height / 2; +} + +//### const item? +//local position +static qreal position(QSGItem *item, QSGAnchorLine::AnchorLine anchorLine) +{ + qreal ret = 0.0; + switch(anchorLine) { + case QSGAnchorLine::Left: + ret = item->x(); + break; + case QSGAnchorLine::Right: + ret = item->x() + item->width(); + break; + case QSGAnchorLine::Top: + ret = item->y(); + break; + case QSGAnchorLine::Bottom: + ret = item->y() + item->height(); + break; + case QSGAnchorLine::HCenter: + ret = item->x() + hcenter(item); + break; + case QSGAnchorLine::VCenter: + ret = item->y() + vcenter(item); + break; + case QSGAnchorLine::Baseline: + ret = item->y() + item->baselineOffset(); + break; + default: + break; + } + + return ret; +} + +//position when origin is 0,0 +static qreal adjustedPosition(QSGItem *item, QSGAnchorLine::AnchorLine anchorLine) +{ + qreal ret = 0.0; + switch(anchorLine) { + case QSGAnchorLine::Left: + ret = 0.0; + break; + case QSGAnchorLine::Right: + ret = item->width(); + break; + case QSGAnchorLine::Top: + ret = 0.0; + break; + case QSGAnchorLine::Bottom: + ret = item->height(); + break; + case QSGAnchorLine::HCenter: + ret = hcenter(item); + break; + case QSGAnchorLine::VCenter: + ret = vcenter(item); + break; + case QSGAnchorLine::Baseline: + ret = item->baselineOffset(); + break; + default: + break; + } + + return ret; +} + +QSGAnchors::QSGAnchors(QSGItem *item, QObject *parent) +: QObject(*new QSGAnchorsPrivate(item), parent) +{ +} + +QSGAnchors::~QSGAnchors() +{ + Q_D(QSGAnchors); + d->remDepend(d->fill); + d->remDepend(d->centerIn); + d->remDepend(d->left.item); + d->remDepend(d->right.item); + d->remDepend(d->top.item); + d->remDepend(d->bottom.item); + d->remDepend(d->vCenter.item); + d->remDepend(d->hCenter.item); + d->remDepend(d->baseline.item); +} + +void QSGAnchorsPrivate::fillChanged() +{ + Q_Q(QSGAnchors); + if (!fill || !isItemComplete()) + return; + + if (updatingFill < 2) { + ++updatingFill; + + qreal horizontalMargin = q->mirrored() ? rightMargin : leftMargin; + + if (fill == item->parentItem()) { //child-parent + setItemPos(QPointF(horizontalMargin, topMargin)); + } else if (fill->parentItem() == item->parentItem()) { //siblings + setItemPos(QPointF(fill->x()+horizontalMargin, fill->y()+topMargin)); + } + setItemSize(QSizeF(fill->width()-leftMargin-rightMargin, fill->height()-topMargin-bottomMargin)); + + --updatingFill; + } else { + // ### Make this certain :) + qmlInfo(item) << QSGAnchors::tr("Possible anchor loop detected on fill."); + } + +} + +void QSGAnchorsPrivate::centerInChanged() +{ + Q_Q(QSGAnchors); + if (!centerIn || fill || !isItemComplete()) + return; + + if (updatingCenterIn < 2) { + ++updatingCenterIn; + + qreal effectiveHCenterOffset = q->mirrored() ? -hCenterOffset : hCenterOffset; + if (centerIn == item->parentItem()) { + QPointF p(hcenter(item->parentItem()) - hcenter(item) + effectiveHCenterOffset, + vcenter(item->parentItem()) - vcenter(item) + vCenterOffset); + setItemPos(p); + + } else if (centerIn->parentItem() == item->parentItem()) { + QPointF p(centerIn->x() + hcenter(centerIn) - hcenter(item) + effectiveHCenterOffset, + centerIn->y() + vcenter(centerIn) - vcenter(item) + vCenterOffset); + setItemPos(p); + } + + --updatingCenterIn; + } else { + // ### Make this certain :) + qmlInfo(item) << QSGAnchors::tr("Possible anchor loop detected on centerIn."); + } +} + +void QSGAnchorsPrivate::clearItem(QSGItem *item) +{ + if (!item) + return; + if (fill == item) + fill = 0; + if (centerIn == item) + centerIn = 0; + if (left.item == item) { + left.item = 0; + usedAnchors &= ~QSGAnchors::LeftAnchor; + } + if (right.item == item) { + right.item = 0; + usedAnchors &= ~QSGAnchors::RightAnchor; + } + if (top.item == item) { + top.item = 0; + usedAnchors &= ~QSGAnchors::TopAnchor; + } + if (bottom.item == item) { + bottom.item = 0; + usedAnchors &= ~QSGAnchors::BottomAnchor; + } + if (vCenter.item == item) { + vCenter.item = 0; + usedAnchors &= ~QSGAnchors::VCenterAnchor; + } + if (hCenter.item == item) { + hCenter.item = 0; + usedAnchors &= ~QSGAnchors::HCenterAnchor; + } + if (baseline.item == item) { + baseline.item = 0; + usedAnchors &= ~QSGAnchors::BaselineAnchor; + } +} + +void QSGAnchorsPrivate::addDepend(QSGItem *item) +{ + if (!item) + return; + + QSGItemPrivate *p = QSGItemPrivate::get(item); + p->addItemChangeListener(this, QSGItemPrivate::Geometry); +} + +void QSGAnchorsPrivate::remDepend(QSGItem *item) +{ + if (!item) + return; + + QSGItemPrivate *p = QSGItemPrivate::get(item); + p->removeItemChangeListener(this, QSGItemPrivate::Geometry); +} + +bool QSGAnchors::mirrored() +{ + Q_D(QSGAnchors); + return QSGItemPrivate::get(d->item)->effectiveLayoutMirror; +} + +bool QSGAnchorsPrivate::isItemComplete() const +{ + return componentComplete; +} + +void QSGAnchors::classBegin() +{ + Q_D(QSGAnchors); + d->componentComplete = false; +} + +void QSGAnchors::componentComplete() +{ + Q_D(QSGAnchors); + d->componentComplete = true; +} + +void QSGAnchorsPrivate::setItemHeight(qreal v) +{ + updatingMe = true; + item->setHeight(v); + updatingMe = false; +} + +void QSGAnchorsPrivate::setItemWidth(qreal v) +{ + updatingMe = true; + item->setWidth(v); + updatingMe = false; +} + +void QSGAnchorsPrivate::setItemX(qreal v) +{ + updatingMe = true; + item->setX(v); + updatingMe = false; +} + +void QSGAnchorsPrivate::setItemY(qreal v) +{ + updatingMe = true; + item->setY(v); + updatingMe = false; +} + +void QSGAnchorsPrivate::setItemPos(const QPointF &v) +{ + updatingMe = true; + item->setPos(v); + updatingMe = false; +} + +void QSGAnchorsPrivate::setItemSize(const QSizeF &v) +{ + updatingMe = true; + item->setSize(v); + updatingMe = false; +} + +void QSGAnchorsPrivate::updateMe() +{ + if (updatingMe) { + updatingMe = false; + return; + } + + fillChanged(); + centerInChanged(); + updateHorizontalAnchors(); + updateVerticalAnchors(); +} + +void QSGAnchorsPrivate::updateOnComplete() +{ + fillChanged(); + centerInChanged(); + updateHorizontalAnchors(); + updateVerticalAnchors(); +} + +void QSGAnchorsPrivate::itemGeometryChanged(QSGItem *, const QRectF &newG, const QRectF &oldG) +{ + fillChanged(); + centerInChanged(); + if (newG.x() != oldG.x() || newG.width() != oldG.width()) + updateHorizontalAnchors(); + if (newG.y() != oldG.y() || newG.height() != oldG.height()) + updateVerticalAnchors(); +} + +QSGItem *QSGAnchors::fill() const +{ + Q_D(const QSGAnchors); + return d->fill; +} + +void QSGAnchors::setFill(QSGItem *f) +{ + Q_D(QSGAnchors); + if (d->fill == f) + return; + + if (!f) { + d->remDepend(d->fill); + d->fill = f; + emit fillChanged(); + return; + } + if (f != d->item->parentItem() && f->parentItem() != d->item->parentItem()){ + qmlInfo(d->item) << tr("Cannot anchor to an item that isn't a parent or sibling."); + return; + } + d->remDepend(d->fill); + d->fill = f; + d->addDepend(d->fill); + emit fillChanged(); + d->fillChanged(); +} + +void QSGAnchors::resetFill() +{ + setFill(0); +} + +QSGItem *QSGAnchors::centerIn() const +{ + Q_D(const QSGAnchors); + return d->centerIn; +} + +void QSGAnchors::setCenterIn(QSGItem* c) +{ + Q_D(QSGAnchors); + if (d->centerIn == c) + return; + + if (!c) { + d->remDepend(d->centerIn); + d->centerIn = c; + emit centerInChanged(); + return; + } + if (c != d->item->parentItem() && c->parentItem() != d->item->parentItem()){ + qmlInfo(d->item) << tr("Cannot anchor to an item that isn't a parent or sibling."); + return; + } + + d->remDepend(d->centerIn); + d->centerIn = c; + d->addDepend(d->centerIn); + emit centerInChanged(); + d->centerInChanged(); +} + +void QSGAnchors::resetCenterIn() +{ + setCenterIn(0); +} + +bool QSGAnchorsPrivate::calcStretch(const QSGAnchorLine &edge1, + const QSGAnchorLine &edge2, + qreal offset1, + qreal offset2, + QSGAnchorLine::AnchorLine line, + qreal &stretch) +{ + bool edge1IsParent = (edge1.item == item->parentItem()); + bool edge2IsParent = (edge2.item == item->parentItem()); + bool edge1IsSibling = (edge1.item->parentItem() == item->parentItem()); + bool edge2IsSibling = (edge2.item->parentItem() == item->parentItem()); + + bool invalid = false; + if ((edge2IsParent && edge1IsParent) || (edge2IsSibling && edge1IsSibling)) { + stretch = (position(edge2.item, edge2.anchorLine) + offset2) + - (position(edge1.item, edge1.anchorLine) + offset1); + } else if (edge2IsParent && edge1IsSibling) { + stretch = (position(edge2.item, edge2.anchorLine) + offset2) + - (position(item->parentItem(), line) + + position(edge1.item, edge1.anchorLine) + offset1); + } else if (edge2IsSibling && edge1IsParent) { + stretch = (position(item->parentItem(), line) + position(edge2.item, edge2.anchorLine) + offset2) + - (position(edge1.item, edge1.anchorLine) + offset1); + } else + invalid = true; + + return invalid; +} + +void QSGAnchorsPrivate::updateVerticalAnchors() +{ + if (fill || centerIn || !isItemComplete()) + return; + + if (updatingVerticalAnchor < 2) { + ++updatingVerticalAnchor; + if (usedAnchors & QSGAnchors::TopAnchor) { + //Handle stretching + bool invalid = true; + qreal height = 0.0; + if (usedAnchors & QSGAnchors::BottomAnchor) { + invalid = calcStretch(top, bottom, topMargin, -bottomMargin, QSGAnchorLine::Top, height); + } else if (usedAnchors & QSGAnchors::VCenterAnchor) { + invalid = calcStretch(top, vCenter, topMargin, vCenterOffset, QSGAnchorLine::Top, height); + height *= 2; + } + if (!invalid) + setItemHeight(height); + + //Handle top + if (top.item == item->parentItem()) { + setItemY(adjustedPosition(top.item, top.anchorLine) + topMargin); + } else if (top.item->parentItem() == item->parentItem()) { + setItemY(position(top.item, top.anchorLine) + topMargin); + } + } else if (usedAnchors & QSGAnchors::BottomAnchor) { + //Handle stretching (top + bottom case is handled above) + if (usedAnchors & QSGAnchors::VCenterAnchor) { + qreal height = 0.0; + bool invalid = calcStretch(vCenter, bottom, vCenterOffset, -bottomMargin, + QSGAnchorLine::Top, height); + if (!invalid) + setItemHeight(height*2); + } + + //Handle bottom + if (bottom.item == item->parentItem()) { + setItemY(adjustedPosition(bottom.item, bottom.anchorLine) - item->height() - bottomMargin); + } else if (bottom.item->parentItem() == item->parentItem()) { + setItemY(position(bottom.item, bottom.anchorLine) - item->height() - bottomMargin); + } + } else if (usedAnchors & QSGAnchors::VCenterAnchor) { + //(stetching handled above) + + //Handle vCenter + if (vCenter.item == item->parentItem()) { + setItemY(adjustedPosition(vCenter.item, vCenter.anchorLine) + - vcenter(item) + vCenterOffset); + } else if (vCenter.item->parentItem() == item->parentItem()) { + setItemY(position(vCenter.item, vCenter.anchorLine) - vcenter(item) + vCenterOffset); + } + } else if (usedAnchors & QSGAnchors::BaselineAnchor) { + //Handle baseline + if (baseline.item == item->parentItem()) { + setItemY(adjustedPosition(baseline.item, baseline.anchorLine) - item->baselineOffset() + baselineOffset); + } else if (baseline.item->parentItem() == item->parentItem()) { + setItemY(position(baseline.item, baseline.anchorLine) - item->baselineOffset() + baselineOffset); + } + } + --updatingVerticalAnchor; + } else { + // ### Make this certain :) + qmlInfo(item) << QSGAnchors::tr("Possible anchor loop detected on vertical anchor."); + } +} + +inline QSGAnchorLine::AnchorLine reverseAnchorLine(QSGAnchorLine::AnchorLine anchorLine) +{ + if (anchorLine == QSGAnchorLine::Left) { + return QSGAnchorLine::Right; + } else if (anchorLine == QSGAnchorLine::Right) { + return QSGAnchorLine::Left; + } else { + return anchorLine; + } +} + +void QSGAnchorsPrivate::updateHorizontalAnchors() +{ + Q_Q(QSGAnchors); + if (fill || centerIn || !isItemComplete()) + return; + + if (updatingHorizontalAnchor < 3) { + ++updatingHorizontalAnchor; + qreal effectiveRightMargin, effectiveLeftMargin, effectiveHorizontalCenterOffset; + QSGAnchorLine effectiveLeft, effectiveRight, effectiveHorizontalCenter; + QSGAnchors::Anchor effectiveLeftAnchor, effectiveRightAnchor; + if (q->mirrored()) { + effectiveLeftAnchor = QSGAnchors::RightAnchor; + effectiveRightAnchor = QSGAnchors::LeftAnchor; + effectiveLeft.item = right.item; + effectiveLeft.anchorLine = reverseAnchorLine(right.anchorLine); + effectiveRight.item = left.item; + effectiveRight.anchorLine = reverseAnchorLine(left.anchorLine); + effectiveHorizontalCenter.item = hCenter.item; + effectiveHorizontalCenter.anchorLine = reverseAnchorLine(hCenter.anchorLine); + effectiveLeftMargin = rightMargin; + effectiveRightMargin = leftMargin; + effectiveHorizontalCenterOffset = -hCenterOffset; + } else { + effectiveLeftAnchor = QSGAnchors::LeftAnchor; + effectiveRightAnchor = QSGAnchors::RightAnchor; + effectiveLeft = left; + effectiveRight = right; + effectiveHorizontalCenter = hCenter; + effectiveLeftMargin = leftMargin; + effectiveRightMargin = rightMargin; + effectiveHorizontalCenterOffset = hCenterOffset; + } + + if (usedAnchors & effectiveLeftAnchor) { + //Handle stretching + bool invalid = true; + qreal width = 0.0; + if (usedAnchors & effectiveRightAnchor) { + invalid = calcStretch(effectiveLeft, effectiveRight, effectiveLeftMargin, -effectiveRightMargin, QSGAnchorLine::Left, width); + } else if (usedAnchors & QSGAnchors::HCenterAnchor) { + invalid = calcStretch(effectiveLeft, effectiveHorizontalCenter, effectiveLeftMargin, effectiveHorizontalCenterOffset, QSGAnchorLine::Left, width); + width *= 2; + } + if (!invalid) + setItemWidth(width); + + //Handle left + if (effectiveLeft.item == item->parentItem()) { + setItemX(adjustedPosition(effectiveLeft.item, effectiveLeft.anchorLine) + effectiveLeftMargin); + } else if (effectiveLeft.item->parentItem() == item->parentItem()) { + setItemX(position(effectiveLeft.item, effectiveLeft.anchorLine) + effectiveLeftMargin); + } + } else if (usedAnchors & effectiveRightAnchor) { + //Handle stretching (left + right case is handled in updateLeftAnchor) + if (usedAnchors & QSGAnchors::HCenterAnchor) { + qreal width = 0.0; + bool invalid = calcStretch(effectiveHorizontalCenter, effectiveRight, effectiveHorizontalCenterOffset, -effectiveRightMargin, + QSGAnchorLine::Left, width); + if (!invalid) + setItemWidth(width*2); + } + + //Handle right + if (effectiveRight.item == item->parentItem()) { + setItemX(adjustedPosition(effectiveRight.item, effectiveRight.anchorLine) - item->width() - effectiveRightMargin); + } else if (effectiveRight.item->parentItem() == item->parentItem()) { + setItemX(position(effectiveRight.item, effectiveRight.anchorLine) - item->width() - effectiveRightMargin); + } + } else if (usedAnchors & QSGAnchors::HCenterAnchor) { + //Handle hCenter + if (effectiveHorizontalCenter.item == item->parentItem()) { + setItemX(adjustedPosition(effectiveHorizontalCenter.item, effectiveHorizontalCenter.anchorLine) - hcenter(item) + effectiveHorizontalCenterOffset); + } else if (effectiveHorizontalCenter.item->parentItem() == item->parentItem()) { + setItemX(position(effectiveHorizontalCenter.item, effectiveHorizontalCenter.anchorLine) - hcenter(item) + effectiveHorizontalCenterOffset); + } + } + --updatingHorizontalAnchor; + } else { + // ### Make this certain :) + qmlInfo(item) << QSGAnchors::tr("Possible anchor loop detected on horizontal anchor."); + } +} + +QSGAnchorLine QSGAnchors::top() const +{ + Q_D(const QSGAnchors); + return d->top; +} + +void QSGAnchors::setTop(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkVAnchorValid(edge) || d->top == edge) + return; + + d->usedAnchors |= TopAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~TopAnchor; + return; + } + + d->remDepend(d->top.item); + d->top = edge; + d->addDepend(d->top.item); + emit topChanged(); + d->updateVerticalAnchors(); +} + +void QSGAnchors::resetTop() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~TopAnchor; + d->remDepend(d->top.item); + d->top = QSGAnchorLine(); + emit topChanged(); + d->updateVerticalAnchors(); +} + +QSGAnchorLine QSGAnchors::bottom() const +{ + Q_D(const QSGAnchors); + return d->bottom; +} + +void QSGAnchors::setBottom(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkVAnchorValid(edge) || d->bottom == edge) + return; + + d->usedAnchors |= BottomAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~BottomAnchor; + return; + } + + d->remDepend(d->bottom.item); + d->bottom = edge; + d->addDepend(d->bottom.item); + emit bottomChanged(); + d->updateVerticalAnchors(); +} + +void QSGAnchors::resetBottom() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~BottomAnchor; + d->remDepend(d->bottom.item); + d->bottom = QSGAnchorLine(); + emit bottomChanged(); + d->updateVerticalAnchors(); +} + +QSGAnchorLine QSGAnchors::verticalCenter() const +{ + Q_D(const QSGAnchors); + return d->vCenter; +} + +void QSGAnchors::setVerticalCenter(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkVAnchorValid(edge) || d->vCenter == edge) + return; + + d->usedAnchors |= VCenterAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~VCenterAnchor; + return; + } + + d->remDepend(d->vCenter.item); + d->vCenter = edge; + d->addDepend(d->vCenter.item); + emit verticalCenterChanged(); + d->updateVerticalAnchors(); +} + +void QSGAnchors::resetVerticalCenter() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~VCenterAnchor; + d->remDepend(d->vCenter.item); + d->vCenter = QSGAnchorLine(); + emit verticalCenterChanged(); + d->updateVerticalAnchors(); +} + +QSGAnchorLine QSGAnchors::baseline() const +{ + Q_D(const QSGAnchors); + return d->baseline; +} + +void QSGAnchors::setBaseline(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkVAnchorValid(edge) || d->baseline == edge) + return; + + d->usedAnchors |= BaselineAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~BaselineAnchor; + return; + } + + d->remDepend(d->baseline.item); + d->baseline = edge; + d->addDepend(d->baseline.item); + emit baselineChanged(); + d->updateVerticalAnchors(); +} + +void QSGAnchors::resetBaseline() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~BaselineAnchor; + d->remDepend(d->baseline.item); + d->baseline = QSGAnchorLine(); + emit baselineChanged(); + d->updateVerticalAnchors(); +} + +QSGAnchorLine QSGAnchors::left() const +{ + Q_D(const QSGAnchors); + return d->left; +} + +void QSGAnchors::setLeft(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkHAnchorValid(edge) || d->left == edge) + return; + + d->usedAnchors |= LeftAnchor; + + if (!d->checkHValid()) { + d->usedAnchors &= ~LeftAnchor; + return; + } + + d->remDepend(d->left.item); + d->left = edge; + d->addDepend(d->left.item); + emit leftChanged(); + d->updateHorizontalAnchors(); +} + +void QSGAnchors::resetLeft() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~LeftAnchor; + d->remDepend(d->left.item); + d->left = QSGAnchorLine(); + emit leftChanged(); + d->updateHorizontalAnchors(); +} + +QSGAnchorLine QSGAnchors::right() const +{ + Q_D(const QSGAnchors); + return d->right; +} + +void QSGAnchors::setRight(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkHAnchorValid(edge) || d->right == edge) + return; + + d->usedAnchors |= RightAnchor; + + if (!d->checkHValid()) { + d->usedAnchors &= ~RightAnchor; + return; + } + + d->remDepend(d->right.item); + d->right = edge; + d->addDepend(d->right.item); + emit rightChanged(); + d->updateHorizontalAnchors(); +} + +void QSGAnchors::resetRight() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~RightAnchor; + d->remDepend(d->right.item); + d->right = QSGAnchorLine(); + emit rightChanged(); + d->updateHorizontalAnchors(); +} + +QSGAnchorLine QSGAnchors::horizontalCenter() const +{ + Q_D(const QSGAnchors); + return d->hCenter; +} + +void QSGAnchors::setHorizontalCenter(const QSGAnchorLine &edge) +{ + Q_D(QSGAnchors); + if (!d->checkHAnchorValid(edge) || d->hCenter == edge) + return; + + d->usedAnchors |= HCenterAnchor; + + if (!d->checkHValid()) { + d->usedAnchors &= ~HCenterAnchor; + return; + } + + d->remDepend(d->hCenter.item); + d->hCenter = edge; + d->addDepend(d->hCenter.item); + emit horizontalCenterChanged(); + d->updateHorizontalAnchors(); +} + +void QSGAnchors::resetHorizontalCenter() +{ + Q_D(QSGAnchors); + d->usedAnchors &= ~HCenterAnchor; + d->remDepend(d->hCenter.item); + d->hCenter = QSGAnchorLine(); + emit horizontalCenterChanged(); + d->updateHorizontalAnchors(); +} + +qreal QSGAnchors::leftMargin() const +{ + Q_D(const QSGAnchors); + return d->leftMargin; +} + +void QSGAnchors::setLeftMargin(qreal offset) +{ + Q_D(QSGAnchors); + if (d->leftMargin == offset) + return; + d->leftMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateHorizontalAnchors(); + emit leftMarginChanged(); +} + +qreal QSGAnchors::rightMargin() const +{ + Q_D(const QSGAnchors); + return d->rightMargin; +} + +void QSGAnchors::setRightMargin(qreal offset) +{ + Q_D(QSGAnchors); + if (d->rightMargin == offset) + return; + d->rightMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateHorizontalAnchors(); + emit rightMarginChanged(); +} + +qreal QSGAnchors::margins() const +{ + Q_D(const QSGAnchors); + return d->margins; +} + +void QSGAnchors::setMargins(qreal offset) +{ + Q_D(QSGAnchors); + if (d->margins == offset) + return; + //###Is it significantly faster to set them directly so we can call fillChanged only once? + if(!d->rightMargin || d->rightMargin == d->margins) + setRightMargin(offset); + if(!d->leftMargin || d->leftMargin == d->margins) + setLeftMargin(offset); + if(!d->topMargin || d->topMargin == d->margins) + setTopMargin(offset); + if(!d->bottomMargin || d->bottomMargin == d->margins) + setBottomMargin(offset); + d->margins = offset; + emit marginsChanged(); + +} + +qreal QSGAnchors::horizontalCenterOffset() const +{ + Q_D(const QSGAnchors); + return d->hCenterOffset; +} + +void QSGAnchors::setHorizontalCenterOffset(qreal offset) +{ + Q_D(QSGAnchors); + if (d->hCenterOffset == offset) + return; + d->hCenterOffset = offset; + if(d->centerIn) + d->centerInChanged(); + else + d->updateHorizontalAnchors(); + emit horizontalCenterOffsetChanged(); +} + +qreal QSGAnchors::topMargin() const +{ + Q_D(const QSGAnchors); + return d->topMargin; +} + +void QSGAnchors::setTopMargin(qreal offset) +{ + Q_D(QSGAnchors); + if (d->topMargin == offset) + return; + d->topMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateVerticalAnchors(); + emit topMarginChanged(); +} + +qreal QSGAnchors::bottomMargin() const +{ + Q_D(const QSGAnchors); + return d->bottomMargin; +} + +void QSGAnchors::setBottomMargin(qreal offset) +{ + Q_D(QSGAnchors); + if (d->bottomMargin == offset) + return; + d->bottomMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateVerticalAnchors(); + emit bottomMarginChanged(); +} + +qreal QSGAnchors::verticalCenterOffset() const +{ + Q_D(const QSGAnchors); + return d->vCenterOffset; +} + +void QSGAnchors::setVerticalCenterOffset(qreal offset) +{ + Q_D(QSGAnchors); + if (d->vCenterOffset == offset) + return; + d->vCenterOffset = offset; + if(d->centerIn) + d->centerInChanged(); + else + d->updateVerticalAnchors(); + emit verticalCenterOffsetChanged(); +} + +qreal QSGAnchors::baselineOffset() const +{ + Q_D(const QSGAnchors); + return d->baselineOffset; +} + +void QSGAnchors::setBaselineOffset(qreal offset) +{ + Q_D(QSGAnchors); + if (d->baselineOffset == offset) + return; + d->baselineOffset = offset; + d->updateVerticalAnchors(); + emit baselineOffsetChanged(); +} + +QSGAnchors::Anchors QSGAnchors::usedAnchors() const +{ + Q_D(const QSGAnchors); + return d->usedAnchors; +} + +bool QSGAnchorsPrivate::checkHValid() const +{ + if (usedAnchors & QSGAnchors::LeftAnchor && + usedAnchors & QSGAnchors::RightAnchor && + usedAnchors & QSGAnchors::HCenterAnchor) { + qmlInfo(item) << QSGAnchors::tr("Cannot specify left, right, and hcenter anchors."); + return false; + } + + return true; +} + +bool QSGAnchorsPrivate::checkHAnchorValid(QSGAnchorLine anchor) const +{ + if (!anchor.item) { + qmlInfo(item) << QSGAnchors::tr("Cannot anchor to a null item."); + return false; + } else if (anchor.anchorLine & QSGAnchorLine::Vertical_Mask) { + qmlInfo(item) << QSGAnchors::tr("Cannot anchor a horizontal edge to a vertical edge."); + return false; + } else if (anchor.item != item->parentItem() && anchor.item->parentItem() != item->parentItem()){ + qmlInfo(item) << QSGAnchors::tr("Cannot anchor to an item that isn't a parent or sibling."); + return false; + } else if (anchor.item == item) { + qmlInfo(item) << QSGAnchors::tr("Cannot anchor item to self."); + return false; + } + + return true; +} + +bool QSGAnchorsPrivate::checkVValid() const +{ + if (usedAnchors & QSGAnchors::TopAnchor && + usedAnchors & QSGAnchors::BottomAnchor && + usedAnchors & QSGAnchors::VCenterAnchor) { + qmlInfo(item) << QSGAnchors::tr("Cannot specify top, bottom, and vcenter anchors."); + return false; + } else if (usedAnchors & QSGAnchors::BaselineAnchor && + (usedAnchors & QSGAnchors::TopAnchor || + usedAnchors & QSGAnchors::BottomAnchor || + usedAnchors & QSGAnchors::VCenterAnchor)) { + qmlInfo(item) << QSGAnchors::tr("Baseline anchor cannot be used in conjunction with top, bottom, or vcenter anchors."); + return false; + } + + return true; +} + +bool QSGAnchorsPrivate::checkVAnchorValid(QSGAnchorLine anchor) const +{ + if (!anchor.item) { + qmlInfo(item) << QSGAnchors::tr("Cannot anchor to a null item."); + return false; + } else if (anchor.anchorLine & QSGAnchorLine::Horizontal_Mask) { + qmlInfo(item) << QSGAnchors::tr("Cannot anchor a vertical edge to a horizontal edge."); + return false; + } else if (anchor.item != item->parentItem() && anchor.item->parentItem() != item->parentItem()){ + qmlInfo(item) << QSGAnchors::tr("Cannot anchor to an item that isn't a parent or sibling."); + return false; + } else if (anchor.item == item){ + qmlInfo(item) << QSGAnchors::tr("Cannot anchor item to self."); + return false; + } + + return true; +} + +QT_END_NAMESPACE + +#include <moc_qsganchors_p.cpp> + diff --git a/src/declarative/items/qsganchors_p.h b/src/declarative/items/qsganchors_p.h new file mode 100644 index 0000000000..d26fb57961 --- /dev/null +++ b/src/declarative/items/qsganchors_p.h @@ -0,0 +1,201 @@ +// Commit: 2c7cab4172f1acc86fd49345a2847417e162f2c3 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGANCHORS_P_H +#define QSGANCHORS_P_H + +#include <qdeclarative.h> + +#include <QtCore/QObject> + +#include <private/qdeclarativeglobal_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGItem; +class QSGAnchorsPrivate; +class QSGAnchorLine; +class Q_DECLARATIVE_PRIVATE_EXPORT QSGAnchors : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QSGAnchorLine left READ left WRITE setLeft RESET resetLeft NOTIFY leftChanged) + Q_PROPERTY(QSGAnchorLine right READ right WRITE setRight RESET resetRight NOTIFY rightChanged) + Q_PROPERTY(QSGAnchorLine horizontalCenter READ horizontalCenter WRITE setHorizontalCenter RESET resetHorizontalCenter NOTIFY horizontalCenterChanged) + Q_PROPERTY(QSGAnchorLine top READ top WRITE setTop RESET resetTop NOTIFY topChanged) + Q_PROPERTY(QSGAnchorLine bottom READ bottom WRITE setBottom RESET resetBottom NOTIFY bottomChanged) + Q_PROPERTY(QSGAnchorLine verticalCenter READ verticalCenter WRITE setVerticalCenter RESET resetVerticalCenter NOTIFY verticalCenterChanged) + Q_PROPERTY(QSGAnchorLine baseline READ baseline WRITE setBaseline RESET resetBaseline NOTIFY baselineChanged) + Q_PROPERTY(qreal margins READ margins WRITE setMargins NOTIFY marginsChanged) + Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged) + Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged) + Q_PROPERTY(qreal horizontalCenterOffset READ horizontalCenterOffset WRITE setHorizontalCenterOffset NOTIFY horizontalCenterOffsetChanged) + Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged) + Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged) + Q_PROPERTY(qreal verticalCenterOffset READ verticalCenterOffset WRITE setVerticalCenterOffset NOTIFY verticalCenterOffsetChanged) + Q_PROPERTY(qreal baselineOffset READ baselineOffset WRITE setBaselineOffset NOTIFY baselineOffsetChanged) + Q_PROPERTY(QSGItem *fill READ fill WRITE setFill RESET resetFill NOTIFY fillChanged) + Q_PROPERTY(QSGItem *centerIn READ centerIn WRITE setCenterIn RESET resetCenterIn NOTIFY centerInChanged) + Q_PROPERTY(bool mirrored READ mirrored NOTIFY mirroredChanged) + +public: + QSGAnchors(QSGItem *item, QObject *parent=0); + virtual ~QSGAnchors(); + + enum Anchor { + LeftAnchor = 0x01, + RightAnchor = 0x02, + TopAnchor = 0x04, + BottomAnchor = 0x08, + HCenterAnchor = 0x10, + VCenterAnchor = 0x20, + BaselineAnchor = 0x40, + Horizontal_Mask = LeftAnchor | RightAnchor | HCenterAnchor, + Vertical_Mask = TopAnchor | BottomAnchor | VCenterAnchor | BaselineAnchor + }; + Q_DECLARE_FLAGS(Anchors, Anchor) + + QSGAnchorLine left() const; + void setLeft(const QSGAnchorLine &edge); + void resetLeft(); + + QSGAnchorLine right() const; + void setRight(const QSGAnchorLine &edge); + void resetRight(); + + QSGAnchorLine horizontalCenter() const; + void setHorizontalCenter(const QSGAnchorLine &edge); + void resetHorizontalCenter(); + + QSGAnchorLine top() const; + void setTop(const QSGAnchorLine &edge); + void resetTop(); + + QSGAnchorLine bottom() const; + void setBottom(const QSGAnchorLine &edge); + void resetBottom(); + + QSGAnchorLine verticalCenter() const; + void setVerticalCenter(const QSGAnchorLine &edge); + void resetVerticalCenter(); + + QSGAnchorLine baseline() const; + void setBaseline(const QSGAnchorLine &edge); + void resetBaseline(); + + qreal leftMargin() const; + void setLeftMargin(qreal); + + qreal rightMargin() const; + void setRightMargin(qreal); + + qreal horizontalCenterOffset() const; + void setHorizontalCenterOffset(qreal); + + qreal topMargin() const; + void setTopMargin(qreal); + + qreal bottomMargin() const; + void setBottomMargin(qreal); + + qreal margins() const; + void setMargins(qreal); + + qreal verticalCenterOffset() const; + void setVerticalCenterOffset(qreal); + + qreal baselineOffset() const; + void setBaselineOffset(qreal); + + QSGItem *fill() const; + void setFill(QSGItem *); + void resetFill(); + + QSGItem *centerIn() const; + void setCenterIn(QSGItem *); + void resetCenterIn(); + + Anchors usedAnchors() const; + + bool mirrored(); + + void classBegin(); + void componentComplete(); + +Q_SIGNALS: + void leftChanged(); + void rightChanged(); + void topChanged(); + void bottomChanged(); + void verticalCenterChanged(); + void horizontalCenterChanged(); + void baselineChanged(); + void fillChanged(); + void centerInChanged(); + void leftMarginChanged(); + void rightMarginChanged(); + void topMarginChanged(); + void bottomMarginChanged(); + void marginsChanged(); + void verticalCenterOffsetChanged(); + void horizontalCenterOffsetChanged(); + void baselineOffsetChanged(); + void mirroredChanged(); + +private: + friend class QSGItemPrivate; + Q_DISABLE_COPY(QSGAnchors) + Q_DECLARE_PRIVATE(QSGAnchors) +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QSGAnchors::Anchors) + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGAnchors) + +QT_END_HEADER + +#endif // QSGANCHORS_P_H diff --git a/src/declarative/items/qsganchors_p_p.h b/src/declarative/items/qsganchors_p_p.h new file mode 100644 index 0000000000..cb9b950c8f --- /dev/null +++ b/src/declarative/items/qsganchors_p_p.h @@ -0,0 +1,173 @@ +// Commit: 2c7cab4172f1acc86fd49345a2847417e162f2c3 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGANCHORS_P_P_H +#define QSGANCHORS_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsganchors_p.h" +#include "qsgitemchangelistener_p.h" +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QSGAnchorLine +{ +public: + QSGAnchorLine() : item(0), anchorLine(Invalid) {} + + enum AnchorLine { + Invalid = 0x0, + Left = 0x01, + Right = 0x02, + Top = 0x04, + Bottom = 0x08, + HCenter = 0x10, + VCenter = 0x20, + Baseline = 0x40, + Horizontal_Mask = Left | Right | HCenter, + Vertical_Mask = Top | Bottom | VCenter | Baseline + }; + + QSGItem *item; + AnchorLine anchorLine; +}; + +inline bool operator==(const QSGAnchorLine& a, const QSGAnchorLine& b) +{ + return a.item == b.item && a.anchorLine == b.anchorLine; +} + +class QSGAnchorsPrivate : public QObjectPrivate, public QSGItemChangeListener +{ + Q_DECLARE_PUBLIC(QSGAnchors) +public: + QSGAnchorsPrivate(QSGItem *i) + : componentComplete(true), updatingMe(false), updatingHorizontalAnchor(0), + updatingVerticalAnchor(0), updatingFill(0), updatingCenterIn(0), item(i), usedAnchors(0), fill(0), + centerIn(0), leftMargin(0), rightMargin(0), topMargin(0), bottomMargin(0), + margins(0), vCenterOffset(0), hCenterOffset(0), baselineOffset(0) + { + } + + void clearItem(QSGItem *); + + void addDepend(QSGItem *); + void remDepend(QSGItem *); + bool isItemComplete() const; + + bool componentComplete:1; + bool updatingMe:1; + uint updatingHorizontalAnchor:2; + uint updatingVerticalAnchor:2; + uint updatingFill:2; + uint updatingCenterIn:2; + + void setItemHeight(qreal); + void setItemWidth(qreal); + void setItemX(qreal); + void setItemY(qreal); + void setItemPos(const QPointF &); + void setItemSize(const QSizeF &); + + void updateOnComplete(); + void updateMe(); + + // QSGItemGeometryListener interface + void itemGeometryChanged(QSGItem *, const QRectF &, const QRectF &); + QSGAnchorsPrivate *anchorPrivate() { return this; } + + bool checkHValid() const; + bool checkVValid() const; + bool checkHAnchorValid(QSGAnchorLine anchor) const; + bool checkVAnchorValid(QSGAnchorLine anchor) const; + bool calcStretch(const QSGAnchorLine &edge1, const QSGAnchorLine &edge2, qreal offset1, qreal offset2, QSGAnchorLine::AnchorLine line, qreal &stretch); + + bool isMirrored() const; + void updateHorizontalAnchors(); + void updateVerticalAnchors(); + void fillChanged(); + void centerInChanged(); + + QSGItem *item; + QSGAnchors::Anchors usedAnchors; + + QSGItem *fill; + QSGItem *centerIn; + + QSGAnchorLine left; + QSGAnchorLine right; + QSGAnchorLine top; + QSGAnchorLine bottom; + QSGAnchorLine vCenter; + QSGAnchorLine hCenter; + QSGAnchorLine baseline; + + qreal leftMargin; + qreal rightMargin; + qreal topMargin; + qreal bottomMargin; + qreal margins; + qreal vCenterOffset; + qreal hCenterOffset; + qreal baselineOffset; + + static inline QSGAnchorsPrivate *get(QSGAnchors *o) { + return static_cast<QSGAnchorsPrivate *>(QObjectPrivate::get(o)); + } +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSGAnchorLine) + +#endif diff --git a/src/declarative/items/qsganimatedimage.cpp b/src/declarative/items/qsganimatedimage.cpp new file mode 100644 index 0000000000..f036042ce2 --- /dev/null +++ b/src/declarative/items/qsganimatedimage.cpp @@ -0,0 +1,304 @@ +// Commit: af33f9f2e7ec433b81f5c18e3e7395db4a56c5fe +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsganimatedimage_p.h" +#include "qsganimatedimage_p_p.h" + +#ifndef QT_NO_MOVIE + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qmovie.h> +#include <QtNetwork/qnetworkrequest.h> +#include <QtNetwork/qnetworkreply.h> + +#include <private/qdeclarativeengine_p.h> + +QT_BEGIN_NAMESPACE + +QSGAnimatedImage::QSGAnimatedImage(QSGItem *parent) + : QSGImage(*(new QSGAnimatedImagePrivate), parent) +{ +} + +QSGAnimatedImage::~QSGAnimatedImage() +{ + Q_D(QSGAnimatedImage); + delete d->_movie; +} + +bool QSGAnimatedImage::isPaused() const +{ + Q_D(const QSGAnimatedImage); + if(!d->_movie) + return false; + return d->_movie->state()==QMovie::Paused; +} + +void QSGAnimatedImage::setPaused(bool pause) +{ + Q_D(QSGAnimatedImage); + if(pause == d->paused) + return; + d->paused = pause; + if(!d->_movie) + return; + d->_movie->setPaused(pause); +} + +bool QSGAnimatedImage::isPlaying() const +{ + Q_D(const QSGAnimatedImage); + if (!d->_movie) + return false; + return d->_movie->state()!=QMovie::NotRunning; +} + +void QSGAnimatedImage::setPlaying(bool play) +{ + Q_D(QSGAnimatedImage); + if(play == d->playing) + return; + d->playing = play; + if (!d->_movie) + return; + if (play) + d->_movie->start(); + else + d->_movie->stop(); +} + +int QSGAnimatedImage::currentFrame() const +{ + Q_D(const QSGAnimatedImage); + if (!d->_movie) + return d->preset_currentframe; + return d->_movie->currentFrameNumber(); +} + +void QSGAnimatedImage::setCurrentFrame(int frame) +{ + Q_D(QSGAnimatedImage); + if (!d->_movie) { + d->preset_currentframe = frame; + return; + } + d->_movie->jumpToFrame(frame); +} + +int QSGAnimatedImage::frameCount() const +{ + Q_D(const QSGAnimatedImage); + if (!d->_movie) + return 0; + return d->_movie->frameCount(); +} + +void QSGAnimatedImage::setSource(const QUrl &url) +{ + Q_D(QSGAnimatedImage); + if (url == d->url) + return; + + delete d->_movie; + d->_movie = 0; + + if (d->reply) { + d->reply->deleteLater(); + d->reply = 0; + } + + d->url = url; + emit sourceChanged(d->url); + + if (isComponentComplete()) + load(); +} + +void QSGAnimatedImage::load() +{ + Q_D(QSGAnimatedImage); + + QSGImageBase::Status oldStatus = d->status; + qreal oldProgress = d->progress; + + if (d->url.isEmpty()) { + delete d->_movie; + d->setPixmap(QPixmap()); + d->progress = 0; + d->status = Null; + if (d->status != oldStatus) + emit statusChanged(d->status); + if (d->progress != oldProgress) + emit progressChanged(d->progress); + } else { +#ifndef QT_NO_LOCALFILE_OPTIMIZED_QML + QString lf = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(d->url); + if (!lf.isEmpty()) { + //### should be unified with movieRequestFinished + d->_movie = new QMovie(lf); + if (!d->_movie->isValid()){ + qmlInfo(this) << "Error Reading Animated Image File " << d->url.toString(); + delete d->_movie; + d->_movie = 0; + d->status = Error; + if (d->status != oldStatus) + emit statusChanged(d->status); + return; + } + connect(d->_movie, SIGNAL(stateChanged(QMovie::MovieState)), + this, SLOT(playingStatusChanged())); + connect(d->_movie, SIGNAL(frameChanged(int)), + this, SLOT(movieUpdate())); + d->_movie->setCacheMode(QMovie::CacheAll); + if(d->playing) + d->_movie->start(); + else + d->_movie->jumpToFrame(0); + if(d->paused) + d->_movie->setPaused(true); + d->setPixmap(d->_movie->currentPixmap()); + d->status = Ready; + d->progress = 1.0; + if (d->status != oldStatus) + emit statusChanged(d->status); + if (d->progress != oldProgress) + emit progressChanged(d->progress); + return; + } +#endif + d->status = Loading; + d->progress = 0; + emit statusChanged(d->status); + emit progressChanged(d->progress); + QNetworkRequest req(d->url); + req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + d->reply = qmlEngine(this)->networkAccessManager()->get(req); + QObject::connect(d->reply, SIGNAL(finished()), + this, SLOT(movieRequestFinished())); + QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), + this, SLOT(requestProgress(qint64,qint64))); + } +} + +#define ANIMATEDIMAGE_MAXIMUM_REDIRECT_RECURSION 16 + +void QSGAnimatedImage::movieRequestFinished() +{ + Q_D(QSGAnimatedImage); + + d->redirectCount++; + if (d->redirectCount < ANIMATEDIMAGE_MAXIMUM_REDIRECT_RECURSION) { + QVariant redirect = d->reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (redirect.isValid()) { + QUrl url = d->reply->url().resolved(redirect.toUrl()); + d->reply->deleteLater(); + d->reply = 0; + setSource(url); + return; + } + } + d->redirectCount=0; + + d->_movie = new QMovie(d->reply); + if (!d->_movie->isValid()){ +#ifndef QT_NO_DEBUG_STREAM + qmlInfo(this) << "Error Reading Animated Image File " << d->url; +#endif + delete d->_movie; + d->_movie = 0; + d->status = Error; + emit statusChanged(d->status); + return; + } + connect(d->_movie, SIGNAL(stateChanged(QMovie::MovieState)), + this, SLOT(playingStatusChanged())); + connect(d->_movie, SIGNAL(frameChanged(int)), + this, SLOT(movieUpdate())); + d->_movie->setCacheMode(QMovie::CacheAll); + if(d->playing) + d->_movie->start(); + if (d->paused || !d->playing) { + d->_movie->jumpToFrame(d->preset_currentframe); + d->preset_currentframe = 0; + } + if(d->paused) + d->_movie->setPaused(true); + d->setPixmap(d->_movie->currentPixmap()); + d->status = Ready; + emit statusChanged(d->status); +} + +void QSGAnimatedImage::movieUpdate() +{ + Q_D(QSGAnimatedImage); + d->setPixmap(d->_movie->currentPixmap()); + emit frameChanged(); +} + +void QSGAnimatedImage::playingStatusChanged() +{ + Q_D(QSGAnimatedImage); + if((d->_movie->state() != QMovie::NotRunning) != d->playing){ + d->playing = (d->_movie->state() != QMovie::NotRunning); + emit playingChanged(); + } + if((d->_movie->state() == QMovie::Paused) != d->paused){ + d->playing = (d->_movie->state() == QMovie::Paused); + emit pausedChanged(); + } +} + +void QSGAnimatedImage::componentComplete() +{ + Q_D(QSGAnimatedImage); + QSGItem::componentComplete(); // NOT QSGImage + if (d->url.isValid()) + load(); + if (!d->reply) { + setCurrentFrame(d->preset_currentframe); + d->preset_currentframe = 0; + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_MOVIE diff --git a/src/declarative/items/qsganimatedimage_p.h b/src/declarative/items/qsganimatedimage_p.h new file mode 100644 index 0000000000..64319a0f0d --- /dev/null +++ b/src/declarative/items/qsganimatedimage_p.h @@ -0,0 +1,117 @@ +// Commit: 80d0fe9cbd92288a08d5ced8767f1edb651dae37 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGANIMATEDIMAGE_P_H +#define QSGANIMATEDIMAGE_P_H + +#include "qsgimage_p.h" + +#ifndef QT_NO_MOVIE + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QMovie; +class QSGAnimatedImagePrivate; + +class Q_AUTOTEST_EXPORT QSGAnimatedImage : public QSGImage +{ + Q_OBJECT + + Q_PROPERTY(bool playing READ isPlaying WRITE setPlaying NOTIFY playingChanged) + Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged) + Q_PROPERTY(int currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY frameChanged) + Q_PROPERTY(int frameCount READ frameCount) + + // read-only for AnimatedImage + Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) + +public: + QSGAnimatedImage(QSGItem *parent=0); + ~QSGAnimatedImage(); + + bool isPlaying() const; + void setPlaying(bool play); + + bool isPaused() const; + void setPaused(bool pause); + + int currentFrame() const; + void setCurrentFrame(int frame); + + int frameCount() const; + + // Extends QSGImage's src property*/ + virtual void setSource(const QUrl&); + +Q_SIGNALS: + void playingChanged(); + void pausedChanged(); + void frameChanged(); + void sourceSizeChanged(); + +private Q_SLOTS: + void movieUpdate(); + void movieRequestFinished(); + void playingStatusChanged(); + +protected: + virtual void load(); + void componentComplete(); + +private: + Q_DISABLE_COPY(QSGAnimatedImage) + Q_DECLARE_PRIVATE(QSGAnimatedImage) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGAnimatedImage) + +QT_END_HEADER + +#endif // QT_NO_MOVIE + +#endif // QSGANIMATEDIMAGE_P_H diff --git a/src/declarative/items/qsganimatedimage_p_p.h b/src/declarative/items/qsganimatedimage_p_p.h new file mode 100644 index 0000000000..560c8c1d43 --- /dev/null +++ b/src/declarative/items/qsganimatedimage_p_p.h @@ -0,0 +1,88 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGANIMATEDIMAGE_P_P_H +#define QSGANIMATEDIMAGE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgimage_p_p.h" + +#ifndef QT_NO_MOVIE + +QT_BEGIN_NAMESPACE + +class QMovie; +class QNetworkReply; + +class QSGAnimatedImagePrivate : public QSGImagePrivate +{ + Q_DECLARE_PUBLIC(QSGAnimatedImage) + +public: + QSGAnimatedImagePrivate() + : playing(true), paused(false), preset_currentframe(0), _movie(0), reply(0), redirectCount(0) + { + } + + bool playing; + bool paused; + int preset_currentframe; + QMovie *_movie; + QNetworkReply *reply; + int redirectCount; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_MOVIE + +#endif // QSGANIMATEDIMAGE_P_P_H diff --git a/src/declarative/items/qsganimation.cpp b/src/declarative/items/qsganimation.cpp new file mode 100644 index 0000000000..ad6ed030fd --- /dev/null +++ b/src/declarative/items/qsganimation.cpp @@ -0,0 +1,442 @@ +// Commit: 91501cc9b542de644cd70098a6bc5ff738cdeb49 +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsganimation_p.h" +#include "qsganimation_p_p.h" +#include "qsgstateoperations_p.h" + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtCore/qmath.h> +#include <QtCore/qsequentialanimationgroup.h> +#include <QtCore/qparallelanimationgroup.h> + +QT_BEGIN_NAMESPACE + +QSGParentAnimation::QSGParentAnimation(QObject *parent) + : QDeclarativeAnimationGroup(*(new QSGParentAnimationPrivate), parent) +{ + Q_D(QSGParentAnimation); + d->topLevelGroup = new QSequentialAnimationGroup; + QDeclarative_setParent_noEvent(d->topLevelGroup, this); + + d->startAction = new QActionAnimation; + QDeclarative_setParent_noEvent(d->startAction, d->topLevelGroup); + d->topLevelGroup->addAnimation(d->startAction); + + d->ag = new QParallelAnimationGroup; + QDeclarative_setParent_noEvent(d->ag, d->topLevelGroup); + d->topLevelGroup->addAnimation(d->ag); + + d->endAction = new QActionAnimation; + QDeclarative_setParent_noEvent(d->endAction, d->topLevelGroup); + d->topLevelGroup->addAnimation(d->endAction); +} + +QSGParentAnimation::~QSGParentAnimation() +{ +} + +QSGItem *QSGParentAnimation::target() const +{ + Q_D(const QSGParentAnimation); + return d->target; +} + +void QSGParentAnimation::setTarget(QSGItem *target) +{ + Q_D(QSGParentAnimation); + if (target == d->target) + return; + + d->target = target; + emit targetChanged(); +} + +QSGItem *QSGParentAnimation::newParent() const +{ + Q_D(const QSGParentAnimation); + return d->newParent; +} + +void QSGParentAnimation::setNewParent(QSGItem *newParent) +{ + Q_D(QSGParentAnimation); + if (newParent == d->newParent) + return; + + d->newParent = newParent; + emit newParentChanged(); +} + +QSGItem *QSGParentAnimation::via() const +{ + Q_D(const QSGParentAnimation); + return d->via; +} + +void QSGParentAnimation::setVia(QSGItem *via) +{ + Q_D(QSGParentAnimation); + if (via == d->via) + return; + + d->via = via; + emit viaChanged(); +} + +//### mirrors same-named function in QSGItem +QPointF QSGParentAnimationPrivate::computeTransformOrigin(QSGItem::TransformOrigin origin, qreal width, qreal height) const +{ + switch(origin) { + default: + case QSGItem::TopLeft: + return QPointF(0, 0); + case QSGItem::Top: + return QPointF(width / 2., 0); + case QSGItem::TopRight: + return QPointF(width, 0); + case QSGItem::Left: + return QPointF(0, height / 2.); + case QSGItem::Center: + return QPointF(width / 2., height / 2.); + case QSGItem::Right: + return QPointF(width, height / 2.); + case QSGItem::BottomLeft: + return QPointF(0, height); + case QSGItem::Bottom: + return QPointF(width / 2., height); + case QSGItem::BottomRight: + return QPointF(width, height); + } +} + +void QSGParentAnimation::transition(QDeclarativeStateActions &actions, + QDeclarativeProperties &modified, + TransitionDirection direction) +{ + Q_D(QSGParentAnimation); + + struct QSGParentAnimationData : public QAbstractAnimationAction + { + QSGParentAnimationData() {} + ~QSGParentAnimationData() { qDeleteAll(pc); } + + QDeclarativeStateActions actions; + //### reverse should probably apply on a per-action basis + bool reverse; + QList<QSGParentChange *> pc; + virtual void doAction() + { + for (int ii = 0; ii < actions.count(); ++ii) { + const QDeclarativeAction &action = actions.at(ii); + if (reverse) + action.event->reverse(); + else + action.event->execute(); + } + } + }; + + QSGParentAnimationData *data = new QSGParentAnimationData; + QSGParentAnimationData *viaData = new QSGParentAnimationData; + + bool hasExplicit = false; + if (d->target && d->newParent) { + data->reverse = false; + QDeclarativeAction myAction; + QSGParentChange *pc = new QSGParentChange; + pc->setObject(d->target); + pc->setParent(d->newParent); + myAction.event = pc; + data->pc << pc; + data->actions << myAction; + hasExplicit = true; + if (d->via) { + viaData->reverse = false; + QDeclarativeAction myVAction; + QSGParentChange *vpc = new QSGParentChange; + vpc->setObject(d->target); + vpc->setParent(d->via); + myVAction.event = vpc; + viaData->pc << vpc; + viaData->actions << myVAction; + } + //### once actions have concept of modified, + // loop to match appropriate ParentChanges and mark as modified + } + + if (!hasExplicit) + for (int i = 0; i < actions.size(); ++i) { + QDeclarativeAction &action = actions[i]; + if (action.event && action.event->typeName() == QLatin1String("ParentChange") + && (!d->target || static_cast<QSGParentChange*>(action.event)->object() == d->target)) { + + QSGParentChange *pc = static_cast<QSGParentChange*>(action.event); + QDeclarativeAction myAction = action; + data->reverse = action.reverseEvent; + + //### this logic differs from PropertyAnimation + // (probably a result of modified vs. done) + if (d->newParent) { + QSGParentChange *epc = new QSGParentChange; + epc->setObject(static_cast<QSGParentChange*>(action.event)->object()); + epc->setParent(d->newParent); + myAction.event = epc; + data->pc << epc; + data->actions << myAction; + pc = epc; + } else { + action.actionDone = true; + data->actions << myAction; + } + + if (d->via) { + viaData->reverse = false; + QDeclarativeAction myAction; + QSGParentChange *vpc = new QSGParentChange; + vpc->setObject(pc->object()); + vpc->setParent(d->via); + myAction.event = vpc; + viaData->pc << vpc; + viaData->actions << myAction; + QDeclarativeAction dummyAction; + QDeclarativeAction &xAction = pc->xIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; + QDeclarativeAction &yAction = pc->yIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; + QDeclarativeAction &sAction = pc->scaleIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; + QDeclarativeAction &rAction = pc->rotationIsSet() && i < actions.size()-1 ? actions[++i] : dummyAction; + QSGItem *target = pc->object(); + QSGItem *targetParent = action.reverseEvent ? pc->originalParent() : pc->parent(); + + //### this mirrors the logic in QSGParentChange. + bool ok; + const QTransform &transform = targetParent->itemTransform(d->via, &ok); + if (transform.type() >= QTransform::TxShear || !ok) { + qmlInfo(this) << QSGParentAnimation::tr("Unable to preserve appearance under complex transform"); + ok = false; + } + + qreal scale = 1; + qreal rotation = 0; + bool isRotate = (transform.type() == QTransform::TxRotate) || (transform.m11() < 0); + if (ok && !isRotate) { + if (transform.m11() == transform.m22()) + scale = transform.m11(); + else { + qmlInfo(this) << QSGParentAnimation::tr("Unable to preserve appearance under non-uniform scale"); + ok = false; + } + } else if (ok && isRotate) { + if (transform.m11() == transform.m22()) + scale = qSqrt(transform.m11()*transform.m11() + transform.m12()*transform.m12()); + else { + qmlInfo(this) << QSGParentAnimation::tr("Unable to preserve appearance under non-uniform scale"); + ok = false; + } + + if (scale != 0) + rotation = atan2(transform.m12()/scale, transform.m11()/scale) * 180/M_PI; + else { + qmlInfo(this) << QSGParentAnimation::tr("Unable to preserve appearance under scale of 0"); + ok = false; + } + } + + const QPointF &point = transform.map(QPointF(xAction.toValue.toReal(),yAction.toValue.toReal())); + qreal x = point.x(); + qreal y = point.y(); + if (ok && target->transformOrigin() != QSGItem::TopLeft) { + qreal w = target->width(); + qreal h = target->height(); + if (pc->widthIsSet() && i < actions.size() - 1) + w = actions[++i].toValue.toReal(); + if (pc->heightIsSet() && i < actions.size() - 1) + h = actions[++i].toValue.toReal(); + const QPointF &transformOrigin + = d->computeTransformOrigin(target->transformOrigin(), w,h); + qreal tempxt = transformOrigin.x(); + qreal tempyt = transformOrigin.y(); + QTransform t; + t.translate(-tempxt, -tempyt); + t.rotate(rotation); + t.scale(scale, scale); + t.translate(tempxt, tempyt); + const QPointF &offset = t.map(QPointF(0,0)); + x += offset.x(); + y += offset.y(); + } + + if (ok) { + //qDebug() << x << y << rotation << scale; + xAction.toValue = x; + yAction.toValue = y; + sAction.toValue = sAction.toValue.toReal() * scale; + rAction.toValue = rAction.toValue.toReal() + rotation; + } + } + } + } + + if (data->actions.count()) { + if (direction == QDeclarativeAbstractAnimation::Forward) { + d->startAction->setAnimAction(d->via ? viaData : data, QActionAnimation::DeleteWhenStopped); + d->endAction->setAnimAction(d->via ? data : 0, QActionAnimation::DeleteWhenStopped); + } else { + d->endAction->setAnimAction(d->via ? viaData : data, QActionAnimation::DeleteWhenStopped); + d->startAction->setAnimAction(d->via ? data : 0, QActionAnimation::DeleteWhenStopped); + } + } else { + delete data; + delete viaData; + } + + //take care of any child animations + bool valid = d->defaultProperty.isValid(); + for (int ii = 0; ii < d->animations.count(); ++ii) { + if (valid) + d->animations.at(ii)->setDefaultTarget(d->defaultProperty); + d->animations.at(ii)->transition(actions, modified, direction); + } + +} + +QAbstractAnimation *QSGParentAnimation::qtAnimation() +{ + Q_D(QSGParentAnimation); + return d->topLevelGroup; +} + +QSGAnchorAnimation::QSGAnchorAnimation(QObject *parent) +: QDeclarativeAbstractAnimation(*(new QSGAnchorAnimationPrivate), parent) +{ + Q_D(QSGAnchorAnimation); + d->va = new QDeclarativeBulkValueAnimator; + QDeclarative_setParent_noEvent(d->va, this); +} + +QSGAnchorAnimation::~QSGAnchorAnimation() +{ +} + +QAbstractAnimation *QSGAnchorAnimation::qtAnimation() +{ + Q_D(QSGAnchorAnimation); + return d->va; +} + +QDeclarativeListProperty<QSGItem> QSGAnchorAnimation::targets() +{ + Q_D(QSGAnchorAnimation); + return QDeclarativeListProperty<QSGItem>(this, d->targets); +} + +int QSGAnchorAnimation::duration() const +{ + Q_D(const QSGAnchorAnimation); + return d->va->duration(); +} + +void QSGAnchorAnimation::setDuration(int duration) +{ + if (duration < 0) { + qmlInfo(this) << tr("Cannot set a duration of < 0"); + return; + } + + Q_D(QSGAnchorAnimation); + if (d->va->duration() == duration) + return; + d->va->setDuration(duration); + emit durationChanged(duration); +} + +QEasingCurve QSGAnchorAnimation::easing() const +{ + Q_D(const QSGAnchorAnimation); + return d->va->easingCurve(); +} + +void QSGAnchorAnimation::setEasing(const QEasingCurve &e) +{ + Q_D(QSGAnchorAnimation); + if (d->va->easingCurve() == e) + return; + + d->va->setEasingCurve(e); + emit easingChanged(e); +} + +void QSGAnchorAnimation::transition(QDeclarativeStateActions &actions, + QDeclarativeProperties &modified, + TransitionDirection direction) +{ + Q_UNUSED(modified); + Q_D(QSGAnchorAnimation); + QDeclarativeAnimationPropertyUpdater *data = new QDeclarativeAnimationPropertyUpdater; + data->interpolatorType = QMetaType::QReal; + data->interpolator = d->interpolator; + + data->reverse = direction == Backward ? true : false; + data->fromSourced = false; + data->fromDefined = false; + + for (int ii = 0; ii < actions.count(); ++ii) { + QDeclarativeAction &action = actions[ii]; + if (action.event && action.event->typeName() == QLatin1String("AnchorChanges") + && (d->targets.isEmpty() || d->targets.contains(static_cast<QSGAnchorChanges*>(action.event)->object()))) { + data->actions << static_cast<QSGAnchorChanges*>(action.event)->additionalActions(); + } + } + + if (data->actions.count()) { + if (!d->rangeIsSet) { + d->va->setStartValue(qreal(0)); + d->va->setEndValue(qreal(1)); + d->rangeIsSet = true; + } + d->va->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped); + d->va->setFromSourcedValue(&data->fromSourced); + } else { + delete data; + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsganimation_p.h b/src/declarative/items/qsganimation_p.h new file mode 100644 index 0000000000..7c63331cfe --- /dev/null +++ b/src/declarative/items/qsganimation_p.h @@ -0,0 +1,132 @@ +// Commit: e39a2e39451bf106a9845f8a60fc571faaa4dde5 +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGANIMATION_H +#define QSGANIMATION_H + +#include "qsgitem.h" + +#include <private/qdeclarativeanimation_p.h> + +#include <QtCore/qabstractanimation.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGParentAnimationPrivate; +class QSGParentAnimation : public QDeclarativeAnimationGroup +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGParentAnimation) + + Q_PROPERTY(QSGItem *target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(QSGItem *newParent READ newParent WRITE setNewParent NOTIFY newParentChanged) + Q_PROPERTY(QSGItem *via READ via WRITE setVia NOTIFY viaChanged) + +public: + QSGParentAnimation(QObject *parent=0); + virtual ~QSGParentAnimation(); + + QSGItem *target() const; + void setTarget(QSGItem *); + + QSGItem *newParent() const; + void setNewParent(QSGItem *); + + QSGItem *via() const; + void setVia(QSGItem *); + +Q_SIGNALS: + void targetChanged(); + void newParentChanged(); + void viaChanged(); + +protected: + virtual void transition(QDeclarativeStateActions &actions, + QDeclarativeProperties &modified, + TransitionDirection direction); + virtual QAbstractAnimation *qtAnimation(); +}; + +class QSGAnchorAnimationPrivate; +class QSGAnchorAnimation : public QDeclarativeAbstractAnimation +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGAnchorAnimation) + Q_PROPERTY(QDeclarativeListProperty<QSGItem> targets READ targets) + Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged) + Q_PROPERTY(QEasingCurve easing READ easing WRITE setEasing NOTIFY easingChanged) + +public: + QSGAnchorAnimation(QObject *parent=0); + virtual ~QSGAnchorAnimation(); + + QDeclarativeListProperty<QSGItem> targets(); + + int duration() const; + void setDuration(int); + + QEasingCurve easing() const; + void setEasing(const QEasingCurve &); + +Q_SIGNALS: + void durationChanged(int); + void easingChanged(const QEasingCurve&); + +protected: + virtual void transition(QDeclarativeStateActions &actions, + QDeclarativeProperties &modified, + TransitionDirection direction); + virtual QAbstractAnimation *qtAnimation(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGParentAnimation) +QML_DECLARE_TYPE(QSGAnchorAnimation) + +QT_END_HEADER + +#endif // QSGANIMATION_H diff --git a/src/declarative/items/qsganimation_p_p.h b/src/declarative/items/qsganimation_p_p.h new file mode 100644 index 0000000000..10457d6b52 --- /dev/null +++ b/src/declarative/items/qsganimation_p_p.h @@ -0,0 +1,97 @@ +// Commit: 0ade09152067324f74678f2de4d447b6e0280600 +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGANIMATION_P_H +#define QSGANIMATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsganimation_p.h" + +#include <private/qdeclarativeanimation_p_p.h> + +QT_BEGIN_NAMESPACE + +class QSGParentAnimationPrivate : public QDeclarativeAnimationGroupPrivate +{ + Q_DECLARE_PUBLIC(QSGParentAnimation) +public: + QSGParentAnimationPrivate() + : QDeclarativeAnimationGroupPrivate(), target(0), newParent(0), + via(0), topLevelGroup(0), startAction(0), endAction(0) {} + + QSGItem *target; + QSGItem *newParent; + QSGItem *via; + + QSequentialAnimationGroup *topLevelGroup; + QActionAnimation *startAction; + QActionAnimation *endAction; + + QPointF computeTransformOrigin(QSGItem::TransformOrigin origin, qreal width, qreal height) const; +}; + +class QSGAnchorAnimationPrivate : public QDeclarativeAbstractAnimationPrivate +{ + Q_DECLARE_PUBLIC(QSGAnchorAnimation) +public: + QSGAnchorAnimationPrivate() : rangeIsSet(false), va(0), + interpolator(QVariantAnimationPrivate::getInterpolator(QMetaType::QReal)) {} + + bool rangeIsSet; + QDeclarativeBulkValueAnimator *va; + QVariantAnimation::Interpolator interpolator; + QList<QSGItem*> targets; +}; + +QT_END_NAMESPACE + +#endif // QSGANIMATION_P_H diff --git a/src/declarative/items/qsgborderimage.cpp b/src/declarative/items/qsgborderimage.cpp new file mode 100644 index 0000000000..108d87ef28 --- /dev/null +++ b/src/declarative/items/qsgborderimage.cpp @@ -0,0 +1,359 @@ +// Commit: 462429f5692f810bdd4e04b916db5f9af428d9e4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgborderimage_p.h" +#include "qsgborderimage_p_p.h" +#include "qsgninepatchnode_p.h" + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtCore/qfile.h> + +#include <private/qdeclarativeengine_p.h> + +QT_BEGIN_NAMESPACE + +QSGBorderImage::QSGBorderImage(QSGItem *parent) +: QSGImageBase(*(new QSGBorderImagePrivate), parent) +{ +} + +QSGBorderImage::~QSGBorderImage() +{ + Q_D(QSGBorderImage); + if (d->sciReply) + d->sciReply->deleteLater(); +} + +void QSGBorderImage::setSource(const QUrl &url) +{ + Q_D(QSGBorderImage); + //equality is fairly expensive, so we bypass for simple, common case + if ((d->url.isEmpty() == url.isEmpty()) && url == d->url) + return; + + if (d->sciReply) { + d->sciReply->deleteLater(); + d->sciReply = 0; + } + + d->url = url; + d->sciurl = QUrl(); + emit sourceChanged(d->url); + + if (isComponentComplete()) + load(); +} + +void QSGBorderImage::load() +{ + Q_D(QSGBorderImage); + if (d->progress != 0.0) { + d->progress = 0.0; + emit progressChanged(d->progress); + } + + if (d->url.isEmpty()) { + d->pix.clear(this); + d->status = Null; + setImplicitWidth(0); + setImplicitHeight(0); + emit statusChanged(d->status); + update(); + } else { + d->status = Loading; + if (d->url.path().endsWith(QLatin1String("sci"))) { +#ifndef QT_NO_LOCALFILE_OPTIMIZED_QML + QString lf = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(d->url); + if (!lf.isEmpty()) { + QFile file(lf); + file.open(QIODevice::ReadOnly); + setGridScaledImage(QSGGridScaledImage(&file)); + } else +#endif + { + QNetworkRequest req(d->url); + d->sciReply = qmlEngine(this)->networkAccessManager()->get(req); + + static int sciReplyFinished = -1; + static int thisSciRequestFinished = -1; + if (sciReplyFinished == -1) { + sciReplyFinished = + QNetworkReply::staticMetaObject.indexOfSignal("finished()"); + thisSciRequestFinished = + QSGBorderImage::staticMetaObject.indexOfSlot("sciRequestFinished()"); + } + + QMetaObject::connect(d->sciReply, sciReplyFinished, this, + thisSciRequestFinished, Qt::DirectConnection); + } + } else { + + QDeclarativePixmap::Options options; + if (d->async) + options |= QDeclarativePixmap::Asynchronous; + if (d->cache) + options |= QDeclarativePixmap::Cache; + d->pix.clear(this); + d->pix.load(qmlEngine(this), d->url, options); + + if (d->pix.isLoading()) { + d->pix.connectFinished(this, SLOT(requestFinished())); + d->pix.connectDownloadProgress(this, SLOT(requestProgress(qint64,qint64))); + } else { + QSize impsize = d->pix.implicitSize(); + setImplicitWidth(impsize.width()); + setImplicitHeight(impsize.height()); + + if (d->pix.isReady()) { + d->status = Ready; + } else { + d->status = Error; + qmlInfo(this) << d->pix.error(); + } + + d->progress = 1.0; + emit statusChanged(d->status); + emit progressChanged(d->progress); + update(); + } + } + } + + emit statusChanged(d->status); +} + +QSGScaleGrid *QSGBorderImage::border() +{ + Q_D(QSGBorderImage); + return d->getScaleGrid(); +} + +QSGBorderImage::TileMode QSGBorderImage::horizontalTileMode() const +{ + Q_D(const QSGBorderImage); + return d->horizontalTileMode; +} + +void QSGBorderImage::setHorizontalTileMode(TileMode t) +{ + Q_D(QSGBorderImage); + if (t != d->horizontalTileMode) { + d->horizontalTileMode = t; + emit horizontalTileModeChanged(); + update(); + } +} + +QSGBorderImage::TileMode QSGBorderImage::verticalTileMode() const +{ + Q_D(const QSGBorderImage); + return d->verticalTileMode; +} + +void QSGBorderImage::setVerticalTileMode(TileMode t) +{ + Q_D(QSGBorderImage); + if (t != d->verticalTileMode) { + d->verticalTileMode = t; + emit verticalTileModeChanged(); + update(); + } +} + +void QSGBorderImage::setGridScaledImage(const QSGGridScaledImage& sci) +{ + Q_D(QSGBorderImage); + if (!sci.isValid()) { + d->status = Error; + emit statusChanged(d->status); + } else { + QSGScaleGrid *sg = border(); + sg->setTop(sci.gridTop()); + sg->setBottom(sci.gridBottom()); + sg->setLeft(sci.gridLeft()); + sg->setRight(sci.gridRight()); + d->horizontalTileMode = sci.horizontalTileRule(); + d->verticalTileMode = sci.verticalTileRule(); + + d->sciurl = d->url.resolved(QUrl(sci.pixmapUrl())); + + QDeclarativePixmap::Options options; + if (d->async) + options |= QDeclarativePixmap::Asynchronous; + if (d->cache) + options |= QDeclarativePixmap::Cache; + d->pix.clear(this); + d->pix.load(qmlEngine(this), d->sciurl, options); + + if (d->pix.isLoading()) { + static int thisRequestProgress = -1; + static int thisRequestFinished = -1; + if (thisRequestProgress == -1) { + thisRequestProgress = + QSGBorderImage::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)"); + thisRequestFinished = + QSGBorderImage::staticMetaObject.indexOfSlot("requestFinished()"); + } + + d->pix.connectFinished(this, thisRequestFinished); + d->pix.connectDownloadProgress(this, thisRequestProgress); + + } else { + + QSize impsize = d->pix.implicitSize(); + setImplicitWidth(impsize.width()); + setImplicitHeight(impsize.height()); + + if (d->pix.isReady()) { + d->status = Ready; + } else { + d->status = Error; + qmlInfo(this) << d->pix.error(); + } + + d->progress = 1.0; + emit statusChanged(d->status); + emit progressChanged(1.0); + update(); + + } + } +} + +void QSGBorderImage::requestFinished() +{ + Q_D(QSGBorderImage); + + QSize impsize = d->pix.implicitSize(); + if (d->pix.isError()) { + d->status = Error; + qmlInfo(this) << d->pix.error(); + } else { + d->status = Ready; + } + + setImplicitWidth(impsize.width()); + setImplicitHeight(impsize.height()); + + if (d->sourcesize.width() != d->pix.width() || d->sourcesize.height() != d->pix.height()) + emit sourceSizeChanged(); + + d->progress = 1.0; + emit statusChanged(d->status); + emit progressChanged(1.0); + update(); +} + +#define BORDERIMAGE_MAX_REDIRECT 16 + +void QSGBorderImage::sciRequestFinished() +{ + Q_D(QSGBorderImage); + + d->redirectCount++; + if (d->redirectCount < BORDERIMAGE_MAX_REDIRECT) { + QVariant redirect = d->sciReply->attribute(QNetworkRequest::RedirectionTargetAttribute); + if (redirect.isValid()) { + QUrl url = d->sciReply->url().resolved(redirect.toUrl()); + setSource(url); + return; + } + } + d->redirectCount=0; + + if (d->sciReply->error() != QNetworkReply::NoError) { + d->status = Error; + d->sciReply->deleteLater(); + d->sciReply = 0; + emit statusChanged(d->status); + } else { + QSGGridScaledImage sci(d->sciReply); + d->sciReply->deleteLater(); + d->sciReply = 0; + setGridScaledImage(sci); + } +} + +void QSGBorderImage::doUpdate() +{ + update(); +} + +QSGNode *QSGBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + Q_D(QSGBorderImage); + + if (!d->pix.texture() || width() <= 0 || height() <= 0) { + delete oldNode; + return 0; + } + + QSGNinePatchNode *node = static_cast<QSGNinePatchNode *>(oldNode); + + if (!node) { + node = new QSGNinePatchNode(); + } + + node->setTexture(d->pix.texture()); + + const QSGScaleGrid *border = d->getScaleGrid(); + node->setInnerRect(QRectF(border->left(), + border->top(), + d->pix.width() - border->right() - border->left(), + d->pix.height() - border->bottom() - border->top())); + node->setRect(QRectF(0, 0, width(), height())); + node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + node->setHorzontalTileMode(d->horizontalTileMode); + node->setVerticalTileMode(d->verticalTileMode); + node->update(); + + return node; +} + +void QSGBorderImage::pixmapChange() +{ + Q_D(QSGBorderImage); + + d->pixmapChanged = true; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgborderimage_p.h b/src/declarative/items/qsgborderimage_p.h new file mode 100644 index 0000000000..1386264779 --- /dev/null +++ b/src/declarative/items/qsgborderimage_p.h @@ -0,0 +1,110 @@ +// Commit: ebd4bc73c46c2962742a682b6a391fb68c482aec +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGBORDERIMAGE_P_H +#define QSGBORDERIMAGE_P_H + +#include "qsgimagebase_p.h" + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGScaleGrid; +class QSGGridScaledImage; +class QSGBorderImagePrivate; +class Q_AUTOTEST_EXPORT QSGBorderImage : public QSGImageBase +{ + Q_OBJECT + Q_ENUMS(TileMode) + + Q_PROPERTY(QSGScaleGrid *border READ border CONSTANT) + Q_PROPERTY(TileMode horizontalTileMode READ horizontalTileMode WRITE setHorizontalTileMode NOTIFY horizontalTileModeChanged) + Q_PROPERTY(TileMode verticalTileMode READ verticalTileMode WRITE setVerticalTileMode NOTIFY verticalTileModeChanged) + // read-only for BorderImage + Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) + +public: + QSGBorderImage(QSGItem *parent=0); + ~QSGBorderImage(); + + QSGScaleGrid *border(); + + enum TileMode { Stretch = Qt::StretchTile, Repeat = Qt::RepeatTile, Round = Qt::RoundTile }; + + TileMode horizontalTileMode() const; + void setHorizontalTileMode(TileMode); + + TileMode verticalTileMode() const; + void setVerticalTileMode(TileMode); + + void setSource(const QUrl &url); + +Q_SIGNALS: + void horizontalTileModeChanged(); + void verticalTileModeChanged(); + void sourceSizeChanged(); + +protected: + virtual void load(); + virtual void pixmapChange(); + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private: + void setGridScaledImage(const QSGGridScaledImage& sci); + +private Q_SLOTS: + void doUpdate(); + void requestFinished(); + void sciRequestFinished(); + +private: + Q_DISABLE_COPY(QSGBorderImage) + Q_DECLARE_PRIVATE(QSGBorderImage) +}; + +QT_END_NAMESPACE +QML_DECLARE_TYPE(QSGBorderImage) +QT_END_HEADER + +#endif // QSGBORDERIMAGE_P_H diff --git a/src/declarative/items/qsgborderimage_p_p.h b/src/declarative/items/qsgborderimage_p_p.h new file mode 100644 index 0000000000..2fb88d9ffd --- /dev/null +++ b/src/declarative/items/qsgborderimage_p_p.h @@ -0,0 +1,109 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGBORDERIMAGE_P_P_H +#define QSGBORDERIMAGE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgimagebase_p_p.h" +#include "qsgscalegrid_p_p.h" + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QSGBorderImagePrivate : public QSGImageBasePrivate +{ + Q_DECLARE_PUBLIC(QSGBorderImage) + +public: + QSGBorderImagePrivate() + : border(0), sciReply(0), + horizontalTileMode(QSGBorderImage::Stretch), + verticalTileMode(QSGBorderImage::Stretch), + redirectCount(0), pixmapChanged(false) + { + } + + ~QSGBorderImagePrivate() + { + } + + + QSGScaleGrid *getScaleGrid() + { + Q_Q(QSGBorderImage); + if (!border) { + border = new QSGScaleGrid(q); + static int borderChangedSignalIdx = -1; + static int doUpdateSlotIdx = -1; + if (borderChangedSignalIdx < 0) + borderChangedSignalIdx = QSGScaleGrid::staticMetaObject.indexOfSignal("borderChanged()"); + if (doUpdateSlotIdx < 0) + doUpdateSlotIdx = QSGBorderImage::staticMetaObject.indexOfSlot("doUpdate()"); + QMetaObject::connect(border, borderChangedSignalIdx, q, doUpdateSlotIdx); + } + return border; + } + + QSGScaleGrid *border; + QUrl sciurl; + QNetworkReply *sciReply; + QSGBorderImage::TileMode horizontalTileMode; + QSGBorderImage::TileMode verticalTileMode; + int redirectCount; + + bool pixmapChanged : 1; +}; + +QT_END_NAMESPACE + +#endif // QSGBORDERIMAGE_P_P_H diff --git a/src/declarative/items/qsgcanvas.cpp b/src/declarative/items/qsgcanvas.cpp new file mode 100644 index 0000000000..5d5c49d981 --- /dev/null +++ b/src/declarative/items/qsgcanvas.cpp @@ -0,0 +1,1897 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgcanvas.h" +#include "qsgcanvas_p.h" + +#include "qsgitem.h" +#include "qsgitem_p.h" + +#include <private/qsgrenderer_p.h> +#include <private/qsgflashnode_p.h> + +#include <private/qabstractanimation_p.h> + +#include <QtGui/qpainter.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qmatrix4x4.h> +#include <QtGui/qinputcontext.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qabstractanimation.h> + +#include <private/qdeclarativedebugtrace_p.h> + +QT_BEGIN_NAMESPACE + +DEFINE_BOOL_CONFIG_OPTION(qmlThreadedRenderer, QML_THREADED_RENDERER) +DEFINE_BOOL_CONFIG_OPTION(qmlFixedAnimationStep, QML_FIXED_ANIMATION_STEP) + +/* +Focus behavior +============== + +Prior to being added to a valid canvas items can set and clear focus with no +effect. Only once items are added to a canvas (by way of having a parent set that +already belongs to a canvas) do the focus rules apply. Focus goes back to +having no effect if an item is removed from a canvas. + +When an item is moved into a new focus scope (either being added to a canvas +for the first time, or having its parent changed), if the focus scope already has +a scope focused item that takes precedence over the item being added. Otherwise, +the focus of the added tree is used. In the case of of a tree of items being +added to a canvas for the first time, which may have a conflicted focus state (two +or more items in one scope having focus set), the same rule is applied item by item - +thus the first item that has focus will get it (assuming the scope doesn't already +have a scope focused item), and the other items will have their focus cleared. +*/ + +// #define FOCUS_DEBUG +// #define MOUSE_DEBUG +// #define TOUCH_DEBUG +// #define DIRTY_DEBUG +// #define THREAD_DEBUG + +// #define FRAME_TIMING + +#ifdef FRAME_TIMING +static QTime frameTimer; +int sceneGraphRenderTime; +int readbackTime; +#endif + + +class QSGAnimationDriver : public QAnimationDriver +{ +public: + QSGAnimationDriver(QWidget *w, QObject *parent) + : QAnimationDriver(parent), widget(w) + { + Q_ASSERT(w); + } + + void started() + { + widget->update(); + } + + QWidget *widget; +}; + +QSGItem::UpdatePaintNodeData::UpdatePaintNodeData() +: transformNode(0) +{ +} + +QSGRootItem::QSGRootItem() +{ +} + +QSGThreadedRendererAnimationDriver::QSGThreadedRendererAnimationDriver(QSGCanvasPrivate *r, QObject *parent) + : QAnimationDriver(parent) + , renderer(r) +{ +} + +void QSGThreadedRendererAnimationDriver::started() +{ +#ifdef THREAD_DEBUG + qWarning("AnimationDriver: Main Thread: started"); +#endif + renderer->mutex.lock(); + renderer->animationRunning = true; + if (renderer->idle) + renderer->wait.wakeOne(); + renderer->mutex.unlock(); + + +} + +void QSGThreadedRendererAnimationDriver::stopped() +{ +#ifdef THREAD_DEBUG + qWarning("AnimationDriver: Main Thread: stopped"); +#endif + renderer->mutex.lock(); + renderer->animationRunning = false; + renderer->mutex.unlock(); +} + +void QSGCanvas::paintEvent(QPaintEvent *) +{ + Q_D(QSGCanvas); + + if (!d->threadedRendering) { +#ifdef FRAME_TIMING + int lastFrame = frameTimer.restart(); +#endif + + if (d->animationDriver->isRunning()) + d->animationDriver->advance(); + +#ifdef FRAME_TIMING + int animationTime = frameTimer.elapsed(); +#endif + + Q_ASSERT(d->context); + + d->polishItems(); + + QDeclarativeDebugTrace::addEvent(QDeclarativeDebugTrace::FramePaint); + QDeclarativeDebugTrace::startRange(QDeclarativeDebugTrace::Painting); + +#ifdef FRAME_TIMING + int polishTime = frameTimer.elapsed(); +#endif + + makeCurrent(); + +#ifdef FRAME_TIMING + int makecurrentTime = frameTimer.elapsed(); +#endif + + d->syncSceneGraph(); + +#ifdef FRAME_TIMING + int syncTime = frameTimer.elapsed(); +#endif + + d->renderSceneGraph(); + +#ifdef FRAME_TIMING + printf("FrameTimes, last=%d, animations=%d, polish=%d, makeCurrent=%d, sync=%d, sgrender=%d, readback=%d, total=%d\n", + lastFrame, + animationTime, + polishTime - animationTime, + makecurrentTime - polishTime, + syncTime - makecurrentTime, + sceneGraphRenderTime - syncTime, + readbackTime - sceneGraphRenderTime, + frameTimer.elapsed()); +#endif + + QDeclarativeDebugTrace::endRange(QDeclarativeDebugTrace::Painting); + + if (d->animationDriver->isRunning()) + update(); + } +} + +void QSGCanvas::resizeEvent(QResizeEvent *e) +{ + Q_D(QSGCanvas); + if (d->threadedRendering) { + d->mutex.lock(); + QGLWidget::resizeEvent(e); + d->widgetSize = e->size(); + d->mutex.unlock(); + } else { + d->widgetSize = e->size(); + d->viewportSize = d->widgetSize; + QGLWidget::resizeEvent(e); + } +} + +void QSGCanvas::showEvent(QShowEvent *e) +{ + Q_D(QSGCanvas); + + QGLWidget::showEvent(e); + + if (d->threadedRendering) { + d->contextInThread = true; + doneCurrent(); + if (!d->animationDriver) + d->animationDriver = new QSGThreadedRendererAnimationDriver(d, this); + d->animationDriver->install(); + d->mutex.lock(); + d->thread->start(); + d->wait.wait(&d->mutex); + d->mutex.unlock(); + } else { + makeCurrent(); + + if (!d->context || !d->context->isReady()) { + d->initializeSceneGraph(); + d->animationDriver = new QSGAnimationDriver(this, this); + } + + d->animationDriver->install(); + } +} + +void QSGCanvas::hideEvent(QHideEvent *e) +{ + Q_D(QSGCanvas); + + if (d->threadedRendering) { + d->mutex.lock(); + d->exitThread = true; + d->wait.wakeOne(); + d->wait.wait(&d->mutex); + d->exitThread = false; + d->mutex.unlock(); + d->thread->wait(); + } + + d->animationDriver->uninstall(); + + QGLWidget::hideEvent(e); +} + + +void QSGCanvasPrivate::initializeSceneGraph() +{ + if (!context) + context = QSGContext::createDefaultContext(); + + if (context->isReady()) + return; + + QGLContext *glctx = const_cast<QGLContext *>(QGLContext::currentContext()); + context->initialize(glctx); + + if (!threadedRendering) { + Q_Q(QSGCanvas); + QObject::connect(context->renderer(), SIGNAL(sceneGraphChanged()), q, SLOT(maybeUpdate()), + Qt::DirectConnection); + } + + if (!QSGItemPrivate::get(rootItem)->itemNode()->parent()) { + context->rootNode()->appendChildNode(QSGItemPrivate::get(rootItem)->itemNode()); + } + + emit q_func()->sceneGraphInitialized(); +} + +void QSGCanvasPrivate::polishItems() +{ + while (!itemsToPolish.isEmpty()) { + QSet<QSGItem *>::Iterator iter = itemsToPolish.begin(); + QSGItem *item = *iter; + itemsToPolish.erase(iter); + QSGItemPrivate::get(item)->polishScheduled = false; + item->updatePolish(); + } +} + + +void QSGCanvasPrivate::syncSceneGraph() +{ + updateDirtyNodes(); +} + + +void QSGCanvasPrivate::renderSceneGraph() +{ + QGLContext *glctx = const_cast<QGLContext *>(QGLContext::currentContext()); + + context->renderer()->setDeviceRect(QRect(QPoint(0, 0), viewportSize)); + context->renderer()->setViewportRect(QRect(QPoint(0, 0), viewportSize)); + context->renderer()->setProjectMatrixToDeviceRect(); + + context->renderNextFrame(); + +#ifdef FRAME_TIMING + sceneGraphRenderTime = frameTimer.elapsed(); +#endif + + +#ifdef FRAME_TIMING +// int pixel; +// glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel); + readbackTime = frameTimer.elapsed(); +#endif + + glctx->swapBuffers(); +} + + +void QSGCanvas::sceneGraphChanged() +{ + Q_D(QSGCanvas); + d->needsRepaint = true; +} + + +void QSGCanvasPrivate::runThread() +{ +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render thread running"); +#endif + Q_Q(QSGCanvas); + + printf("QSGCanvas::runThread(), rendering in a thread...\n"); + + q->makeCurrent(); + initializeSceneGraph(); + + QObject::connect(context->renderer(), SIGNAL(sceneGraphChanged()), + q, SLOT(sceneGraphChanged()), + Qt::DirectConnection); + + mutex.lock(); + wait.wakeOne(); // Wake the main thread waiting for us to start + + while (true) { + QSize s; + s = widgetSize; + + if (exitThread) + break; + + if (s != viewportSize) { + glViewport(0, 0, s.width(), s.height()); + viewportSize = s; + } + +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: Waiting for main thread to stop"); +#endif + QCoreApplication::postEvent(q, new QEvent(QEvent::User)); + wait.wait(&mutex); + + if (exitThread) { +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: Shutting down..."); +#endif + break; + } + +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: Main thread has stopped, syncing scene"); +#endif + + // Do processing while main thread is frozen + syncSceneGraph(); + +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: Resuming main thread"); +#endif + + // Read animationRunning while inside the locked section + bool continous = animationRunning; + + wait.wakeOne(); + mutex.unlock(); + + bool enterIdle = false; + if (needsRepaint) { +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: rendering scene"); +#endif + renderSceneGraph(); + needsRepaint = false; + } else if (continous) { +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: waiting a while..."); +#endif + MyThread::doWait(); + } else { + enterIdle = true; + } + + mutex.lock(); + + if (enterIdle) { +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: Nothing has changed, going idle..."); +#endif + idle = true; + wait.wait(&mutex); + idle = false; +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: waking up from idle"); +#endif + } + + } + + +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Render Thread: shutting down, waking up main thread"); +#endif + wait.wakeOne(); + mutex.unlock(); + + q->doneCurrent(); +} + +QSGCanvasPrivate::QSGCanvasPrivate() + : rootItem(0) + , activeFocusItem(0) + , mouseGrabberItem(0) + , hoverItem(0) + , dirtyItemList(0) + , context(0) + , contextInThread(false) + , threadedRendering(false) + , exitThread(false) + , animationRunning(false) + , idle(false) + , needsRepaint(true) + , renderThreadAwakened(false) + , thread(new MyThread(this)) + , animationDriver(0) +{ + threadedRendering = qmlThreadedRenderer(); +} + +QSGCanvasPrivate::~QSGCanvasPrivate() +{ +} + +void QSGCanvasPrivate::init(QSGCanvas *c) +{ + QUnifiedTimer::instance(true)->setConsistentTiming(qmlFixedAnimationStep()); + + q_ptr = c; + + Q_Q(QSGCanvas); + + q->setAttribute(Qt::WA_AcceptTouchEvents); + q->setFocusPolicy(Qt::StrongFocus); + + rootItem = new QSGRootItem; + QSGItemPrivate *rootItemPrivate = QSGItemPrivate::get(rootItem); + rootItemPrivate->canvas = q; + rootItemPrivate->flags |= QSGItem::ItemIsFocusScope; + rootItemPrivate->focus = true; + rootItemPrivate->activeFocus = true; + activeFocusItem = rootItem; + + context = QSGContext::createDefaultContext(); +} + +void QSGCanvasPrivate::sceneMouseEventForTransform(QGraphicsSceneMouseEvent &sceneEvent, + const QTransform &transform) +{ + sceneEvent.setPos(transform.map(sceneEvent.scenePos())); + sceneEvent.setLastPos(transform.map(sceneEvent.lastScenePos())); + for (int ii = 0; ii < 5; ++ii) { + if (sceneEvent.buttons() & (1 << ii)) { + sceneEvent.setButtonDownPos((Qt::MouseButton)(1 << ii), + transform.map(sceneEvent.buttonDownScenePos((Qt::MouseButton)(1 << ii)))); + } + } +} + +void QSGCanvasPrivate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform) +{ + for (int i=0; i<touchPoints.count(); i++) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + touchPoint.setRect(transform.mapRect(touchPoint.sceneRect())); + touchPoint.setStartPos(transform.map(touchPoint.startScenePos())); + touchPoint.setLastPos(transform.map(touchPoint.lastScenePos())); + } +} + +QEvent::Type QSGCanvasPrivate::sceneMouseEventTypeFromMouseEvent(QMouseEvent *event) +{ + switch(event->type()) { + default: + Q_ASSERT(!"Unknown event type"); + case QEvent::MouseButtonPress: + return QEvent::GraphicsSceneMousePress; + case QEvent::MouseButtonRelease: + return QEvent::GraphicsSceneMouseRelease; + case QEvent::MouseButtonDblClick: + return QEvent::GraphicsSceneMouseDoubleClick; + case QEvent::MouseMove: + return QEvent::GraphicsSceneMouseMove; + } +} + +/*! +Fill in the data in \a sceneEvent based on \a event. This method leaves the item local positions in +\a sceneEvent untouched. Use sceneMouseEventForTransform() to fill in those details. +*/ +void QSGCanvasPrivate::sceneMouseEventFromMouseEvent(QGraphicsSceneMouseEvent &sceneEvent, QMouseEvent *event) +{ + Q_Q(QSGCanvas); + + Q_ASSERT(event); + + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) { + if ((event->button() & event->buttons()) == event->buttons()) { + lastMousePosition = event->pos(); + } + + switch (event->button()) { + default: + Q_ASSERT(!"Unknown button"); + case Qt::LeftButton: + buttonDownPositions[0] = event->pos(); + break; + case Qt::RightButton: + buttonDownPositions[1] = event->pos(); + break; + case Qt::MiddleButton: + buttonDownPositions[2] = event->pos(); + break; + case Qt::XButton1: + buttonDownPositions[3] = event->pos(); + break; + case Qt::XButton2: + buttonDownPositions[4] = event->pos(); + break; + } + } + + sceneEvent.setScenePos(event->pos()); + sceneEvent.setScreenPos(event->globalPos()); + sceneEvent.setLastScenePos(lastMousePosition); + sceneEvent.setLastScreenPos(q->mapToGlobal(lastMousePosition)); + sceneEvent.setButtons(event->buttons()); + sceneEvent.setButton(event->button()); + sceneEvent.setModifiers(event->modifiers()); + sceneEvent.setWidget(q); + + for (int ii = 0; ii < 5; ++ii) { + if (sceneEvent.buttons() & (1 << ii)) { + sceneEvent.setButtonDownScenePos((Qt::MouseButton)(1 << ii), buttonDownPositions[ii]); + sceneEvent.setButtonDownScreenPos((Qt::MouseButton)(1 << ii), q->mapToGlobal(buttonDownPositions[ii])); + } + } + + lastMousePosition = event->pos(); +} + +/*! +Fill in the data in \a hoverEvent based on \a mouseEvent. This method leaves the item local positions in +\a hoverEvent untouched (these are filled in later). +*/ +void QSGCanvasPrivate::sceneHoverEventFromMouseEvent(QGraphicsSceneHoverEvent &hoverEvent, QMouseEvent *mouseEvent) +{ + Q_Q(QSGCanvas); + hoverEvent.setWidget(q); + hoverEvent.setScenePos(mouseEvent->pos()); + hoverEvent.setScreenPos(mouseEvent->globalPos()); + if (lastMousePosition.isNull()) lastMousePosition = mouseEvent->pos(); + hoverEvent.setLastScenePos(lastMousePosition); + hoverEvent.setLastScreenPos(q->mapToGlobal(lastMousePosition)); + hoverEvent.setModifiers(mouseEvent->modifiers()); + hoverEvent.setAccepted(mouseEvent->isAccepted()); + + lastMousePosition = mouseEvent->pos(); +} + +/*! +Translates the data in \a touchEvent to this canvas. This method leaves the item local positions in +\a touchEvent untouched (these are filled in later). +*/ +void QSGCanvasPrivate::translateTouchEvent(QTouchEvent *touchEvent) +{ + Q_Q(QSGCanvas); + + touchEvent->setWidget(q); + + QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->touchPoints(); + for (int i = 0; i < touchPoints.count(); ++i) { + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + + touchPoint.setScreenRect(touchPoint.sceneRect()); + touchPoint.setStartScreenPos(touchPoint.startScenePos()); + touchPoint.setLastScreenPos(touchPoint.lastScenePos()); + + touchPoint.setSceneRect(touchPoint.rect()); + touchPoint.setStartScenePos(touchPoint.startPos()); + touchPoint.setLastScenePos(touchPoint.lastPos()); + + if (touchPoint.isPrimary()) + lastMousePosition = touchPoint.pos().toPoint(); + } + touchEvent->setTouchPoints(touchPoints); +} + +void QSGCanvasPrivate::setFocusInScope(QSGItem *scope, QSGItem *item, FocusOptions options) +{ + Q_Q(QSGCanvas); + + Q_ASSERT(item); + Q_ASSERT(scope); + +#ifdef FOCUS_DEBUG + qWarning() << "QSGCanvasPrivate::setFocusInScope():"; + qWarning() << " scope:" << (QObject *)scope; + qWarning() << " scopeSubFocusItem:" << (QObject *)QSGItemPrivate::get(scope)->subFocusItem; + qWarning() << " item:" << (QObject *)item; + qWarning() << " activeFocusItem:" << (QObject *)activeFocusItem; +#endif + + QSGItemPrivate *scopePrivate = QSGItemPrivate::get(scope); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + + QSGItem *oldActiveFocusItem = 0; + QSGItem *newActiveFocusItem = 0; + + QVarLengthArray<QSGItem *, 20> changed; + + // Does this change the active focus? + if (scopePrivate->activeFocus) { + oldActiveFocusItem = activeFocusItem; + newActiveFocusItem = item; + while (newActiveFocusItem->isFocusScope() && newActiveFocusItem->scopedFocusItem()) + newActiveFocusItem = newActiveFocusItem->scopedFocusItem(); + + Q_ASSERT(oldActiveFocusItem); + +#ifndef QT_NO_IM + if (QInputContext *ic = inputContext()) + ic->reset(); +#endif + + activeFocusItem = 0; + QFocusEvent event(QEvent::FocusOut, Qt::OtherFocusReason); + q->sendEvent(oldActiveFocusItem, &event); + + QSGItem *afi = oldActiveFocusItem; + while (afi != scope) { + if (QSGItemPrivate::get(afi)->activeFocus) { + QSGItemPrivate::get(afi)->activeFocus = false; + changed << afi; + } + afi = afi->parentItem(); + } + } + + QSGItem *oldSubFocusItem = scopePrivate->subFocusItem; + // Correct focus chain in scope + if (oldSubFocusItem) { + QSGItem *sfi = scopePrivate->subFocusItem->parentItem(); + while (sfi != scope) { + QSGItemPrivate::get(sfi)->subFocusItem = 0; + sfi = sfi->parentItem(); + } + } + { + scopePrivate->subFocusItem = item; + QSGItem *sfi = scopePrivate->subFocusItem->parentItem(); + while (sfi != scope) { + QSGItemPrivate::get(sfi)->subFocusItem = item; + sfi = sfi->parentItem(); + } + } + + if (oldSubFocusItem) { + QSGItemPrivate::get(oldSubFocusItem)->focus = false; + changed << oldSubFocusItem; + } + + if (!(options & DontChangeFocusProperty)) { + itemPrivate->focus = true; + changed << item; + } + + if (newActiveFocusItem) { + activeFocusItem = newActiveFocusItem; + + QSGItemPrivate::get(newActiveFocusItem)->activeFocus = true; + changed << newActiveFocusItem; + + QSGItem *afi = newActiveFocusItem->parentItem(); + while (afi != scope) { + if (afi->isFocusScope()) { + QSGItemPrivate::get(afi)->activeFocus = true; + changed << afi; + } + afi = afi->parentItem(); + } + + updateInputMethodData(); + + QFocusEvent event(QEvent::FocusIn, Qt::OtherFocusReason); + q->sendEvent(newActiveFocusItem, &event); + } else { + updateInputMethodData(); + } + + if (!changed.isEmpty()) + notifyFocusChangesRecur(changed.data(), changed.count() - 1); +} + +void QSGCanvasPrivate::clearFocusInScope(QSGItem *scope, QSGItem *item, FocusOptions options) +{ + Q_Q(QSGCanvas); + + Q_ASSERT(item); + Q_ASSERT(scope); + +#ifdef FOCUS_DEBUG + qWarning() << "QSGCanvasPrivate::clearFocusInScope():"; + qWarning() << " scope:" << (QObject *)scope; + qWarning() << " item:" << (QObject *)item; + qWarning() << " activeFocusItem:" << (QObject *)activeFocusItem; +#endif + + QSGItemPrivate *scopePrivate = QSGItemPrivate::get(scope); + + QSGItem *oldActiveFocusItem = 0; + QSGItem *newActiveFocusItem = 0; + + QVarLengthArray<QSGItem *, 20> changed; + + Q_ASSERT(item == scopePrivate->subFocusItem); + + // Does this change the active focus? + if (scopePrivate->activeFocus) { + oldActiveFocusItem = activeFocusItem; + newActiveFocusItem = scope; + + Q_ASSERT(oldActiveFocusItem); + +#ifndef QT_NO_IM + if (QInputContext *ic = inputContext()) + ic->reset(); +#endif + + activeFocusItem = 0; + QFocusEvent event(QEvent::FocusOut, Qt::OtherFocusReason); + q->sendEvent(oldActiveFocusItem, &event); + + QSGItem *afi = oldActiveFocusItem; + while (afi != scope) { + if (QSGItemPrivate::get(afi)->activeFocus) { + QSGItemPrivate::get(afi)->activeFocus = false; + changed << afi; + } + afi = afi->parentItem(); + } + } + + QSGItem *oldSubFocusItem = scopePrivate->subFocusItem; + // Correct focus chain in scope + if (oldSubFocusItem) { + QSGItem *sfi = scopePrivate->subFocusItem->parentItem(); + while (sfi != scope) { + QSGItemPrivate::get(sfi)->subFocusItem = 0; + sfi = sfi->parentItem(); + } + } + scopePrivate->subFocusItem = 0; + + if (oldSubFocusItem && !(options & DontChangeFocusProperty)) { + QSGItemPrivate::get(oldSubFocusItem)->focus = false; + changed << oldSubFocusItem; + } + + if (newActiveFocusItem) { + Q_ASSERT(newActiveFocusItem == scope); + activeFocusItem = scope; + + updateInputMethodData(); + + QFocusEvent event(QEvent::FocusIn, Qt::OtherFocusReason); + q->sendEvent(newActiveFocusItem, &event); + } else { + updateInputMethodData(); + } + + if (!changed.isEmpty()) + notifyFocusChangesRecur(changed.data(), changed.count() - 1); +} + +void QSGCanvasPrivate::notifyFocusChangesRecur(QSGItem **items, int remaining) +{ + QDeclarativeGuard<QSGItem> item(*items); + + if (remaining) + notifyFocusChangesRecur(items + 1, remaining - 1); + + if (item) { + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + + if (itemPrivate->notifiedFocus != itemPrivate->focus) { + itemPrivate->notifiedFocus = itemPrivate->focus; + emit item->focusChanged(itemPrivate->focus); + } + + if (item && itemPrivate->notifiedActiveFocus != itemPrivate->activeFocus) { + itemPrivate->notifiedActiveFocus = itemPrivate->activeFocus; + itemPrivate->itemChange(QSGItem::ItemActiveFocusHasChanged, itemPrivate->activeFocus); + emit item->activeFocusChanged(itemPrivate->activeFocus); + } + } +} + +void QSGCanvasPrivate::updateInputMethodData() +{ + Q_Q(QSGCanvas); + bool enabled = activeFocusItem + && (QSGItemPrivate::get(activeFocusItem)->flags & QSGItem::ItemAcceptsInputMethod); + q->setAttribute(Qt::WA_InputMethodEnabled, enabled); + q->setInputMethodHints(enabled ? activeFocusItem->inputMethodHints() : Qt::ImhNone); +} + +QVariant QSGCanvas::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QSGCanvas); + if (!d->activeFocusItem || !(QSGItemPrivate::get(d->activeFocusItem)->flags & QSGItem::ItemAcceptsInputMethod)) + return QVariant(); + QVariant value = d->activeFocusItem->inputMethodQuery(query); + + //map geometry types + QVariant::Type type = value.type(); + if (type == QVariant::RectF || type == QVariant::Rect) { + const QTransform transform = QSGItemPrivate::get(d->activeFocusItem)->itemToCanvasTransform(); + value = transform.mapRect(value.toRectF()); + } else if (type == QVariant::PointF || type == QVariant::Point) { + const QTransform transform = QSGItemPrivate::get(d->activeFocusItem)->itemToCanvasTransform(); + value = transform.map(value.toPointF()); + } + return value; +} + +void QSGCanvasPrivate::dirtyItem(QSGItem *) +{ + Q_Q(QSGCanvas); + q->maybeUpdate(); +} + +void QSGCanvasPrivate::cleanup(QSGNode *n) +{ + Q_Q(QSGCanvas); + + Q_ASSERT(!cleanupNodeList.contains(n)); + cleanupNodeList.append(n); + q->maybeUpdate(); +} + +static QGLFormat tweakFormat(const QGLFormat &format = QGLFormat::defaultFormat()) +{ + QGLFormat f = format; + f.setSwapInterval(1); + return f; +} + +QSGCanvas::QSGCanvas(QWidget *parent, Qt::WindowFlags f) + : QGLWidget(*(new QSGCanvasPrivate), tweakFormat(), parent, (QGLWidget *) 0, f) +{ + Q_D(QSGCanvas); + + d->init(this); +} + +QSGCanvas::QSGCanvas(const QGLFormat &format, QWidget *parent, Qt::WindowFlags f) + : QGLWidget(*(new QSGCanvasPrivate), tweakFormat(format), parent, (QGLWidget *) 0, f) +{ + Q_D(QSGCanvas); + + d->init(this); +} + +QSGCanvas::QSGCanvas(QSGCanvasPrivate &dd, QWidget *parent, Qt::WindowFlags f) +: QGLWidget(dd, tweakFormat(), parent, 0, f) +{ + Q_D(QSGCanvas); + + d->init(this); +} + +QSGCanvas::QSGCanvas(QSGCanvasPrivate &dd, const QGLFormat &format, QWidget *parent, Qt::WindowFlags f) +: QGLWidget(dd, tweakFormat(format), parent, 0, f) +{ + Q_D(QSGCanvas); + + d->init(this); +} + +QSGCanvas::~QSGCanvas() +{ + Q_D(QSGCanvas); + + // ### should we change ~QSGItem to handle this better? + // 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(); + + delete d->context; +} + +QSGItem *QSGCanvas::rootItem() const +{ + Q_D(const QSGCanvas); + + return d->rootItem; +} + +QSGItem *QSGCanvas::activeFocusItem() const +{ + Q_D(const QSGCanvas); + + return d->activeFocusItem; +} + +QSGItem *QSGCanvas::mouseGrabberItem() const +{ + Q_D(const QSGCanvas); + + return d->mouseGrabberItem; +} + + +void QSGCanvasPrivate::clearHover() +{ + Q_Q(QSGCanvas); + if (!hoverItem) + return; + + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setWidget(q); + + QPoint cursorPos = QCursor::pos(); + hoverEvent.setScenePos(q->mapFromGlobal(cursorPos)); + hoverEvent.setLastScenePos(hoverEvent.scenePos()); + hoverEvent.setScreenPos(cursorPos); + hoverEvent.setLastScreenPos(hoverEvent.screenPos()); + + QSGItem *item = hoverItem; + hoverItem = 0; + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, item, &hoverEvent); +} + + +bool QSGCanvas::event(QEvent *e) +{ + Q_D(QSGCanvas); + + if (e->type() == QEvent::User) { + Q_ASSERT(d->threadedRendering); + + d->mutex.lock(); +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Main Thread: Stopped"); +#endif + + d->polishItems(); + + d->renderThreadAwakened = false; + + d->wait.wakeOne(); + + // The thread is exited when the widget has been hidden. We then need to + // skip the waiting, otherwise we would be waiting for a wakeup that never + // comes. + if (d->thread->isRunning()) + d->wait.wait(&d->mutex); +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: Main Thread: Resumed"); +#endif + d->mutex.unlock(); + + if (d->animationRunning) + d->animationDriver->advance(); + } + + switch (e->type()) { + + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + QTouchEvent *touch = static_cast<QTouchEvent *>(e); + d->translateTouchEvent(touch); + d->deliverTouchEvent(touch); + if (!touch->isAccepted()) + return false; + } + case QEvent::Leave: + d->clearHover(); + d->lastMousePosition = QPoint(); + break; + default: + break; + } + + return QGLWidget::event(e); +} + +void QSGCanvas::keyPressEvent(QKeyEvent *e) +{ + Q_D(QSGCanvas); + + sendEvent(d->activeFocusItem, e); +} + +void QSGCanvas::keyReleaseEvent(QKeyEvent *e) +{ + Q_D(QSGCanvas); + + sendEvent(d->activeFocusItem, e); +} + +void QSGCanvas::inputMethodEvent(QInputMethodEvent *e) +{ + Q_D(QSGCanvas); + + sendEvent(d->activeFocusItem, e); +} + +bool QSGCanvasPrivate::deliverInitialMousePressEvent(QSGItem *item, QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGCanvas); + + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QSGItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(event->scenePos()); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QSGItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QSGItem *child = children.at(ii); + if (!child->isVisible() || !child->isEnabled()) + continue; + if (deliverInitialMousePressEvent(child, event)) + return true; + } + + if (itemPrivate->acceptedMouseButtons & event->button()) { + QPointF p = item->mapFromScene(event->scenePos()); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + sceneMouseEventForTransform(*event, itemPrivate->canvasToItemTransform()); + event->accept(); + mouseGrabberItem = item; + q->sendEvent(item, event); + if (event->isAccepted()) + return true; + mouseGrabberItem->ungrabMouse(); + mouseGrabberItem = 0; + } + } + + return false; +} + +bool QSGCanvasPrivate::deliverMouseEvent(QGraphicsSceneMouseEvent *sceneEvent) +{ + Q_Q(QSGCanvas); + + if (!mouseGrabberItem && + sceneEvent->type() == QEvent::GraphicsSceneMousePress && + (sceneEvent->button() & sceneEvent->buttons()) == sceneEvent->buttons()) { + + return deliverInitialMousePressEvent(rootItem, sceneEvent); + } + + if (mouseGrabberItem) { + QSGItemPrivate *mgPrivate = QSGItemPrivate::get(mouseGrabberItem); + sceneMouseEventForTransform(*sceneEvent, mgPrivate->canvasToItemTransform()); + + sceneEvent->accept(); + q->sendEvent(mouseGrabberItem, sceneEvent); + if (sceneEvent->isAccepted()) + return true; + } + + return false; +} + +void QSGCanvas::mousePressEvent(QMouseEvent *event) +{ + Q_D(QSGCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QSGCanvas::mousePressEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + QGraphicsSceneMouseEvent sceneEvent(d->sceneMouseEventTypeFromMouseEvent(event)); + d->sceneMouseEventFromMouseEvent(sceneEvent, event); + + d->deliverMouseEvent(&sceneEvent); + event->setAccepted(sceneEvent.isAccepted()); +} + +void QSGCanvas::mouseReleaseEvent(QMouseEvent *event) +{ + Q_D(QSGCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QSGCanvas::mouseReleaseEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + if (!d->mouseGrabberItem) { + QGLWidget::mouseReleaseEvent(event); + return; + } + + QGraphicsSceneMouseEvent sceneEvent(d->sceneMouseEventTypeFromMouseEvent(event)); + d->sceneMouseEventFromMouseEvent(sceneEvent, event); + + d->deliverMouseEvent(&sceneEvent); + event->setAccepted(sceneEvent.isAccepted()); + + d->mouseGrabberItem = 0; +} + +void QSGCanvas::mouseDoubleClickEvent(QMouseEvent *event) +{ + Q_D(QSGCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QSGCanvas::mouseDoubleClickEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + QGraphicsSceneMouseEvent sceneEvent(d->sceneMouseEventTypeFromMouseEvent(event)); + d->sceneMouseEventFromMouseEvent(sceneEvent, event); + + if (!d->mouseGrabberItem && (event->button() & event->buttons()) == event->buttons()) { + if (d->deliverInitialMousePressEvent(d->rootItem, &sceneEvent)) + event->accept(); + else + event->ignore(); + return; + } + + d->deliverMouseEvent(&sceneEvent); + event->setAccepted(sceneEvent.isAccepted()); +} + +void QSGCanvasPrivate::sendHoverEvent(QEvent::Type type, QSGItem *item, + QGraphicsSceneHoverEvent *event) +{ + Q_Q(QSGCanvas); + const QTransform transform = QSGItemPrivate::get(item)->canvasToItemTransform(); + + //create copy of event + QGraphicsSceneHoverEvent hoverEvent(type); + hoverEvent.setWidget(event->widget()); + hoverEvent.setPos(transform.map(event->scenePos())); + hoverEvent.setScenePos(event->scenePos()); + hoverEvent.setScreenPos(event->screenPos()); + hoverEvent.setLastPos(transform.map(event->lastScenePos())); + hoverEvent.setLastScenePos(event->lastScenePos()); + hoverEvent.setLastScreenPos(event->lastScreenPos()); + hoverEvent.setModifiers(event->modifiers()); + hoverEvent.setAccepted(event->isAccepted()); + + q->sendEvent(item, &hoverEvent); +} + +void QSGCanvas::mouseMoveEvent(QMouseEvent *event) +{ + Q_D(QSGCanvas); + +#ifdef MOUSE_DEBUG + qWarning() << "QSGCanvas::mouseMoveEvent()" << event->pos() << event->button() << event->buttons(); +#endif + + if (!d->mouseGrabberItem) { + QGraphicsSceneHoverEvent hoverEvent; + d->sceneHoverEventFromMouseEvent(hoverEvent, event); + + bool delivered = d->deliverHoverEvent(d->rootItem, &hoverEvent); + if (!delivered) { + //take care of any exits + if (d->hoverItem) { + QSGItem *item = d->hoverItem; + d->hoverItem = 0; + d->sendHoverEvent(QEvent::GraphicsSceneHoverLeave, item, &hoverEvent); + } + } + event->setAccepted(hoverEvent.isAccepted()); + return; + } + + QGraphicsSceneMouseEvent sceneEvent(d->sceneMouseEventTypeFromMouseEvent(event)); + d->sceneMouseEventFromMouseEvent(sceneEvent, event); + + d->deliverMouseEvent(&sceneEvent); + event->setAccepted(sceneEvent.isAccepted()); +} + +bool QSGCanvasPrivate::deliverHoverEvent(QSGItem *item, QGraphicsSceneHoverEvent *event) +{ + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QSGItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(event->scenePos()); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QSGItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QSGItem *child = children.at(ii); + if (!child->isEnabled()) + continue; + if (deliverHoverEvent(child, event)) + return true; + } + + if (itemPrivate->hoverEnabled) { + QPointF p = item->mapFromScene(event->scenePos()); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (hoverItem == item) { + //move + sendHoverEvent(QEvent::GraphicsSceneHoverMove, item, event); + } else { + //exit from previous + if (hoverItem) { + QSGItem *item = hoverItem; + hoverItem = 0; + sendHoverEvent(QEvent::GraphicsSceneHoverLeave, item, event); + } + + //enter new item + hoverItem = item; + sendHoverEvent(QEvent::GraphicsSceneHoverEnter, item, event); + } + return true; + } + } + + return false; +} + +bool QSGCanvasPrivate::deliverWheelEvent(QSGItem *item, QGraphicsSceneWheelEvent *event) +{ + Q_Q(QSGCanvas); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QSGItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(event->scenePos()); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QSGItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QSGItem *child = children.at(ii); + if (!child->isEnabled()) + continue; + if (deliverWheelEvent(child, event)) + return true; + } + + QPointF p = item->mapFromScene(event->scenePos()); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + event->setPos(itemPrivate->canvasToItemTransform().map(event->scenePos())); + event->accept(); + q->sendEvent(item, event); + if (event->isAccepted()) + return true; + } + + return false; +} + +#ifndef QT_NO_WHEELEVENT +void QSGCanvas::wheelEvent(QWheelEvent *event) +{ + Q_D(QSGCanvas); +#ifdef MOUSE_DEBUG + qWarning() << "QSGCanvas::wheelEvent()" << event->pos() << event->delta() << event->orientation(); +#endif + QGraphicsSceneWheelEvent wheelEvent(QEvent::GraphicsSceneWheel); + wheelEvent.setWidget(this); + wheelEvent.setScenePos(event->pos()); + wheelEvent.setScreenPos(event->globalPos()); + wheelEvent.setButtons(event->buttons()); + wheelEvent.setModifiers(event->modifiers()); + wheelEvent.setDelta(event->delta()); + wheelEvent.setOrientation(event->orientation()); + wheelEvent.setAccepted(false); + + d->deliverWheelEvent(d->rootItem, &wheelEvent); + event->setAccepted(wheelEvent.isAccepted()); +} +#endif // QT_NO_WHEELEVENT + +bool QSGCanvasPrivate::deliverTouchEvent(QTouchEvent *event) +{ +#ifdef TOUCH_DEBUG + if (event->type() == QEvent::TouchBegin) + qWarning("touchBeginEvent"); + else if (event->type() == QEvent::TouchUpdate) + qWarning("touchUpdateEvent"); + else if (event->type() == QEvent::TouchEnd) + qWarning("touchEndEvent"); +#endif + + QHash<QSGItem *, QList<QTouchEvent::TouchPoint> > updatedPoints; + + if (event->type() == QTouchEvent::TouchBegin) { // all points are new touch points + QSet<int> acceptedNewPoints; + deliverTouchPoints(rootItem, event, event->touchPoints(), &acceptedNewPoints, &updatedPoints); + if (acceptedNewPoints.count() > 0) + event->accept(); + return event->isAccepted(); + } + + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); + QList<QTouchEvent::TouchPoint> newPoints; + QSGItem *item = 0; + for (int i=0; i<touchPoints.count(); i++) { + const QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; + switch (touchPoint.state()) { + case Qt::TouchPointPressed: + newPoints << touchPoint; + break; + case Qt::TouchPointMoved: + case Qt::TouchPointStationary: + case Qt::TouchPointReleased: + if (itemForTouchPointId.contains(touchPoint.id())) { + item = itemForTouchPointId[touchPoint.id()]; + if (item) + updatedPoints[item].append(touchPoint); + } + break; + default: + break; + } + } + + if (newPoints.count() > 0 || updatedPoints.count() > 0) { + QSet<int> acceptedNewPoints; + int prevCount = updatedPoints.count(); + deliverTouchPoints(rootItem, event, newPoints, &acceptedNewPoints, &updatedPoints); + if (acceptedNewPoints.count() > 0 || updatedPoints.count() != prevCount) + event->accept(); + } + + if (event->touchPointStates() & Qt::TouchPointReleased) { + for (int i=0; i<touchPoints.count(); i++) { + if (touchPoints[i].state() == Qt::TouchPointReleased) + itemForTouchPointId.remove(touchPoints[i].id()); + } + } + + return event->isAccepted(); +} + +bool QSGCanvasPrivate::deliverTouchPoints(QSGItem *item, QTouchEvent *event, const QList<QTouchEvent::TouchPoint> &newPoints, QSet<int> *acceptedNewPoints, QHash<QSGItem *, QList<QTouchEvent::TouchPoint> > *updatedPoints) +{ + Q_Q(QSGCanvas); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QSGItem::ItemClipsChildrenToShape) { + QRectF bounds(0, 0, item->width(), item->height()); + for (int i=0; i<newPoints.count(); i++) { + QPointF p = item->mapFromScene(newPoints[i].scenePos()); + if (!bounds.contains(p)) + return false; + } + } + + QList<QSGItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QSGItem *child = children.at(ii); + if (!child->isEnabled()) + continue; + if (deliverTouchPoints(child, event, newPoints, acceptedNewPoints, updatedPoints)) + return true; + } + + QList<QTouchEvent::TouchPoint> matchingPoints; + if (newPoints.count() > 0 && acceptedNewPoints->count() < newPoints.count()) { + QRectF bounds(0, 0, item->width(), item->height()); + for (int i=0; i<newPoints.count(); i++) { + if (acceptedNewPoints->contains(newPoints[i].id())) + continue; + QPointF p = item->mapFromScene(newPoints[i].scenePos()); + if (bounds.contains(p)) + matchingPoints << newPoints[i]; + } + } + + if (matchingPoints.count() > 0 || (*updatedPoints)[item].count() > 0) { + QList<QTouchEvent::TouchPoint> &eventPoints = (*updatedPoints)[item]; + eventPoints.append(matchingPoints); + transformTouchPoints(eventPoints, itemPrivate->canvasToItemTransform()); + + Qt::TouchPointStates eventStates; + for (int i=0; i<eventPoints.count(); i++) + eventStates |= eventPoints[i].state(); + // if all points have the same state, set the event type accordingly + QEvent::Type eventType; + switch (eventStates) { + case Qt::TouchPointPressed: + eventType = QEvent::TouchBegin; + break; + case Qt::TouchPointReleased: + eventType = QEvent::TouchEnd; + break; + default: + eventType = QEvent::TouchUpdate; + break; + } + + if (eventStates != Qt::TouchPointStationary) { + QTouchEvent touchEvent(eventType); + touchEvent.setWidget(q); + touchEvent.setDeviceType(event->deviceType()); + touchEvent.setModifiers(event->modifiers()); + touchEvent.setTouchPointStates(eventStates); + touchEvent.setTouchPoints(eventPoints); + + touchEvent.accept(); + q->sendEvent(item, &touchEvent); + + if (touchEvent.isAccepted()) { + for (int i=0; i<matchingPoints.count(); i++) { + itemForTouchPointId[matchingPoints[i].id()] = item; + acceptedNewPoints->insert(matchingPoints[i].id()); + } + } + } + } + + updatedPoints->remove(item); + if (acceptedNewPoints->count() == newPoints.count() && updatedPoints->isEmpty()) + return true; + + return false; +} + +bool QSGCanvasPrivate::sendFilteredMouseEvent(QSGItem *target, QSGItem *item, QGraphicsSceneMouseEvent *event) +{ + if (!target) + return false; + + if (sendFilteredMouseEvent(target->parentItem(), item, event)) + return true; + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(target); + if (targetPrivate->filtersChildMouseEvents) + if (target->childMouseEventFilter(item, event)) + return true; + + return false; +} + +bool QSGCanvas::sendEvent(QSGItem *item, QEvent *e) +{ + Q_D(QSGCanvas); + + if (!item) { + qWarning("QSGCanvas::sendEvent: Cannot send event to a null item"); + return false; + } + + Q_ASSERT(e); + + switch (e->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: + e->accept(); + QSGItemPrivate::get(item)->deliverKeyEvent(static_cast<QKeyEvent *>(e)); + while (!e->isAccepted() && (item = item->parentItem())) { + e->accept(); + QSGItemPrivate::get(item)->deliverKeyEvent(static_cast<QKeyEvent *>(e)); + } + break; + case QEvent::InputMethod: + e->accept(); + QSGItemPrivate::get(item)->deliverInputMethodEvent(static_cast<QInputMethodEvent *>(e)); + while (!e->isAccepted() && (item = item->parentItem())) { + e->accept(); + QSGItemPrivate::get(item)->deliverInputMethodEvent(static_cast<QInputMethodEvent *>(e)); + } + break; + case QEvent::FocusIn: + case QEvent::FocusOut: + QSGItemPrivate::get(item)->deliverFocusEvent(static_cast<QFocusEvent *>(e)); + break; + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneMouseMove: + // XXX todo - should sendEvent be doing this? how does it relate to forwarded events? + { + QGraphicsSceneMouseEvent *se = static_cast<QGraphicsSceneMouseEvent *>(e); + if (!d->sendFilteredMouseEvent(item->parentItem(), item, se)) { + se->accept(); + QSGItemPrivate::get(item)->deliverMouseEvent(se); + } + } + break; + case QEvent::GraphicsSceneWheel: + QSGItemPrivate::get(item)->deliverWheelEvent(static_cast<QGraphicsSceneWheelEvent *>(e)); + break; + case QEvent::GraphicsSceneHoverEnter: + case QEvent::GraphicsSceneHoverLeave: + case QEvent::GraphicsSceneHoverMove: + QSGItemPrivate::get(item)->deliverHoverEvent(static_cast<QGraphicsSceneHoverEvent *>(e)); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + QSGItemPrivate::get(item)->deliverTouchEvent(static_cast<QTouchEvent *>(e)); + break; + default: + break; + } + + return false; +} + +void QSGCanvasPrivate::cleanupNodes() +{ + for (int ii = 0; ii < cleanupNodeList.count(); ++ii) + delete cleanupNodeList.at(ii); + cleanupNodeList.clear(); +} + +void QSGCanvasPrivate::updateDirtyNodes() +{ +#ifdef DIRTY_DEBUG + qWarning() << "QSGCanvasPrivate::updateDirtyNodes():"; +#endif + + cleanupNodes(); + + QSGItem *updateList = dirtyItemList; + dirtyItemList = 0; + if (updateList) QSGItemPrivate::get(updateList)->prevDirtyItem = &updateList; + + while (updateList) { + QSGItem *item = updateList; + QSGItemPrivate *itemPriv = QSGItemPrivate::get(item); + itemPriv->removeFromDirtyList(); + +#ifdef DIRTY_DEBUG + qWarning() << " QSGNode:" << item << qPrintable(itemPriv->dirtyToString()); +#endif + updateDirtyNode(item); + } +} + +void QSGCanvasPrivate::updateDirtyNode(QSGItem *item) +{ +#ifdef QML_RUNTIME_TESTING + bool didFlash = false; +#endif + + QSGItemPrivate *itemPriv = QSGItemPrivate::get(item); + quint32 dirty = itemPriv->dirtyAttributes; + itemPriv->dirtyAttributes = 0; + + if ((dirty & QSGItemPrivate::TransformUpdateMask) || + (dirty & QSGItemPrivate::Size && itemPriv->origin != QSGItem::TopLeft && + (itemPriv->scale != 1. || itemPriv->rotation != 0.))) { + + QMatrix4x4 matrix; + + if (itemPriv->x != 0. || itemPriv->y != 0.) + matrix.translate(itemPriv->x, itemPriv->y); + + if (dirty & QSGItemPrivate::ComplexTransformUpdateMask) { + for (int ii = itemPriv->transforms.count() - 1; ii >= 0; --ii) + itemPriv->transforms.at(ii)->applyTo(&matrix); + } + + if (itemPriv->scale != 1. || itemPriv->rotation != 0.) { + QPointF origin = itemPriv->computeTransformOrigin(); + matrix.translate(origin.x(), origin.y()); + if (itemPriv->scale != 1.) + matrix.scale(itemPriv->scale, itemPriv->scale); + if (itemPriv->rotation != 0.) + matrix.rotate(itemPriv->rotation, 0, 0, 1); + matrix.translate(-origin.x(), -origin.y()); + } + + itemPriv->itemNode()->setMatrix(matrix); + } + + bool clipEffectivelyChanged = dirty & QSGItemPrivate::Clip && + ((item->clip() == false) != (itemPriv->clipNode == 0)); + bool effectRefEffectivelyChanged = dirty & QSGItemPrivate::EffectReference && + ((itemPriv->effectRefCount == 0) != (itemPriv->rootNode == 0)); + + if (clipEffectivelyChanged) { + QSGNode *parent = itemPriv->opacityNode ? (QSGNode *) itemPriv->opacityNode : (QSGNode *)itemPriv->itemNode(); + QSGNode *child = itemPriv->rootNode ? (QSGNode *)itemPriv->rootNode : (QSGNode *)itemPriv->groupNode; + + if (item->clip()) { + Q_ASSERT(itemPriv->clipNode == 0); + itemPriv->clipNode = new QSGDefaultClipNode(QRectF(0, 0, itemPriv->width, itemPriv->height)); + + if (child) + parent->removeChildNode(child); + parent->appendChildNode(itemPriv->clipNode); + if (child) + itemPriv->clipNode->appendChildNode(child); + + } else { + Q_ASSERT(itemPriv->clipNode != 0); + parent->removeChildNode(itemPriv->clipNode); + if (child) + itemPriv->clipNode->removeChildNode(child); + delete itemPriv->clipNode; + itemPriv->clipNode = 0; + if (child) + parent->appendChildNode(child); + } + } + + if (dirty & QSGItemPrivate::ChildrenUpdateMask) { + while (itemPriv->childContainerNode()->childCount()) + itemPriv->childContainerNode()->removeChildNode(itemPriv->childContainerNode()->childAtIndex(0)); + } + + if (effectRefEffectivelyChanged) { + QSGNode *parent = itemPriv->clipNode; + if (!parent) + parent = itemPriv->opacityNode; + if (!parent) + parent = itemPriv->itemNode(); + QSGNode *child = itemPriv->groupNode; + + if (itemPriv->effectRefCount) { + Q_ASSERT(itemPriv->rootNode == 0); + itemPriv->rootNode = new QSGRootNode; + + if (child) + parent->removeChildNode(child); + parent->appendChildNode(itemPriv->rootNode); + if (child) + itemPriv->rootNode->appendChildNode(child); + } else { + Q_ASSERT(itemPriv->rootNode != 0); + parent->removeChildNode(itemPriv->rootNode); + if (child) + itemPriv->rootNode->removeChildNode(child); + delete itemPriv->rootNode; + itemPriv->rootNode = 0; + if (child) + parent->appendChildNode(child); + } + } + + if (dirty & QSGItemPrivate::ChildrenUpdateMask) { + QSGNode *groupNode = itemPriv->groupNode; + if (groupNode) { + for (int count = groupNode->childCount(); count; --count) + groupNode->removeChildNode(groupNode->childAtIndex(0)); + } + + QList<QSGItem *> orderedChildren = itemPriv->paintOrderChildItems(); + int ii = 0; + + itemPriv->paintNodeIndex = 0; + for (; ii < orderedChildren.count() && orderedChildren.at(ii)->z() < 0; ++ii) { + QSGItemPrivate *childPrivate = QSGItemPrivate::get(orderedChildren.at(ii)); + if (!childPrivate->explicitVisible && !childPrivate->effectRefCount) + continue; + if (childPrivate->itemNode()->parent()) + childPrivate->itemNode()->parent()->removeChildNode(childPrivate->itemNode()); + + itemPriv->childContainerNode()->appendChildNode(childPrivate->itemNode()); + itemPriv->paintNodeIndex++; + } + + if (itemPriv->paintNode) + itemPriv->childContainerNode()->appendChildNode(itemPriv->paintNode); + + for (; ii < orderedChildren.count(); ++ii) { + QSGItemPrivate *childPrivate = QSGItemPrivate::get(orderedChildren.at(ii)); + if (!childPrivate->explicitVisible && !childPrivate->effectRefCount) + continue; + if (childPrivate->itemNode()->parent()) + childPrivate->itemNode()->parent()->removeChildNode(childPrivate->itemNode()); + + itemPriv->childContainerNode()->appendChildNode(childPrivate->itemNode()); + } + } + + if ((dirty & QSGItemPrivate::Size || clipEffectivelyChanged) && itemPriv->clipNode) { + itemPriv->clipNode->setRect(QRectF(0, 0, itemPriv->width, itemPriv->height)); + itemPriv->clipNode->update(); + } + + if (dirty & (QSGItemPrivate::OpacityValue | QSGItemPrivate::Visible | QSGItemPrivate::HideReference)) { + qreal opacity = itemPriv->explicitVisible && itemPriv->hideRefCount == 0 + ? itemPriv->opacity : qreal(0); + + if (opacity != 1 && !itemPriv->opacityNode) { + itemPriv->opacityNode = new QSGOpacityNode; + + QSGNode *parent = itemPriv->itemNode(); + QSGNode *child = itemPriv->clipNode; + if (!child) + child = itemPriv->rootNode; + if (!child) + child = itemPriv->groupNode; + + if (child) + parent->removeChildNode(child); + parent->appendChildNode(itemPriv->opacityNode); + if (child) + itemPriv->opacityNode->appendChildNode(child); + } + if (itemPriv->opacityNode) + itemPriv->opacityNode->setOpacity(opacity); + } + + if (dirty & QSGItemPrivate::ContentUpdateMask) { + + if (itemPriv->flags & QSGItem::ItemHasContents) { + updatePaintNodeData.transformNode = itemPriv->itemNode(); + itemPriv->paintNode = item->updatePaintNode(itemPriv->paintNode, &updatePaintNodeData); + + Q_ASSERT(itemPriv->paintNode == 0 || + itemPriv->paintNode->parent() == 0 || + itemPriv->paintNode->parent() == itemPriv->childContainerNode()); + + if (itemPriv->paintNode && itemPriv->paintNode->parent() == 0) { + if (itemPriv->childContainerNode()->childCount() == itemPriv->paintNodeIndex) + itemPriv->childContainerNode()->appendChildNode(itemPriv->paintNode); + else + itemPriv->childContainerNode()->insertChildNodeBefore(itemPriv->paintNode, itemPriv->childContainerNode()->childAtIndex(itemPriv->paintNodeIndex)); + } + } else if (itemPriv->paintNode) { + delete itemPriv->paintNode; + } + } + +#ifndef QT_NO_DEBUG + // Check consistency. + const QSGNode *nodeChain[] = { + itemPriv->itemNodeInstance, + itemPriv->opacityNode, + itemPriv->clipNode, + itemPriv->rootNode, + itemPriv->groupNode, + itemPriv->paintNode, + }; + + int ip = 0; + for (;;) { + while (ip < 5 && nodeChain[ip] == 0) + ++ip; + if (ip == 5) + break; + int ic = ip + 1; + while (ic < 5 && nodeChain[ic] == 0) + ++ic; + const QSGNode *parent = nodeChain[ip]; + const QSGNode *child = nodeChain[ic]; + if (child == 0) { + Q_ASSERT(parent == itemPriv->groupNode || parent->childCount() == 0); + } else { + Q_ASSERT(parent == itemPriv->groupNode || parent->childCount() == 1); + Q_ASSERT(child->parent() == parent); + bool containsChild = false; + for (int i = 0; i < parent->childCount(); ++i) + containsChild |= (parent->childAtIndex(i) == child); + Q_ASSERT(containsChild); + } + ip = ic; + } +#endif + +#ifdef QML_RUNTIME_TESTING + if (itemPriv->sceneGraphContext()->isFlashModeEnabled()) { + QSGFlashNode *flash = new QSGFlashNode(); + flash->setRect(item->boundingRect()); + itemPriv->childContainerNode()->appendChildNode(flash); + didFlash = true; + } + Q_Q(QSGCanvas); + if (didFlash) { + q->maybeUpdate(); + } +#endif + +} + +void QSGCanvas::maybeUpdate() +{ + Q_D(QSGCanvas); + + if (d->threadedRendering) { + if (!d->renderThreadAwakened) { + d->renderThreadAwakened = true; + bool locked = d->mutex.tryLock(); + if (d->idle && locked) { +#ifdef THREAD_DEBUG + qWarning("QSGRenderer: now maybe I should update..."); +#endif + d->wait.wakeOne(); + } + if (locked) + d->mutex.unlock(); + } + } else if (!d->animationDriver || !d->animationDriver->isRunning()) { + update(); + } +} + +/*! + \fn void QSGEngine::sceneGraphInitialized(); + + This signal is emitted when the scene graph has been initialized. + + This signal will be emitted from the scene graph rendering thread. + */ + +/*! + Returns the QSGEngine used for this scene. + + The engine will only be available once the scene graph has been + initialized. Register for the sceneGraphEngine() signal to get + notification about this. + */ + +QSGEngine *QSGCanvas::sceneGraphEngine() const +{ + Q_D(const QSGCanvas); + if (d->context->isReady()) + return d->context->engine(); + return 0; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgcanvas.h b/src/declarative/items/qsgcanvas.h new file mode 100644 index 0000000000..8f3b3038f8 --- /dev/null +++ b/src/declarative/items/qsgcanvas.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGCANVAS_H +#define QSGCANVAS_H + +#include <QtCore/qmetatype.h> +#include <QtOpenGL/qgl.h> +#include <QtGui/qwidget.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGItem; +class QSGEngine; +class QSGCanvasPrivate; +class Q_DECLARATIVE_EXPORT QSGCanvas : public QGLWidget +{ +Q_OBJECT +Q_DECLARE_PRIVATE(QSGCanvas) +public: + QSGCanvas(QWidget *parent = 0, Qt::WindowFlags f = 0); + QSGCanvas(const QGLFormat &format, QWidget *parent = 0, Qt::WindowFlags f = 0); + virtual ~QSGCanvas(); + + QSGItem *rootItem() const; + QSGItem *activeFocusItem() const; + + QSGItem *mouseGrabberItem() const; + + bool sendEvent(QSGItem *, QEvent *); + + QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + QSGEngine *sceneGraphEngine() const; + +signals: + void sceneGraphInitialized(); + +protected: + QSGCanvas(QSGCanvasPrivate &dd, QWidget *parent = 0, Qt::WindowFlags f = 0); + QSGCanvas(QSGCanvasPrivate &dd, const QGLFormat &format, QWidget *parent = 0, Qt::WindowFlags f = 0); + + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + + virtual void showEvent(QShowEvent *); + virtual void hideEvent(QHideEvent *); + + virtual bool event(QEvent *); + virtual void keyPressEvent(QKeyEvent *); + virtual void keyReleaseEvent(QKeyEvent *); + virtual void inputMethodEvent(QInputMethodEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseDoubleClickEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); +#ifndef QT_NO_WHEELEVENT + virtual void wheelEvent(QWheelEvent *); +#endif + +private slots: + void sceneGraphChanged(); + void maybeUpdate(); + +private: + Q_DISABLE_COPY(QSGCanvas); +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSGCanvas *); + +QT_END_HEADER + +#endif // QSGCANVAS_H + diff --git a/src/declarative/items/qsgcanvas_p.h b/src/declarative/items/qsgcanvas_p.h new file mode 100644 index 0000000000..63bd2dfb28 --- /dev/null +++ b/src/declarative/items/qsgcanvas_p.h @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGCANVAS_P_H +#define QSGCANVAS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgitem.h" +#include "qsgcanvas.h" +#include <private/qdeclarativeguard_p.h> + +#include <private/qsgcontext_p.h> + +#include <QtCore/qthread.h> +#include <QtCore/qmutex.h> +#include <QtCore/qwaitcondition.h> +#include <private/qwidget_p.h> +#include <private/qgl_p.h> + +QT_BEGIN_NAMESPACE + +//Make it easy to identify and customize the root item if needed +class QSGRootItem : public QSGItem +{ + Q_OBJECT +public: + QSGRootItem(); +}; + +class QSGCanvasPrivate; + +class QSGThreadedRendererAnimationDriver : public QAnimationDriver +{ +public: + QSGThreadedRendererAnimationDriver(QSGCanvasPrivate *r, QObject *parent); + +protected: + virtual void started(); + virtual void stopped(); + + QSGCanvasPrivate *renderer; +}; + +class QTouchEvent; +class QSGCanvasPrivate : public QGLWidgetPrivate +{ +public: + Q_DECLARE_PUBLIC(QSGCanvas) + + static inline QSGCanvasPrivate *get(QSGCanvas *c) { return c->d_func(); } + + QSGCanvasPrivate(); + virtual ~QSGCanvasPrivate(); + + void init(QSGCanvas *); + + QSGRootItem *rootItem; + + QSGItem *activeFocusItem; + QSGItem *mouseGrabberItem; + + // Mouse positions are saved in widget coordinates + QPoint lastMousePosition; + QPoint buttonDownPositions[5]; // Left, Right, Middle, XButton1, XButton2 + void sceneMouseEventFromMouseEvent(QGraphicsSceneMouseEvent &, QMouseEvent *); + void translateTouchEvent(QTouchEvent *touchEvent); + static QEvent::Type sceneMouseEventTypeFromMouseEvent(QMouseEvent *); + static void sceneMouseEventForTransform(QGraphicsSceneMouseEvent &, const QTransform &); + static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform); + bool deliverInitialMousePressEvent(QSGItem *, QGraphicsSceneMouseEvent *); + bool deliverMouseEvent(QGraphicsSceneMouseEvent *); + bool sendFilteredMouseEvent(QSGItem *, QSGItem *, QGraphicsSceneMouseEvent *); + bool deliverWheelEvent(QSGItem *, QGraphicsSceneWheelEvent *); + bool deliverTouchPoints(QSGItem *, QTouchEvent *, const QList<QTouchEvent::TouchPoint> &, QSet<int> *, + QHash<QSGItem *, QList<QTouchEvent::TouchPoint> > *); + bool deliverTouchEvent(QTouchEvent *); + void sceneHoverEventFromMouseEvent(QGraphicsSceneHoverEvent &, QMouseEvent *); + bool deliverHoverEvent(QSGItem *, QGraphicsSceneHoverEvent *); + void sendHoverEvent(QEvent::Type, QSGItem *, QGraphicsSceneHoverEvent *); + void clearHover(); + + QDeclarativeGuard<QSGItem> hoverItem; + enum FocusOption { + DontChangeFocusProperty = 0x01, + }; + Q_DECLARE_FLAGS(FocusOptions, FocusOption) + + void setFocusInScope(QSGItem *scope, QSGItem *item, FocusOptions = 0); + void clearFocusInScope(QSGItem *scope, QSGItem *item, FocusOptions = 0); + void notifyFocusChangesRecur(QSGItem **item, int remaining); + + void updateInputMethodData(); + + void dirtyItem(QSGItem *); + void cleanup(QSGNode *); + + void initializeSceneGraph(); + void polishItems(); + void syncSceneGraph(); + void renderSceneGraph(); + void runThread(); + + QSGItem::UpdatePaintNodeData updatePaintNodeData; + + QSGItem *dirtyItemList; + QList<QSGNode *> cleanupNodeList; + + QSet<QSGItem *> itemsToPolish; + + void updateDirtyNodes(); + void cleanupNodes(); + bool updateEffectiveOpacity(QSGItem *); + void updateEffectiveOpacityRoot(QSGItem *, qreal); + void updateDirtyNode(QSGItem *); + + QSGContext *context; + + uint contextInThread : 1; + uint threadedRendering : 1; + uint exitThread : 1; + uint animationRunning: 1; + uint idle : 1; // Set to true when render thread sees no change and enters a wait() + uint needsRepaint : 1; // Set by callback from render if scene needs repainting. + uint renderThreadAwakened : 1; + + struct MyThread : public QThread { + MyThread(QSGCanvasPrivate *r) : renderer(r) {} + virtual void run() { renderer->runThread(); } + static void doWait() { QThread::msleep(16); } + QSGCanvasPrivate *renderer; + }; + MyThread *thread; + QMutex mutex; + QWaitCondition wait; + QSize widgetSize; + QSize viewportSize; + + QAnimationDriver *animationDriver; + + QHash<int, QSGItem *> itemForTouchPointId; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSGCanvasPrivate::FocusOptions) + +QT_END_NAMESPACE + +#endif // QSGCANVAS_P_H diff --git a/src/declarative/items/qsgclipnode.cpp b/src/declarative/items/qsgclipnode.cpp new file mode 100644 index 0000000000..2e40972620 --- /dev/null +++ b/src/declarative/items/qsgclipnode.cpp @@ -0,0 +1,121 @@ + +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qsgclipnode_p.h" + +#include <QtGui/qvector2d.h> +#include <QtCore/qmath.h> + +QSGDefaultClipNode::QSGDefaultClipNode(const QRectF &rect) + : m_rect(rect) + , m_radius(0) + , m_dirty_geometry(true) + , m_geometry(QSGGeometry::defaultAttributes_Point2D(), 0) +{ + setGeometry(&m_geometry); + setIsRectangular(true); +} + +void QSGDefaultClipNode::setRect(const QRectF &rect) +{ + m_rect = rect; + m_dirty_geometry = true; +} + +void QSGDefaultClipNode::setRadius(qreal radius) +{ + m_radius = radius; + m_dirty_geometry = true; + setIsRectangular(radius == 0); +} + +void QSGDefaultClipNode::update() +{ + if (m_dirty_geometry) { + updateGeometry(); + m_dirty_geometry = false; + } +} + +void QSGDefaultClipNode::updateGeometry() +{ + QSGGeometry *g = geometry(); + + if (qFuzzyIsNull(m_radius)) { + g->allocate(4); + QSGGeometry::updateRectGeometry(g, m_rect); + + } else { + int vertexCount = 0; + + // Radius should never exceeds half of the width or half of the height + qreal radius = qMin(qMin(m_rect.width() / 2, m_rect.height() / 2), m_radius); + QRectF rect = m_rect; + rect.adjust(radius, radius, -radius, -radius); + + int segments = qMin(30, qCeil(radius)); // Number of segments per corner. + + g->allocate((segments + 1) * 2); + + QVector2D *vertices = (QVector2D *)g->vertexData(); + + for (int part = 0; part < 2; ++part) { + for (int i = 0; i <= segments; ++i) { + //### Should change to calculate sin/cos only once. + qreal angle = qreal(0.5 * M_PI) * (part + i / qreal(segments)); + qreal s = qFastSin(angle); + qreal c = qFastCos(angle); + qreal y = (part ? rect.bottom() : rect.top()) - radius * c; // current inner y-coordinate. + qreal lx = rect.left() - radius * s; // current inner left x-coordinate. + qreal rx = rect.right() + radius * s; // current inner right x-coordinate. + + vertices[vertexCount++] = QVector2D(rx, y); + vertices[vertexCount++] = QVector2D(lx, y); + } + } + + markDirty(DirtyGeometry); + } + setClipRect(m_rect); +} + diff --git a/src/declarative/items/qsgclipnode_p.h b/src/declarative/items/qsgclipnode_p.h new file mode 100644 index 0000000000..aa1d01efdd --- /dev/null +++ b/src/declarative/items/qsgclipnode_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGCLIPNODE_P_H +#define QSGCLIPNODE_P_H + +#include <qsgnode.h> + +class QSGDefaultClipNode : public QSGClipNode +{ +public: + QSGDefaultClipNode(const QRectF &); + + void setRect(const QRectF &); + QRectF rect() const { return m_rect; } + + void setRadius(qreal radius); + qreal radius() const { return m_radius; } + + virtual void update(); + +private: + void updateGeometry(); + QRectF m_rect; + qreal m_radius; + + uint m_dirty_geometry : 1; + uint m_reserved : 31; + + QSGGeometry m_geometry; +}; + +#endif // QSGCLIPNODE_P_H diff --git a/src/declarative/items/qsgevents.cpp b/src/declarative/items/qsgevents.cpp new file mode 100644 index 0000000000..44ef38b037 --- /dev/null +++ b/src/declarative/items/qsgevents.cpp @@ -0,0 +1,47 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgevents_p_p.h" + +QT_BEGIN_NAMESPACE + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgevents_p_p.h b/src/declarative/items/qsgevents_p_p.h new file mode 100644 index 0000000000..f0d434db32 --- /dev/null +++ b/src/declarative/items/qsgevents_p_p.h @@ -0,0 +1,142 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGEVENTS_P_P_H +#define QSGEVENTS_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qdeclarative.h> + +#include <QtCore/qobject.h> +#include <QtGui/qevent.h> + +QT_BEGIN_NAMESPACE + +class QSGKeyEvent : public QObject +{ + Q_OBJECT + Q_PROPERTY(int key READ key) + Q_PROPERTY(QString text READ text) + Q_PROPERTY(int modifiers READ modifiers) + Q_PROPERTY(bool isAutoRepeat READ isAutoRepeat) + Q_PROPERTY(int count READ count) + Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted) + +public: + QSGKeyEvent(QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, const QString &text=QString(), bool autorep=false, ushort count=1) + : event(type, key, modifiers, text, autorep, count) { event.setAccepted(false); } + QSGKeyEvent(const QKeyEvent &ke) + : event(ke) { event.setAccepted(false); } + + int key() const { return event.key(); } + QString text() const { return event.text(); } + int modifiers() const { return event.modifiers(); } + bool isAutoRepeat() const { return event.isAutoRepeat(); } + int count() const { return event.count(); } + + bool isAccepted() { return event.isAccepted(); } + void setAccepted(bool accepted) { event.setAccepted(accepted); } + +private: + QKeyEvent event; +}; + +class QSGMouseEvent : public QObject +{ + Q_OBJECT + Q_PROPERTY(int x READ x) + Q_PROPERTY(int y READ y) + Q_PROPERTY(int button READ button) + Q_PROPERTY(int buttons READ buttons) + Q_PROPERTY(int modifiers READ modifiers) + Q_PROPERTY(bool wasHeld READ wasHeld) + Q_PROPERTY(bool isClick READ isClick) + Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted) + +public: + QSGMouseEvent(int x, int y, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers + , bool isClick=false, bool wasHeld=false) + : _x(x), _y(y), _button(button), _buttons(buttons), _modifiers(modifiers) + , _wasHeld(wasHeld), _isClick(isClick), _accepted(true) {} + + int x() const { return _x; } + int y() const { return _y; } + int button() const { return _button; } + int buttons() const { return _buttons; } + int modifiers() const { return _modifiers; } + bool wasHeld() const { return _wasHeld; } + bool isClick() const { return _isClick; } + + // only for internal usage + void setX(int x) { _x = x; } + void setY(int y) { _y = y; } + + bool isAccepted() { return _accepted; } + void setAccepted(bool accepted) { _accepted = accepted; } + +private: + int _x; + int _y; + Qt::MouseButton _button; + Qt::MouseButtons _buttons; + Qt::KeyboardModifiers _modifiers; + bool _wasHeld; + bool _isClick; + bool _accepted; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGKeyEvent) +QML_DECLARE_TYPE(QSGMouseEvent) + +#endif // QSGEVENTS_P_P_H diff --git a/src/declarative/items/qsgflickable.cpp b/src/declarative/items/qsgflickable.cpp new file mode 100644 index 0000000000..be0ac13bbf --- /dev/null +++ b/src/declarative/items/qsgflickable.cpp @@ -0,0 +1,1495 @@ +// Commit: d4fa1878ff1e7628d3e984d54f8a93810353c71b +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgflickable_p.h" +#include "qsgflickable_p_p.h" +#include "qsgcanvas.h" +#include "qsgcanvas_p.h" + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qapplication.h> +#include "qplatformdefs.h" + +QT_BEGIN_NAMESPACE + +// The maximum number of pixels a flick can overshoot +#ifndef QML_FLICK_OVERSHOOT +#define QML_FLICK_OVERSHOOT 200 +#endif + +// The number of samples to use in calculating the velocity of a flick +#ifndef QML_FLICK_SAMPLEBUFFER +#define QML_FLICK_SAMPLEBUFFER 3 +#endif + +// The number of samples to discard when calculating the flick velocity. +// Touch panels often produce inaccurate results as the finger is lifted. +#ifndef QML_FLICK_DISCARDSAMPLES +#define QML_FLICK_DISCARDSAMPLES 1 +#endif + +// The default maximum velocity of a flick. +#ifndef QML_FLICK_DEFAULTMAXVELOCITY +#define QML_FLICK_DEFAULTMAXVELOCITY 2500 +#endif + +// The default deceleration of a flick. +#ifndef QML_FLICK_DEFAULTDECELERATION +#define QML_FLICK_DEFAULTDECELERATION 1500 +#endif + +// How much faster to decelerate when overshooting +#ifndef QML_FLICK_OVERSHOOTFRICTION +#define QML_FLICK_OVERSHOOTFRICTION 8 +#endif + +// FlickThreshold determines how far the "mouse" must have moved +// before we perform a flick. +static const int FlickThreshold = 20; + +// RetainGrabVelocity is the maxmimum instantaneous velocity that +// will ensure the Flickable retains the grab on consecutive flicks. +static const int RetainGrabVelocity = 15; + +QSGFlickableVisibleArea::QSGFlickableVisibleArea(QSGFlickable *parent) + : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.) + , m_yPosition(0.), m_heightRatio(0.) +{ +} + +qreal QSGFlickableVisibleArea::widthRatio() const +{ + return m_widthRatio; +} + +qreal QSGFlickableVisibleArea::xPosition() const +{ + return m_xPosition; +} + +qreal QSGFlickableVisibleArea::heightRatio() const +{ + return m_heightRatio; +} + +qreal QSGFlickableVisibleArea::yPosition() const +{ + return m_yPosition; +} + +void QSGFlickableVisibleArea::updateVisible() +{ + QSGFlickablePrivate *p = QSGFlickablePrivate::get(flickable); + + bool changeX = false; + bool changeY = false; + bool changeWidth = false; + bool changeHeight = false; + + // Vertical + const qreal viewheight = flickable->height(); + const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent(); + qreal pagePos = (-p->vData.move.value() + flickable->minYExtent()) / (maxyextent + viewheight); + qreal pageSize = viewheight / (maxyextent + viewheight); + + if (pageSize != m_heightRatio) { + m_heightRatio = pageSize; + changeHeight = true; + } + if (pagePos != m_yPosition) { + m_yPosition = pagePos; + changeY = true; + } + + // Horizontal + const qreal viewwidth = flickable->width(); + const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent(); + pagePos = (-p->hData.move.value() + flickable->minXExtent()) / (maxxextent + viewwidth); + pageSize = viewwidth / (maxxextent + viewwidth); + + if (pageSize != m_widthRatio) { + m_widthRatio = pageSize; + changeWidth = true; + } + if (pagePos != m_xPosition) { + m_xPosition = pagePos; + changeX = true; + } + + if (changeX) + emit xPositionChanged(m_xPosition); + if (changeY) + emit yPositionChanged(m_yPosition); + if (changeWidth) + emit widthRatioChanged(m_widthRatio); + if (changeHeight) + emit heightRatioChanged(m_heightRatio); +} + + +QSGFlickablePrivate::QSGFlickablePrivate() + : contentItem(new QSGItem) + , hData(this, &QSGFlickablePrivate::setViewportX) + , vData(this, &QSGFlickablePrivate::setViewportY) + , flickingHorizontally(false), flickingVertically(false) + , hMoved(false), vMoved(false) + , movingHorizontally(false), movingVertically(false) + , stealMouse(false), pressed(false), interactive(true), calcVelocity(false) + , deceleration(QML_FLICK_DEFAULTDECELERATION) + , maxVelocity(QML_FLICK_DEFAULTMAXVELOCITY), reportedVelocitySmoothing(100) + , delayedPressEvent(0), delayedPressTarget(0), pressDelay(0), fixupDuration(400) + , fixupMode(Normal), vTime(0), visibleArea(0) + , flickableDirection(QSGFlickable::AutoFlickDirection) + , boundsBehavior(QSGFlickable::DragAndOvershootBounds) +{ +} + +void QSGFlickablePrivate::init() +{ + Q_Q(QSGFlickable); + QDeclarative_setParent_noEvent(contentItem, q); + contentItem->setParentItem(q); + static int timelineUpdatedIdx = -1; + static int timelineCompletedIdx = -1; + static int flickableTickedIdx = -1; + static int flickableMovementEndingIdx = -1; + if (timelineUpdatedIdx == -1) { + timelineUpdatedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("updated()"); + timelineCompletedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("completed()"); + flickableTickedIdx = QSGFlickable::staticMetaObject.indexOfSlot("ticked()"); + flickableMovementEndingIdx = QSGFlickable::staticMetaObject.indexOfSlot("movementEnding()"); + } + QMetaObject::connect(&timeline, timelineUpdatedIdx, + q, flickableTickedIdx, Qt::DirectConnection); + QMetaObject::connect(&timeline, timelineCompletedIdx, + q, flickableMovementEndingIdx, Qt::DirectConnection); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFiltersChildMouseEvents(true); + QSGItemPrivate *viewportPrivate = QSGItemPrivate::get(contentItem); + viewportPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + lastPosTime.invalidate(); +} + +/* + Returns the amount to overshoot by given a velocity. + Will be roughly in range 0 - size/4 +*/ +qreal QSGFlickablePrivate::overShootDistance(qreal size) +{ + if (maxVelocity <= 0) + return 0.0; + + return qMin(qreal(QML_FLICK_OVERSHOOT), size/3); +} + +void QSGFlickablePrivate::AxisData::addVelocitySample(qreal v, qreal maxVelocity) +{ + if (v > maxVelocity) + v = maxVelocity; + else if (v < -maxVelocity) + v = -maxVelocity; + velocityBuffer.append(v); + if (velocityBuffer.count() > QML_FLICK_SAMPLEBUFFER) + velocityBuffer.remove(0); +} + +void QSGFlickablePrivate::AxisData::updateVelocity() +{ + if (velocityBuffer.count() > QML_FLICK_DISCARDSAMPLES) { + velocity = 0; + int count = velocityBuffer.count()-QML_FLICK_DISCARDSAMPLES; + for (int i = 0; i < count; ++i) { + qreal v = velocityBuffer.at(i); + velocity += v; + } + velocity /= count; + } +} + +void QSGFlickablePrivate::itemGeometryChanged(QSGItem *item, const QRectF &newGeom, const QRectF &oldGeom) +{ + Q_Q(QSGFlickable); + if (item == contentItem) { + if (newGeom.x() != oldGeom.x()) + emit q->contentXChanged(); + if (newGeom.y() != oldGeom.y()) + emit q->contentYChanged(); + } +} + +void QSGFlickablePrivate::flickX(qreal velocity) +{ + Q_Q(QSGFlickable); + flick(hData, q->minXExtent(), q->maxXExtent(), q->width(), fixupX_callback, velocity); +} + +void QSGFlickablePrivate::flickY(qreal velocity) +{ + Q_Q(QSGFlickable); + flick(vData, q->minYExtent(), q->maxYExtent(), q->height(), fixupY_callback, velocity); +} + +void QSGFlickablePrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity) +{ + Q_Q(QSGFlickable); + qreal maxDistance = -1; + data.fixingUp = false; + // -ve velocity means list is moving up + if (velocity > 0) { + maxDistance = qAbs(minExtent - data.move.value()); + data.flickTarget = minExtent; + } else { + maxDistance = qAbs(maxExtent - data.move.value()); + data.flickTarget = maxExtent; + } + if (maxDistance > 0) { + qreal v = velocity; + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { + if (v < 0) + v = -maxVelocity; + else + v = maxVelocity; + } + timeline.reset(data.move); + if (boundsBehavior == QSGFlickable::DragAndOvershootBounds) + timeline.accel(data.move, v, deceleration); + else + timeline.accel(data.move, v, deceleration, maxDistance); + timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this)); + if (!flickingHorizontally && q->xflick()) { + flickingHorizontally = true; + emit q->flickingChanged(); + emit q->flickingHorizontallyChanged(); + if (!flickingVertically) + emit q->flickStarted(); + } + if (!flickingVertically && q->yflick()) { + flickingVertically = true; + emit q->flickingChanged(); + emit q->flickingVerticallyChanged(); + if (!flickingHorizontally) + emit q->flickStarted(); + } + } else { + timeline.reset(data.move); + fixup(data, minExtent, maxExtent); + } +} + +void QSGFlickablePrivate::fixupY_callback(void *data) +{ + ((QSGFlickablePrivate *)data)->fixupY(); +} + +void QSGFlickablePrivate::fixupX_callback(void *data) +{ + ((QSGFlickablePrivate *)data)->fixupX(); +} + +void QSGFlickablePrivate::fixupX() +{ + Q_Q(QSGFlickable); + fixup(hData, q->minXExtent(), q->maxXExtent()); +} + +void QSGFlickablePrivate::fixupY() +{ + Q_Q(QSGFlickable); + fixup(vData, q->minYExtent(), q->maxYExtent()); +} + +void QSGFlickablePrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) +{ + if (data.move.value() > minExtent || maxExtent > minExtent) { + timeline.reset(data.move); + if (data.move.value() != minExtent) { + switch (fixupMode) { + case Immediate: + timeline.set(data.move, minExtent); + break; + case ExtentChanged: + // The target has changed. Don't start from the beginning; just complete the + // second half of the animation using the new extent. + timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); + data.fixingUp = true; + break; + default: { + qreal dist = minExtent - data.move; + timeline.move(data.move, minExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4); + timeline.move(data.move, minExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); + data.fixingUp = true; + } + } + } + } else if (data.move.value() < maxExtent) { + timeline.reset(data.move); + switch (fixupMode) { + case Immediate: + timeline.set(data.move, maxExtent); + break; + case ExtentChanged: + // The target has changed. Don't start from the beginning; just complete the + // second half of the animation using the new extent. + timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); + data.fixingUp = true; + break; + default: { + qreal dist = maxExtent - data.move; + timeline.move(data.move, maxExtent - dist/2, QEasingCurve(QEasingCurve::InQuad), fixupDuration/4); + timeline.move(data.move, maxExtent, QEasingCurve(QEasingCurve::OutExpo), 3*fixupDuration/4); + data.fixingUp = true; + } + } + } + data.inOvershoot = false; + fixupMode = Normal; + vTime = timeline.time(); +} + +void QSGFlickablePrivate::updateBeginningEnd() +{ + Q_Q(QSGFlickable); + bool atBoundaryChange = false; + + // Vertical + const int maxyextent = int(-q->maxYExtent()); + const qreal ypos = -vData.move.value(); + bool atBeginning = (ypos <= -q->minYExtent()); + bool atEnd = (maxyextent <= ypos); + + if (atBeginning != vData.atBeginning) { + vData.atBeginning = atBeginning; + atBoundaryChange = true; + } + if (atEnd != vData.atEnd) { + vData.atEnd = atEnd; + atBoundaryChange = true; + } + + // Horizontal + const int maxxextent = int(-q->maxXExtent()); + const qreal xpos = -hData.move.value(); + atBeginning = (xpos <= -q->minXExtent()); + atEnd = (maxxextent <= xpos); + + if (atBeginning != hData.atBeginning) { + hData.atBeginning = atBeginning; + atBoundaryChange = true; + } + if (atEnd != hData.atEnd) { + hData.atEnd = atEnd; + atBoundaryChange = true; + } + + if (atBoundaryChange) + emit q->isAtBoundaryChanged(); + + if (visibleArea) + visibleArea->updateVisible(); +} + +QSGFlickable::QSGFlickable(QSGItem *parent) + : QSGItem(*(new QSGFlickablePrivate), parent) +{ + Q_D(QSGFlickable); + d->init(); +} + +QSGFlickable::QSGFlickable(QSGFlickablePrivate &dd, QSGItem *parent) + : QSGItem(dd, parent) +{ + Q_D(QSGFlickable); + d->init(); +} + +QSGFlickable::~QSGFlickable() +{ +} + +qreal QSGFlickable::contentX() const +{ + Q_D(const QSGFlickable); + return -d->contentItem->x(); +} + +void QSGFlickable::setContentX(qreal pos) +{ + Q_D(QSGFlickable); + d->timeline.reset(d->hData.move); + d->vTime = d->timeline.time(); + movementXEnding(); + if (-pos != d->hData.move.value()) { + d->hData.move.setValue(-pos); + viewportMoved(); + } +} + +qreal QSGFlickable::contentY() const +{ + Q_D(const QSGFlickable); + return -d->contentItem->y(); +} + +void QSGFlickable::setContentY(qreal pos) +{ + Q_D(QSGFlickable); + d->timeline.reset(d->vData.move); + d->vTime = d->timeline.time(); + movementYEnding(); + if (-pos != d->vData.move.value()) { + d->vData.move.setValue(-pos); + viewportMoved(); + } +} + +bool QSGFlickable::isInteractive() const +{ + Q_D(const QSGFlickable); + return d->interactive; +} + +void QSGFlickable::setInteractive(bool interactive) +{ + Q_D(QSGFlickable); + if (interactive != d->interactive) { + d->interactive = interactive; + if (!interactive && (d->flickingHorizontally || d->flickingVertically)) { + d->timeline.clear(); + d->vTime = d->timeline.time(); + d->flickingHorizontally = false; + d->flickingVertically = false; + emit flickingChanged(); + emit flickingHorizontallyChanged(); + emit flickingVerticallyChanged(); + emit flickEnded(); + } + emit interactiveChanged(); + } +} + +qreal QSGFlickable::horizontalVelocity() const +{ + Q_D(const QSGFlickable); + return d->hData.smoothVelocity.value(); +} + +qreal QSGFlickable::verticalVelocity() const +{ + Q_D(const QSGFlickable); + return d->vData.smoothVelocity.value(); +} + +bool QSGFlickable::isAtXEnd() const +{ + Q_D(const QSGFlickable); + return d->hData.atEnd; +} + +bool QSGFlickable::isAtXBeginning() const +{ + Q_D(const QSGFlickable); + return d->hData.atBeginning; +} + +bool QSGFlickable::isAtYEnd() const +{ + Q_D(const QSGFlickable); + return d->vData.atEnd; +} + +bool QSGFlickable::isAtYBeginning() const +{ + Q_D(const QSGFlickable); + return d->vData.atBeginning; +} + +void QSGFlickable::ticked() +{ + viewportMoved(); +} + +QSGItem *QSGFlickable::contentItem() +{ + Q_D(QSGFlickable); + return d->contentItem; +} + +QSGFlickableVisibleArea *QSGFlickable::visibleArea() +{ + Q_D(QSGFlickable); + if (!d->visibleArea) + d->visibleArea = new QSGFlickableVisibleArea(this); + return d->visibleArea; +} + +QSGFlickable::FlickableDirection QSGFlickable::flickableDirection() const +{ + Q_D(const QSGFlickable); + return d->flickableDirection; +} + +void QSGFlickable::setFlickableDirection(FlickableDirection direction) +{ + Q_D(QSGFlickable); + if (direction != d->flickableDirection) { + d->flickableDirection = direction; + emit flickableDirectionChanged(); + } +} + +void QSGFlickablePrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGFlickable); + if (interactive && timeline.isActive() + && (qAbs(hData.smoothVelocity.value()) > RetainGrabVelocity + || qAbs(vData.smoothVelocity.value()) > RetainGrabVelocity)) { + stealMouse = true; // If we've been flicked then steal the click. + } else { + stealMouse = false; + } + q->setKeepMouseGrab(stealMouse); + pressed = true; + timeline.clear(); + hData.reset(); + vData.reset(); + hData.dragMinBound = q->minXExtent(); + vData.dragMinBound = q->minYExtent(); + hData.dragMaxBound = q->maxXExtent(); + vData.dragMaxBound = q->maxYExtent(); + fixupMode = Normal; + lastPos = QPoint(); + QSGItemPrivate::start(lastPosTime); + pressPos = event->pos(); + hData.pressPos = hData.move.value(); + vData.pressPos = vData.move.value(); + flickingHorizontally = false; + flickingVertically = false; + QSGItemPrivate::start(pressTime); + QSGItemPrivate::start(velocityTime); +} + +void QSGFlickablePrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGFlickable); + if (!interactive || !lastPosTime.isValid()) + return; + bool rejectY = false; + bool rejectX = false; + + bool stealY = stealMouse; + bool stealX = stealMouse; + + if (q->yflick()) { + int dy = int(event->pos().y() - pressPos.y()); + if (qAbs(dy) > QApplication::startDragDistance() || QSGItemPrivate::elapsed(pressTime) > 200) { + if (!vMoved) + vData.dragStartOffset = dy; + qreal newY = dy + vData.pressPos - vData.dragStartOffset; + const qreal minY = vData.dragMinBound; + const qreal maxY = vData.dragMaxBound; + if (newY > minY) + newY = minY + (newY - minY) / 2; + if (newY < maxY && maxY - minY <= 0) + newY = maxY + (newY - maxY) / 2; + if (boundsBehavior == QSGFlickable::StopAtBounds && (newY > minY || newY < maxY)) { + rejectY = true; + if (newY < maxY) { + newY = maxY; + rejectY = false; + } + if (newY > minY) { + newY = minY; + rejectY = false; + } + } + if (!rejectY && stealMouse) { + vData.move.setValue(qRound(newY)); + vMoved = true; + } + if (qAbs(dy) > QApplication::startDragDistance()) + stealY = true; + } + } + + if (q->xflick()) { + int dx = int(event->pos().x() - pressPos.x()); + if (qAbs(dx) > QApplication::startDragDistance() || QSGItemPrivate::elapsed(pressTime) > 200) { + if (!hMoved) + hData.dragStartOffset = dx; + qreal newX = dx + hData.pressPos - hData.dragStartOffset; + const qreal minX = hData.dragMinBound; + const qreal maxX = hData.dragMaxBound; + if (newX > minX) + newX = minX + (newX - minX) / 2; + if (newX < maxX && maxX - minX <= 0) + newX = maxX + (newX - maxX) / 2; + if (boundsBehavior == QSGFlickable::StopAtBounds && (newX > minX || newX < maxX)) { + rejectX = true; + if (newX < maxX) { + newX = maxX; + rejectX = false; + } + if (newX > minX) { + newX = minX; + rejectX = false; + } + } + if (!rejectX && stealMouse) { + hData.move.setValue(qRound(newX)); + hMoved = true; + } + + if (qAbs(dx) > QApplication::startDragDistance()) + stealX = true; + } + } + + stealMouse = stealX || stealY; + if (stealMouse) + q->setKeepMouseGrab(true); + + if (rejectY) { + vData.velocityBuffer.clear(); + vData.velocity = 0; + } + if (rejectX) { + hData.velocityBuffer.clear(); + hData.velocity = 0; + } + + if (hMoved || vMoved) { + q->movementStarting(); + q->viewportMoved(); + } + + if (!lastPos.isNull()) { + qreal elapsed = qreal(QSGItemPrivate::elapsed(lastPosTime)) / 1000.; + if (elapsed <= 0) + return; + QSGItemPrivate::restart(lastPosTime); + qreal dy = event->pos().y()-lastPos.y(); + if (q->yflick() && !rejectY) + vData.addVelocitySample(dy/elapsed, maxVelocity); + qreal dx = event->pos().x()-lastPos.x(); + if (q->xflick() && !rejectX) + hData.addVelocitySample(dx/elapsed, maxVelocity); + } + + lastPos = event->pos(); +} + +void QSGFlickablePrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGFlickable); + stealMouse = false; + q->setKeepMouseGrab(false); + pressed = false; + if (!lastPosTime.isValid()) + return; + + // if we drag then pause before release we should not cause a flick. + if (QSGItemPrivate::elapsed(lastPosTime) < 100) { + vData.updateVelocity(); + hData.updateVelocity(); + } else { + hData.velocity = 0.0; + vData.velocity = 0.0; + } + + vTime = timeline.time(); + + qreal velocity = vData.velocity; + if (vData.atBeginning || vData.atEnd) + velocity /= 2; + if (qAbs(velocity) > MinimumFlickVelocity && qAbs(event->pos().y() - pressPos.y()) > FlickThreshold) + flickY(velocity); + else + fixupY(); + + velocity = hData.velocity; + if (hData.atBeginning || hData.atEnd) + velocity /= 2; + if (qAbs(velocity) > MinimumFlickVelocity && qAbs(event->pos().x() - pressPos.x()) > FlickThreshold) + flickX(velocity); + else + fixupX(); + + if (!timeline.isActive()) + q->movementEnding(); +} + +void QSGFlickable::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGFlickable); + if (d->interactive) { + if (!d->pressed) + d->handleMousePressEvent(event); + event->accept(); + } else { + QSGItem::mousePressEvent(event); + } +} + +void QSGFlickable::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGFlickable); + if (d->interactive) { + d->handleMouseMoveEvent(event); + event->accept(); + } else { + QSGItem::mouseMoveEvent(event); + } +} + +void QSGFlickable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGFlickable); + if (d->interactive) { + d->clearDelayedPress(); + d->handleMouseReleaseEvent(event); + event->accept(); + ungrabMouse(); + } else { + QSGItem::mouseReleaseEvent(event); + } +} + +void QSGFlickable::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + Q_D(QSGFlickable); + if (!d->interactive) { + QSGItem::wheelEvent(event); + } else if (yflick() && event->orientation() == Qt::Vertical) { + bool valid = false; + if (event->delta() > 0 && contentY() > -minYExtent()) { + d->vData.velocity = qMax(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(d->maxVelocity/4)); + valid = true; + } else if (event->delta() < 0 && contentY() < -maxYExtent()) { + d->vData.velocity = qMin(event->delta()*2 - d->vData.smoothVelocity.value(), qreal(-d->maxVelocity/4)); + valid = true; + } + if (valid) { + d->flickingVertically = false; + d->flickY(d->vData.velocity); + if (d->flickingVertically) { + d->vMoved = true; + movementStarting(); + } + event->accept(); + } + } else if (xflick() && event->orientation() == Qt::Horizontal) { + bool valid = false; + if (event->delta() > 0 && contentX() > -minXExtent()) { + d->hData.velocity = qMax(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(d->maxVelocity/4)); + valid = true; + } else if (event->delta() < 0 && contentX() < -maxXExtent()) { + d->hData.velocity = qMin(event->delta()*2 - d->hData.smoothVelocity.value(), qreal(-d->maxVelocity/4)); + valid = true; + } + if (valid) { + d->flickingHorizontally = false; + d->flickX(d->hData.velocity); + if (d->flickingHorizontally) { + d->hMoved = true; + movementStarting(); + } + event->accept(); + } + } else { + QSGItem::wheelEvent(event); + } +} + +bool QSGFlickablePrivate::isOutermostPressDelay() const +{ + Q_Q(const QSGFlickable); + QSGItem *item = q->parentItem(); + while (item) { + QSGFlickable *flick = qobject_cast<QSGFlickable*>(item); + if (flick && flick->pressDelay() > 0 && flick->isInteractive()) + return false; + item = item->parentItem(); + } + + return true; +} + +void QSGFlickablePrivate::captureDelayedPress(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGFlickable); + if (!q->canvas() || pressDelay <= 0) + return; + if (!isOutermostPressDelay()) + return; + delayedPressTarget = q->canvas()->mouseGrabberItem(); + delayedPressEvent = new QGraphicsSceneMouseEvent(event->type()); + delayedPressEvent->setAccepted(false); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + delayedPressEvent->setButtonDownPos(button, event->buttonDownPos(button)); + delayedPressEvent->setButtonDownScenePos(button, event->buttonDownScenePos(button)); + delayedPressEvent->setButtonDownScreenPos(button, event->buttonDownScreenPos(button)); + } + } + delayedPressEvent->setButtons(event->buttons()); + delayedPressEvent->setButton(event->button()); + delayedPressEvent->setPos(event->pos()); + delayedPressEvent->setScenePos(event->scenePos()); + delayedPressEvent->setScreenPos(event->screenPos()); + delayedPressEvent->setLastPos(event->lastPos()); + delayedPressEvent->setLastScenePos(event->lastScenePos()); + delayedPressEvent->setLastScreenPos(event->lastScreenPos()); + delayedPressEvent->setModifiers(event->modifiers()); + delayedPressTimer.start(pressDelay, q); +} + +void QSGFlickablePrivate::clearDelayedPress() +{ + if (delayedPressEvent) { + delayedPressTimer.stop(); + delete delayedPressEvent; + delayedPressEvent = 0; + } +} + +void QSGFlickablePrivate::setViewportX(qreal x) +{ + contentItem->setX(x); +} + +void QSGFlickablePrivate::setViewportY(qreal y) +{ + contentItem->setY(y); +} + +void QSGFlickable::timerEvent(QTimerEvent *event) +{ + Q_D(QSGFlickable); + if (event->timerId() == d->delayedPressTimer.timerId()) { + d->delayedPressTimer.stop(); + if (d->delayedPressEvent) { + QSGItem *grabber = canvas() ? canvas()->mouseGrabberItem() : 0; + if (!grabber || grabber != this) { + // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay) + // so we reset the grabber + if (canvas()->mouseGrabberItem() == d->delayedPressTarget) + d->delayedPressTarget->ungrabMouse(); + // Use the event handler that will take care of finding the proper item to propagate the event + QSGCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent); + } + delete d->delayedPressEvent; + d->delayedPressEvent = 0; + } + } +} + +qreal QSGFlickable::minYExtent() const +{ + return 0.0; +} + +qreal QSGFlickable::minXExtent() const +{ + return 0.0; +} + +/* returns -ve */ +qreal QSGFlickable::maxXExtent() const +{ + return width() - vWidth(); +} +/* returns -ve */ +qreal QSGFlickable::maxYExtent() const +{ + return height() - vHeight(); +} + +void QSGFlickable::viewportMoved() +{ + Q_D(QSGFlickable); + + qreal prevX = d->lastFlickablePosition.x(); + qreal prevY = d->lastFlickablePosition.y(); + d->velocityTimeline.clear(); + if (d->pressed || d->calcVelocity) { + int elapsed = QSGItemPrivate::restart(d->velocityTime); + if (elapsed > 0) { + qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / elapsed; + qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / elapsed; + d->velocityTimeline.move(d->hData.smoothVelocity, horizontalVelocity, d->reportedVelocitySmoothing); + d->velocityTimeline.move(d->hData.smoothVelocity, 0, d->reportedVelocitySmoothing); + d->velocityTimeline.move(d->vData.smoothVelocity, verticalVelocity, d->reportedVelocitySmoothing); + d->velocityTimeline.move(d->vData.smoothVelocity, 0, d->reportedVelocitySmoothing); + } + } else { + if (d->timeline.time() > d->vTime) { + qreal horizontalVelocity = (prevX - d->hData.move.value()) * 1000 / (d->timeline.time() - d->vTime); + qreal verticalVelocity = (prevY - d->vData.move.value()) * 1000 / (d->timeline.time() - d->vTime); + d->hData.smoothVelocity.setValue(horizontalVelocity); + d->vData.smoothVelocity.setValue(verticalVelocity); + } + } + + if (!d->vData.inOvershoot && !d->vData.fixingUp && d->flickingVertically + && (d->vData.move.value() > minYExtent() || d->vData.move.value() < maxYExtent()) + && qAbs(d->vData.smoothVelocity.value()) > 100) { + // Increase deceleration if we've passed a bound + d->vData.inOvershoot = true; + qreal maxDistance = d->overShootDistance(height()); + d->timeline.reset(d->vData.move); + d->timeline.accel(d->vData.move, -d->vData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); + d->timeline.callback(QDeclarativeTimeLineCallback(&d->vData.move, d->fixupY_callback, d)); + } + if (!d->hData.inOvershoot && !d->hData.fixingUp && d->flickingHorizontally + && (d->hData.move.value() > minXExtent() || d->hData.move.value() < maxXExtent()) + && qAbs(d->hData.smoothVelocity.value()) > 100) { + // Increase deceleration if we've passed a bound + d->hData.inOvershoot = true; + qreal maxDistance = d->overShootDistance(width()); + d->timeline.reset(d->hData.move); + d->timeline.accel(d->hData.move, -d->hData.smoothVelocity.value(), d->deceleration*QML_FLICK_OVERSHOOTFRICTION, maxDistance); + d->timeline.callback(QDeclarativeTimeLineCallback(&d->hData.move, d->fixupX_callback, d)); + } + + d->lastFlickablePosition = QPointF(d->hData.move.value(), d->vData.move.value()); + + d->vTime = d->timeline.time(); + d->updateBeginningEnd(); +} + +void QSGFlickable::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + Q_D(QSGFlickable); + QSGItem::geometryChanged(newGeometry, oldGeometry); + + bool changed = false; + if (newGeometry.width() != oldGeometry.width()) { + if (xflick()) + changed = true; + if (d->hData.viewSize < 0) { + d->contentItem->setWidth(width()); + emit contentWidthChanged(); + } + // Make sure that we're entirely in view. + if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { + d->fixupMode = QSGFlickablePrivate::Immediate; + d->fixupX(); + } + } + if (newGeometry.height() != oldGeometry.height()) { + if (yflick()) + changed = true; + if (d->vData.viewSize < 0) { + d->contentItem->setHeight(height()); + emit contentHeightChanged(); + } + // Make sure that we're entirely in view. + if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { + d->fixupMode = QSGFlickablePrivate::Immediate; + d->fixupY(); + } + } + + if (changed) + d->updateBeginningEnd(); +} + +void QSGFlickable::cancelFlick() +{ + Q_D(QSGFlickable); + d->timeline.reset(d->hData.move); + d->timeline.reset(d->vData.move); + movementEnding(); +} + +void QSGFlickablePrivate::data_append(QDeclarativeListProperty<QObject> *prop, QObject *o) +{ + QSGItem *i = qobject_cast<QSGItem *>(o); + if (i) { + i->setParentItem(static_cast<QSGFlickablePrivate*>(prop->data)->contentItem); + } else { + o->setParent(prop->object); // XXX todo - do we want this? + } +} + +int QSGFlickablePrivate::data_count(QDeclarativeListProperty<QObject> *) +{ + // XXX todo + return 0; +} + +QObject *QSGFlickablePrivate::data_at(QDeclarativeListProperty<QObject> *, int) +{ + // XXX todo + return 0; +} + +void QSGFlickablePrivate::data_clear(QDeclarativeListProperty<QObject> *) +{ + // XXX todo +} + +QDeclarativeListProperty<QObject> QSGFlickable::flickableData() +{ + Q_D(QSGFlickable); + return QDeclarativeListProperty<QObject>(this, (void *)d, QSGFlickablePrivate::data_append, + QSGFlickablePrivate::data_count, + QSGFlickablePrivate::data_at, + QSGFlickablePrivate::data_clear); +} + +QDeclarativeListProperty<QSGItem> QSGFlickable::flickableChildren() +{ + Q_D(QSGFlickable); + return QSGItemPrivate::get(d->contentItem)->children(); +} + +QSGFlickable::BoundsBehavior QSGFlickable::boundsBehavior() const +{ + Q_D(const QSGFlickable); + return d->boundsBehavior; +} + +void QSGFlickable::setBoundsBehavior(BoundsBehavior b) +{ + Q_D(QSGFlickable); + if (b == d->boundsBehavior) + return; + d->boundsBehavior = b; + emit boundsBehaviorChanged(); +} + +qreal QSGFlickable::contentWidth() const +{ + Q_D(const QSGFlickable); + return d->hData.viewSize; +} + +void QSGFlickable::setContentWidth(qreal w) +{ + Q_D(QSGFlickable); + if (d->hData.viewSize == w) + return; + d->hData.viewSize = w; + if (w < 0) + d->contentItem->setWidth(width()); + else + d->contentItem->setWidth(w); + // Make sure that we're entirely in view. + if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { + d->fixupMode = QSGFlickablePrivate::Immediate; + d->fixupX(); + } else if (!d->pressed && d->hData.fixingUp) { + d->fixupMode = QSGFlickablePrivate::ExtentChanged; + d->fixupX(); + } + emit contentWidthChanged(); + d->updateBeginningEnd(); +} + +qreal QSGFlickable::contentHeight() const +{ + Q_D(const QSGFlickable); + return d->vData.viewSize; +} + +void QSGFlickable::setContentHeight(qreal h) +{ + Q_D(QSGFlickable); + if (d->vData.viewSize == h) + return; + d->vData.viewSize = h; + if (h < 0) + d->contentItem->setHeight(height()); + else + d->contentItem->setHeight(h); + // Make sure that we're entirely in view. + if (!d->pressed && !d->movingHorizontally && !d->movingVertically) { + d->fixupMode = QSGFlickablePrivate::Immediate; + d->fixupY(); + } else if (!d->pressed && d->vData.fixingUp) { + d->fixupMode = QSGFlickablePrivate::ExtentChanged; + d->fixupY(); + } + emit contentHeightChanged(); + d->updateBeginningEnd(); +} + +void QSGFlickable::resizeContent(qreal w, qreal h, QPointF center) +{ + Q_D(QSGFlickable); + if (w != d->hData.viewSize) { + qreal oldSize = d->hData.viewSize; + d->hData.viewSize = w; + d->contentItem->setWidth(w); + emit contentWidthChanged(); + if (center.x() != 0) { + qreal pos = center.x() * w / oldSize; + setContentX(contentX() + pos - center.x()); + } + } + if (h != d->vData.viewSize) { + qreal oldSize = d->vData.viewSize; + d->vData.viewSize = h; + d->contentItem->setHeight(h); + emit contentHeightChanged(); + if (center.y() != 0) { + qreal pos = center.y() * h / oldSize; + setContentY(contentY() + pos - center.y()); + } + } + d->updateBeginningEnd(); +} + +void QSGFlickable::returnToBounds() +{ + Q_D(QSGFlickable); + d->fixupX(); + d->fixupY(); +} + +qreal QSGFlickable::vWidth() const +{ + Q_D(const QSGFlickable); + if (d->hData.viewSize < 0) + return width(); + else + return d->hData.viewSize; +} + +qreal QSGFlickable::vHeight() const +{ + Q_D(const QSGFlickable); + if (d->vData.viewSize < 0) + return height(); + else + return d->vData.viewSize; +} + +bool QSGFlickable::xflick() const +{ + Q_D(const QSGFlickable); + if (d->flickableDirection == QSGFlickable::AutoFlickDirection) + return vWidth() != width(); + return d->flickableDirection & QSGFlickable::HorizontalFlick; +} + +bool QSGFlickable::yflick() const +{ + Q_D(const QSGFlickable); + if (d->flickableDirection == QSGFlickable::AutoFlickDirection) + return vHeight() != height(); + return d->flickableDirection & QSGFlickable::VerticalFlick; +} + +void QSGFlickable::mouseUngrabEvent() +{ + Q_D(QSGFlickable); + if (d->pressed) { + // if our mouse grab has been removed (probably by another Flickable), + // fix our state + d->pressed = false; + d->stealMouse = false; + setKeepMouseGrab(false); + } +} + +bool QSGFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGFlickable); + QGraphicsSceneMouseEvent mouseEvent(event->type()); + QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + + QSGCanvas *c = canvas(); + QSGItem *grabber = c ? c->mouseGrabberItem() : 0; + bool disabledItem = grabber && !grabber->isEnabled(); + bool stealThisEvent = d->stealMouse; + if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { + mouseEvent.setAccepted(false); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button))); + } + } + mouseEvent.setScenePos(event->scenePos()); + mouseEvent.setLastScenePos(event->lastScenePos()); + mouseEvent.setPos(mapFromScene(event->scenePos())); + mouseEvent.setLastPos(mapFromScene(event->lastScenePos())); + + switch(mouseEvent.type()) { + case QEvent::GraphicsSceneMouseMove: + d->handleMouseMoveEvent(&mouseEvent); + break; + case QEvent::GraphicsSceneMousePress: + if (d->pressed) // we are already pressed - this is a delayed replay + return false; + + d->handleMousePressEvent(&mouseEvent); + d->captureDelayedPress(event); + stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above + break; + case QEvent::GraphicsSceneMouseRelease: + if (d->delayedPressEvent) { + // We replay the mouse press but the grabber we had might not be interessted by the event (e.g. overlay) + // so we reset the grabber + if (c->mouseGrabberItem() == d->delayedPressTarget) + d->delayedPressTarget->ungrabMouse(); + //Use the event handler that will take care of finding the proper item to propagate the event + QSGCanvasPrivate::get(canvas())->deliverMouseEvent(d->delayedPressEvent); + d->clearDelayedPress(); + // We send the release + canvas()->sendEvent(c->mouseGrabberItem(), event); + // And the event has been consumed + d->stealMouse = false; + d->pressed = false; + return true; + } + d->handleMouseReleaseEvent(&mouseEvent); + break; + default: + break; + } + grabber = qobject_cast<QSGItem*>(c->mouseGrabberItem()); + if ((grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) || disabledItem) { + d->clearDelayedPress(); + grabMouse(); + } + + return stealThisEvent || d->delayedPressEvent || disabledItem; + } else if (d->lastPosTime.isValid()) { + d->lastPosTime.invalidate(); + } + if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) { + d->clearDelayedPress(); + d->stealMouse = false; + d->pressed = false; + } + return false; +} + + +bool QSGFlickable::childMouseEventFilter(QSGItem *i, QEvent *e) +{ + Q_D(QSGFlickable); + if (!isVisible() || !d->interactive) + return QSGItem::childMouseEventFilter(i, e); + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); + default: + break; + } + + return QSGItem::childMouseEventFilter(i, e); +} + +qreal QSGFlickable::maximumFlickVelocity() const +{ + Q_D(const QSGFlickable); + return d->maxVelocity; +} + +void QSGFlickable::setMaximumFlickVelocity(qreal v) +{ + Q_D(QSGFlickable); + if (v == d->maxVelocity) + return; + d->maxVelocity = v; + emit maximumFlickVelocityChanged(); +} + +qreal QSGFlickable::flickDeceleration() const +{ + Q_D(const QSGFlickable); + return d->deceleration; +} + +void QSGFlickable::setFlickDeceleration(qreal deceleration) +{ + Q_D(QSGFlickable); + if (deceleration == d->deceleration) + return; + d->deceleration = deceleration; + emit flickDecelerationChanged(); +} + +bool QSGFlickable::isFlicking() const +{ + Q_D(const QSGFlickable); + return d->flickingHorizontally || d->flickingVertically; +} + +bool QSGFlickable::isFlickingHorizontally() const +{ + Q_D(const QSGFlickable); + return d->flickingHorizontally; +} + +bool QSGFlickable::isFlickingVertically() const +{ + Q_D(const QSGFlickable); + return d->flickingVertically; +} + +int QSGFlickable::pressDelay() const +{ + Q_D(const QSGFlickable); + return d->pressDelay; +} + +void QSGFlickable::setPressDelay(int delay) +{ + Q_D(QSGFlickable); + if (d->pressDelay == delay) + return; + d->pressDelay = delay; + emit pressDelayChanged(); +} + + +bool QSGFlickable::isMoving() const +{ + Q_D(const QSGFlickable); + return d->movingHorizontally || d->movingVertically; +} + +bool QSGFlickable::isMovingHorizontally() const +{ + Q_D(const QSGFlickable); + return d->movingHorizontally; +} + +bool QSGFlickable::isMovingVertically() const +{ + Q_D(const QSGFlickable); + return d->movingVertically; +} + +void QSGFlickable::movementStarting() +{ + Q_D(QSGFlickable); + if (d->hMoved && !d->movingHorizontally) { + d->movingHorizontally = true; + emit movingChanged(); + emit movingHorizontallyChanged(); + if (!d->movingVertically) + emit movementStarted(); + } + else if (d->vMoved && !d->movingVertically) { + d->movingVertically = true; + emit movingChanged(); + emit movingVerticallyChanged(); + if (!d->movingHorizontally) + emit movementStarted(); + } +} + +void QSGFlickable::movementEnding() +{ + Q_D(QSGFlickable); + movementXEnding(); + movementYEnding(); + d->hData.smoothVelocity.setValue(0); + d->vData.smoothVelocity.setValue(0); +} + +void QSGFlickable::movementXEnding() +{ + Q_D(QSGFlickable); + if (d->flickingHorizontally) { + d->flickingHorizontally = false; + emit flickingChanged(); + emit flickingHorizontallyChanged(); + if (!d->flickingVertically) + emit flickEnded(); + } + if (!d->pressed && !d->stealMouse) { + if (d->movingHorizontally) { + d->movingHorizontally = false; + d->hMoved = false; + emit movingChanged(); + emit movingHorizontallyChanged(); + if (!d->movingVertically) + emit movementEnded(); + } + } + d->hData.fixingUp = false; +} + +void QSGFlickable::movementYEnding() +{ + Q_D(QSGFlickable); + if (d->flickingVertically) { + d->flickingVertically = false; + emit flickingChanged(); + emit flickingVerticallyChanged(); + if (!d->flickingHorizontally) + emit flickEnded(); + } + if (!d->pressed && !d->stealMouse) { + if (d->movingVertically) { + d->movingVertically = false; + d->vMoved = false; + emit movingChanged(); + emit movingVerticallyChanged(); + if (!d->movingHorizontally) + emit movementEnded(); + } + } + d->vData.fixingUp = false; +} + +void QSGFlickablePrivate::updateVelocity() +{ + Q_Q(QSGFlickable); + emit q->horizontalVelocityChanged(); + emit q->verticalVelocityChanged(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgflickable_p.h b/src/declarative/items/qsgflickable_p.h new file mode 100644 index 0000000000..c1ed024527 --- /dev/null +++ b/src/declarative/items/qsgflickable_p.h @@ -0,0 +1,230 @@ +// Commit: 1bcddaaf318fc37c71c5191913f3487c49444ec6 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGFLICKABLE_P_H +#define QSGFLICKABLE_P_H + +#include "qsgitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGFlickablePrivate; +class QSGFlickableVisibleArea; +class Q_AUTOTEST_EXPORT QSGFlickable : public QSGItem +{ + Q_OBJECT + + Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth NOTIFY contentWidthChanged) + Q_PROPERTY(qreal contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged) + Q_PROPERTY(qreal contentX READ contentX WRITE setContentX NOTIFY contentXChanged) + Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged) + Q_PROPERTY(QSGItem *contentItem READ contentItem CONSTANT) + + Q_PROPERTY(qreal horizontalVelocity READ horizontalVelocity NOTIFY horizontalVelocityChanged) + Q_PROPERTY(qreal verticalVelocity READ verticalVelocity NOTIFY verticalVelocityChanged) + + Q_PROPERTY(BoundsBehavior boundsBehavior READ boundsBehavior WRITE setBoundsBehavior NOTIFY boundsBehaviorChanged) + Q_PROPERTY(qreal maximumFlickVelocity READ maximumFlickVelocity WRITE setMaximumFlickVelocity NOTIFY maximumFlickVelocityChanged) + Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) + Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged) + Q_PROPERTY(bool movingHorizontally READ isMovingHorizontally NOTIFY movingHorizontallyChanged) + Q_PROPERTY(bool movingVertically READ isMovingVertically NOTIFY movingVerticallyChanged) + Q_PROPERTY(bool flicking READ isFlicking NOTIFY flickingChanged) + Q_PROPERTY(bool flickingHorizontally READ isFlickingHorizontally NOTIFY flickingHorizontallyChanged) + Q_PROPERTY(bool flickingVertically READ isFlickingVertically NOTIFY flickingVerticallyChanged) + Q_PROPERTY(FlickableDirection flickableDirection READ flickableDirection WRITE setFlickableDirection NOTIFY flickableDirectionChanged) + + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged) + Q_PROPERTY(int pressDelay READ pressDelay WRITE setPressDelay NOTIFY pressDelayChanged) + + Q_PROPERTY(bool atXEnd READ isAtXEnd NOTIFY isAtBoundaryChanged) + Q_PROPERTY(bool atYEnd READ isAtYEnd NOTIFY isAtBoundaryChanged) + Q_PROPERTY(bool atXBeginning READ isAtXBeginning NOTIFY isAtBoundaryChanged) + Q_PROPERTY(bool atYBeginning READ isAtYBeginning NOTIFY isAtBoundaryChanged) + + Q_PROPERTY(QSGFlickableVisibleArea *visibleArea READ visibleArea CONSTANT) + + Q_PROPERTY(QDeclarativeListProperty<QObject> flickableData READ flickableData) + Q_PROPERTY(QDeclarativeListProperty<QSGItem> flickableChildren READ flickableChildren) + Q_CLASSINFO("DefaultProperty", "flickableData") + + Q_ENUMS(FlickableDirection) + Q_ENUMS(BoundsBehavior) + +public: + QSGFlickable(QSGItem *parent=0); + ~QSGFlickable(); + + QDeclarativeListProperty<QObject> flickableData(); + QDeclarativeListProperty<QSGItem> flickableChildren(); + + enum BoundsBehavior { StopAtBounds, DragOverBounds, DragAndOvershootBounds }; + BoundsBehavior boundsBehavior() const; + void setBoundsBehavior(BoundsBehavior); + + qreal contentWidth() const; + void setContentWidth(qreal); + + qreal contentHeight() const; + void setContentHeight(qreal); + + qreal contentX() const; + virtual void setContentX(qreal pos); + + qreal contentY() const; + virtual void setContentY(qreal pos); + + bool isMoving() const; + bool isMovingHorizontally() const; + bool isMovingVertically() const; + bool isFlicking() const; + bool isFlickingHorizontally() const; + bool isFlickingVertically() const; + + int pressDelay() const; + void setPressDelay(int delay); + + qreal maximumFlickVelocity() const; + void setMaximumFlickVelocity(qreal); + + qreal flickDeceleration() const; + void setFlickDeceleration(qreal); + + bool isInteractive() const; + void setInteractive(bool); + + qreal horizontalVelocity() const; + qreal verticalVelocity() const; + + bool isAtXEnd() const; + bool isAtXBeginning() const; + bool isAtYEnd() const; + bool isAtYBeginning() const; + + QSGItem *contentItem(); + + enum FlickableDirection { AutoFlickDirection=0x00, HorizontalFlick=0x01, VerticalFlick=0x02, HorizontalAndVerticalFlick=0x03 }; + FlickableDirection flickableDirection() const; + void setFlickableDirection(FlickableDirection); + + Q_INVOKABLE void resizeContent(qreal w, qreal h, QPointF center); + Q_INVOKABLE void returnToBounds(); + +Q_SIGNALS: + void contentWidthChanged(); + void contentHeightChanged(); + void contentXChanged(); + void contentYChanged(); + void movingChanged(); + void movingHorizontallyChanged(); + void movingVerticallyChanged(); + void flickingChanged(); + void flickingHorizontallyChanged(); + void flickingVerticallyChanged(); + void horizontalVelocityChanged(); + void verticalVelocityChanged(); + void isAtBoundaryChanged(); + void flickableDirectionChanged(); + void interactiveChanged(); + void boundsBehaviorChanged(); + void maximumFlickVelocityChanged(); + void flickDecelerationChanged(); + void pressDelayChanged(); + void movementStarted(); + void movementEnded(); + void flickStarted(); + void flickEnded(); + +protected: + virtual bool childMouseEventFilter(QSGItem *, QEvent *); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + virtual void timerEvent(QTimerEvent *event); + + QSGFlickableVisibleArea *visibleArea(); + +protected Q_SLOTS: + virtual void ticked(); + void movementStarting(); + void movementEnding(); + +protected: + void movementXEnding(); + void movementYEnding(); + virtual qreal minXExtent() const; + virtual qreal minYExtent() const; + virtual qreal maxXExtent() const; + virtual qreal maxYExtent() const; + qreal vWidth() const; + qreal vHeight() const; + virtual void viewportMoved(); + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + void mouseUngrabEvent(); + bool sendMouseEvent(QGraphicsSceneMouseEvent *event); + + bool xflick() const; + bool yflick() const; + void cancelFlick(); + +protected: + QSGFlickable(QSGFlickablePrivate &dd, QSGItem *parent); + +private: + Q_DISABLE_COPY(QSGFlickable) + Q_DECLARE_PRIVATE(QSGFlickable) + friend class QSGFlickableVisibleArea; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGFlickable) + +QT_END_HEADER + +#endif // QSGFLICKABLE_P_H diff --git a/src/declarative/items/qsgflickable_p_p.h b/src/declarative/items/qsgflickable_p_p.h new file mode 100644 index 0000000000..114db53668 --- /dev/null +++ b/src/declarative/items/qsgflickable_p_p.h @@ -0,0 +1,243 @@ +// Commit: 160f1867868cdea916923652b00484ed11f90aaa +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGFLICKABLE_P_P_H +#define QSGFLICKABLE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgflickable_p.h" +#include "qsgitem_p.h" +#include "qsgitemchangelistener_p.h" + +#include <QtDeclarative/qdeclarative.h> +#include <QtCore/qdatetime.h> + +#include <private/qdeclarativetimeline_p_p.h> +#include <private/qdeclarativeanimation_p_p.h> + +QT_BEGIN_NAMESPACE + +// Really slow flicks can be annoying. +const qreal MinimumFlickVelocity = 75.0; + +class QSGFlickableVisibleArea; +class QSGFlickablePrivate : public QSGItemPrivate, public QSGItemChangeListener +{ + Q_DECLARE_PUBLIC(QSGFlickable) + +public: + static inline QSGFlickablePrivate *get(QSGFlickable *o) { return o->d_func(); } + + QSGFlickablePrivate(); + void init(); + + struct Velocity : public QDeclarativeTimeLineValue + { + Velocity(QSGFlickablePrivate *p) + : parent(p) {} + virtual void setValue(qreal v) { + if (v != value()) { + QDeclarativeTimeLineValue::setValue(v); + parent->updateVelocity(); + } + } + QSGFlickablePrivate *parent; + }; + + struct AxisData { + AxisData(QSGFlickablePrivate *fp, void (QSGFlickablePrivate::*func)(qreal)) + : move(fp, func), viewSize(-1), smoothVelocity(fp), atEnd(false), atBeginning(true) + , fixingUp(false), inOvershoot(false) + {} + + void reset() { + velocityBuffer.clear(); + dragStartOffset = 0; + fixingUp = false; + inOvershoot = false; + } + + void addVelocitySample(qreal v, qreal maxVelocity); + void updateVelocity(); + + QDeclarativeTimeLineValueProxy<QSGFlickablePrivate> move; + qreal viewSize; + qreal pressPos; + qreal dragStartOffset; + qreal dragMinBound; + qreal dragMaxBound; + qreal velocity; + qreal flickTarget; + QSGFlickablePrivate::Velocity smoothVelocity; + QPODVector<qreal,10> velocityBuffer; + bool atEnd : 1; + bool atBeginning : 1; + bool fixingUp : 1; + bool inOvershoot : 1; + }; + + void flickX(qreal velocity); + void flickY(qreal velocity); + virtual void flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity); + + void fixupX(); + void fixupY(); + virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent); + + void updateBeginningEnd(); + + bool isOutermostPressDelay() const; + void captureDelayedPress(QGraphicsSceneMouseEvent *event); + void clearDelayedPress(); + + void setViewportX(qreal x); + void setViewportY(qreal y); + + qreal overShootDistance(qreal size); + + void itemGeometryChanged(QSGItem *, const QRectF &, const QRectF &); + +public: + QSGItem *contentItem; + + AxisData hData; + AxisData vData; + + QDeclarativeTimeLine timeline; + bool flickingHorizontally : 1; + bool flickingVertically : 1; + bool hMoved : 1; + bool vMoved : 1; + bool movingHorizontally : 1; + bool movingVertically : 1; + bool stealMouse : 1; + bool pressed : 1; + bool interactive : 1; + bool calcVelocity : 1; + QElapsedTimer lastPosTime; + QPointF lastPos; + QPointF pressPos; + QElapsedTimer pressTime; + qreal deceleration; + qreal maxVelocity; + QElapsedTimer velocityTime; + QPointF lastFlickablePosition; + qreal reportedVelocitySmoothing; + QGraphicsSceneMouseEvent *delayedPressEvent; + QSGItem *delayedPressTarget; + QBasicTimer delayedPressTimer; + int pressDelay; + int fixupDuration; + + enum FixupMode { Normal, Immediate, ExtentChanged }; + FixupMode fixupMode; + + static void fixupY_callback(void *); + static void fixupX_callback(void *); + + void updateVelocity(); + int vTime; + QDeclarativeTimeLine velocityTimeline; + QSGFlickableVisibleArea *visibleArea; + QSGFlickable::FlickableDirection flickableDirection; + QSGFlickable::BoundsBehavior boundsBehavior; + + void handleMousePressEvent(QGraphicsSceneMouseEvent *); + void handleMouseMoveEvent(QGraphicsSceneMouseEvent *); + void handleMouseReleaseEvent(QGraphicsSceneMouseEvent *); + + // flickableData property + static void data_append(QDeclarativeListProperty<QObject> *, QObject *); + static int data_count(QDeclarativeListProperty<QObject> *); + static QObject *data_at(QDeclarativeListProperty<QObject> *, int); + static void data_clear(QDeclarativeListProperty<QObject> *); +}; + +class QSGFlickableVisibleArea : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal xPosition READ xPosition NOTIFY xPositionChanged) + Q_PROPERTY(qreal yPosition READ yPosition NOTIFY yPositionChanged) + Q_PROPERTY(qreal widthRatio READ widthRatio NOTIFY widthRatioChanged) + Q_PROPERTY(qreal heightRatio READ heightRatio NOTIFY heightRatioChanged) + +public: + QSGFlickableVisibleArea(QSGFlickable *parent=0); + + qreal xPosition() const; + qreal widthRatio() const; + qreal yPosition() const; + qreal heightRatio() const; + + void updateVisible(); + +signals: + void xPositionChanged(qreal xPosition); + void yPositionChanged(qreal yPosition); + void widthRatioChanged(qreal widthRatio); + void heightRatioChanged(qreal heightRatio); + +private: + QSGFlickable *flickable; + qreal m_xPosition; + qreal m_widthRatio; + qreal m_yPosition; + qreal m_heightRatio; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGFlickableVisibleArea) + +#endif // QSGFLICKABLE_P_P_H diff --git a/src/declarative/items/qsgflipable.cpp b/src/declarative/items/qsgflipable.cpp new file mode 100644 index 0000000000..a856d6360b --- /dev/null +++ b/src/declarative/items/qsgflipable.cpp @@ -0,0 +1,255 @@ +// Commit: caee66da925949cf7aef2ff8e1a86c38dd6e6efd +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgflipable_p.h" +#include "qsgitem_p.h" + +#include <private/qdeclarativeguard_p.h> + +#include <QtDeclarative/qdeclarativeinfo.h> + +QT_BEGIN_NAMESPACE + +// XXX todo - i think this needs work and a bit of a re-think + +class QSGLocalTransform : public QSGTransform +{ + Q_OBJECT +public: + QSGLocalTransform(QObject *parent) : QSGTransform(parent) {} + + void setTransform(const QTransform &t) { + transform = t; + update(); + } + virtual void applyTo(QMatrix4x4 *matrix) const { + *matrix *= transform; + } +private: + QTransform transform; +}; + +class QSGFlipablePrivate : public QSGItemPrivate +{ + Q_DECLARE_PUBLIC(QSGFlipable) +public: + QSGFlipablePrivate() : current(QSGFlipable::Front), front(0), back(0), sideDirty(false) {} + + virtual void transformChanged(); + void updateSide(); + void setBackTransform(); + + QSGFlipable::Side current; + QDeclarativeGuard<QSGLocalTransform> backTransform; + QDeclarativeGuard<QSGItem> front; + QDeclarativeGuard<QSGItem> back; + + bool sideDirty; + bool wantBackXFlipped; + bool wantBackYFlipped; +}; + +QSGFlipable::QSGFlipable(QSGItem *parent) +: QSGItem(*(new QSGFlipablePrivate), parent) +{ +} + +QSGFlipable::~QSGFlipable() +{ +} + +QSGItem *QSGFlipable::front() +{ + Q_D(const QSGFlipable); + return d->front; +} + +void QSGFlipable::setFront(QSGItem *front) +{ + Q_D(QSGFlipable); + if (d->front) { + qmlInfo(this) << tr("front is a write-once property"); + return; + } + d->front = front; + d->front->setParentItem(this); + if (Back == d->current) + d->front->setOpacity(0.); + emit frontChanged(); +} + +QSGItem *QSGFlipable::back() +{ + Q_D(const QSGFlipable); + return d->back; +} + +void QSGFlipable::setBack(QSGItem *back) +{ + Q_D(QSGFlipable); + if (d->back) { + qmlInfo(this) << tr("back is a write-once property"); + return; + } + if (back == 0) + return; + d->back = back; + d->back->setParentItem(this); + + d->backTransform = new QSGLocalTransform(d->back); + d->backTransform->prependToItem(d->back); + + if (Front == d->current) + d->back->setOpacity(0.); + connect(back, SIGNAL(widthChanged()), + this, SLOT(retransformBack())); + connect(back, SIGNAL(heightChanged()), + this, SLOT(retransformBack())); + emit backChanged(); +} + +void QSGFlipable::retransformBack() +{ + Q_D(QSGFlipable); + if (d->current == QSGFlipable::Back && d->back) + d->setBackTransform(); +} + +QSGFlipable::Side QSGFlipable::side() const +{ + Q_D(const QSGFlipable); + + const_cast<QSGFlipablePrivate *>(d)->updateSide(); + return d->current; +} + +void QSGFlipablePrivate::transformChanged() +{ + Q_Q(QSGFlipable); + + if (!sideDirty) { + sideDirty = true; + q->polish(); + } + + QSGItemPrivate::transformChanged(); +} + +void QSGFlipable::updatePolish() +{ + Q_D(QSGFlipable); + d->updateSide(); +} + +// determination on the currently visible side of the flipable +// has to be done on the complete scene transform to give +// correct results. +void QSGFlipablePrivate::updateSide() +{ + Q_Q(QSGFlipable); + + if (!sideDirty) + return; + + sideDirty = false; + + QTransform sceneTransform; + itemToParentTransform(sceneTransform); + + QPointF p1(0, 0); + QPointF p2(1, 0); + QPointF p3(1, 1); + + QPointF scenep1 = sceneTransform.map(p1); + QPointF scenep2 = sceneTransform.map(p2); + QPointF scenep3 = sceneTransform.map(p3); +#if 0 + p1 = q->mapToParent(p1); + p2 = q->mapToParent(p2); + p3 = q->mapToParent(p3); +#endif + + qreal cross = (scenep1.x() - scenep2.x()) * (scenep3.y() - scenep2.y()) - + (scenep1.y() - scenep2.y()) * (scenep3.x() - scenep2.x()); + + wantBackYFlipped = scenep1.x() >= scenep2.x(); + wantBackXFlipped = scenep2.y() >= scenep3.y(); + + QSGFlipable::Side newSide; + if (cross > 0) { + newSide = QSGFlipable::Back; + } else { + newSide = QSGFlipable::Front; + } + + if (newSide != current) { + current = newSide; + if (current == QSGFlipable::Back && back) + setBackTransform(); + if (front) + front->setOpacity((current==QSGFlipable::Front)?1.:0.); + if (back) + back->setOpacity((current==QSGFlipable::Back)?1.:0.); + emit q->sideChanged(); + } +} + +/* Depends on the width/height of the back item, and so needs reevaulating + if those change. +*/ +void QSGFlipablePrivate::setBackTransform() +{ + QTransform mat; + mat.translate(back->width()/2,back->height()/2); + if (back->width() && wantBackYFlipped) + mat.rotate(180, Qt::YAxis); + if (back->height() && wantBackXFlipped) + mat.rotate(180, Qt::XAxis); + mat.translate(-back->width()/2,-back->height()/2); + + if (backTransform) + backTransform->setTransform(mat); +} + +QT_END_NAMESPACE + +#include "qsgflipable.moc" diff --git a/src/declarative/items/qsgflipable_p.h b/src/declarative/items/qsgflipable_p.h new file mode 100644 index 0000000000..02178adca8 --- /dev/null +++ b/src/declarative/items/qsgflipable_p.h @@ -0,0 +1,104 @@ +// Commit: ebd4bc73c46c2962742a682b6a391fb68c482aec +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGFLIPABLE_P_H +#define QSGFLIPABLE_P_H + +#include "qsgitem.h" + +#include <QtGui/qtransform.h> +#include <QtGui/qvector3d.h> +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGFlipablePrivate; +class Q_AUTOTEST_EXPORT QSGFlipable : public QSGItem +{ + Q_OBJECT + + Q_ENUMS(Side) + Q_PROPERTY(QSGItem *front READ front WRITE setFront NOTIFY frontChanged) + Q_PROPERTY(QSGItem *back READ back WRITE setBack NOTIFY backChanged) + Q_PROPERTY(Side side READ side NOTIFY sideChanged) + //### flipAxis + //### flipRotation +public: + QSGFlipable(QSGItem *parent=0); + ~QSGFlipable(); + + QSGItem *front(); + void setFront(QSGItem *); + + QSGItem *back(); + void setBack(QSGItem *); + + enum Side { Front, Back }; + Side side() const; + +Q_SIGNALS: + void frontChanged(); + void backChanged(); + void sideChanged(); + +protected: + virtual void updatePolish(); + +private Q_SLOTS: + void retransformBack(); + +private: + Q_DISABLE_COPY(QSGFlipable) + Q_DECLARE_PRIVATE(QSGFlipable) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGFlipable) + +QT_END_HEADER + +#endif // QSGFLIPABLE_P_H diff --git a/src/declarative/items/qsgfocusscope.cpp b/src/declarative/items/qsgfocusscope.cpp new file mode 100644 index 0000000000..84f19b1671 --- /dev/null +++ b/src/declarative/items/qsgfocusscope.cpp @@ -0,0 +1,57 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgfocusscope_p.h" + +QT_BEGIN_NAMESPACE + +QSGFocusScope::QSGFocusScope(QSGItem *parent) +: QSGItem(parent) +{ + setFlag(ItemIsFocusScope); +} + +QSGFocusScope::~QSGFocusScope() +{ +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgfocusscope_p.h b/src/declarative/items/qsgfocusscope_p.h new file mode 100644 index 0000000000..ceffd9f089 --- /dev/null +++ b/src/declarative/items/qsgfocusscope_p.h @@ -0,0 +1,68 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGFOCUSSCOPE_P_H +#define QSGFOCUSSCOPE_P_H + +#include "qsgitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_AUTOTEST_EXPORT QSGFocusScope : public QSGItem +{ + Q_OBJECT +public: + QSGFocusScope(QSGItem *parent=0); + virtual ~QSGFocusScope(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGFocusScope) + +QT_END_HEADER + +#endif // QSGFOCUSSCOPE_P_H diff --git a/src/declarative/items/qsggridview.cpp b/src/declarative/items/qsggridview.cpp new file mode 100644 index 0000000000..43f7f982a4 --- /dev/null +++ b/src/declarative/items/qsggridview.cpp @@ -0,0 +1,2657 @@ +// Commit: fda9cc1d8a0e49817d1c6192c52d18dffcecf327 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsggridview_p.h" +#include "qsgvisualitemmodel_p.h" +#include "qsgflickable_p_p.h" + +#include <private/qdeclarativesmoothedanimation_p_p.h> +#include <private/qlistmodelinterface_p.h> + +#include <QtGui/qevent.h> +#include <QtCore/qmath.h> +#include <QtCore/qcoreapplication.h> +#include <math.h> + +QT_BEGIN_NAMESPACE + +//---------------------------------------------------------------------------- + +class FxGridItemSG +{ +public: + FxGridItemSG(QSGItem *i, QSGGridView *v) : item(i), view(v) { + attached = static_cast<QSGGridViewAttached*>(qmlAttachedPropertiesObject<QSGGridView>(item)); + if (attached) + attached->setView(view); + } + ~FxGridItemSG() {} + + qreal rowPos() const { + qreal rowPos = 0; + if (view->flow() == QSGGridView::LeftToRight) { + rowPos = item->y(); + } else { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) + rowPos = -view->cellWidth()-item->x(); + else + rowPos = item->x(); + } + return rowPos; + } + qreal colPos() const { + qreal colPos = 0; + if (view->flow() == QSGGridView::LeftToRight) { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) { + int colSize = view->cellWidth(); + int columns = view->width()/colSize; + colPos = colSize * (columns-1) - item->x(); + } else { + colPos = item->x(); + } + } else { + colPos = item->y(); + } + + return colPos; + } + qreal endRowPos() const { + if (view->flow() == QSGGridView::LeftToRight) { + return item->y() + view->cellHeight() - 1; + } else { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) + return -item->x() - 1; + else + return item->x() + view->cellWidth() - 1; + } + } + void setPosition(qreal col, qreal row) { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) { + if (view->flow() == QSGGridView::LeftToRight) { + int columns = view->width()/view->cellWidth(); + item->setPos(QPointF((view->cellWidth() * (columns-1) - col), row)); + } else { + item->setPos(QPointF(-view->cellWidth()-row, col)); + } + } else { + if (view->flow() == QSGGridView::LeftToRight) + item->setPos(QPointF(col, row)); + else + item->setPos(QPointF(row, col)); + } + } + bool contains(qreal x, qreal y) const { + return (x >= item->x() && x < item->x() + view->cellWidth() && + y >= item->y() && y < item->y() + view->cellHeight()); + } + + QSGItem *item; + QSGGridView *view; + QSGGridViewAttached *attached; + int index; +}; + +//---------------------------------------------------------------------------- + +class QSGGridViewPrivate : public QSGFlickablePrivate +{ + Q_DECLARE_PUBLIC(QSGGridView) + +public: + QSGGridViewPrivate() + : currentItem(0), layoutDirection(Qt::LeftToRight), flow(QSGGridView::LeftToRight) + , visibleIndex(0) , currentIndex(-1) + , cellWidth(100), cellHeight(100), columns(1), requestedIndex(-1), itemCount(0) + , highlightRangeStart(0), highlightRangeEnd(0) + , highlightRange(QSGGridView::NoHighlightRange) + , highlightComponent(0), highlight(0), trackedItem(0) + , moveReason(Other), buffer(0), highlightXAnimator(0), highlightYAnimator(0) + , highlightMoveDuration(150) + , footerComponent(0), footer(0), headerComponent(0), header(0) + , bufferMode(BufferBefore | BufferAfter), snapMode(QSGGridView::NoSnap) + , ownModel(false), wrap(false), autoHighlight(true) + , fixCurrentVisibility(false), lazyRelease(false), layoutScheduled(false) + , deferredRelease(false), haveHighlightRange(false), currentIndexCleared(false) + , highlightRangeStartValid(false), highlightRangeEndValid(false) {} + + void init(); + void clear(); + FxGridItemSG *createItem(int modelIndex); + void releaseItem(FxGridItemSG *item); + void refill(qreal from, qreal to, bool doBuffer=false); + + void updateGrid(); + void scheduleLayout(); + void layout(); + void updateUnrequestedIndexes(); + void updateUnrequestedPositions(); + void updateTrackedItem(); + void createHighlight(); + void updateHighlight(); + void updateCurrent(int modelIndex); + void updateHeader(); + void updateFooter(); + void fixupPosition(); + + FxGridItemSG *visibleItem(int modelIndex) const { + if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) { + for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) { + FxGridItemSG *item = visibleItems.at(i); + if (item->index == modelIndex) + return item; + } + } + return 0; + } + + bool isRightToLeftTopToBottom() const { + Q_Q(const QSGGridView); + return flow == QSGGridView::TopToBottom && q->effectiveLayoutDirection() == Qt::RightToLeft; + } + + void regenerate() { + Q_Q(QSGGridView); + if (q->isComponentComplete()) { + clear(); + updateGrid(); + setPosition(0); + q->refill(); + updateCurrent(currentIndex); + } + } + + void mirrorChange() { + Q_Q(QSGGridView); + regenerate(); + emit q->effectiveLayoutDirectionChanged(); + } + + qreal position() const { + Q_Q(const QSGGridView); + return flow == QSGGridView::LeftToRight ? q->contentY() : q->contentX(); + } + void setPosition(qreal pos) { + Q_Q(QSGGridView); + if (flow == QSGGridView::LeftToRight) { + q->QSGFlickable::setContentY(pos); + q->QSGFlickable::setContentX(0); + } else { + if (q->effectiveLayoutDirection() == Qt::LeftToRight) + q->QSGFlickable::setContentX(pos); + else + q->QSGFlickable::setContentX(-pos-size()); + q->QSGFlickable::setContentY(0); + } + } + int size() const { + Q_Q(const QSGGridView); + return flow == QSGGridView::LeftToRight ? q->height() : q->width(); + } + qreal originPosition() const { + qreal pos = 0; + if (!visibleItems.isEmpty()) + pos = visibleItems.first()->rowPos() - visibleIndex / columns * rowSize(); + return pos; + } + + qreal lastPosition() const { + qreal pos = 0; + if (model && model->count()) + pos = rowPosAt(model->count() - 1) + rowSize(); + return pos; + } + + qreal startPosition() const { + return isRightToLeftTopToBottom() ? -lastPosition()+1 : originPosition(); + } + + qreal endPosition() const { + return isRightToLeftTopToBottom() ? -originPosition()+1 : lastPosition(); + + } + + bool isValid() const { + return model && model->count() && model->isValid(); + } + + int rowSize() const { + return flow == QSGGridView::LeftToRight ? cellHeight : cellWidth; + } + int colSize() const { + return flow == QSGGridView::LeftToRight ? cellWidth : cellHeight; + } + + qreal colPosAt(int modelIndex) const { + if (FxGridItemSG *item = visibleItem(modelIndex)) + return item->colPos(); + if (!visibleItems.isEmpty()) { + if (modelIndex < visibleIndex) { + int count = (visibleIndex - modelIndex) % columns; + int col = visibleItems.first()->colPos() / colSize(); + col = (columns - count + col) % columns; + return col * colSize(); + } else { + int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns; + return visibleItems.last()->colPos() - count * colSize(); + } + } else { + return (modelIndex % columns) * colSize(); + } + return 0; + } + qreal rowPosAt(int modelIndex) const { + if (FxGridItemSG *item = visibleItem(modelIndex)) + return item->rowPos(); + if (!visibleItems.isEmpty()) { + if (modelIndex < visibleIndex) { + int firstCol = visibleItems.first()->colPos() / colSize(); + int col = visibleIndex - modelIndex + (columns - firstCol - 1); + int rows = col / columns; + return visibleItems.first()->rowPos() - rows * rowSize(); + } else { + int count = modelIndex - visibleItems.last()->index; + int col = visibleItems.last()->colPos() + count * colSize(); + int rows = col / (columns * colSize()); + return visibleItems.last()->rowPos() + rows * rowSize(); + } + } else { + qreal pos = (modelIndex / columns) * rowSize(); + if (header) + pos += headerSize(); + return pos; + } + return 0; + } + + FxGridItemSG *firstVisibleItem() const { + const qreal pos = isRightToLeftTopToBottom() ? -position()-size() : position(); + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *item = visibleItems.at(i); + if (item->index != -1 && item->endRowPos() > pos) + return item; + } + return visibleItems.count() ? visibleItems.first() : 0; + } + + int lastVisibleIndex() const { + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *item = visibleItems.at(i); + if (item->index != -1) + return item->index; + } + return -1; + } + + // Map a model index to visibleItems list index. + // These may differ if removed items are still present in the visible list, + // e.g. doing a removal animation + int mapFromModel(int modelIndex) const { + if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.count()) + return -1; + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *listItem = visibleItems.at(i); + if (listItem->index == modelIndex) + return i + visibleIndex; + if (listItem->index > modelIndex) + return -1; + } + return -1; // Not in visibleList + } + + qreal snapPosAt(qreal pos) const { + Q_Q(const QSGGridView); + qreal snapPos = 0; + if (!visibleItems.isEmpty()) { + pos += rowSize()/2; + snapPos = visibleItems.first()->rowPos() - visibleIndex / columns * rowSize(); + snapPos = pos - fmodf(pos - snapPos, qreal(rowSize())); + qreal maxExtent; + qreal minExtent; + if (isRightToLeftTopToBottom()) { + maxExtent = q->minXExtent(); + minExtent = q->maxXExtent(); + } else { + maxExtent = flow == QSGGridView::LeftToRight ? -q->maxYExtent() : -q->maxXExtent(); + minExtent = flow == QSGGridView::LeftToRight ? -q->minYExtent() : -q->minXExtent(); + } + if (snapPos > maxExtent) + snapPos = maxExtent; + if (snapPos < minExtent) + snapPos = minExtent; + } + return snapPos; + } + + FxGridItemSG *snapItemAt(qreal pos) { + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->rowPos(); + if (itemTop+rowSize()/2 >= pos && itemTop - rowSize()/2 <= pos) + return item; + } + return 0; + } + + int snapIndex() { + int index = currentIndex; + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->rowPos(); + if (itemTop >= highlight->rowPos()-rowSize()/2 && itemTop < highlight->rowPos()+rowSize()/2) { + index = item->index; + if (item->colPos() >= highlight->colPos()-colSize()/2 && item->colPos() < highlight->colPos()+colSize()/2) + return item->index; + } + } + return index; + } + + qreal headerSize() const { + if (!header) + return 0.0; + + return flow == QSGGridView::LeftToRight + ? header->item->height() + : header->item->width(); + } + + + virtual void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) { + Q_Q(const QSGGridView); + QSGFlickablePrivate::itemGeometryChanged(item, newGeometry, oldGeometry); + if (item == q) { + if (newGeometry.height() != oldGeometry.height() + || newGeometry.width() != oldGeometry.width()) { + if (q->isComponentComplete()) { + updateGrid(); + scheduleLayout(); + } + } + } else if ((header && header->item == item) || (footer && footer->item == item)) { + if (header) + updateHeader(); + if (footer) + updateFooter(); + } + } + + void positionViewAtIndex(int index, int mode); + virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent); + virtual void flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity); + + // for debugging only + void checkVisible() const { + int skip = 0; + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItemSG *listItem = visibleItems.at(i); + if (listItem->index == -1) { + ++skip; + } else if (listItem->index != visibleIndex + i - skip) { + for (int j = 0; j < visibleItems.count(); j++) + qDebug() << " index" << j << "item index" << visibleItems.at(j)->index; + qFatal("index %d %d %d", visibleIndex, i, listItem->index); + } + } + } + + QDeclarativeGuard<QSGVisualModel> model; + QVariant modelVariant; + QList<FxGridItemSG*> visibleItems; + QHash<QSGItem*,int> unrequestedItems; + FxGridItemSG *currentItem; + Qt::LayoutDirection layoutDirection; + QSGGridView::Flow flow; + int visibleIndex; + int currentIndex; + int cellWidth; + int cellHeight; + int columns; + int requestedIndex; + int itemCount; + qreal highlightRangeStart; + qreal highlightRangeEnd; + QSGGridView::HighlightRangeMode highlightRange; + QDeclarativeComponent *highlightComponent; + FxGridItemSG *highlight; + FxGridItemSG *trackedItem; + enum MovementReason { Other, SetIndex, Mouse }; + MovementReason moveReason; + int buffer; + QSmoothedAnimation *highlightXAnimator; + QSmoothedAnimation *highlightYAnimator; + int highlightMoveDuration; + QDeclarativeComponent *footerComponent; + FxGridItemSG *footer; + QDeclarativeComponent *headerComponent; + FxGridItemSG *header; + enum BufferMode { NoBuffer = 0x00, BufferBefore = 0x01, BufferAfter = 0x02 }; + int bufferMode; + QSGGridView::SnapMode snapMode; + + bool ownModel : 1; + bool wrap : 1; + bool autoHighlight : 1; + bool fixCurrentVisibility : 1; + bool lazyRelease : 1; + bool layoutScheduled : 1; + bool deferredRelease : 1; + bool haveHighlightRange : 1; + bool currentIndexCleared : 1; + bool highlightRangeStartValid : 1; + bool highlightRangeEndValid : 1; +}; + +void QSGGridViewPrivate::init() +{ + Q_Q(QSGGridView); + QSGItemPrivate::get(contentItem)->childrenDoNotOverlap = true; + QObject::connect(q, SIGNAL(movementEnded()), q, SLOT(animStopped())); + q->setFlag(QSGItem::ItemIsFocusScope); + q->setFlickableDirection(QSGFlickable::VerticalFlick); + addItemChangeListener(this, Geometry); +} + +void QSGGridViewPrivate::clear() +{ + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + visibleIndex = 0; + releaseItem(currentItem); + currentItem = 0; + createHighlight(); + trackedItem = 0; + itemCount = 0; +} + +FxGridItemSG *QSGGridViewPrivate::createItem(int modelIndex) +{ + Q_Q(QSGGridView); + // create object + requestedIndex = modelIndex; + FxGridItemSG *listItem = 0; + if (QSGItem *item = model->item(modelIndex, false)) { + listItem = new FxGridItemSG(item, q); + listItem->index = modelIndex; + if (model->completePending()) { + // complete + listItem->item->setZ(1); + listItem->item->setParentItem(q->contentItem()); + model->completeItem(); + } else { + listItem->item->setParentItem(q->contentItem()); + } + unrequestedItems.remove(listItem->item); + } + requestedIndex = -1; + return listItem; +} + + +void QSGGridViewPrivate::releaseItem(FxGridItemSG *item) +{ + Q_Q(QSGGridView); + if (!item || !model) + return; + if (trackedItem == item) { + QObject::disconnect(trackedItem->item, SIGNAL(yChanged()), q, SLOT(trackedPositionChanged())); + QObject::disconnect(trackedItem->item, SIGNAL(xChanged()), q, SLOT(trackedPositionChanged())); + trackedItem = 0; + } + if (model->release(item->item) == 0) { + // item was not destroyed, and we no longer reference it. + unrequestedItems.insert(item->item, model->indexOf(item->item, q)); + } + delete item; +} + +void QSGGridViewPrivate::refill(qreal from, qreal to, bool doBuffer) +{ + Q_Q(QSGGridView); + if (!isValid() || !q->isComponentComplete()) + return; + itemCount = model->count(); + qreal bufferFrom = from - buffer; + qreal bufferTo = to + buffer; + qreal fillFrom = from; + qreal fillTo = to; + if (doBuffer && (bufferMode & BufferAfter)) + fillTo = bufferTo; + if (doBuffer && (bufferMode & BufferBefore)) + fillFrom = bufferFrom; + + bool changed = false; + + int colPos = colPosAt(visibleIndex); + int rowPos = rowPosAt(visibleIndex); + int modelIndex = visibleIndex; + if (visibleItems.count()) { + rowPos = visibleItems.last()->rowPos(); + colPos = visibleItems.last()->colPos() + colSize(); + if (colPos > colSize() * (columns-1)) { + colPos = 0; + rowPos += rowSize(); + } + int i = visibleItems.count() - 1; + while (i > 0 && visibleItems.at(i)->index == -1) + --i; + modelIndex = visibleItems.at(i)->index + 1; + } + + if (visibleItems.count() && (fillFrom > rowPos + rowSize()*2 + || fillTo < rowPosAt(visibleIndex) - rowSize())) { + // We've jumped more than a page. Estimate which items are now + // visible and fill from there. + int count = (fillFrom - (rowPos + rowSize())) / (rowSize()) * columns; + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + modelIndex += count; + if (modelIndex >= model->count()) + modelIndex = model->count() - 1; + else if (modelIndex < 0) + modelIndex = 0; + modelIndex = modelIndex / columns * columns; + visibleIndex = modelIndex; + colPos = colPosAt(visibleIndex); + rowPos = rowPosAt(visibleIndex); + } + + int colNum = colPos / colSize(); + + FxGridItemSG *item = 0; + + // Item creation and release is staggered in order to avoid + // creating/releasing multiple items in one frame + // while flicking (as much as possible). + while (modelIndex < model->count() && rowPos <= fillTo + rowSize()*(columns - colNum)/(columns+1)) { +// qDebug() << "refill: append item" << modelIndex; + if (!(item = createItem(modelIndex))) + break; + item->setPosition(colPos, rowPos); + visibleItems.append(item); + colPos += colSize(); + colNum++; + if (colPos > colSize() * (columns-1)) { + colPos = 0; + colNum = 0; + rowPos += rowSize(); + } + ++modelIndex; + changed = true; + if (doBuffer) // never buffer more than one item per frame + break; + } + + if (visibleItems.count()) { + rowPos = visibleItems.first()->rowPos(); + colPos = visibleItems.first()->colPos() - colSize(); + if (colPos < 0) { + colPos = colSize() * (columns - 1); + rowPos -= rowSize(); + } + } + colNum = colPos / colSize(); + while (visibleIndex > 0 && rowPos + rowSize() - 1 >= fillFrom - rowSize()*(colNum+1)/(columns+1)){ +// qDebug() << "refill: prepend item" << visibleIndex-1 << "top pos" << rowPos << colPos; + if (!(item = createItem(visibleIndex-1))) + break; + --visibleIndex; + item->setPosition(colPos, rowPos); + visibleItems.prepend(item); + colPos -= colSize(); + colNum--; + if (colPos < 0) { + colPos = colSize() * (columns - 1); + colNum = columns-1; + rowPos -= rowSize(); + } + changed = true; + if (doBuffer) // never buffer more than one item per frame + break; + } + + if (!lazyRelease || !changed || deferredRelease) { // avoid destroying items in the same frame that we create + while (visibleItems.count() > 1 + && (item = visibleItems.first()) + && item->rowPos()+rowSize()-1 < bufferFrom - rowSize()*(item->colPos()/colSize()+1)/(columns+1)) { + if (item->attached->delayRemove()) + break; +// qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endRowPos(); + if (item->index != -1) + visibleIndex++; + visibleItems.removeFirst(); + releaseItem(item); + changed = true; + } + while (visibleItems.count() > 1 + && (item = visibleItems.last()) + && item->rowPos() > bufferTo + rowSize()*(columns - item->colPos()/colSize())/(columns+1)) { + if (item->attached->delayRemove()) + break; +// qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1; + visibleItems.removeLast(); + releaseItem(item); + changed = true; + } + deferredRelease = false; + } else { + deferredRelease = true; + } + if (changed) { + if (header) + updateHeader(); + if (footer) + updateFooter(); + if (flow == QSGGridView::LeftToRight) + q->setContentHeight(endPosition() - startPosition()); + else + q->setContentWidth(endPosition() - startPosition()); + } else if (!doBuffer && buffer && bufferMode != NoBuffer) { + refill(from, to, true); + } + lazyRelease = false; +} + +void QSGGridViewPrivate::updateGrid() +{ + Q_Q(QSGGridView); + columns = (int)qMax((flow == QSGGridView::LeftToRight ? q->width() : q->height()) / colSize(), qreal(1.)); + if (isValid()) { + if (flow == QSGGridView::LeftToRight) + q->setContentHeight(endPosition() - startPosition()); + else + q->setContentWidth(lastPosition() - originPosition()); + } +} + +void QSGGridViewPrivate::scheduleLayout() +{ + Q_Q(QSGGridView); + if (!layoutScheduled) { + layoutScheduled = true; + q->polish(); + } +} + +void QSGGridViewPrivate::layout() +{ + Q_Q(QSGGridView); + layoutScheduled = false; + if (!isValid() && !visibleItems.count()) { + clear(); + return; + } + if (visibleItems.count()) { + qreal rowPos = visibleItems.first()->rowPos(); + qreal colPos = visibleItems.first()->colPos(); + int col = visibleIndex % columns; + if (colPos != col * colSize()) { + colPos = col * colSize(); + visibleItems.first()->setPosition(colPos, rowPos); + } + for (int i = 1; i < visibleItems.count(); ++i) { + FxGridItemSG *item = visibleItems.at(i); + colPos += colSize(); + if (colPos > colSize() * (columns-1)) { + colPos = 0; + rowPos += rowSize(); + } + item->setPosition(colPos, rowPos); + } + } + if (header) + updateHeader(); + if (footer) + updateFooter(); + q->refill(); + updateHighlight(); + moveReason = Other; + if (flow == QSGGridView::LeftToRight) { + q->setContentHeight(endPosition() - startPosition()); + fixupY(); + } else { + q->setContentWidth(endPosition() - startPosition()); + fixupX(); + } + updateUnrequestedPositions(); +} + +void QSGGridViewPrivate::updateUnrequestedIndexes() +{ + Q_Q(QSGGridView); + QHash<QSGItem*,int>::iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) + *it = model->indexOf(it.key(), q); +} + +void QSGGridViewPrivate::updateUnrequestedPositions() +{ + QHash<QSGItem*,int>::const_iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) { + QSGItem *item = it.key(); + if (flow == QSGGridView::LeftToRight) { + item->setPos(QPointF(colPosAt(*it), rowPosAt(*it))); + } else { + if (isRightToLeftTopToBottom()) + item->setPos(QPointF(-rowPosAt(*it)-item->width(), colPosAt(*it))); + else + item->setPos(QPointF(rowPosAt(*it), colPosAt(*it))); + } + } +} + +void QSGGridViewPrivate::updateTrackedItem() +{ + Q_Q(QSGGridView); + FxGridItemSG *item = currentItem; + if (highlight) + item = highlight; + + if (trackedItem && item != trackedItem) { + QObject::disconnect(trackedItem->item, SIGNAL(yChanged()), q, SLOT(trackedPositionChanged())); + QObject::disconnect(trackedItem->item, SIGNAL(xChanged()), q, SLOT(trackedPositionChanged())); + trackedItem = 0; + } + + if (!trackedItem && item) { + trackedItem = item; + QObject::connect(trackedItem->item, SIGNAL(yChanged()), q, SLOT(trackedPositionChanged())); + QObject::connect(trackedItem->item, SIGNAL(xChanged()), q, SLOT(trackedPositionChanged())); + } + if (trackedItem) + q->trackedPositionChanged(); +} + +void QSGGridViewPrivate::createHighlight() +{ + Q_Q(QSGGridView); + bool changed = false; + if (highlight) { + if (trackedItem == highlight) + trackedItem = 0; + delete highlight->item; + delete highlight; + highlight = 0; + delete highlightXAnimator; + delete highlightYAnimator; + highlightXAnimator = 0; + highlightYAnimator = 0; + changed = true; + } + + if (currentItem) { + QSGItem *item = 0; + if (highlightComponent) { + QDeclarativeContext *highlightContext = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = highlightComponent->create(highlightContext); + if (nobj) { + QDeclarative_setParent_noEvent(highlightContext, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete highlightContext; + } + } else { + item = new QSGItem; + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + } + if (item) { + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + highlight = new FxGridItemSG(item, q); + if (currentItem && autoHighlight) + highlight->setPosition(currentItem->colPos(), currentItem->rowPos()); + highlightXAnimator = new QSmoothedAnimation(q); + highlightXAnimator->target = QDeclarativeProperty(highlight->item, QLatin1String("x")); + highlightXAnimator->userDuration = highlightMoveDuration; + highlightYAnimator = new QSmoothedAnimation(q); + highlightYAnimator->target = QDeclarativeProperty(highlight->item, QLatin1String("y")); + highlightYAnimator->userDuration = highlightMoveDuration; + if (autoHighlight) { + highlightXAnimator->restart(); + highlightYAnimator->restart(); + } + changed = true; + } + } + if (changed) + emit q->highlightItemChanged(); +} + +void QSGGridViewPrivate::updateHighlight() +{ + if ((!currentItem && highlight) || (currentItem && !highlight)) + createHighlight(); + if (currentItem && autoHighlight && highlight && !movingHorizontally && !movingVertically) { + // auto-update highlight + highlightXAnimator->to = currentItem->item->x(); + highlightYAnimator->to = currentItem->item->y(); + highlight->item->setWidth(currentItem->item->width()); + highlight->item->setHeight(currentItem->item->height()); + highlightXAnimator->restart(); + highlightYAnimator->restart(); + } + updateTrackedItem(); +} + +void QSGGridViewPrivate::updateCurrent(int modelIndex) +{ + Q_Q(QSGGridView); + if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) { + if (currentItem) { + currentItem->attached->setIsCurrentItem(false); + releaseItem(currentItem); + currentItem = 0; + currentIndex = modelIndex; + emit q->currentIndexChanged(); + updateHighlight(); + } else if (currentIndex != modelIndex) { + currentIndex = modelIndex; + emit q->currentIndexChanged(); + } + return; + } + + if (currentItem && currentIndex == modelIndex) { + updateHighlight(); + return; + } + + FxGridItemSG *oldCurrentItem = currentItem; + currentIndex = modelIndex; + currentItem = createItem(modelIndex); + fixCurrentVisibility = true; + if (oldCurrentItem && (!currentItem || oldCurrentItem->item != currentItem->item)) + oldCurrentItem->attached->setIsCurrentItem(false); + if (currentItem) { + currentItem->setPosition(colPosAt(modelIndex), rowPosAt(modelIndex)); + currentItem->item->setFocus(true); + currentItem->attached->setIsCurrentItem(true); + } + updateHighlight(); + emit q->currentIndexChanged(); + releaseItem(oldCurrentItem); +} + +void QSGGridViewPrivate::updateFooter() +{ + Q_Q(QSGGridView); + if (!footer && footerComponent) { + QSGItem *item = 0; + QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = footerComponent->create(context); + if (nobj) { + QDeclarative_setParent_noEvent(context, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete context; + } + if (item) { + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + item->setZ(1); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + footer = new FxGridItemSG(item, q); + } + } + if (footer) { + qreal colOffset = 0; + qreal rowOffset; + if (isRightToLeftTopToBottom()) { + rowOffset = footer->item->width()-cellWidth; + } else { + rowOffset = 0; + if (q->effectiveLayoutDirection() == Qt::RightToLeft) + colOffset = footer->item->width()-cellWidth; + } + if (visibleItems.count()) { + qreal endPos = lastPosition(); + if (lastVisibleIndex() == model->count()-1) { + footer->setPosition(colOffset, endPos + rowOffset); + } else { + qreal visiblePos = isRightToLeftTopToBottom() ? -position() : position() + size(); + if (endPos <= visiblePos || footer->endRowPos() < endPos + rowOffset) + footer->setPosition(colOffset, endPos + rowOffset); + } + } else { + qreal endPos = 0; + if (header) { + endPos += (flow == QSGGridView::LeftToRight) ? header->item->height() : header->item->width(); + } + footer->setPosition(colOffset, endPos); + } + } +} + +void QSGGridViewPrivate::updateHeader() +{ + Q_Q(QSGGridView); + if (!header && headerComponent) { + QSGItem *item = 0; + QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = headerComponent->create(context); + if (nobj) { + QDeclarative_setParent_noEvent(context, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete context; + } + if (item) { + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + item->setZ(1); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + header = new FxGridItemSG(item, q); + } + } + if (header) { + qreal colOffset = 0; + qreal rowOffset; + if (isRightToLeftTopToBottom()) { + rowOffset = -cellWidth; + } else { + rowOffset = -headerSize(); + if (q->effectiveLayoutDirection() == Qt::RightToLeft) + colOffset = header->item->width()-cellWidth; + } + if (visibleItems.count()) { + qreal startPos = originPosition(); + if (visibleIndex == 0) { + header->setPosition(colOffset, startPos + rowOffset); + } else { + qreal tempPos = isRightToLeftTopToBottom() ? -position()-size() : position(); + qreal headerPos = isRightToLeftTopToBottom() ? header->rowPos() + cellWidth - headerSize() : header->rowPos(); + if (tempPos <= startPos || headerPos > startPos + rowOffset) + header->setPosition(colOffset, startPos + rowOffset); + } + } else { + header->setPosition(colOffset, 0); + } + } +} + +void QSGGridViewPrivate::fixupPosition() +{ + moveReason = Other; + if (flow == QSGGridView::LeftToRight) + fixupY(); + else + fixupX(); +} + +void QSGGridViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) +{ + if ((flow == QSGGridView::TopToBottom && &data == &vData) + || (flow == QSGGridView::LeftToRight && &data == &hData)) + return; + + fixupMode = moveReason == Mouse ? fixupMode : Immediate; + + qreal highlightStart; + qreal highlightEnd; + qreal viewPos; + if (isRightToLeftTopToBottom()) { + // Handle Right-To-Left exceptions + viewPos = -position()-size(); + highlightStart = highlightRangeStartValid ? size()-highlightRangeEnd : highlightRangeStart; + highlightEnd = highlightRangeEndValid ? size()-highlightRangeStart : highlightRangeEnd; + } else { + viewPos = position(); + highlightStart = highlightRangeStart; + highlightEnd = highlightRangeEnd; + } + + if (snapMode != QSGGridView::NoSnap) { + qreal tempPosition = isRightToLeftTopToBottom() ? -position()-size() : position(); + FxGridItemSG *topItem = snapItemAt(tempPosition+highlightStart); + FxGridItemSG *bottomItem = snapItemAt(tempPosition+highlightEnd); + qreal pos; + if (topItem && bottomItem && haveHighlightRange && highlightRange == QSGGridView::StrictlyEnforceRange) { + qreal topPos = qMin(topItem->rowPos() - highlightStart, -maxExtent); + qreal bottomPos = qMax(bottomItem->rowPos() - highlightEnd, -minExtent); + pos = qAbs(data.move + topPos) < qAbs(data.move + bottomPos) ? topPos : bottomPos; + } else if (topItem) { + qreal headerPos = 0; + if (header) + headerPos = isRightToLeftTopToBottom() ? header->rowPos() + cellWidth - headerSize() : header->rowPos(); + if (topItem->index == 0 && header && tempPosition+highlightStart < headerPos+headerSize()/2) { + pos = isRightToLeftTopToBottom() ? - headerPos + highlightStart - size() : headerPos - highlightStart; + } else { + if (isRightToLeftTopToBottom()) + pos = qMax(qMin(-topItem->rowPos() + highlightStart - size(), -maxExtent), -minExtent); + else + pos = qMax(qMin(topItem->rowPos() - highlightStart, -maxExtent), -minExtent); + } + } else if (bottomItem) { + if (isRightToLeftTopToBottom()) + pos = qMax(qMin(-bottomItem->rowPos() + highlightStart - size(), -maxExtent), -minExtent); + else + pos = qMax(qMin(bottomItem->rowPos() - highlightStart, -maxExtent), -minExtent); + } else { + QSGFlickablePrivate::fixup(data, minExtent, maxExtent); + return; + } + if (currentItem && haveHighlightRange && highlightRange == QSGGridView::StrictlyEnforceRange) { + updateHighlight(); + qreal currPos = currentItem->rowPos(); + if (isRightToLeftTopToBottom()) + pos = -pos-size(); // Transform Pos if required + if (pos < currPos + rowSize() - highlightEnd) + pos = currPos + rowSize() - highlightEnd; + if (pos > currPos - highlightStart) + pos = currPos - highlightStart; + if (isRightToLeftTopToBottom()) + pos = -pos-size(); // Untransform + } + + qreal dist = qAbs(data.move + pos); + if (dist > 0) { + timeline.reset(data.move); + if (fixupMode != Immediate) { + timeline.move(data.move, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); + data.fixingUp = true; + } else { + timeline.set(data.move, -pos); + } + vTime = timeline.time(); + } + } else if (haveHighlightRange && highlightRange == QSGGridView::StrictlyEnforceRange) { + if (currentItem) { + updateHighlight(); + qreal pos = currentItem->rowPos(); + if (viewPos < pos + rowSize() - highlightEnd) + viewPos = pos + rowSize() - highlightEnd; + if (viewPos > pos - highlightStart) + viewPos = pos - highlightStart; + if (isRightToLeftTopToBottom()) + viewPos = -viewPos-size(); + timeline.reset(data.move); + if (viewPos != position()) { + if (fixupMode != Immediate) { + timeline.move(data.move, -viewPos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); + data.fixingUp = true; + } else { + timeline.set(data.move, -viewPos); + } + } + vTime = timeline.time(); + } + } else { + QSGFlickablePrivate::fixup(data, minExtent, maxExtent); + } + data.inOvershoot = false; + fixupMode = Normal; +} + +void QSGGridViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity) +{ + Q_Q(QSGGridView); + data.fixingUp = false; + moveReason = Mouse; + if ((!haveHighlightRange || highlightRange != QSGGridView::StrictlyEnforceRange) + && snapMode == QSGGridView::NoSnap) { + QSGFlickablePrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity); + return; + } + qreal maxDistance = 0; + qreal dataValue = isRightToLeftTopToBottom() ? -data.move.value()+size() : data.move.value(); + // -ve velocity means list is moving up/left + if (velocity > 0) { + if (data.move.value() < minExtent) { + if (snapMode == QSGGridView::SnapOneRow) { + if (FxGridItemSG *item = firstVisibleItem()) + maxDistance = qAbs(item->rowPos() + dataValue); + } else { + maxDistance = qAbs(minExtent - data.move.value()); + } + } + if (snapMode == QSGGridView::NoSnap && highlightRange != QSGGridView::StrictlyEnforceRange) + data.flickTarget = minExtent; + } else { + if (data.move.value() > maxExtent) { + if (snapMode == QSGGridView::SnapOneRow) { + qreal pos = snapPosAt(-dataValue) + (isRightToLeftTopToBottom() ? 0 : rowSize()); + maxDistance = qAbs(pos + dataValue); + } else { + maxDistance = qAbs(maxExtent - data.move.value()); + } + } + if (snapMode == QSGGridView::NoSnap && highlightRange != QSGGridView::StrictlyEnforceRange) + data.flickTarget = maxExtent; + } + bool overShoot = boundsBehavior == QSGFlickable::DragAndOvershootBounds; + qreal highlightStart = isRightToLeftTopToBottom() && highlightRangeStartValid ? size()-highlightRangeEnd : highlightRangeStart; + if (maxDistance > 0 || overShoot) { + // This mode requires the grid to stop exactly on a row boundary. + qreal v = velocity; + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { + if (v < 0) + v = -maxVelocity; + else + v = maxVelocity; + } + qreal accel = deceleration; + qreal v2 = v * v; + qreal overshootDist = 0.0; + if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QSGGridView::SnapOneRow) { + // + rowSize()/4 to encourage moving at least one item in the flick direction + qreal dist = v2 / (accel * 2.0) + rowSize()/4; + dist = qMin(dist, maxDistance); + if (v > 0) + dist = -dist; + qreal distTemp = isRightToLeftTopToBottom() ? -dist : dist; + data.flickTarget = -snapPosAt(-(dataValue - highlightStart) + distTemp) + highlightStart; + data.flickTarget = isRightToLeftTopToBottom() ? -data.flickTarget+size() : data.flickTarget; + qreal adjDist = -data.flickTarget + data.move.value(); + if (qAbs(adjDist) > qAbs(dist)) { + // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration + qreal adjv2 = accel * 2.0f * qAbs(adjDist); + if (adjv2 > v2) { + v2 = adjv2; + v = qSqrt(v2); + if (dist > 0) + v = -v; + } + } + dist = adjDist; + accel = v2 / (2.0f * qAbs(dist)); + } else { + data.flickTarget = velocity > 0 ? minExtent : maxExtent; + overshootDist = overShoot ? overShootDistance(vSize) : 0; + } + timeline.reset(data.move); + timeline.accel(data.move, v, accel, maxDistance + overshootDist); + timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this)); + if (!flickingHorizontally && q->xflick()) { + flickingHorizontally = true; + emit q->flickingChanged(); + emit q->flickingHorizontallyChanged(); + emit q->flickStarted(); + } + if (!flickingVertically && q->yflick()) { + flickingVertically = true; + emit q->flickingChanged(); + emit q->flickingVerticallyChanged(); + emit q->flickStarted(); + } + } else { + timeline.reset(data.move); + fixup(data, minExtent, maxExtent); + } +} + + +//---------------------------------------------------------------------------- + +QSGGridView::QSGGridView(QSGItem *parent) + : QSGFlickable(*(new QSGGridViewPrivate), parent) +{ + Q_D(QSGGridView); + d->init(); +} + +QSGGridView::~QSGGridView() +{ + Q_D(QSGGridView); + d->clear(); + if (d->ownModel) + delete d->model; + delete d->header; + delete d->footer; +} + +// For internal use +int QSGGridView::modelCount() const +{ + Q_D(const QSGGridView); + return d->model->count(); +} + +QVariant QSGGridView::model() const +{ + Q_D(const QSGGridView); + return d->modelVariant; +} + +void QSGGridView::setModel(const QVariant &model) +{ + Q_D(QSGGridView); + if (d->modelVariant == model) + return; + if (d->model) { + disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + disconnect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + disconnect(d->model, SIGNAL(destroyingItem(QSGItem*)), this, SLOT(destroyingItem(QSGItem*))); + } + d->clear(); + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QSGVisualModel *vim = 0; + if (object && (vim = qobject_cast<QSGVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this), this); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + dataModel->setModel(model); + } + if (d->model) { + d->bufferMode = QSGGridViewPrivate::BufferBefore | QSGGridViewPrivate::BufferAfter; + if (isComponentComplete()) { + refill(); + if ((d->currentIndex >= d->model->count() || d->currentIndex < 0) && !d->currentIndexCleared) { + setCurrentIndex(0); + } else { + d->moveReason = QSGGridViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); + d->updateTrackedItem(); + } + d->moveReason = QSGGridViewPrivate::Other; + } + } + connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + connect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + connect(d->model, SIGNAL(destroyingItem(QSGItem*)), this, SLOT(destroyingItem(QSGItem*))); + emit countChanged(); + } + emit modelChanged(); +} + +QDeclarativeComponent *QSGGridView::delegate() const +{ + Q_D(const QSGGridView); + if (d->model) { + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QSGGridView::setDelegate(QDeclarativeComponent *delegate) +{ + Q_D(QSGGridView); + if (delegate == this->delegate()) + return; + + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) { + dataModel->setDelegate(delegate); + if (isComponentComplete()) { + for (int i = 0; i < d->visibleItems.count(); ++i) + d->releaseItem(d->visibleItems.at(i)); + d->visibleItems.clear(); + d->releaseItem(d->currentItem); + d->currentItem = 0; + refill(); + d->moveReason = QSGGridViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); + d->updateTrackedItem(); + } + d->moveReason = QSGGridViewPrivate::Other; + } + emit delegateChanged(); + } +} + +int QSGGridView::currentIndex() const +{ + Q_D(const QSGGridView); + return d->currentIndex; +} + +void QSGGridView::setCurrentIndex(int index) +{ + Q_D(QSGGridView); + if (d->requestedIndex >= 0) // currently creating item + return; + d->currentIndexCleared = (index == -1); + if (index == d->currentIndex) + return; + if (isComponentComplete() && d->isValid()) { + d->moveReason = QSGGridViewPrivate::SetIndex; + d->updateCurrent(index); + } else { + d->currentIndex = index; + emit currentIndexChanged(); + } +} + +QSGItem *QSGGridView::currentItem() +{ + Q_D(QSGGridView); + if (!d->currentItem) + return 0; + return d->currentItem->item; +} + +QSGItem *QSGGridView::highlightItem() +{ + Q_D(QSGGridView); + if (!d->highlight) + return 0; + return d->highlight->item; +} + +int QSGGridView::count() const +{ + Q_D(const QSGGridView); + if (d->model) + return d->model->count(); + return 0; +} + +QDeclarativeComponent *QSGGridView::highlight() const +{ + Q_D(const QSGGridView); + return d->highlightComponent; +} + +void QSGGridView::setHighlight(QDeclarativeComponent *highlight) +{ + Q_D(QSGGridView); + if (highlight != d->highlightComponent) { + d->highlightComponent = highlight; + d->updateCurrent(d->currentIndex); + emit highlightChanged(); + } +} + +bool QSGGridView::highlightFollowsCurrentItem() const +{ + Q_D(const QSGGridView); + return d->autoHighlight; +} + +void QSGGridView::setHighlightFollowsCurrentItem(bool autoHighlight) +{ + Q_D(QSGGridView); + if (d->autoHighlight != autoHighlight) { + d->autoHighlight = autoHighlight; + if (autoHighlight) { + d->updateHighlight(); + } else if (d->highlightXAnimator) { + d->highlightXAnimator->stop(); + d->highlightYAnimator->stop(); + } + } +} + +int QSGGridView::highlightMoveDuration() const +{ + Q_D(const QSGGridView); + return d->highlightMoveDuration; +} + +void QSGGridView::setHighlightMoveDuration(int duration) +{ + Q_D(QSGGridView); + if (d->highlightMoveDuration != duration) { + d->highlightMoveDuration = duration; + if (d->highlightYAnimator) { + d->highlightXAnimator->userDuration = d->highlightMoveDuration; + d->highlightYAnimator->userDuration = d->highlightMoveDuration; + } + emit highlightMoveDurationChanged(); + } +} + +qreal QSGGridView::preferredHighlightBegin() const +{ + Q_D(const QSGGridView); + return d->highlightRangeStart; +} + +void QSGGridView::setPreferredHighlightBegin(qreal start) +{ + Q_D(QSGGridView); + d->highlightRangeStartValid = true; + if (d->highlightRangeStart == start) + return; + d->highlightRangeStart = start; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit preferredHighlightBeginChanged(); +} + +void QSGGridView::resetPreferredHighlightBegin() +{ + Q_D(QSGGridView); + d->highlightRangeStartValid = false; + if (d->highlightRangeStart == 0) + return; + d->highlightRangeStart = 0; + emit preferredHighlightBeginChanged(); +} + +qreal QSGGridView::preferredHighlightEnd() const +{ + Q_D(const QSGGridView); + return d->highlightRangeEnd; +} + +void QSGGridView::setPreferredHighlightEnd(qreal end) +{ + Q_D(QSGGridView); + d->highlightRangeEndValid = true; + if (d->highlightRangeEnd == end) + return; + d->highlightRangeEnd = end; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit preferredHighlightEndChanged(); +} + +void QSGGridView::resetPreferredHighlightEnd() +{ + Q_D(QSGGridView); + d->highlightRangeEndValid = false; + if (d->highlightRangeEnd == 0) + return; + d->highlightRangeEnd = 0; + emit preferredHighlightEndChanged(); +} + +QSGGridView::HighlightRangeMode QSGGridView::highlightRangeMode() const +{ + Q_D(const QSGGridView); + return d->highlightRange; +} + +void QSGGridView::setHighlightRangeMode(HighlightRangeMode mode) +{ + Q_D(QSGGridView); + if (d->highlightRange == mode) + return; + d->highlightRange = mode; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit highlightRangeModeChanged(); +} + +Qt::LayoutDirection QSGGridView::layoutDirection() const +{ + Q_D(const QSGGridView); + return d->layoutDirection; +} + +void QSGGridView::setLayoutDirection(Qt::LayoutDirection layoutDirection) +{ + Q_D(QSGGridView); + if (d->layoutDirection != layoutDirection) { + d->layoutDirection = layoutDirection; + d->regenerate(); + emit layoutDirectionChanged(); + emit effectiveLayoutDirectionChanged(); + } +} + +Qt::LayoutDirection QSGGridView::effectiveLayoutDirection() const +{ + Q_D(const QSGGridView); + if (d->effectiveLayoutMirror) + return d->layoutDirection == Qt::RightToLeft ? Qt::LeftToRight : Qt::RightToLeft; + else + return d->layoutDirection; +} + +QSGGridView::Flow QSGGridView::flow() const +{ + Q_D(const QSGGridView); + return d->flow; +} + +void QSGGridView::setFlow(Flow flow) +{ + Q_D(QSGGridView); + if (d->flow != flow) { + d->flow = flow; + if (d->flow == LeftToRight) { + setContentWidth(-1); + setFlickableDirection(QSGFlickable::VerticalFlick); + } else { + setContentHeight(-1); + setFlickableDirection(QSGFlickable::HorizontalFlick); + } + setContentX(0); + setContentY(0); + d->regenerate(); + emit flowChanged(); + } +} + +bool QSGGridView::isWrapEnabled() const +{ + Q_D(const QSGGridView); + return d->wrap; +} + +void QSGGridView::setWrapEnabled(bool wrap) +{ + Q_D(QSGGridView); + if (d->wrap == wrap) + return; + d->wrap = wrap; + emit keyNavigationWrapsChanged(); +} + +int QSGGridView::cacheBuffer() const +{ + Q_D(const QSGGridView); + return d->buffer; +} + +void QSGGridView::setCacheBuffer(int buffer) +{ + Q_D(QSGGridView); + if (d->buffer != buffer) { + d->buffer = buffer; + if (isComponentComplete()) + refill(); + emit cacheBufferChanged(); + } +} + +int QSGGridView::cellWidth() const +{ + Q_D(const QSGGridView); + return d->cellWidth; +} + +void QSGGridView::setCellWidth(int cellWidth) +{ + Q_D(QSGGridView); + if (cellWidth != d->cellWidth && cellWidth > 0) { + d->cellWidth = qMax(1, cellWidth); + d->updateGrid(); + emit cellWidthChanged(); + d->layout(); + } +} + +int QSGGridView::cellHeight() const +{ + Q_D(const QSGGridView); + return d->cellHeight; +} + +void QSGGridView::setCellHeight(int cellHeight) +{ + Q_D(QSGGridView); + if (cellHeight != d->cellHeight && cellHeight > 0) { + d->cellHeight = qMax(1, cellHeight); + d->updateGrid(); + emit cellHeightChanged(); + d->layout(); + } +} + +QSGGridView::SnapMode QSGGridView::snapMode() const +{ + Q_D(const QSGGridView); + return d->snapMode; +} + +void QSGGridView::setSnapMode(SnapMode mode) +{ + Q_D(QSGGridView); + if (d->snapMode != mode) { + d->snapMode = mode; + emit snapModeChanged(); + } +} + +QDeclarativeComponent *QSGGridView::footer() const +{ + Q_D(const QSGGridView); + return d->footerComponent; +} + +void QSGGridView::setFooter(QDeclarativeComponent *footer) +{ + Q_D(QSGGridView); + if (d->footerComponent != footer) { + if (d->footer) { + // XXX todo - the original did scene()->removeItem(). Why? + d->footer->item->setParentItem(0); + d->footer->item->deleteLater(); + delete d->footer; + d->footer = 0; + } + d->footerComponent = footer; + if (isComponentComplete()) { + d->updateFooter(); + d->updateGrid(); + d->fixupPosition(); + } + emit footerChanged(); + } +} + +QDeclarativeComponent *QSGGridView::header() const +{ + Q_D(const QSGGridView); + return d->headerComponent; +} + +void QSGGridView::setHeader(QDeclarativeComponent *header) +{ + Q_D(QSGGridView); + if (d->headerComponent != header) { + if (d->header) { + // XXX todo - the original did scene()->removeItem(). Why? + d->header->item->setParentItem(0); + d->header->item->deleteLater(); + delete d->header; + d->header = 0; + } + d->headerComponent = header; + if (isComponentComplete()) { + d->updateHeader(); + d->updateFooter(); + d->updateGrid(); + d->fixupPosition(); + } + emit headerChanged(); + } +} + +void QSGGridView::setContentX(qreal pos) +{ + Q_D(QSGGridView); + // Positioning the view manually should override any current movement state + d->moveReason = QSGGridViewPrivate::Other; + QSGFlickable::setContentX(pos); +} + +void QSGGridView::setContentY(qreal pos) +{ + Q_D(QSGGridView); + // Positioning the view manually should override any current movement state + d->moveReason = QSGGridViewPrivate::Other; + QSGFlickable::setContentY(pos); +} + +void QSGGridView::updatePolish() +{ + Q_D(QSGGridView); + QSGFlickable::updatePolish(); + d->layout(); +} + +void QSGGridView::viewportMoved() +{ + Q_D(QSGGridView); + QSGFlickable::viewportMoved(); + if (!d->itemCount) + return; + d->lazyRelease = true; + if (d->flickingHorizontally || d->flickingVertically) { + if (yflick()) { + if (d->vData.velocity > 0) + d->bufferMode = QSGGridViewPrivate::BufferBefore; + else if (d->vData.velocity < 0) + d->bufferMode = QSGGridViewPrivate::BufferAfter; + } + + if (xflick()) { + if (d->hData.velocity > 0) + d->bufferMode = QSGGridViewPrivate::BufferBefore; + else if (d->hData.velocity < 0) + d->bufferMode = QSGGridViewPrivate::BufferAfter; + } + } + refill(); + if (d->flickingHorizontally || d->flickingVertically || d->movingHorizontally || d->movingVertically) + d->moveReason = QSGGridViewPrivate::Mouse; + if (d->moveReason != QSGGridViewPrivate::SetIndex) { + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { + // reposition highlight + qreal pos = d->highlight->rowPos(); + qreal viewPos; + qreal highlightStart; + qreal highlightEnd; + if (d->isRightToLeftTopToBottom()) { + highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart; + highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd; + viewPos = -d->position()-d->size(); + } else { + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + viewPos = d->position(); + } + if (pos > viewPos + highlightEnd - d->rowSize()) + pos = viewPos + highlightEnd - d->rowSize(); + if (pos < viewPos + highlightStart) + pos = viewPos + highlightStart; + d->highlight->setPosition(d->highlight->colPos(), qRound(pos)); + + // update current index + int idx = d->snapIndex(); + if (idx >= 0 && idx != d->currentIndex) { + d->updateCurrent(idx); + if (d->currentItem && d->currentItem->colPos() != d->highlight->colPos() && d->autoHighlight) { + if (d->flow == LeftToRight) + d->highlightXAnimator->to = d->currentItem->item->x(); + else + d->highlightYAnimator->to = d->currentItem->item->y(); + } + } + } + } +} + +qreal QSGGridView::minYExtent() const +{ + Q_D(const QSGGridView); + if (d->flow == QSGGridView::TopToBottom) + return QSGFlickable::minYExtent(); + qreal extent = -d->startPosition(); + if (d->header && d->visibleItems.count()) + extent += d->header->item->height(); + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + extent += d->highlightRangeStart; + extent = qMax(extent, -(d->rowPosAt(0) + d->rowSize() - d->highlightRangeEnd)); + } + return extent; +} + +qreal QSGGridView::maxYExtent() const +{ + Q_D(const QSGGridView); + if (d->flow == QSGGridView::TopToBottom) + return QSGFlickable::maxYExtent(); + qreal extent; + if (!d->model || !d->model->count()) { + extent = 0; + } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + extent = -(d->rowPosAt(d->model->count()-1) - d->highlightRangeStart); + if (d->highlightRangeEnd != d->highlightRangeStart) + extent = qMin(extent, -(d->endPosition() - d->highlightRangeEnd + 1)); + } else { + extent = -(d->endPosition() - height()); + } + if (d->footer) + extent -= d->footer->item->height(); + const qreal minY = minYExtent(); + if (extent > minY) + extent = minY; + return extent; +} + +qreal QSGGridView::minXExtent() const +{ + Q_D(const QSGGridView); + if (d->flow == QSGGridView::LeftToRight) + return QSGFlickable::minXExtent(); + qreal extent = -d->startPosition(); + qreal highlightStart; + qreal highlightEnd; + qreal endPositionFirstItem; + if (d->isRightToLeftTopToBottom()) { + endPositionFirstItem = d->rowPosAt(d->model->count()-1); + highlightStart = d->highlightRangeStartValid + ? d->highlightRangeStart - (d->lastPosition()-endPositionFirstItem) + : d->size() - (d->lastPosition()-endPositionFirstItem); + highlightEnd = d->highlightRangeEndValid ? d->highlightRangeEnd : d->size(); + if (d->footer && d->visibleItems.count()) + extent += d->footer->item->width(); + } else { + endPositionFirstItem = d->rowPosAt(0)+d->rowSize(); + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + if (d->header && d->visibleItems.count()) + extent += d->header->item->width(); + } + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + extent += highlightStart; + extent = qMax(extent, -(endPositionFirstItem - highlightEnd)); + } + return extent; +} + +qreal QSGGridView::maxXExtent() const +{ + Q_D(const QSGGridView); + if (d->flow == QSGGridView::LeftToRight) + return QSGFlickable::maxXExtent(); + qreal extent; + qreal highlightStart; + qreal highlightEnd; + qreal lastItemPosition = 0; + if (d->isRightToLeftTopToBottom()){ + highlightStart = d->highlightRangeStartValid ? d->highlightRangeEnd : d->size(); + highlightEnd = d->highlightRangeEndValid ? d->highlightRangeStart : d->size(); + lastItemPosition = d->endPosition(); + } else { + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + lastItemPosition = 0; + if (d->model && d->model->count()) + lastItemPosition = d->rowPosAt(d->model->count()-1); + } + if (!d->model || !d->model->count()) { + extent = 0; + } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + extent = -(lastItemPosition - highlightStart); + if (highlightEnd != highlightStart) + extent = d->isRightToLeftTopToBottom() + ? qMax(extent, -(d->endPosition() - highlightEnd + 1)) + : qMin(extent, -(d->endPosition() - highlightEnd + 1)); + } else { + extent = -(d->endPosition() - width()); + } + if (d->isRightToLeftTopToBottom()) { + if (d->header) + extent -= d->header->item->width(); + } else { + if (d->footer) + extent -= d->footer->item->width(); + } + + const qreal minX = minXExtent(); + if (extent > minX) + extent = minX; + return extent; +} + +void QSGGridView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QSGGridView); + if (d->model && d->model->count() && d->interactive) { + d->moveReason = QSGGridViewPrivate::SetIndex; + int oldCurrent = currentIndex(); + switch (event->key()) { + case Qt::Key_Up: + moveCurrentIndexUp(); + break; + case Qt::Key_Down: + moveCurrentIndexDown(); + break; + case Qt::Key_Left: + moveCurrentIndexLeft(); + break; + case Qt::Key_Right: + moveCurrentIndexRight(); + break; + default: + break; + } + if (oldCurrent != currentIndex()) { + event->accept(); + return; + } + } + d->moveReason = QSGGridViewPrivate::Other; + event->ignore(); + QSGFlickable::keyPressEvent(event); +} + +void QSGGridView::moveCurrentIndexUp() +{ + Q_D(QSGGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (d->flow == QSGGridView::LeftToRight) { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } else { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } +} + +void QSGGridView::moveCurrentIndexDown() +{ + Q_D(QSGGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (d->flow == QSGGridView::LeftToRight) { + if (currentIndex() < count - d->columns || d->wrap) { + int index = currentIndex()+d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } else { + if (currentIndex() < count - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } +} + +void QSGGridView::moveCurrentIndexLeft() +{ + Q_D(QSGGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (effectiveLayoutDirection() == Qt::LeftToRight) { + if (d->flow == QSGGridView::LeftToRight) { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } else { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } + } else { + if (d->flow == QSGGridView::LeftToRight) { + if (currentIndex() < count - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } else { + if (currentIndex() < count - d->columns || d->wrap) { + int index = currentIndex() + d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } + } +} + +void QSGGridView::moveCurrentIndexRight() +{ + Q_D(QSGGridView); + const int count = d->model ? d->model->count() : 0; + if (!count) + return; + if (effectiveLayoutDirection() == Qt::LeftToRight) { + if (d->flow == QSGGridView::LeftToRight) { + if (currentIndex() < count - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } else { + if (currentIndex() < count - d->columns || d->wrap) { + int index = currentIndex()+d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } + } + } else { + if (d->flow == QSGGridView::LeftToRight) { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } else { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } + } + } +} + +void QSGGridViewPrivate::positionViewAtIndex(int index, int mode) +{ + Q_Q(QSGGridView); + if (!isValid()) + return; + if (mode < QSGGridView::Beginning || mode > QSGGridView::Contain) + return; + + int idx = qMax(qMin(index, model->count()-1), 0); + + if (layoutScheduled) + layout(); + qreal pos = isRightToLeftTopToBottom() ? -position() - size() : position(); + FxGridItemSG *item = visibleItem(idx); + qreal maxExtent; + if (flow == QSGGridView::LeftToRight) + maxExtent = -q->maxYExtent(); + else + maxExtent = isRightToLeftTopToBottom() ? q->minXExtent()-size() : -q->maxXExtent(); + if (!item) { + int itemPos = rowPosAt(idx); + // save the currently visible items in case any of them end up visible again + QList<FxGridItemSG*> oldVisible = visibleItems; + visibleItems.clear(); + visibleIndex = idx - idx % columns; + if (flow == QSGGridView::LeftToRight) + maxExtent = -q->maxYExtent(); + else + maxExtent = isRightToLeftTopToBottom() ? q->minXExtent()-size() : -q->maxXExtent(); + setPosition(qMin(qreal(itemPos), maxExtent)); + // now release the reference to all the old visible items. + for (int i = 0; i < oldVisible.count(); ++i) + releaseItem(oldVisible.at(i)); + item = visibleItem(idx); + } + if (item) { + qreal itemPos = item->rowPos(); + switch (mode) { + case QSGGridView::Beginning: + pos = itemPos; + if (index < 0 && header) { + pos -= flow == QSGGridView::LeftToRight + ? header->item->height() + : header->item->width(); + } + break; + case QSGGridView::Center: + pos = itemPos - (size() - rowSize())/2; + break; + case QSGGridView::End: + pos = itemPos - size() + rowSize(); + if (index >= model->count() && footer) { + pos += flow == QSGGridView::LeftToRight + ? footer->item->height() + : footer->item->width(); + } + break; + case QSGGridView::Visible: + if (itemPos > pos + size()) + pos = itemPos - size() + rowSize(); + else if (item->endRowPos() < pos) + pos = itemPos; + break; + case QSGGridView::Contain: + if (item->endRowPos() > pos + size()) + pos = itemPos - size() + rowSize(); + if (itemPos < pos) + pos = itemPos; + } + pos = qMin(pos, maxExtent); + qreal minExtent; + if (flow == QSGGridView::LeftToRight) + minExtent = -q->minYExtent(); + else + minExtent = isRightToLeftTopToBottom() ? q->maxXExtent()-size() : -q->minXExtent(); + pos = qMax(pos, minExtent); + moveReason = QSGGridViewPrivate::Other; + q->cancelFlick(); + setPosition(pos); + } + fixupPosition(); +} + +void QSGGridView::positionViewAtIndex(int index, int mode) +{ + Q_D(QSGGridView); + if (!d->isValid() || index < 0 || index >= d->model->count()) + return; + d->positionViewAtIndex(index, mode); +} + +void QSGGridView::positionViewAtBeginning() +{ + Q_D(QSGGridView); + if (!d->isValid()) + return; + d->positionViewAtIndex(-1, Beginning); +} + +void QSGGridView::positionViewAtEnd() +{ + Q_D(QSGGridView); + if (!d->isValid()) + return; + d->positionViewAtIndex(d->model->count(), End); +} + +int QSGGridView::indexAt(qreal x, qreal y) const +{ + Q_D(const QSGGridView); + for (int i = 0; i < d->visibleItems.count(); ++i) { + const FxGridItemSG *listItem = d->visibleItems.at(i); + if(listItem->contains(x, y)) + return listItem->index; + } + + return -1; +} + +void QSGGridView::componentComplete() +{ + Q_D(QSGGridView); + QSGFlickable::componentComplete(); + d->updateHeader(); + d->updateFooter(); + d->updateGrid(); + if (d->isValid()) { + refill(); + d->moveReason = QSGGridViewPrivate::SetIndex; + if (d->currentIndex < 0 && !d->currentIndexCleared) + d->updateCurrent(0); + else + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); + d->updateTrackedItem(); + } + d->moveReason = QSGGridViewPrivate::Other; + d->fixupPosition(); + } +} + +void QSGGridView::trackedPositionChanged() +{ + Q_D(QSGGridView); + if (!d->trackedItem || !d->currentItem) + return; + if (d->moveReason == QSGGridViewPrivate::SetIndex) { + const qreal trackedPos = d->trackedItem->rowPos(); + qreal viewPos; + qreal highlightStart; + qreal highlightEnd; + if (d->isRightToLeftTopToBottom()) { + viewPos = -d->position()-d->size(); + highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart; + highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd; + } else { + viewPos = d->position(); + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + } + qreal pos = viewPos; + if (d->haveHighlightRange) { + if (d->highlightRange == StrictlyEnforceRange) { + if (trackedPos > pos + highlightEnd - d->rowSize()) + pos = trackedPos - highlightEnd + d->rowSize(); + if (trackedPos < pos + highlightStart) + pos = trackedPos - highlightStart; + } else { + if (trackedPos < d->startPosition() + highlightStart) { + pos = d->startPosition(); + } else if (d->trackedItem->endRowPos() > d->endPosition() - d->size() + highlightEnd) { + pos = d->endPosition() - d->size() + 1; + if (pos < d->startPosition()) + pos = d->startPosition(); + } else { + if (trackedPos < viewPos + highlightStart) { + pos = trackedPos - highlightStart; + } else if (trackedPos > viewPos + highlightEnd - d->rowSize()) { + pos = trackedPos - highlightEnd + d->rowSize(); + } + } + } + } else { + if (trackedPos < viewPos && d->currentItem->rowPos() < viewPos) { + pos = qMax(trackedPos, d->currentItem->rowPos()); + } else if (d->trackedItem->endRowPos() >= viewPos + d->size() + && d->currentItem->endRowPos() >= viewPos + d->size()) { + if (d->trackedItem->endRowPos() <= d->currentItem->endRowPos()) { + pos = d->trackedItem->endRowPos() - d->size() + 1; + if (d->rowSize() > d->size()) + pos = trackedPos; + } else { + pos = d->currentItem->endRowPos() - d->size() + 1; + if (d->rowSize() > d->size()) + pos = d->currentItem->rowPos(); + } + } + } + if (viewPos != pos) { + cancelFlick(); + d->calcVelocity = true; + d->setPosition(pos); + d->calcVelocity = false; + } + } +} + +void QSGGridView::itemsInserted(int modelIndex, int count) +{ + Q_D(QSGGridView); + if (!isComponentComplete()) + return; + + int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0; + if (index < 0) { + int i = d->visibleItems.count() - 1; + while (i > 0 && d->visibleItems.at(i)->index == -1) + --i; + if (d->visibleItems.at(i)->index + 1 == modelIndex) { + // Special case of appending an item to the model. + index = d->visibleIndex + d->visibleItems.count(); + } else { + if (modelIndex <= d->visibleIndex) { + // Insert before visible items + d->visibleIndex += count; + for (int i = 0; i < d->visibleItems.count(); ++i) { + FxGridItemSG *listItem = d->visibleItems.at(i); + if (listItem->index != -1 && listItem->index >= modelIndex) + listItem->index += count; + } + } + if (d->currentIndex >= modelIndex) { + // adjust current item index + d->currentIndex += count; + if (d->currentItem) + d->currentItem->index = d->currentIndex; + emit currentIndexChanged(); + } + d->scheduleLayout(); + d->itemCount += count; + emit countChanged(); + return; + } + } + + int insertCount = count; + if (index < d->visibleIndex && d->visibleItems.count()) { + insertCount -= d->visibleIndex - index; + index = d->visibleIndex; + modelIndex = d->visibleIndex; + } + + qreal tempPos = d->isRightToLeftTopToBottom() ? -d->position()-d->size()+width()+1 : d->position(); + int to = d->buffer+tempPos+d->size()-1; + int colPos = 0; + int rowPos = 0; + if (d->visibleItems.count()) { + index -= d->visibleIndex; + if (index < d->visibleItems.count()) { + colPos = d->visibleItems.at(index)->colPos(); + rowPos = d->visibleItems.at(index)->rowPos(); + } else { + // appending items to visible list + colPos = d->visibleItems.at(index-1)->colPos() + d->colSize(); + rowPos = d->visibleItems.at(index-1)->rowPos(); + if (colPos > d->colSize() * (d->columns-1)) { + colPos = 0; + rowPos += d->rowSize(); + } + } + } else if (d->itemCount == 0 && d->header) { + rowPos = d->headerSize(); + } + + // Update the indexes of the following visible items. + for (int i = 0; i < d->visibleItems.count(); ++i) { + FxGridItemSG *listItem = d->visibleItems.at(i); + if (listItem->index != -1 && listItem->index >= modelIndex) + listItem->index += count; + } + + bool addedVisible = false; + QList<FxGridItemSG*> added; + int i = 0; + while (i < insertCount && rowPos <= to + d->rowSize()*(d->columns - (colPos/d->colSize()))/qreal(d->columns)) { + if (!addedVisible) { + d->scheduleLayout(); + addedVisible = true; + } + FxGridItemSG *item = d->createItem(modelIndex + i); + d->visibleItems.insert(index, item); + item->setPosition(colPos, rowPos); + added.append(item); + colPos += d->colSize(); + if (colPos > d->colSize() * (d->columns-1)) { + colPos = 0; + rowPos += d->rowSize(); + } + ++index; + ++i; + } + if (i < insertCount) { + // We didn't insert all our new items, which means anything + // beyond the current index is not visible - remove it. + while (d->visibleItems.count() > index) { + d->releaseItem(d->visibleItems.takeLast()); + } + } + + // update visibleIndex + d->visibleIndex = 0; + for (QList<FxGridItemSG*>::Iterator it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + if (d->itemCount && d->currentIndex >= modelIndex) { + // adjust current item index + d->currentIndex += count; + if (d->currentItem) { + d->currentItem->index = d->currentIndex; + d->currentItem->setPosition(d->colPosAt(d->currentIndex), d->rowPosAt(d->currentIndex)); + } + emit currentIndexChanged(); + } else if (d->itemCount == 0 && (!d->currentIndex || (d->currentIndex < 0 && !d->currentIndexCleared))) { + setCurrentIndex(0); + } + + // everything is in order now - emit add() signal + for (int j = 0; j < added.count(); ++j) + added.at(j)->attached->emitAdd(); + + d->itemCount += count; + emit countChanged(); +} + +void QSGGridView::itemsRemoved(int modelIndex, int count) +{ + Q_D(QSGGridView); + if (!isComponentComplete()) + return; + + d->itemCount -= count; + bool currentRemoved = d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count; + bool removedVisible = false; + + // Remove the items from the visible list, skipping anything already marked for removal + QList<FxGridItemSG*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxGridItemSG *item = *it; + if (item->index == -1 || item->index < modelIndex) { + // already removed, or before removed items + if (item->index < modelIndex && !removedVisible) { + d->scheduleLayout(); + removedVisible = true; + } + ++it; + } else if (item->index >= modelIndex + count) { + // after removed items + item->index -= count; + ++it; + } else { + // removed item + if (!removedVisible) { + d->scheduleLayout(); + removedVisible = true; + } + item->attached->emitRemove(); + if (item->attached->delayRemove()) { + item->index = -1; + connect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()), Qt::QueuedConnection); + ++it; + } else { + it = d->visibleItems.erase(it); + d->releaseItem(item); + } + } + } + + // fix current + if (d->currentIndex >= modelIndex + count) { + d->currentIndex -= count; + if (d->currentItem) + d->currentItem->index -= count; + emit currentIndexChanged(); + } else if (currentRemoved) { + // current item has been removed. + d->releaseItem(d->currentItem); + d->currentItem = 0; + d->currentIndex = -1; + if (d->itemCount) + d->updateCurrent(qMin(modelIndex, d->itemCount-1)); + else + emit currentIndexChanged(); + } + + // update visibleIndex + d->visibleIndex = 0; + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + if (removedVisible && d->visibleItems.isEmpty()) { + d->timeline.clear(); + if (d->itemCount == 0) { + d->setPosition(0); + d->updateHeader(); + d->updateFooter(); + } + } + + emit countChanged(); +} + +void QSGGridView::destroyRemoved() +{ + Q_D(QSGGridView); + for (QList<FxGridItemSG*>::Iterator it = d->visibleItems.begin(); + it != d->visibleItems.end();) { + FxGridItemSG *listItem = *it; + if (listItem->index == -1 && listItem->attached->delayRemove() == false) { + d->releaseItem(listItem); + it = d->visibleItems.erase(it); + } else { + ++it; + } + } + + // Correct the positioning of the items + d->layout(); +} + +void QSGGridView::itemsMoved(int from, int to, int count) +{ + Q_D(QSGGridView); + if (!isComponentComplete()) + return; + QHash<int,FxGridItemSG*> moved; + + FxGridItemSG *firstItem = d->firstVisibleItem(); + + QList<FxGridItemSG*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxGridItemSG *item = *it; + if (item->index >= from && item->index < from + count) { + // take the items that are moving + item->index += (to-from); + moved.insert(item->index, item); + it = d->visibleItems.erase(it); + } else { + if (item->index > from && item->index != -1) { + // move everything after the moved items. + item->index -= count; + if (item->index < d->visibleIndex) + d->visibleIndex = item->index; + } + ++it; + } + } + + int remaining = count; + int endIndex = d->visibleIndex; + it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxGridItemSG *item = *it; + if (remaining && item->index >= to && item->index < to + count) { + // place items in the target position, reusing any existing items + FxGridItemSG *movedItem = moved.take(item->index); + if (!movedItem) + movedItem = d->createItem(item->index); + it = d->visibleItems.insert(it, movedItem); + if (it == d->visibleItems.begin() && firstItem) + movedItem->setPosition(firstItem->colPos(), firstItem->rowPos()); + ++it; + --remaining; + } else { + if (item->index != -1) { + if (item->index >= to) { + // update everything after the moved items. + item->index += count; + } + endIndex = item->index; + } + ++it; + } + } + + // If we have moved items to the end of the visible items + // then add any existing moved items that we have + while (FxGridItemSG *item = moved.take(endIndex+1)) { + d->visibleItems.append(item); + ++endIndex; + } + + // update visibleIndex + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + // Fix current index + if (d->currentIndex >= 0 && d->currentItem) { + int oldCurrent = d->currentIndex; + d->currentIndex = d->model->indexOf(d->currentItem->item, this); + if (oldCurrent != d->currentIndex) { + d->currentItem->index = d->currentIndex; + emit currentIndexChanged(); + } + } + + // Whatever moved items remain are no longer visible items. + while (moved.count()) { + int idx = moved.begin().key(); + FxGridItemSG *item = moved.take(idx); + if (d->currentItem && item->item == d->currentItem->item) + item->setPosition(d->colPosAt(idx), d->rowPosAt(idx)); + d->releaseItem(item); + } + + d->layout(); +} + +void QSGGridView::modelReset() +{ + Q_D(QSGGridView); + d->clear(); + refill(); + d->moveReason = QSGGridViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->colPos(), d->currentItem->rowPos()); + d->updateTrackedItem(); + } + d->moveReason = QSGGridViewPrivate::Other; + + emit countChanged(); +} + +void QSGGridView::createdItem(int index, QSGItem *item) +{ + Q_D(QSGGridView); + if (d->requestedIndex != index) { + item->setParentItem(this); + d->unrequestedItems.insert(item, index); + if (d->flow == QSGGridView::LeftToRight) { + item->setPos(QPointF(d->colPosAt(index), d->rowPosAt(index))); + } else { + item->setPos(QPointF(d->rowPosAt(index), d->colPosAt(index))); + } + } +} + +void QSGGridView::destroyingItem(QSGItem *item) +{ + Q_D(QSGGridView); + d->unrequestedItems.remove(item); +} + +void QSGGridView::animStopped() +{ + Q_D(QSGGridView); + d->bufferMode = QSGGridViewPrivate::NoBuffer; + if (d->haveHighlightRange && d->highlightRange == QSGGridView::StrictlyEnforceRange) + d->updateHighlight(); +} + +void QSGGridView::refill() +{ + Q_D(QSGGridView); + if (d->isRightToLeftTopToBottom()) + d->refill(-d->position()-d->size()+1, -d->position()); + else + d->refill(d->position(), d->position()+d->size()-1); +} + + +QSGGridViewAttached *QSGGridView::qmlAttachedProperties(QObject *obj) +{ + return new QSGGridViewAttached(obj); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsggridview_p.h b/src/declarative/items/qsggridview_p.h new file mode 100644 index 0000000000..8eca17df55 --- /dev/null +++ b/src/declarative/items/qsggridview_p.h @@ -0,0 +1,290 @@ +// Commit: 95814418f9d6adeba365c795462e8afb00138211 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGGRIDVIEW_P_H +#define QSGGRIDVIEW_P_H + +#include "qsgflickable_p.h" + +#include <private/qdeclarativeguard_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class QSGVisualModel; +class QSGGridViewAttached; +class QSGGridViewPrivate; +class Q_AUTOTEST_EXPORT QSGGridView : public QSGFlickable +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGGridView) + + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QSGItem *currentItem READ currentItem NOTIFY currentIndexChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + + Q_PROPERTY(QDeclarativeComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged) + Q_PROPERTY(QSGItem *highlightItem READ highlightItem NOTIFY highlightItemChanged) + Q_PROPERTY(bool highlightFollowsCurrentItem READ highlightFollowsCurrentItem WRITE setHighlightFollowsCurrentItem) + Q_PROPERTY(int highlightMoveDuration READ highlightMoveDuration WRITE setHighlightMoveDuration NOTIFY highlightMoveDurationChanged) + + Q_PROPERTY(qreal preferredHighlightBegin READ preferredHighlightBegin WRITE setPreferredHighlightBegin NOTIFY preferredHighlightBeginChanged RESET resetPreferredHighlightBegin) + Q_PROPERTY(qreal preferredHighlightEnd READ preferredHighlightEnd WRITE setPreferredHighlightEnd NOTIFY preferredHighlightEndChanged RESET resetPreferredHighlightEnd) + Q_PROPERTY(HighlightRangeMode highlightRangeMode READ highlightRangeMode WRITE setHighlightRangeMode NOTIFY highlightRangeModeChanged) + + 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) + Q_PROPERTY(bool keyNavigationWraps READ isWrapEnabled WRITE setWrapEnabled NOTIFY keyNavigationWrapsChanged) + Q_PROPERTY(int cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged) + Q_PROPERTY(int cellWidth READ cellWidth WRITE setCellWidth NOTIFY cellWidthChanged) + Q_PROPERTY(int cellHeight READ cellHeight WRITE setCellHeight NOTIFY cellHeightChanged) + + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged) + + Q_PROPERTY(QDeclarativeComponent *header READ header WRITE setHeader NOTIFY headerChanged) + Q_PROPERTY(QDeclarativeComponent *footer READ footer WRITE setFooter NOTIFY footerChanged) + + Q_ENUMS(HighlightRangeMode) + Q_ENUMS(SnapMode) + Q_ENUMS(Flow) + Q_ENUMS(PositionMode) + Q_CLASSINFO("DefaultProperty", "data") + +public: + QSGGridView(QSGItem *parent=0); + ~QSGGridView(); + + QVariant model() const; + int modelCount() const; + void setModel(const QVariant &); + + QDeclarativeComponent *delegate() const; + void setDelegate(QDeclarativeComponent *); + + int currentIndex() const; + void setCurrentIndex(int idx); + + QSGItem *currentItem(); + QSGItem *highlightItem(); + int count() const; + + QDeclarativeComponent *highlight() const; + void setHighlight(QDeclarativeComponent *highlight); + + bool highlightFollowsCurrentItem() const; + void setHighlightFollowsCurrentItem(bool); + + int highlightMoveDuration() const; + void setHighlightMoveDuration(int); + + enum HighlightRangeMode { NoHighlightRange, ApplyRange, StrictlyEnforceRange }; + HighlightRangeMode highlightRangeMode() const; + void setHighlightRangeMode(HighlightRangeMode mode); + + qreal preferredHighlightBegin() const; + void setPreferredHighlightBegin(qreal); + void resetPreferredHighlightBegin(); + + qreal preferredHighlightEnd() const; + void setPreferredHighlightEnd(qreal); + void resetPreferredHighlightEnd(); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection(Qt::LayoutDirection); + Qt::LayoutDirection effectiveLayoutDirection() const; + + enum Flow { LeftToRight, TopToBottom }; + Flow flow() const; + void setFlow(Flow); + + bool isWrapEnabled() const; + void setWrapEnabled(bool); + + int cacheBuffer() const; + void setCacheBuffer(int); + + int cellWidth() const; + void setCellWidth(int); + + int cellHeight() const; + void setCellHeight(int); + + enum SnapMode { NoSnap, SnapToRow, SnapOneRow }; + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + QDeclarativeComponent *footer() const; + void setFooter(QDeclarativeComponent *); + + QDeclarativeComponent *header() const; + void setHeader(QDeclarativeComponent *); + + virtual void setContentX(qreal pos); + virtual void setContentY(qreal pos); + + enum PositionMode { Beginning, Center, End, Visible, Contain }; + + Q_INVOKABLE void positionViewAtIndex(int index, int mode); + Q_INVOKABLE int indexAt(qreal x, qreal y) const; + Q_INVOKABLE void positionViewAtBeginning(); + Q_INVOKABLE void positionViewAtEnd(); + + static QSGGridViewAttached *qmlAttachedProperties(QObject *); + +public Q_SLOTS: + void moveCurrentIndexUp(); + void moveCurrentIndexDown(); + void moveCurrentIndexLeft(); + void moveCurrentIndexRight(); + +Q_SIGNALS: + void countChanged(); + void currentIndexChanged(); + void cellWidthChanged(); + void cellHeightChanged(); + void highlightChanged(); + void highlightItemChanged(); + void preferredHighlightBeginChanged(); + void preferredHighlightEndChanged(); + void highlightRangeModeChanged(); + void highlightMoveDurationChanged(); + void modelChanged(); + void delegateChanged(); + void flowChanged(); + void layoutDirectionChanged(); + void effectiveLayoutDirectionChanged(); + void keyNavigationWrapsChanged(); + void cacheBufferChanged(); + void snapModeChanged(); + void headerChanged(); + void footerChanged(); + +protected: + virtual void updatePolish(); + virtual void viewportMoved(); + virtual qreal minYExtent() const; + virtual qreal maxYExtent() const; + virtual qreal minXExtent() const; + virtual qreal maxXExtent() const; + virtual void keyPressEvent(QKeyEvent *); + virtual void componentComplete(); + +private Q_SLOTS: + void trackedPositionChanged(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int from, int to, int count); + void modelReset(); + void destroyRemoved(); + void createdItem(int index, QSGItem *item); + void destroyingItem(QSGItem *item); + void animStopped(); + +private: + void refill(); +}; + +class QSGGridViewAttached : public QObject +{ + Q_OBJECT +public: + QSGGridViewAttached(QObject *parent) + : QObject(parent), m_view(0), m_isCurrent(false), m_delayRemove(false) {} + ~QSGGridViewAttached() {} + + Q_PROPERTY(QSGGridView *view READ view NOTIFY viewChanged) + QSGGridView *view() { return m_view; } + void setView(QSGGridView *view) { + if (view != m_view) { + m_view = view; + emit viewChanged(); + } + } + + Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged) + bool isCurrentItem() const { return m_isCurrent; } + void setIsCurrentItem(bool c) { + if (m_isCurrent != c) { + m_isCurrent = c; + emit currentItemChanged(); + } + } + + Q_PROPERTY(bool delayRemove READ delayRemove WRITE setDelayRemove NOTIFY delayRemoveChanged) + bool delayRemove() const { return m_delayRemove; } + void setDelayRemove(bool delay) { + if (m_delayRemove != delay) { + m_delayRemove = delay; + emit delayRemoveChanged(); + } + } + + void emitAdd() { emit add(); } + void emitRemove() { emit remove(); } + +Q_SIGNALS: + void currentItemChanged(); + void delayRemoveChanged(); + void add(); + void remove(); + void viewChanged(); + +public: + QDeclarativeGuard<QSGGridView> m_view; + bool m_isCurrent : 1; + bool m_delayRemove : 1; +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGGridView) +QML_DECLARE_TYPEINFO(QSGGridView, QML_HAS_ATTACHED_PROPERTIES) + +QT_END_HEADER + +#endif // QSGGRIDVIEW_P_H diff --git a/src/declarative/items/qsgimage.cpp b/src/declarative/items/qsgimage.cpp new file mode 100644 index 0000000000..10670f4015 --- /dev/null +++ b/src/declarative/items/qsgimage.cpp @@ -0,0 +1,298 @@ +// Commit: 051a76c1d65d698f71dc75c89f91ae9021357eae +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgimage_p.h" +#include "qsgimage_p_p.h" + +#include <private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> + +#include <QtGui/qpainter.h> + +QT_BEGIN_NAMESPACE + +QSGImagePrivate::QSGImagePrivate() + : fillMode(QSGImage::Stretch) + , paintedWidth(0) + , paintedHeight(0) + , pixmapChanged(false) +{ +} + +QSGImage::QSGImage(QSGItem *parent) + : QSGImageBase(*(new QSGImagePrivate), parent) +{ +} + +QSGImage::QSGImage(QSGImagePrivate &dd, QSGItem *parent) + : QSGImageBase(dd, parent) +{ +} + +QSGImage::~QSGImage() +{ +} + +void QSGImagePrivate::setPixmap(const QPixmap &pixmap) +{ + Q_Q(QSGImage); + pix.setPixmap(pixmap); + + q->pixmapChange(); + status = pix.isNull() ? QSGImageBase::Null : QSGImageBase::Ready; + + q->update(); +} + +QSGImage::FillMode QSGImage::fillMode() const +{ + Q_D(const QSGImage); + return d->fillMode; +} + +void QSGImage::setFillMode(FillMode mode) +{ + Q_D(QSGImage); + if (d->fillMode == mode) + return; + d->fillMode = mode; + update(); + updatePaintedGeometry(); + emit fillModeChanged(); +} + +qreal QSGImage::paintedWidth() const +{ + Q_D(const QSGImage); + return d->paintedWidth; +} + +qreal QSGImage::paintedHeight() const +{ + Q_D(const QSGImage); + return d->paintedHeight; +} + +void QSGImage::updatePaintedGeometry() +{ + Q_D(QSGImage); + + if (d->fillMode == PreserveAspectFit) { + if (!d->pix.width() || !d->pix.height()) { + setImplicitWidth(0); + setImplicitHeight(0); + return; + } + qreal w = widthValid() ? width() : d->pix.width(); + qreal widthScale = w / qreal(d->pix.width()); + qreal h = heightValid() ? height() : d->pix.height(); + qreal heightScale = h / qreal(d->pix.height()); + if (widthScale <= heightScale) { + d->paintedWidth = w; + d->paintedHeight = widthScale * qreal(d->pix.height()); + } else if(heightScale < widthScale) { + d->paintedWidth = heightScale * qreal(d->pix.width()); + d->paintedHeight = h; + } + if (widthValid() && !heightValid()) { + setImplicitHeight(d->paintedHeight); + } else { + setImplicitHeight(d->pix.height()); + } + if (heightValid() && !widthValid()) { + setImplicitWidth(d->paintedWidth); + } else { + setImplicitWidth(d->pix.width()); + } + } else if (d->fillMode == PreserveAspectCrop) { + if (!d->pix.width() || !d->pix.height()) + return; + qreal widthScale = width() / qreal(d->pix.width()); + qreal heightScale = height() / qreal(d->pix.height()); + if (widthScale < heightScale) { + widthScale = heightScale; + } else if(heightScale < widthScale) { + heightScale = widthScale; + } + + d->paintedHeight = heightScale * qreal(d->pix.height()); + d->paintedWidth = widthScale * qreal(d->pix.width()); + } else { + d->paintedWidth = width(); + d->paintedHeight = height(); + } + emit paintedGeometryChanged(); +} + +void QSGImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QSGImageBase::geometryChanged(newGeometry, oldGeometry); + updatePaintedGeometry(); +} + +QRectF QSGImage::boundingRect() const +{ + Q_D(const QSGImage); + return QRectF(0, 0, qMax(width(), d->paintedWidth), qMax(height(), d->paintedHeight)); +} + +QSGTexture *QSGImage::texture() const +{ + Q_D(const QSGImage); + QSGTexture *t = d->pix.texture(); + t->setFiltering(QSGItemPrivate::get(this)->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + t->setMipmapFiltering(QSGTexture::None); + t->setHorizontalWrapMode(QSGTexture::ClampToEdge); + t->setVerticalWrapMode(QSGTexture::ClampToEdge); + return t; +} + +QSGNode *QSGImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + Q_D(QSGImage); + //XXX Support mirror property + + if (!d->pix.texture() || width() <= 0 || height() <= 0) { + delete oldNode; + return 0; + } + + QSGImageNode *node = static_cast<QSGImageNode *>(oldNode); + if (!node) { + d->pixmapChanged = true; + node = d->sceneGraphContext()->createImageNode(); + node->setTexture(d->pix.texture()); + } + + if (d->pixmapChanged) { + // force update the texture in the node to trigger reconstruction of + // geometry and the likes when a atlas segment has changed. + QSGTexture *t = d->pix.texture(); + node->setTexture(0); + node->setTexture(t); + d->pixmapChanged = false; + } + + QRectF targetRect; + QRectF sourceRect; + QSGTexture::WrapMode hWrap = QSGTexture::ClampToEdge; + QSGTexture::WrapMode vWrap = QSGTexture::ClampToEdge; + + switch (d->fillMode) { + default: + case Stretch: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = d->pix.rect(); + break; + + case PreserveAspectFit: + targetRect = QRectF((width() - d->paintedWidth) / 2., (height() - d->paintedHeight) / 2., + d->paintedWidth, d->paintedHeight); + sourceRect = d->pix.rect(); + break; + + case PreserveAspectCrop: { + targetRect = QRect(0, 0, width(), height()); + qreal wscale = width() / qreal(d->pix.width()); + qreal hscale = height() / qreal(d->pix.height()); + + if (wscale > hscale) { + int src = (hscale / wscale) * qreal(d->pix.height()); + sourceRect = QRectF(0, (d->pix.height() - src) / 2, d->pix.width(), src); + } else { + int src = (wscale / hscale) * qreal(d->pix.width()); + sourceRect = QRectF((d->pix.width() - src) / 2, 0, src, d->pix.height()); + } + } + break; + + case Tile: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = QRectF(0, 0, width(), height()); + hWrap = QSGTexture::Repeat; + vWrap = QSGTexture::Repeat; + break; + + case TileHorizontally: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = QRectF(0, 0, width(), d->pix.height()); + hWrap = QSGTexture::Repeat; + break; + + case TileVertically: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = QRectF(0, 0, d->pix.width(), height()); + vWrap = QSGTexture::Repeat; + break; + + }; + + QRectF nsrect(sourceRect.x() / d->pix.width(), + sourceRect.y() / d->pix.height(), + sourceRect.width() / d->pix.width(), + sourceRect.height() / d->pix.height()); + + node->setHorizontalWrapMode(hWrap); + node->setVerticalWrapMode(vWrap); + node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + + node->setTargetRect(targetRect); + node->setSourceRect(nsrect); + node->update(); + + return node; +} + +void QSGImage::pixmapChange() +{ + Q_D(QSGImage); + // PreserveAspectFit calculates the implicit size differently so we + // don't call our superclass pixmapChange(), since that would + // result in the implicit size being set incorrectly, then updated + // in updatePaintedGeometry() + if (d->fillMode != PreserveAspectFit) + QSGImageBase::pixmapChange(); + updatePaintedGeometry(); + d->pixmapChanged = true; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgimage_p.h b/src/declarative/items/qsgimage_p.h new file mode 100644 index 0000000000..aad63d42c0 --- /dev/null +++ b/src/declarative/items/qsgimage_p.h @@ -0,0 +1,104 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGIMAGE_P_H +#define QSGIMAGE_P_H + +#include "qsgimagebase_p.h" +#include <private/qsgtextureprovider_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGImagePrivate; +class Q_AUTOTEST_EXPORT QSGImage : public QSGImageBase, public QSGTextureProvider +{ + Q_OBJECT + Q_ENUMS(FillMode) + + Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged) + Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedGeometryChanged) + Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedGeometryChanged) + Q_PROPERTY(QSGTexture *texture READ texture) + + Q_INTERFACES(QSGTextureProvider) + +public: + QSGImage(QSGItem *parent=0); + ~QSGImage(); + + enum FillMode { Stretch, PreserveAspectFit, PreserveAspectCrop, Tile, TileVertically, TileHorizontally }; + FillMode fillMode() const; + void setFillMode(FillMode); + + qreal paintedWidth() const; + qreal paintedHeight() const; + + QRectF boundingRect() const; + + virtual QSGTexture *texture() const; + +Q_SIGNALS: + void fillModeChanged(); + void paintedGeometryChanged(); + +protected: + QSGImage(QSGImagePrivate &dd, QSGItem *parent); + void pixmapChange(); + void updatePaintedGeometry(); + + virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private: + Q_DISABLE_COPY(QSGImage) + Q_DECLARE_PRIVATE(QSGImage) +}; + +QT_END_NAMESPACE +QML_DECLARE_TYPE(QSGImage) +QT_END_HEADER + +#endif // QSGIMAGE_P_H diff --git a/src/declarative/items/qsgimage_p_p.h b/src/declarative/items/qsgimage_p_p.h new file mode 100644 index 0000000000..01b549df1f --- /dev/null +++ b/src/declarative/items/qsgimage_p_p.h @@ -0,0 +1,81 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGIMAGE_P_P_H +#define QSGIMAGE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgimagebase_p_p.h" +#include "qsgimage_p.h" + +QT_BEGIN_NAMESPACE + +class QSGImagePrivate; + +class QSGImagePrivate : public QSGImageBasePrivate +{ + Q_DECLARE_PUBLIC(QSGImage) + +public: + QSGImagePrivate(); + + QSGImage::FillMode fillMode; + qreal paintedWidth; + qreal paintedHeight; + void setPixmap(const QPixmap &pix); + + bool pixmapChanged : 1; +}; + +QT_END_NAMESPACE + +#endif // QSGIMAGE_P_P_H diff --git a/src/declarative/items/qsgimagebase.cpp b/src/declarative/items/qsgimagebase.cpp new file mode 100644 index 0000000000..9f2de03bbe --- /dev/null +++ b/src/declarative/items/qsgimagebase.cpp @@ -0,0 +1,285 @@ +// Commit: 051a76c1d65d698f71dc75c89f91ae9021357eae +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgimagebase_p.h" +#include "qsgimagebase_p_p.h" + +#include <QtDeclarative/qdeclarativeinfo.h> + +QT_BEGIN_NAMESPACE + +QSGImageBase::QSGImageBase(QSGItem *parent) +: QSGImplicitSizeItem(*(new QSGImageBasePrivate), parent) +{ + setFlag(ItemHasContents); +} + +QSGImageBase::QSGImageBase(QSGImageBasePrivate &dd, QSGItem *parent) +: QSGImplicitSizeItem(dd, parent) +{ + setFlag(ItemHasContents); +} + +QSGImageBase::~QSGImageBase() +{ +} + +QSGImageBase::Status QSGImageBase::status() const +{ + Q_D(const QSGImageBase); + return d->status; +} + + +qreal QSGImageBase::progress() const +{ + Q_D(const QSGImageBase); + return d->progress; +} + + +bool QSGImageBase::asynchronous() const +{ + Q_D(const QSGImageBase); + return d->async; +} + +void QSGImageBase::setAsynchronous(bool async) +{ + Q_D(QSGImageBase); + if (d->async != async) { + d->async = async; + emit asynchronousChanged(); + } +} + +QUrl QSGImageBase::source() const +{ + Q_D(const QSGImageBase); + return d->url; +} + +void QSGImageBase::setSource(const QUrl &url) +{ + Q_D(QSGImageBase); + //equality is fairly expensive, so we bypass for simple, common case + if ((d->url.isEmpty() == url.isEmpty()) && url == d->url) + return; + + d->url = url; + emit sourceChanged(d->url); + + if (isComponentComplete()) + load(); +} + +void QSGImageBase::setSourceSize(const QSize& size) +{ + Q_D(QSGImageBase); + if (d->sourcesize == size) + return; + + d->sourcesize = size; + d->explicitSourceSize = true; + emit sourceSizeChanged(); + if (isComponentComplete()) + load(); +} + +QSize QSGImageBase::sourceSize() const +{ + Q_D(const QSGImageBase); + + int width = d->sourcesize.width(); + int height = d->sourcesize.height(); + return QSize(width != -1 ? width : d->pix.width(), height != -1 ? height : d->pix.height()); +} + +void QSGImageBase::resetSourceSize() +{ + Q_D(QSGImageBase); + if (!d->explicitSourceSize) + return; + d->explicitSourceSize = false; + d->sourcesize = QSize(); + emit sourceSizeChanged(); + if (isComponentComplete()) + load(); +} + +bool QSGImageBase::cache() const +{ + Q_D(const QSGImageBase); + return d->cache; +} + +void QSGImageBase::setCache(bool cache) +{ + Q_D(QSGImageBase); + if (d->cache == cache) + return; + + d->cache = cache; + emit cacheChanged(); + if (isComponentComplete()) + load(); +} + +void QSGImageBase::setMirror(bool mirror) +{ + Q_D(QSGImageBase); + if (mirror == d->mirror) + return; + + d->mirror = mirror; + + if (isComponentComplete()) + update(); + + emit mirrorChanged(); +} + +bool QSGImageBase::mirror() const +{ + Q_D(const QSGImageBase); + return d->mirror; +} + +void QSGImageBase::load() +{ + Q_D(QSGImageBase); + + if (d->url.isEmpty()) { + d->pix.clear(this); + d->status = Null; + d->progress = 0.0; + pixmapChange(); + emit progressChanged(d->progress); + emit statusChanged(d->status); + update(); + } else { + QDeclarativePixmap::Options options; + if (d->async) + options |= QDeclarativePixmap::Asynchronous; + if (d->cache) + options |= QDeclarativePixmap::Cache; + d->pix.clear(this); + d->pix.load(qmlEngine(this), d->url, d->explicitSourceSize ? sourceSize() : QSize(), options); + + if (d->pix.isLoading()) { + d->progress = 0.0; + d->status = Loading; + emit progressChanged(d->progress); + emit statusChanged(d->status); + + static int thisRequestProgress = -1; + static int thisRequestFinished = -1; + if (thisRequestProgress == -1) { + thisRequestProgress = + QSGImageBase::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)"); + thisRequestFinished = + QSGImageBase::staticMetaObject.indexOfSlot("requestFinished()"); + } + + d->pix.connectFinished(this, thisRequestFinished); + d->pix.connectDownloadProgress(this, thisRequestProgress); + + } else { + requestFinished(); + } + } +} + +void QSGImageBase::requestFinished() +{ + Q_D(QSGImageBase); + + QSGImageBase::Status oldStatus = d->status; + qreal oldProgress = d->progress; + + if (d->pix.isError()) { + d->status = Error; + qmlInfo(this) << d->pix.error(); + } else { + d->status = Ready; + } + + d->progress = 1.0; + + pixmapChange(); + + if (d->sourcesize.width() != d->pix.width() || d->sourcesize.height() != d->pix.height()) + emit sourceSizeChanged(); + + if (d->status != oldStatus) + emit statusChanged(d->status); + if (d->progress != oldProgress) + emit progressChanged(d->progress); + + update(); +} + +void QSGImageBase::requestProgress(qint64 received, qint64 total) +{ + Q_D(QSGImageBase); + if (d->status == Loading && total > 0) { + d->progress = qreal(received)/total; + emit progressChanged(d->progress); + } +} + +void QSGImageBase::componentComplete() +{ + Q_D(QSGImageBase); + QSGItem::componentComplete(); + if (d->url.isValid()) + load(); +} + +void QSGImageBase::pixmapChange() +{ + Q_D(QSGImageBase); + setImplicitWidth(d->pix.width()); + setImplicitHeight(d->pix.height()); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgimagebase_p.h b/src/declarative/items/qsgimagebase_p.h new file mode 100644 index 0000000000..00b14525f2 --- /dev/null +++ b/src/declarative/items/qsgimagebase_p.h @@ -0,0 +1,117 @@ +// Commit: af05f64d3edc860c3cf79c7f0bdf2377faae5f40 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGIMAGEBASE_P_H +#define QSGIMAGEBASE_P_H + +#include "qsgimplicitsizeitem_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QSGImageBasePrivate; +class Q_AUTOTEST_EXPORT QSGImageBase : public QSGImplicitSizeItem +{ + Q_OBJECT + Q_ENUMS(Status) + + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged) + Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged) + Q_PROPERTY(bool cache READ cache WRITE setCache NOTIFY cacheChanged) + Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize RESET resetSourceSize NOTIFY sourceSizeChanged) + Q_PROPERTY(bool mirror READ mirror WRITE setMirror NOTIFY mirrorChanged) + +public: + QSGImageBase(QSGItem *parent=0); + ~QSGImageBase(); + enum Status { Null, Ready, Loading, Error }; + Status status() const; + qreal progress() const; + + QUrl source() const; + virtual void setSource(const QUrl &url); + + bool asynchronous() const; + void setAsynchronous(bool); + + bool cache() const; + void setCache(bool); + + virtual void setSourceSize(const QSize&); + QSize sourceSize() const; + void resetSourceSize(); + + virtual void setMirror(bool mirror); + bool mirror() const; + +Q_SIGNALS: + void sourceChanged(const QUrl &); + void sourceSizeChanged(); + void statusChanged(QSGImageBase::Status); + void progressChanged(qreal progress); + void asynchronousChanged(); + void cacheChanged(); + void mirrorChanged(); + +protected: + virtual void load(); + virtual void componentComplete(); + virtual void pixmapChange(); + QSGImageBase(QSGImageBasePrivate &dd, QSGItem *parent); + +private Q_SLOTS: + virtual void requestFinished(); + void requestProgress(qint64,qint64); + +private: + Q_DISABLE_COPY(QSGImageBase) + Q_DECLARE_PRIVATE(QSGImageBase) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSGIMAGEBASE_P_H diff --git a/src/declarative/items/qsgimagebase_p_p.h b/src/declarative/items/qsgimagebase_p_p.h new file mode 100644 index 0000000000..8c67b41e8b --- /dev/null +++ b/src/declarative/items/qsgimagebase_p_p.h @@ -0,0 +1,93 @@ +// Commit: 6f78a6080b84cc3ef96b73a4ff58d1b5a72f08f4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGIMAGEBASE_P_P_H +#define QSGIMAGEBASE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgimplicitsizeitem_p_p.h" +#include "qsgimagebase_p.h" + +#include <private/qdeclarativepixmapcache_p.h> + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QSGImageBasePrivate : public QSGImplicitSizeItemPrivate +{ + Q_DECLARE_PUBLIC(QSGImageBase) + +public: + QSGImageBasePrivate() + : status(QSGImageBase::Null), + progress(0.0), + explicitSourceSize(false), + async(false), + cache(true), + mirror(false) + { + } + + QDeclarativePixmap pix; + QSGImageBase::Status status; + QUrl url; + qreal progress; + QSize sourcesize; + bool explicitSourceSize : 1; + bool async : 1; + bool cache : 1; + bool mirror: 1; +}; + +QT_END_NAMESPACE + +#endif // QSGIMAGEBASE_P_P_H diff --git a/src/declarative/items/qsgimplicitsizeitem.cpp b/src/declarative/items/qsgimplicitsizeitem.cpp new file mode 100644 index 0000000000..f2cc9bcbdb --- /dev/null +++ b/src/declarative/items/qsgimplicitsizeitem.cpp @@ -0,0 +1,93 @@ +// Commit: 6f78a6080b84cc3ef96b73a4ff58d1b5a72f08f4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "private/qsgimplicitsizeitem_p.h" +#include "private/qsgimplicitsizeitem_p_p.h" + +QT_BEGIN_NAMESPACE + +void QSGImplicitSizeItemPrivate::implicitWidthChanged() +{ + Q_Q(QSGImplicitSizeItem); + emit q->implicitWidthChanged(); +} + +void QSGImplicitSizeItemPrivate::implicitHeightChanged() +{ + Q_Q(QSGImplicitSizeItem); + emit q->implicitHeightChanged(); +} + +QSGImplicitSizeItem::QSGImplicitSizeItem(QSGItem *parent) + : QSGItem(*(new QSGImplicitSizeItemPrivate), parent) +{ +} + +QSGImplicitSizeItem::QSGImplicitSizeItem(QSGImplicitSizeItemPrivate &dd, QSGItem *parent) + : QSGItem(dd, parent) +{ +} + + +void QSGImplicitSizePaintedItemPrivate::implicitWidthChanged() +{ + Q_Q(QSGImplicitSizePaintedItem); + emit q->implicitWidthChanged(); +} + +void QSGImplicitSizePaintedItemPrivate::implicitHeightChanged() +{ + Q_Q(QSGImplicitSizePaintedItem); + emit q->implicitHeightChanged(); +} + +QSGImplicitSizePaintedItem::QSGImplicitSizePaintedItem(QSGItem *parent) + : QSGPaintedItem(*(new QSGImplicitSizePaintedItemPrivate), parent) +{ +} + +QSGImplicitSizePaintedItem::QSGImplicitSizePaintedItem(QSGImplicitSizePaintedItemPrivate &dd, QSGItem *parent) + : QSGPaintedItem(dd, parent) +{ +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgimplicitsizeitem_p.h b/src/declarative/items/qsgimplicitsizeitem_p.h new file mode 100644 index 0000000000..36ef0e7f8b --- /dev/null +++ b/src/declarative/items/qsgimplicitsizeitem_p.h @@ -0,0 +1,101 @@ +// Commit: 6f78a6080b84cc3ef96b73a4ff58d1b5a72f08f4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGIMPLICITSIZEITEM_H +#define QSGIMPLICITSIZEITEM_H + +#include "qsgpainteditem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QSGImplicitSizeItemPrivate; +class Q_AUTOTEST_EXPORT QSGImplicitSizeItem : public QSGItem +{ + Q_OBJECT + Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged) + Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged) + +public: + QSGImplicitSizeItem(QSGItem *parent = 0); + +protected: + QSGImplicitSizeItem(QSGImplicitSizeItemPrivate &dd, QSGItem *parent); + +Q_SIGNALS: + void implicitWidthChanged(); + void implicitHeightChanged(); + +private: + Q_DISABLE_COPY(QSGImplicitSizeItem) + Q_DECLARE_PRIVATE(QSGImplicitSizeItem) +}; + +class QSGImplicitSizePaintedItemPrivate; +class Q_AUTOTEST_EXPORT QSGImplicitSizePaintedItem : public QSGPaintedItem +{ + Q_OBJECT + Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged) + Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged) + +public: + QSGImplicitSizePaintedItem(QSGItem *parent = 0); + +protected: + QSGImplicitSizePaintedItem(QSGImplicitSizePaintedItemPrivate &dd, QSGItem *parent); + virtual void drawContents(QPainter *, const QRect &) {}; + +Q_SIGNALS: + void implicitWidthChanged(); + void implicitHeightChanged(); + +private: + Q_DISABLE_COPY(QSGImplicitSizePaintedItem) + Q_DECLARE_PRIVATE(QSGImplicitSizePaintedItem) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSGIMPLICITSIZEITEM_H diff --git a/src/declarative/items/qsgimplicitsizeitem_p_p.h b/src/declarative/items/qsgimplicitsizeitem_p_p.h new file mode 100644 index 0000000000..f67ecfab9f --- /dev/null +++ b/src/declarative/items/qsgimplicitsizeitem_p_p.h @@ -0,0 +1,92 @@ +// Commit: 6f78a6080b84cc3ef96b73a4ff58d1b5a72f08f4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGIMPLICITSIZEITEM_P_H +#define QSGIMPLICITSIZEITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qsgitem_p.h" +#include "private/qsgpainteditem_p.h" +#include "private/qsgimplicitsizeitem_p.h" + +QT_BEGIN_NAMESPACE + +class QSGImplicitSizeItemPrivate : public QSGItemPrivate +{ + Q_DECLARE_PUBLIC(QSGImplicitSizeItem) + +public: + QSGImplicitSizeItemPrivate() + { + } + + virtual void implicitWidthChanged(); + virtual void implicitHeightChanged(); +}; + + +class QSGImplicitSizePaintedItemPrivate : public QSGPaintedItemPrivate +{ + Q_DECLARE_PUBLIC(QSGImplicitSizePaintedItem) + +public: + QSGImplicitSizePaintedItemPrivate() + { + } + + virtual void implicitWidthChanged(); + virtual void implicitHeightChanged(); +}; + +QT_END_NAMESPACE + +#endif // QSGIMPLICITSIZEITEM_P_H diff --git a/src/declarative/items/qsgitem.cpp b/src/declarative/items/qsgitem.cpp new file mode 100644 index 0000000000..f2d26955aa --- /dev/null +++ b/src/declarative/items/qsgitem.cpp @@ -0,0 +1,3143 @@ +// Commit: c44be8c0b27756a2025ebad1945632f3f7e4bebc +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgitem.h" + +#include "qsgcanvas.h" +#include <QtScript/qscriptengine.h> +#include "qsgcanvas_p.h" + +#include "qsgevents_p_p.h" + +#include <QtDeclarative/qdeclarativeitem.h> +#include <QtDeclarative/qdeclarativeengine.h> +#include <QtDeclarative/qdeclarativeview.h> +#include <QtDeclarative/qdeclarativecomponent.h> +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qgraphicstransform.h> +#include <QtGui/qpen.h> +#include <QtGui/qinputcontext.h> +#include <QtCore/qdebug.h> +#include <QtCore/qcoreevent.h> +#include <QtCore/qnumeric.h> + +#include <private/qdeclarativeengine_p.h> +#include <private/qdeclarativestategroup_p.h> +#include <private/qdeclarativeopenmetaobject_p.h> +#include <private/qdeclarativestate_p.h> +#include <private/qlistmodelinterface_p.h> +#include <private/qsgitem_p.h> + +#include <float.h> + +// XXX todo Readd parentNotifier for faster parent bindings +// XXX todo Check that elements that create items handle memory correctly after visual ownership change + +QT_BEGIN_NAMESPACE + +QSGTransformPrivate::QSGTransformPrivate() +{ +} + +QSGTransform::QSGTransform(QObject *parent) +: QObject(*(new QSGTransformPrivate), parent) +{ +} + +QSGTransform::QSGTransform(QSGTransformPrivate &dd, QObject *parent) +: QObject(dd, parent) +{ +} + +QSGTransform::~QSGTransform() +{ + Q_D(QSGTransform); + for (int ii = 0; ii < d->items.count(); ++ii) { + QSGItemPrivate *p = QSGItemPrivate::get(d->items.at(ii)); + p->transforms.removeOne(this); + p->dirty(QSGItemPrivate::Transform); + } +} + +void QSGTransform::update() +{ + Q_D(QSGTransform); + for (int ii = 0; ii < d->items.count(); ++ii) { + QSGItemPrivate *p = QSGItemPrivate::get(d->items.at(ii)); + p->dirty(QSGItemPrivate::Transform); + } +} + +QSGContents::QSGContents(QSGItem *item) +: m_item(item), m_x(0), m_y(0), m_width(0), m_height(0) +{ + //### optimize + connect(this, SIGNAL(rectChanged(QRectF)), m_item, SIGNAL(childrenRectChanged(QRectF))); +} + +QSGContents::~QSGContents() +{ + QList<QSGItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QSGItem *child = children.at(i); + QSGItemPrivate::get(child)->removeItemChangeListener(this, QSGItemPrivate::Geometry | QSGItemPrivate::Destroyed); + } +} + +QRectF QSGContents::rectF() const +{ + return QRectF(m_x, m_y, m_width, m_height); +} + +void QSGContents::calcHeight(QSGItem *changed) +{ + qreal oldy = m_y; + qreal oldheight = m_height; + + if (changed) { + qreal top = oldy; + qreal bottom = oldy + oldheight; + qreal y = changed->y(); + if (y + changed->height() > bottom) + bottom = y + changed->height(); + if (y < top) + top = y; + m_y = top; + m_height = bottom - top; + } else { + qreal top = FLT_MAX; + qreal bottom = 0; + QList<QSGItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QSGItem *child = children.at(i); + qreal y = child->y(); + if (y + child->height() > bottom) + bottom = y + child->height(); + if (y < top) + top = y; + } + if (!children.isEmpty()) + m_y = top; + m_height = qMax(bottom - top, qreal(0.0)); + } + + if (m_height != oldheight || m_y != oldy) + emit rectChanged(rectF()); +} + +void QSGContents::calcWidth(QSGItem *changed) +{ + qreal oldx = m_x; + qreal oldwidth = m_width; + + if (changed) { + qreal left = oldx; + qreal right = oldx + oldwidth; + qreal x = changed->x(); + if (x + changed->width() > right) + right = x + changed->width(); + if (x < left) + left = x; + m_x = left; + m_width = right - left; + } else { + qreal left = FLT_MAX; + qreal right = 0; + QList<QSGItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QSGItem *child = children.at(i); + qreal x = child->x(); + if (x + child->width() > right) + right = x + child->width(); + if (x < left) + left = x; + } + if (!children.isEmpty()) + m_x = left; + m_width = qMax(right - left, qreal(0.0)); + } + + if (m_width != oldwidth || m_x != oldx) + emit rectChanged(rectF()); +} + +void QSGContents::complete() +{ + QList<QSGItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QSGItem *child = children.at(i); + QSGItemPrivate::get(child)->addItemChangeListener(this, QSGItemPrivate::Geometry | QSGItemPrivate::Destroyed); + //###what about changes to visibility? + } + + calcGeometry(); +} + +void QSGContents::itemGeometryChanged(QSGItem *changed, const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_UNUSED(changed) + //### we can only pass changed if the left edge has moved left, or the right edge has moved right + if (newGeometry.width() != oldGeometry.width() || newGeometry.x() != oldGeometry.x()) + calcWidth(/*changed*/); + if (newGeometry.height() != oldGeometry.height() || newGeometry.y() != oldGeometry.y()) + calcHeight(/*changed*/); +} + +void QSGContents::itemDestroyed(QSGItem *item) +{ + if (item) + QSGItemPrivate::get(item)->removeItemChangeListener(this, QSGItemPrivate::Geometry | QSGItemPrivate::Destroyed); + calcGeometry(); +} + +void QSGContents::childRemoved(QSGItem *item) +{ + if (item) + QSGItemPrivate::get(item)->removeItemChangeListener(this, QSGItemPrivate::Geometry | QSGItemPrivate::Destroyed); + calcGeometry(); +} + +void QSGContents::childAdded(QSGItem *item) +{ + if (item) + QSGItemPrivate::get(item)->addItemChangeListener(this, QSGItemPrivate::Geometry | QSGItemPrivate::Destroyed); + calcWidth(item); + calcHeight(item); +} + +QSGItemKeyFilter::QSGItemKeyFilter(QSGItem *item) +: m_processPost(false), m_next(0) +{ + QSGItemPrivate *p = item?QSGItemPrivate::get(item):0; + if (p) { + m_next = p->keyHandler; + p->keyHandler = this; + } +} + +QSGItemKeyFilter::~QSGItemKeyFilter() +{ +} + +void QSGItemKeyFilter::keyPressed(QKeyEvent *event, bool post) +{ + if (m_next) m_next->keyPressed(event, post); +} + +void QSGItemKeyFilter::keyReleased(QKeyEvent *event, bool post) +{ + if (m_next) m_next->keyReleased(event, post); +} + +void QSGItemKeyFilter::inputMethodEvent(QInputMethodEvent *event, bool post) +{ + if (m_next) + m_next->inputMethodEvent(event, post); + else + event->ignore(); +} + +QVariant QSGItemKeyFilter::inputMethodQuery(Qt::InputMethodQuery query) const +{ + if (m_next) return m_next->inputMethodQuery(query); + return QVariant(); +} + +void QSGItemKeyFilter::componentComplete() +{ + if (m_next) m_next->componentComplete(); +} + +QSGKeyNavigationAttached::QSGKeyNavigationAttached(QObject *parent) +: QObject(*(new QSGKeyNavigationAttachedPrivate), parent), + QSGItemKeyFilter(qobject_cast<QSGItem*>(parent)) +{ + m_processPost = true; +} + +QSGKeyNavigationAttached * +QSGKeyNavigationAttached::qmlAttachedProperties(QObject *obj) +{ + return new QSGKeyNavigationAttached(obj); +} + +QSGItem *QSGKeyNavigationAttached::left() const +{ + Q_D(const QSGKeyNavigationAttached); + return d->left; +} + +void QSGKeyNavigationAttached::setLeft(QSGItem *i) +{ + Q_D(QSGKeyNavigationAttached); + if (d->left == i) + return; + d->left = i; + d->leftSet = true; + QSGKeyNavigationAttached* other = + qobject_cast<QSGKeyNavigationAttached*>(qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(i)); + if (other && !other->d_func()->rightSet){ + other->d_func()->right = qobject_cast<QSGItem*>(parent()); + emit other->rightChanged(); + } + emit leftChanged(); +} + +QSGItem *QSGKeyNavigationAttached::right() const +{ + Q_D(const QSGKeyNavigationAttached); + return d->right; +} + +void QSGKeyNavigationAttached::setRight(QSGItem *i) +{ + Q_D(QSGKeyNavigationAttached); + if (d->right == i) + return; + d->right = i; + d->rightSet = true; + QSGKeyNavigationAttached* other = + qobject_cast<QSGKeyNavigationAttached*>(qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(i)); + if (other && !other->d_func()->leftSet){ + other->d_func()->left = qobject_cast<QSGItem*>(parent()); + emit other->leftChanged(); + } + emit rightChanged(); +} + +QSGItem *QSGKeyNavigationAttached::up() const +{ + Q_D(const QSGKeyNavigationAttached); + return d->up; +} + +void QSGKeyNavigationAttached::setUp(QSGItem *i) +{ + Q_D(QSGKeyNavigationAttached); + if (d->up == i) + return; + d->up = i; + d->upSet = true; + QSGKeyNavigationAttached* other = + qobject_cast<QSGKeyNavigationAttached*>(qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(i)); + if (other && !other->d_func()->downSet){ + other->d_func()->down = qobject_cast<QSGItem*>(parent()); + emit other->downChanged(); + } + emit upChanged(); +} + +QSGItem *QSGKeyNavigationAttached::down() const +{ + Q_D(const QSGKeyNavigationAttached); + return d->down; +} + +void QSGKeyNavigationAttached::setDown(QSGItem *i) +{ + Q_D(QSGKeyNavigationAttached); + if (d->down == i) + return; + d->down = i; + d->downSet = true; + QSGKeyNavigationAttached* other = + qobject_cast<QSGKeyNavigationAttached*>(qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(i)); + if(other && !other->d_func()->upSet){ + other->d_func()->up = qobject_cast<QSGItem*>(parent()); + emit other->upChanged(); + } + emit downChanged(); +} + +QSGItem *QSGKeyNavigationAttached::tab() const +{ + Q_D(const QSGKeyNavigationAttached); + return d->tab; +} + +void QSGKeyNavigationAttached::setTab(QSGItem *i) +{ + Q_D(QSGKeyNavigationAttached); + if (d->tab == i) + return; + d->tab = i; + d->tabSet = true; + QSGKeyNavigationAttached* other = + qobject_cast<QSGKeyNavigationAttached*>(qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(i)); + if(other && !other->d_func()->backtabSet){ + other->d_func()->backtab = qobject_cast<QSGItem*>(parent()); + emit other->backtabChanged(); + } + emit tabChanged(); +} + +QSGItem *QSGKeyNavigationAttached::backtab() const +{ + Q_D(const QSGKeyNavigationAttached); + return d->backtab; +} + +void QSGKeyNavigationAttached::setBacktab(QSGItem *i) +{ + Q_D(QSGKeyNavigationAttached); + if (d->backtab == i) + return; + d->backtab = i; + d->backtabSet = true; + QSGKeyNavigationAttached* other = + qobject_cast<QSGKeyNavigationAttached*>(qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(i)); + if(other && !other->d_func()->tabSet){ + other->d_func()->tab = qobject_cast<QSGItem*>(parent()); + emit other->tabChanged(); + } + emit backtabChanged(); +} + +QSGKeyNavigationAttached::Priority QSGKeyNavigationAttached::priority() const +{ + return m_processPost ? AfterItem : BeforeItem; +} + +void QSGKeyNavigationAttached::setPriority(Priority order) +{ + bool processPost = order == AfterItem; + if (processPost != m_processPost) { + m_processPost = processPost; + emit priorityChanged(); + } +} + +void QSGKeyNavigationAttached::keyPressed(QKeyEvent *event, bool post) +{ + Q_D(QSGKeyNavigationAttached); + event->ignore(); + + if (post != m_processPost) { + QSGItemKeyFilter::keyPressed(event, post); + return; + } + + bool mirror = false; + switch(event->key()) { + case Qt::Key_Left: { + if (QSGItem *parentItem = qobject_cast<QSGItem*>(parent())) + mirror = QSGItemPrivate::get(parentItem)->effectiveLayoutMirror; + QSGItem* leftItem = mirror ? d->right : d->left; + if (leftItem) { + setFocusNavigation(leftItem, mirror ? "right" : "left"); + event->accept(); + } + break; + } + case Qt::Key_Right: { + if (QSGItem *parentItem = qobject_cast<QSGItem*>(parent())) + mirror = QSGItemPrivate::get(parentItem)->effectiveLayoutMirror; + QSGItem* rightItem = mirror ? d->left : d->right; + if (rightItem) { + setFocusNavigation(rightItem, mirror ? "left" : "right"); + event->accept(); + } + break; + } + case Qt::Key_Up: + if (d->up) { + setFocusNavigation(d->up, "up"); + event->accept(); + } + break; + case Qt::Key_Down: + if (d->down) { + setFocusNavigation(d->down, "down"); + event->accept(); + } + break; + case Qt::Key_Tab: + if (d->tab) { + setFocusNavigation(d->tab, "tab"); + event->accept(); + } + break; + case Qt::Key_Backtab: + if (d->backtab) { + setFocusNavigation(d->backtab, "backtab"); + event->accept(); + } + break; + default: + break; + } + + if (!event->isAccepted()) QSGItemKeyFilter::keyPressed(event, post); +} + +void QSGKeyNavigationAttached::keyReleased(QKeyEvent *event, bool post) +{ + Q_D(QSGKeyNavigationAttached); + event->ignore(); + + if (post != m_processPost) { + QSGItemKeyFilter::keyReleased(event, post); + return; + } + + bool mirror = false; + switch(event->key()) { + case Qt::Key_Left: + if (QSGItem *parentItem = qobject_cast<QSGItem*>(parent())) + mirror = QSGItemPrivate::get(parentItem)->effectiveLayoutMirror; + if (mirror ? d->right : d->left) + event->accept(); + break; + case Qt::Key_Right: + if (QSGItem *parentItem = qobject_cast<QSGItem*>(parent())) + mirror = QSGItemPrivate::get(parentItem)->effectiveLayoutMirror; + if (mirror ? d->left : d->right) + event->accept(); + break; + case Qt::Key_Up: + if (d->up) { + event->accept(); + } + break; + case Qt::Key_Down: + if (d->down) { + event->accept(); + } + break; + case Qt::Key_Tab: + if (d->tab) { + event->accept(); + } + break; + case Qt::Key_Backtab: + if (d->backtab) { + event->accept(); + } + break; + default: + break; + } + + if (!event->isAccepted()) QSGItemKeyFilter::keyReleased(event, post); +} + +void QSGKeyNavigationAttached::setFocusNavigation(QSGItem *currentItem, const char *dir) +{ + QSGItem *initialItem = currentItem; + bool isNextItem = false; + do { + isNextItem = false; + if (currentItem->isVisible() && currentItem->isEnabled()) { + currentItem->setFocus(true); + } else { + QObject *attached = + qmlAttachedPropertiesObject<QSGKeyNavigationAttached>(currentItem, false); + if (attached) { + QSGItem *tempItem = qvariant_cast<QSGItem*>(attached->property(dir)); + if (tempItem) { + currentItem = tempItem; + isNextItem = true; + } + } + } + } + while (currentItem != initialItem && isNextItem); +} + +const QSGKeysAttached::SigMap QSGKeysAttached::sigMap[] = { + { Qt::Key_Left, "leftPressed" }, + { Qt::Key_Right, "rightPressed" }, + { Qt::Key_Up, "upPressed" }, + { Qt::Key_Down, "downPressed" }, + { Qt::Key_Tab, "tabPressed" }, + { Qt::Key_Backtab, "backtabPressed" }, + { Qt::Key_Asterisk, "asteriskPressed" }, + { Qt::Key_NumberSign, "numberSignPressed" }, + { Qt::Key_Escape, "escapePressed" }, + { Qt::Key_Return, "returnPressed" }, + { Qt::Key_Enter, "enterPressed" }, + { Qt::Key_Delete, "deletePressed" }, + { Qt::Key_Space, "spacePressed" }, + { Qt::Key_Back, "backPressed" }, + { Qt::Key_Cancel, "cancelPressed" }, + { Qt::Key_Select, "selectPressed" }, + { Qt::Key_Yes, "yesPressed" }, + { Qt::Key_No, "noPressed" }, + { Qt::Key_Context1, "context1Pressed" }, + { Qt::Key_Context2, "context2Pressed" }, + { Qt::Key_Context3, "context3Pressed" }, + { Qt::Key_Context4, "context4Pressed" }, + { Qt::Key_Call, "callPressed" }, + { Qt::Key_Hangup, "hangupPressed" }, + { Qt::Key_Flip, "flipPressed" }, + { Qt::Key_Menu, "menuPressed" }, + { Qt::Key_VolumeUp, "volumeUpPressed" }, + { Qt::Key_VolumeDown, "volumeDownPressed" }, + { 0, 0 } +}; + +bool QSGKeysAttachedPrivate::isConnected(const char *signalName) +{ + return isSignalConnected(signalIndex(signalName)); +} + +QSGKeysAttached::QSGKeysAttached(QObject *parent) +: QObject(*(new QSGKeysAttachedPrivate), parent), + QSGItemKeyFilter(qobject_cast<QSGItem*>(parent)) +{ + Q_D(QSGKeysAttached); + m_processPost = false; + d->item = qobject_cast<QSGItem*>(parent); +} + +QSGKeysAttached::~QSGKeysAttached() +{ +} + +QSGKeysAttached::Priority QSGKeysAttached::priority() const +{ + return m_processPost ? AfterItem : BeforeItem; +} + +void QSGKeysAttached::setPriority(Priority order) +{ + bool processPost = order == AfterItem; + if (processPost != m_processPost) { + m_processPost = processPost; + emit priorityChanged(); + } +} + +void QSGKeysAttached::componentComplete() +{ + Q_D(QSGKeysAttached); + if (d->item) { + for (int ii = 0; ii < d->targets.count(); ++ii) { + QSGItem *targetItem = d->targets.at(ii); + if (targetItem && (targetItem->flags() & QSGItem::ItemAcceptsInputMethod)) { + d->item->setFlag(QSGItem::ItemAcceptsInputMethod); + break; + } + } + } +} + +void QSGKeysAttached::keyPressed(QKeyEvent *event, bool post) +{ + Q_D(QSGKeysAttached); + if (post != m_processPost || !d->enabled || d->inPress) { + event->ignore(); + QSGItemKeyFilter::keyPressed(event, post); + return; + } + + // first process forwards + if (d->item && d->item->canvas()) { + d->inPress = true; + for (int ii = 0; ii < d->targets.count(); ++ii) { + QSGItem *i = d->targets.at(ii); + if (i && i->isVisible()) { + d->item->canvas()->sendEvent(i, event); + if (event->isAccepted()) { + d->inPress = false; + return; + } + } + } + d->inPress = false; + } + + QSGKeyEvent ke(*event); + QByteArray keySignal = keyToSignal(event->key()); + if (!keySignal.isEmpty()) { + keySignal += "(QSGKeyEvent*)"; + if (d->isConnected(keySignal)) { + // If we specifically handle a key then default to accepted + ke.setAccepted(true); + int idx = QSGKeysAttached::staticMetaObject.indexOfSignal(keySignal); + metaObject()->method(idx).invoke(this, Qt::DirectConnection, Q_ARG(QSGKeyEvent*, &ke)); + } + } + if (!ke.isAccepted()) + emit pressed(&ke); + event->setAccepted(ke.isAccepted()); + + if (!event->isAccepted()) QSGItemKeyFilter::keyPressed(event, post); +} + +void QSGKeysAttached::keyReleased(QKeyEvent *event, bool post) +{ + Q_D(QSGKeysAttached); + if (post != m_processPost || !d->enabled || d->inRelease) { + event->ignore(); + QSGItemKeyFilter::keyReleased(event, post); + return; + } + + if (d->item && d->item->canvas()) { + d->inRelease = true; + for (int ii = 0; ii < d->targets.count(); ++ii) { + QSGItem *i = d->targets.at(ii); + if (i && i->isVisible()) { + d->item->canvas()->sendEvent(i, event); + if (event->isAccepted()) { + d->inRelease = false; + return; + } + } + } + d->inRelease = false; + } + + QSGKeyEvent ke(*event); + emit released(&ke); + event->setAccepted(ke.isAccepted()); + + if (!event->isAccepted()) QSGItemKeyFilter::keyReleased(event, post); +} + +void QSGKeysAttached::inputMethodEvent(QInputMethodEvent *event, bool post) +{ + Q_D(QSGKeysAttached); + if (post == m_processPost && d->item && !d->inIM && d->item->canvas()) { + d->inIM = true; + for (int ii = 0; ii < d->targets.count(); ++ii) { + QSGItem *i = d->targets.at(ii); + if (i && i->isVisible() && (i->flags() & QSGItem::ItemAcceptsInputMethod)) { + d->item->canvas()->sendEvent(i, event); + if (event->isAccepted()) { + d->imeItem = i; + d->inIM = false; + return; + } + } + } + d->inIM = false; + } + QSGItemKeyFilter::inputMethodEvent(event, post); +} + +QVariant QSGKeysAttached::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QSGKeysAttached); + 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) { + //### how robust is i == d->imeItem check? + QVariant v = i->inputMethodQuery(query); + if (v.userType() == QVariant::RectF) + v = d->item->mapRectFromItem(i, v.toRectF()); //### cost? + return v; + } + } + } + return QSGItemKeyFilter::inputMethodQuery(query); +} + +QSGKeysAttached *QSGKeysAttached::qmlAttachedProperties(QObject *obj) +{ + return new QSGKeysAttached(obj); +} + + +QSGLayoutMirroringAttached::QSGLayoutMirroringAttached(QObject *parent) : QObject(parent), itemPrivate(0) +{ + if (QSGItem *item = qobject_cast<QSGItem*>(parent)) { + itemPrivate = QSGItemPrivate::get(item); + itemPrivate->attachedLayoutDirection = this; + } else + qmlInfo(parent) << tr("LayoutDirection attached property only works with Items"); +} + +QSGLayoutMirroringAttached * QSGLayoutMirroringAttached::qmlAttachedProperties(QObject *object) +{ + return new QSGLayoutMirroringAttached(object); +} + +bool QSGLayoutMirroringAttached::enabled() const +{ + return itemPrivate ? itemPrivate->effectiveLayoutMirror : false; +} + +void QSGLayoutMirroringAttached::setEnabled(bool enabled) +{ + if (!itemPrivate) + return; + + itemPrivate->isMirrorImplicit = false; + if (enabled != itemPrivate->effectiveLayoutMirror) { + itemPrivate->setLayoutMirror(enabled); + if (itemPrivate->inheritMirrorFromItem) + itemPrivate->resolveLayoutMirror(); + } +} + +void QSGLayoutMirroringAttached::resetEnabled() +{ + if (itemPrivate && !itemPrivate->isMirrorImplicit) { + itemPrivate->isMirrorImplicit = true; + itemPrivate->resolveLayoutMirror(); + } +} + +bool QSGLayoutMirroringAttached::childrenInherit() const +{ + return itemPrivate ? itemPrivate->inheritMirrorFromItem : false; +} + +void QSGLayoutMirroringAttached::setChildrenInherit(bool childrenInherit) { + if (itemPrivate && childrenInherit != itemPrivate->inheritMirrorFromItem) { + itemPrivate->inheritMirrorFromItem = childrenInherit; + itemPrivate->resolveLayoutMirror(); + childrenInheritChanged(); + } +} + +void QSGItemPrivate::resolveLayoutMirror() +{ + Q_Q(QSGItem); + if (QSGItem *parentItem = q->parentItem()) { + QSGItemPrivate *parentPrivate = QSGItemPrivate::get(parentItem); + setImplicitLayoutMirror(parentPrivate->inheritedLayoutMirror, parentPrivate->inheritMirrorFromParent); + } else { + setImplicitLayoutMirror(isMirrorImplicit ? false : effectiveLayoutMirror, inheritMirrorFromItem); + } +} + +void QSGItemPrivate::setImplicitLayoutMirror(bool mirror, bool inherit) +{ + inherit = inherit || inheritMirrorFromItem; + if (!isMirrorImplicit && inheritMirrorFromItem) + mirror = effectiveLayoutMirror; + if (mirror == inheritedLayoutMirror && inherit == inheritMirrorFromParent) + return; + + inheritMirrorFromParent = inherit; + inheritedLayoutMirror = inheritMirrorFromParent ? mirror : false; + + if (isMirrorImplicit) + setLayoutMirror(inherit ? inheritedLayoutMirror : false); + for (int i = 0; i < childItems.count(); ++i) { + if (QSGItem *child = qobject_cast<QSGItem *>(childItems.at(i))) { + QSGItemPrivate *childPrivate = QSGItemPrivate::get(child); + childPrivate->setImplicitLayoutMirror(inheritedLayoutMirror, inheritMirrorFromParent); + } + } +} + +void QSGItemPrivate::setLayoutMirror(bool mirror) +{ + if (mirror != effectiveLayoutMirror) { + effectiveLayoutMirror = mirror; + if (_anchors) { + QSGAnchorsPrivate *anchor_d = QSGAnchorsPrivate::get(_anchors); + anchor_d->fillChanged(); + anchor_d->centerInChanged(); + anchor_d->updateHorizontalAnchors(); + emit _anchors->mirroredChanged(); + } + mirrorChange(); + if (attachedLayoutDirection) { + emit attachedLayoutDirection->enabledChanged(); + } + } +} + +QSGItem::QSGItem(QSGItem* parent) +: QObject(*(new QSGItemPrivate), parent) +{ + Q_D(QSGItem); + d->init(parent); +} + +QSGItem::QSGItem(QSGItemPrivate &dd, QSGItem *parent) +: QObject(dd, parent) +{ + Q_D(QSGItem); + d->init(parent); +} + +QSGItem::~QSGItem() +{ + Q_D(QSGItem); + + // 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) + anchor->clearItem(this); + } + + // XXX todo - the original checks if the parent is being destroyed + for (int ii = 0; ii < d->changeListeners.count(); ++ii) { + QSGAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate(); + 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) + change.listener->itemDestroyed(this); + } + d->changeListeners.clear(); + delete d->_anchorLines; d->_anchorLines = 0; + delete d->_anchors; d->_anchors = 0; + delete d->_stateGroup; d->_stateGroup = 0; + delete d->_contents; d->_contents = 0; +} + +void QSGItem::setParentItem(QSGItem *parentItem) +{ + Q_D(QSGItem); + if (parentItem == d->parentItem) + return; + + d->removeFromDirtyList(); + + QSGItem *oldParentItem = d->parentItem; + QSGItem *scopeFocusedItem = 0; + + if (oldParentItem) { + QSGItemPrivate *op = QSGItemPrivate::get(oldParentItem); + + QSGItem *scopeItem = 0; + + if (d->canvas && hasFocus()) { + scopeItem = oldParentItem; + while (!scopeItem->isFocusScope()) scopeItem = scopeItem->parentItem(); + scopeFocusedItem = this; + } else if (d->canvas && !isFocusScope() && d->subFocusItem) { + scopeItem = oldParentItem; + while (!scopeItem->isFocusScope()) scopeItem = scopeItem->parentItem(); + scopeFocusedItem = d->subFocusItem; + } + + if (scopeFocusedItem) + QSGCanvasPrivate::get(d->canvas)->clearFocusInScope(scopeItem, scopeFocusedItem, + QSGCanvasPrivate::DontChangeFocusProperty); + + op->removeChild(this); + } + + d->parentItem = 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); + + d->setEffectiveVisibleRecur(d->calcEffectiveVisible()); + d->setEffectiveEnableRecur(d->calcEffectiveEnable()); + + if (scopeFocusedItem && d->parentItem && d->canvas) { + // We need to test whether this item becomes scope focused + QSGItem *scopeItem = 0; + scopeItem = d->parentItem; + while (!scopeItem->isFocusScope()) scopeItem = scopeItem->parentItem(); + + if (scopeItem->scopedFocusItem()) { + QSGItemPrivate::get(scopeFocusedItem)->focus = false; + emit scopeFocusedItem->focusChanged(false); + } else { + QSGCanvasPrivate::get(d->canvas)->setFocusInScope(scopeItem, scopeFocusedItem, + QSGCanvasPrivate::DontChangeFocusProperty); + } + } + + d->resolveLayoutMirror(); + + d->itemChange(ItemParentHasChanged, d->parentItem); + + emit parentChanged(d->parentItem); +} + +void QSGItem::stackBefore(const QSGItem *sibling) +{ + Q_D(QSGItem); + if (!sibling || sibling == this || !d->parentItem || d->parentItem != QSGItemPrivate::get(sibling)->parentItem) { + qWarning("QSGItem::stackBefore: Cannot stack before %p, which must be a sibling", sibling); + return; + } + + QSGItemPrivate *parentPrivate = QSGItemPrivate::get(d->parentItem); + + 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) + return; + + parentPrivate->childItems.removeAt(myIndex); + + if (myIndex < siblingIndex) --siblingIndex; + + parentPrivate->childItems.insert(siblingIndex, this); + + parentPrivate->dirty(QSGItemPrivate::ChildrenStackingChanged); + + for (int ii = qMin(siblingIndex, myIndex); ii < parentPrivate->childItems.count(); ++ii) + QSGItemPrivate::get(parentPrivate->childItems.at(ii))->siblingOrderChanged(); +} + +void QSGItem::stackAfter(const QSGItem *sibling) +{ + Q_D(QSGItem); + if (!sibling || sibling == this || !d->parentItem || d->parentItem != QSGItemPrivate::get(sibling)->parentItem) { + qWarning("QSGItem::stackAfter: Cannot stack after %p, which must be a sibling", sibling); + return; + } + + QSGItemPrivate *parentPrivate = QSGItemPrivate::get(d->parentItem); + + 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) + return; + + parentPrivate->childItems.removeAt(myIndex); + + if (myIndex < siblingIndex) --siblingIndex; + + parentPrivate->childItems.insert(siblingIndex + 1, this); + + parentPrivate->dirty(QSGItemPrivate::ChildrenStackingChanged); + + for (int ii = qMin(myIndex, siblingIndex + 1); ii < parentPrivate->childItems.count(); ++ii) + QSGItemPrivate::get(parentPrivate->childItems.at(ii))->siblingOrderChanged(); +} + +/*! + Returns the QSGItem parent of this item. +*/ +QSGItem *QSGItem::parentItem() const +{ + Q_D(const QSGItem); + return d->parentItem; +} + +QSGEngine *QSGItem::sceneGraphEngine() const +{ + return canvas()->sceneGraphEngine(); +} + +QSGCanvas *QSGItem::canvas() const +{ + Q_D(const QSGItem); + return d->canvas; +} + +static bool itemZOrder_sort(QSGItem *lhs, QSGItem *rhs) +{ + return lhs->z() < rhs->z(); +} + +QList<QSGItem *> QSGItemPrivate::paintOrderChildItems() const +{ + // XXX todo - optimize, don't sort and return items that are + // ignored anyway, like invisible or disabled items. + QList<QSGItem *> items = childItems; + qStableSort(items.begin(), items.end(), itemZOrder_sort); + return items; +} + +void QSGItemPrivate::addChild(QSGItem *child) +{ + Q_Q(QSGItem); + + Q_ASSERT(!childItems.contains(child)); + + childItems.append(child); + + dirty(QSGItemPrivate::ChildrenChanged); + + itemChange(QSGItem::ItemChildAddedChange, child); + + emit q->childrenChanged(); +} + +void QSGItemPrivate::removeChild(QSGItem *child) +{ + Q_Q(QSGItem); + + Q_ASSERT(child); + Q_ASSERT(childItems.contains(child)); + childItems.removeOne(child); + Q_ASSERT(!childItems.contains(child)); + + dirty(QSGItemPrivate::ChildrenChanged); + + itemChange(QSGItem::ItemChildRemovedChange, child); + + emit q->childrenChanged(); +} + +void QSGItemPrivate::InitializationState::clear() +{ + focusScope = 0; +} + +void QSGItemPrivate::InitializationState::clear(QSGItem *fs) +{ + focusScope = fs; +} + +QSGItem *QSGItemPrivate::InitializationState::getFocusScope(QSGItem *item) +{ + if (!focusScope) { + QSGItem *fs = item->parentItem(); + while (!fs->isFocusScope()) + fs = fs->parentItem(); + focusScope = fs; + } + return focusScope; +} + +void QSGItemPrivate::initCanvas(InitializationState *state, QSGCanvas *c) +{ + Q_Q(QSGItem); + + if (canvas) { + removeFromDirtyList(); + QSGCanvasPrivate *c = QSGCanvasPrivate::get(canvas); + if (polishScheduled) + c->itemsToPolish.remove(q); + if (c->mouseGrabberItem == q) + c->mouseGrabberItem = 0; + } + + canvas = c; + + if (canvas && polishScheduled) + QSGCanvasPrivate::get(canvas)->itemsToPolish.insert(q); + + if (canvas && hoverEnabled && !canvas->hasMouseTracking()) + canvas->setMouseTracking(true); + + // XXX todo - why aren't these added to the destroy list? + itemNodeInstance = 0; + opacityNode = 0; + clipNode = 0; + rootNode = 0; + groupNode = 0; + paintNode = 0; + paintNodeIndex = 0; + + InitializationState _dummy; + InitializationState *childState = state; + + if (c && q->isFocusScope()) { + _dummy.clear(q); + childState = &_dummy; + } + + for (int ii = 0; ii < childItems.count(); ++ii) { + QSGItem *child = childItems.at(ii); + QSGItemPrivate::get(child)->initCanvas(childState, c); + } + + if (c && focus) { + // Fixup + if (state->getFocusScope(q)->scopedFocusItem()) { + focus = false; + emit q->focusChanged(false); + } else { + QSGCanvasPrivate::get(canvas)->setFocusInScope(state->getFocusScope(q), q); + } + } + + dirty(Canvas); + + itemChange(QSGItem::ItemSceneChange, c); +} + +/*! +Returns a transform that maps points from canvas space into item space. +*/ +QTransform QSGItemPrivate::canvasToItemTransform() const +{ + // XXX todo - optimize + return itemToCanvasTransform().inverted(); +} + +/*! +Returns a transform that maps points from item space into canvas space. +*/ +QTransform QSGItemPrivate::itemToCanvasTransform() const +{ + // XXX todo + QTransform rv = parentItem?QSGItemPrivate::get(parentItem)->itemToCanvasTransform():QTransform(); + itemToParentTransform(rv); + return rv; +} + +/*! +Motifies \a t with this items local transform relative to its parent. +*/ +void QSGItemPrivate::itemToParentTransform(QTransform &t) const +{ + if (x || y) + t.translate(x, y); + + if (!transforms.isEmpty()) { + QMatrix4x4 m(t); + for (int ii = transforms.count() - 1; ii >= 0; --ii) + transforms.at(ii)->applyTo(&m); + t = m.toTransform(); + } + + if (scale != 1. || rotation != 0.) { + QPointF tp = computeTransformOrigin(); + t.translate(tp.x(), tp.y()); + t.scale(scale, scale); + t.rotate(rotation); + t.translate(-tp.x(), -tp.y()); + } +} + +bool QSGItem::isComponentComplete() const +{ + Q_D(const QSGItem); + return d->componentComplete; +} + +QSGItemPrivate::QSGItemPrivate() +: _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), + effectiveVisible(true), explicitEnable(true), effectiveEnable(true), polishScheduled(false), + inheritedLayoutMirror(false), effectiveLayoutMirror(false), isMirrorImplicit(true), + inheritMirrorFromParent(false), inheritMirrorFromItem(false), childrenDoNotOverlap(false), + + canvas(0), parentItem(0), + + subFocusItem(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), + + itemNodeInstance(0), opacityNode(0), clipNode(0), rootNode(0), groupNode(0), paintNode(0) + , paintNodeIndex(0), effectRefCount(0), hideRefCount(0) +{ +} + +void QSGItemPrivate::init(QSGItem *parent) +{ + Q_Q(QSGItem); + baselineOffset.invalidate(); + + if (parent) { + q->setParentItem(parent); + QSGItemPrivate *parentPrivate = QSGItemPrivate::get(parent); + setImplicitLayoutMirror(parentPrivate->inheritedLayoutMirror, parentPrivate->inheritMirrorFromParent); + } +} + +void QSGItemPrivate::data_append(QDeclarativeListProperty<QObject> *prop, QObject *o) +{ + if (!o) + return; + + QSGItem *that = static_cast<QSGItem *>(prop->object); + + // This test is measurably (albeit only slightly) faster than qobject_cast<>() + const QMetaObject *mo = o->metaObject(); + while (mo && mo != &QSGItem::staticMetaObject) { + if (mo == &QDeclarativeItem::staticMetaObject) + qWarning("Cannot add a QtQuick 1.0 item (%s) into a QtQuick 2.0 scene!", o->metaObject()->className()); + mo = mo->d.superdata; + } + + if (mo) { + QSGItem *item = static_cast<QSGItem *>(o); + item->setParentItem(that); + } else { + // XXX todo - do we really want this behavior? + o->setParent(that); + } +} + +int QSGItemPrivate::data_count(QDeclarativeListProperty<QObject> *prop) +{ + Q_UNUSED(prop); + // XXX todo + return 0; +} + +QObject *QSGItemPrivate::data_at(QDeclarativeListProperty<QObject> *prop, int i) +{ + Q_UNUSED(prop); + Q_UNUSED(i); + // XXX todo + return 0; +} + +void QSGItemPrivate::data_clear(QDeclarativeListProperty<QObject> *prop) +{ + Q_UNUSED(prop); + // XXX todo +} + +QObject *QSGItemPrivate::resources_at(QDeclarativeListProperty<QObject> *prop, int index) +{ + const QObjectList children = prop->object->children(); + if (index < children.count()) + return children.at(index); + else + return 0; +} + +void QSGItemPrivate::resources_append(QDeclarativeListProperty<QObject> *prop, QObject *o) +{ + // XXX todo - do we really want this behavior? + o->setParent(prop->object); +} + +int QSGItemPrivate::resources_count(QDeclarativeListProperty<QObject> *prop) +{ + return prop->object->children().count(); +} + +void QSGItemPrivate::resources_clear(QDeclarativeListProperty<QObject> *prop) +{ + // XXX todo - do we really want this behavior? + const QObjectList children = prop->object->children(); + for (int index = 0; index < children.count(); index++) + children.at(index)->setParent(0); +} + +QSGItem *QSGItemPrivate::children_at(QDeclarativeListProperty<QSGItem> *prop, int index) +{ + QSGItemPrivate *p = QSGItemPrivate::get(static_cast<QSGItem *>(prop->object)); + if (index >= p->childItems.count() || index < 0) + return 0; + else + return p->childItems.at(index); +} + +void QSGItemPrivate::children_append(QDeclarativeListProperty<QSGItem> *prop, QSGItem *o) +{ + if (!o) + return; + + QSGItem *that = static_cast<QSGItem *>(prop->object); + if (o->parentItem() == that) + o->setParentItem(0); + + o->setParentItem(that); +} + +int QSGItemPrivate::children_count(QDeclarativeListProperty<QSGItem> *prop) +{ + QSGItemPrivate *p = QSGItemPrivate::get(static_cast<QSGItem *>(prop->object)); + return p->childItems.count(); +} + +void QSGItemPrivate::children_clear(QDeclarativeListProperty<QSGItem> *prop) +{ + QSGItem *that = static_cast<QSGItem *>(prop->object); + QSGItemPrivate *p = QSGItemPrivate::get(that); + while (!p->childItems.isEmpty()) + p->childItems.at(0)->setParentItem(0); +} + +int QSGItemPrivate::transform_count(QDeclarativeListProperty<QSGTransform> *prop) +{ + QSGItem *that = static_cast<QSGItem *>(prop->object); + return QSGItemPrivate::get(that)->transforms.count(); +} + +void QSGTransform::appendToItem(QSGItem *item) +{ + Q_D(QSGTransform); + if (!item) + return; + + QSGItemPrivate *p = QSGItemPrivate::get(item); + + if (!d->items.isEmpty() && !p->transforms.isEmpty() && p->transforms.contains(this)) { + p->transforms.removeOne(this); + p->transforms.append(this); + } else { + p->transforms.append(this); + d->items.append(item); + } + + p->dirty(QSGItemPrivate::Transform); +} + +void QSGTransform::prependToItem(QSGItem *item) +{ + Q_D(QSGTransform); + if (!item) + return; + + QSGItemPrivate *p = QSGItemPrivate::get(item); + + if (!d->items.isEmpty() && !p->transforms.isEmpty() && p->transforms.contains(this)) { + p->transforms.removeOne(this); + p->transforms.prepend(this); + } else { + p->transforms.prepend(this); + d->items.append(item); + } + + p->dirty(QSGItemPrivate::Transform); +} + +void QSGItemPrivate::transform_append(QDeclarativeListProperty<QSGTransform> *prop, QSGTransform *transform) +{ + if (!transform) + return; + + QSGItem *that = static_cast<QSGItem *>(prop->object); + transform->appendToItem(that); +} + +QSGTransform *QSGItemPrivate::transform_at(QDeclarativeListProperty<QSGTransform> *prop, int idx) +{ + QSGItem *that = static_cast<QSGItem *>(prop->object); + QSGItemPrivate *p = QSGItemPrivate::get(that); + + if (idx < 0 || idx >= p->transforms.count()) + return 0; + else + return p->transforms.at(idx); +} + +void QSGItemPrivate::transform_clear(QDeclarativeListProperty<QSGTransform> *prop) +{ + QSGItem *that = static_cast<QSGItem *>(prop->object); + QSGItemPrivate *p = QSGItemPrivate::get(that); + + for (int ii = 0; ii < p->transforms.count(); ++ii) { + QSGTransform *t = p->transforms.at(ii); + QSGTransformPrivate *tp = QSGTransformPrivate::get(t); + tp->items.removeOne(that); + } + + p->transforms.clear(); + + p->dirty(QSGItemPrivate::Transform); +} + +QSGAnchors *QSGItemPrivate::anchors() const +{ + if (!_anchors) { + Q_Q(const QSGItem); + _anchors = new QSGAnchors(const_cast<QSGItem *>(q)); + if (!componentComplete) + _anchors->classBegin(); + } + return _anchors; +} + +QSGItemPrivate::AnchorLines *QSGItemPrivate::anchorLines() const +{ + Q_Q(const QSGItem); + if (!_anchorLines) _anchorLines = + new AnchorLines(const_cast<QSGItem *>(q)); + return _anchorLines; +} + +void QSGItemPrivate::siblingOrderChanged() +{ + Q_Q(QSGItem); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::SiblingOrder) { + change.listener->itemSiblingOrderChanged(q); + } + } +} + +QDeclarativeListProperty<QObject> QSGItemPrivate::data() +{ + return QDeclarativeListProperty<QObject>(q_func(), 0, QSGItemPrivate::data_append, + QSGItemPrivate::data_count, + QSGItemPrivate::data_at, + QSGItemPrivate::data_clear); +} + +QRectF QSGItem::childrenRect() +{ + Q_D(QSGItem); + if (!d->_contents) { + d->_contents = new QSGContents(this); + if (d->componentComplete) + d->_contents->complete(); + } + return d->_contents->rectF(); +} + +QList<QSGItem *> QSGItem::childItems() const +{ + Q_D(const QSGItem); + return d->childItems; +} + +bool QSGItem::clip() const +{ + return flags() & ItemClipsChildrenToShape; +} + +void QSGItem::setClip(bool c) +{ + if (clip() == c) + return; + + setFlag(ItemClipsChildrenToShape, c); + + emit clipChanged(c); +} + +void QSGItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QSGItem); + + if (d->_anchors) + QSGAnchorsPrivate::get(d->_anchors)->updateMe(); + + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QSGItemPrivate::Geometry) + change.listener->itemGeometryChanged(this, newGeometry, oldGeometry); + } + + if (newGeometry.x() != oldGeometry.x()) + emit xChanged(); + if (newGeometry.y() != oldGeometry.y()) + emit yChanged(); + if (newGeometry.width() != oldGeometry.width()) + emit widthChanged(); + if (newGeometry.height() != oldGeometry.height()) + emit heightChanged(); +} + +/*! + Called by the rendering thread when it is time to sync the state of the QML objects with the + scene graph objects. The function should return the root of the scene graph subtree for + this item. \a oldNode is the node that was returned the last time the function was called. + + The main thread is blocked while this function is executed so it is safe to read + values from the QSGItem instance and other objects in the main thread. + + \warning This is the only function in which it is allowed to make use of scene graph + objects from the main thread. Use of scene graph objects outside this function will + result in race conditions and potential crashes. + */ + +QSGNode *QSGItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + delete oldNode; + return 0; +} + +QSGTransformNode *QSGItemPrivate::createTransformNode() +{ + return new QSGTransformNode; +} + +void QSGItem::updatePolish() +{ +} + +void QSGItemPrivate::removeItemChangeListener(QSGItemChangeListener *listener, ChangeTypes types) +{ + ChangeListener change(listener, types); + changeListeners.removeOne(change); +} + +void QSGItem::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QSGItem::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QSGItem::inputMethodEvent(QInputMethodEvent *event) +{ + event->ignore(); +} + +void QSGItem::focusInEvent(QFocusEvent *) +{ +} + +void QSGItem::focusOutEvent(QFocusEvent *) +{ +} + +void QSGItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + event->ignore(); +} + +void QSGItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + event->ignore(); +} + +void QSGItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + event->ignore(); +} + +void QSGItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + mousePressEvent(event); +} + +void QSGItem::mouseUngrabEvent() +{ + // XXX todo +} + +void QSGItem::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + event->ignore(); +} + +void QSGItem::touchEvent(QTouchEvent *event) +{ + event->ignore(); +} + +void QSGItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +void QSGItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +void QSGItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_UNUSED(event); +} + +bool QSGItem::childMouseEventFilter(QSGItem *, QEvent *) +{ + return false; +} + +Qt::InputMethodHints QSGItem::inputMethodHints() const +{ + Q_D(const QSGItem); + return d->imHints; +} + +void QSGItem::setInputMethodHints(Qt::InputMethodHints hints) +{ + Q_D(QSGItem); + d->imHints = hints; + + if (!d->canvas || d->canvas->activeFocusItem() != this) + return; + + QSGCanvasPrivate::get(d->canvas)->updateInputMethodData(); +#ifndef QT_NO_IM + if (d->canvas->hasFocus()) + if (QInputContext *inputContext = d->canvas->inputContext()) + inputContext->update(); +#endif +} + +void QSGItem::updateMicroFocus() +{ +#ifndef QT_NO_IM + Q_D(QSGItem); + if (d->canvas && d->canvas->hasFocus()) + if (QInputContext *inputContext = d->canvas->inputContext()) + inputContext->update(); +#endif +} + +QVariant QSGItem::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QSGItem); + QVariant v; + + if (d->keyHandler) + v = d->keyHandler->inputMethodQuery(query); + + return v; +} + +QSGAnchorLine QSGItemPrivate::left() const +{ + return anchorLines()->left; +} + +QSGAnchorLine QSGItemPrivate::right() const +{ + return anchorLines()->right; +} + +QSGAnchorLine QSGItemPrivate::horizontalCenter() const +{ + return anchorLines()->hCenter; +} + +QSGAnchorLine QSGItemPrivate::top() const +{ + return anchorLines()->top; +} + +QSGAnchorLine QSGItemPrivate::bottom() const +{ + return anchorLines()->bottom; +} + +QSGAnchorLine QSGItemPrivate::verticalCenter() const +{ + return anchorLines()->vCenter; +} + +QSGAnchorLine QSGItemPrivate::baseline() const +{ + return anchorLines()->baseline; +} + +qreal QSGItem::baselineOffset() const +{ + Q_D(const QSGItem); + if (!d->baselineOffset.isValid()) { + return 0.0; + } else + return d->baselineOffset; +} + +void QSGItem::setBaselineOffset(qreal offset) +{ + Q_D(QSGItem); + if (offset == d->baselineOffset) + return; + + d->baselineOffset = offset; + + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QSGItemPrivate::Geometry) { + QSGAnchorsPrivate *anchor = change.listener->anchorPrivate(); + if (anchor) + anchor->updateVerticalAnchors(); + } + } + emit baselineOffsetChanged(offset); +} + +void QSGItem::update() +{ + Q_D(QSGItem); + Q_ASSERT(flags() & ItemHasContents); + d->dirty(QSGItemPrivate::Content); +} + +void QSGItem::polish() +{ + Q_D(QSGItem); + if (!d->polishScheduled) { + d->polishScheduled = true; + if (d->canvas) + QSGCanvasPrivate::get(d->canvas)->itemsToPolish.insert(this); + } +} + +QScriptValue QSGItem::mapFromItem(const QScriptValue &item, qreal x, qreal y) const +{ + QScriptValue sv = QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(this))->newObject(); + QSGItem *itemObj = qobject_cast<QSGItem*>(item.toQObject()); + if (!itemObj && !item.isNull()) { + qmlInfo(this) << "mapFromItem() given argument \"" << item.toString() << "\" which is neither null nor an Item"; + return 0; + } + + // If QSGItem::mapFromItem() is called with 0, behaves the same as mapFromScene() + QPointF p = mapFromItem(itemObj, QPointF(x, y)); + sv.setProperty(QLatin1String("x"), p.x()); + sv.setProperty(QLatin1String("y"), p.y()); + return sv; +} + +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 + // invalid cases + if (ok) *ok = true; + + QTransform t = d->itemToCanvasTransform(); + if (other) t *= QSGItemPrivate::get(other)->canvasToItemTransform(); + + return t; +} + +QScriptValue QSGItem::mapToItem(const QScriptValue &item, qreal x, qreal y) const +{ + QScriptValue sv = QDeclarativeEnginePrivate::getScriptEngine(qmlEngine(this))->newObject(); + QSGItem *itemObj = qobject_cast<QSGItem*>(item.toQObject()); + if (!itemObj && !item.isNull()) { + qmlInfo(this) << "mapToItem() given argument \"" << item.toString() << "\" which is neither null nor an Item"; + return 0; + } + + // If QSGItem::mapToItem() is called with 0, behaves the same as mapToScene() + QPointF p = mapToItem(itemObj, QPointF(x, y)); + sv.setProperty(QLatin1String("x"), p.x()); + sv.setProperty(QLatin1String("y"), p.y()); + return sv; +} + +void QSGItem::forceActiveFocus() +{ + setFocus(true); + QSGItem *parent = parentItem(); + while (parent) { + if (parent->flags() & QSGItem::ItemIsFocusScope) { + parent->setFocus(true); + } + parent = parent->parentItem(); + } +} + +QSGItem *QSGItem::childAt(qreal x, qreal y) const +{ + // XXX todo - should this include transform etc.? + const QList<QSGItem *> children = childItems(); + for (int i = children.count()-1; i >= 0; --i) { + if (QSGItem *child = qobject_cast<QSGItem *>(children.at(i))) { + if (child->isVisible() && child->x() <= x + && child->x() + child->width() >= x + && child->y() <= y + && child->y() + child->height() >= y) + return child; + } + } + return 0; +} + +QDeclarativeListProperty<QObject> QSGItemPrivate::resources() +{ + return QDeclarativeListProperty<QObject>(q_func(), 0, QSGItemPrivate::resources_append, + QSGItemPrivate::resources_count, + QSGItemPrivate::resources_at, + QSGItemPrivate::resources_clear); +} + +QDeclarativeListProperty<QSGItem> QSGItemPrivate::children() +{ + return QDeclarativeListProperty<QSGItem>(q_func(), 0, QSGItemPrivate::children_append, + QSGItemPrivate::children_count, + QSGItemPrivate::children_at, + QSGItemPrivate::children_clear); + +} + +QDeclarativeListProperty<QDeclarativeState> QSGItemPrivate::states() +{ + return _states()->statesProperty(); +} + +QDeclarativeListProperty<QDeclarativeTransition> QSGItemPrivate::transitions() +{ + return _states()->transitionsProperty(); +} + +QString QSGItemPrivate::state() const +{ + if (!_stateGroup) + return QString(); + else + return _stateGroup->state(); +} + +void QSGItemPrivate::setState(const QString &state) +{ + _states()->setState(state); +} + +QDeclarativeListProperty<QSGTransform> QSGItem::transform() +{ + Q_D(QSGItem); + return QDeclarativeListProperty<QSGTransform>(this, 0, d->transform_append, d->transform_count, + d->transform_at, d->transform_clear); +} + +void QSGItem::classBegin() +{ + Q_D(QSGItem); + d->componentComplete = false; + if (d->_stateGroup) + d->_stateGroup->classBegin(); + if (d->_anchors) + d->_anchors->classBegin(); +} + +void QSGItem::componentComplete() +{ + Q_D(QSGItem); + d->componentComplete = true; + if (d->_stateGroup) + d->_stateGroup->componentComplete(); + if (d->_anchors) { + d->_anchors->componentComplete(); + QSGAnchorsPrivate::get(d->_anchors)->updateOnComplete(); + } + if (d->keyHandler) + d->keyHandler->componentComplete(); + if (d->_contents) + d->_contents->complete(); +} + +QDeclarativeStateGroup *QSGItemPrivate::_states() +{ + Q_Q(QSGItem); + if (!_stateGroup) { + _stateGroup = new QDeclarativeStateGroup; + if (!componentComplete) + _stateGroup->classBegin(); + QObject::connect(_stateGroup, SIGNAL(stateChanged(QString)), + q, SIGNAL(stateChanged(QString))); + } + + return _stateGroup; +} + +QSGItemPrivate::AnchorLines::AnchorLines(QSGItem *q) +{ + left.item = q; + left.anchorLine = QSGAnchorLine::Left; + right.item = q; + right.anchorLine = QSGAnchorLine::Right; + hCenter.item = q; + hCenter.anchorLine = QSGAnchorLine::HCenter; + top.item = q; + top.anchorLine = QSGAnchorLine::Top; + bottom.item = q; + bottom.anchorLine = QSGAnchorLine::Bottom; + vCenter.item = q; + vCenter.anchorLine = QSGAnchorLine::VCenter; + baseline.item = q; + baseline.anchorLine = QSGAnchorLine::Baseline; +} + +QPointF QSGItemPrivate::computeTransformOrigin() const +{ + switch(origin) { + default: + case QSGItem::TopLeft: + return QPointF(0, 0); + case QSGItem::Top: + return QPointF(width / 2., 0); + case QSGItem::TopRight: + return QPointF(width, 0); + case QSGItem::Left: + return QPointF(0, height / 2.); + case QSGItem::Center: + return QPointF(width / 2., height / 2.); + case QSGItem::Right: + return QPointF(width, height / 2.); + case QSGItem::BottomLeft: + return QPointF(0, height); + case QSGItem::Bottom: + return QPointF(width / 2., height); + case QSGItem::BottomRight: + return QPointF(width, height); + } +} + +void QSGItemPrivate::transformChanged() +{ +} + +void QSGItemPrivate::deliverKeyEvent(QKeyEvent *e) +{ + Q_Q(QSGItem); + + Q_ASSERT(e->isAccepted()); + if (keyHandler) { + if (e->type() == QEvent::KeyPress) + keyHandler->keyPressed(e, false); + else + keyHandler->keyReleased(e, false); + + if (e->isAccepted()) + return; + else + e->accept(); + } + + if (e->type() == QEvent::KeyPress) + q->keyPressEvent(e); + else + q->keyReleaseEvent(e); + + if (e->isAccepted()) + return; + + if (keyHandler) { + e->accept(); + + if (e->type() == QEvent::KeyPress) + keyHandler->keyPressed(e, true); + else + keyHandler->keyReleased(e, true); + } +} + +void QSGItemPrivate::deliverInputMethodEvent(QInputMethodEvent *e) +{ + Q_Q(QSGItem); + + Q_ASSERT(e->isAccepted()); + if (keyHandler) { + keyHandler->inputMethodEvent(e, false); + + if (e->isAccepted()) + return; + else + e->accept(); + } + + q->inputMethodEvent(e); + + if (e->isAccepted()) + return; + + if (keyHandler) { + e->accept(); + + keyHandler->inputMethodEvent(e, true); + } +} + +void QSGItemPrivate::deliverFocusEvent(QFocusEvent *e) +{ + Q_Q(QSGItem); + + if (e->type() == QEvent::FocusIn) { + q->focusInEvent(e); + } else { + q->focusOutEvent(e); + } +} + +void QSGItemPrivate::deliverMouseEvent(QGraphicsSceneMouseEvent *e) +{ + Q_Q(QSGItem); + + Q_ASSERT(e->isAccepted()); + + switch(e->type()) { + default: + Q_ASSERT(!"Unknown event type"); + case QEvent::GraphicsSceneMouseMove: + q->mouseMoveEvent(e); + break; + case QEvent::GraphicsSceneMousePress: + q->mousePressEvent(e); + break; + case QEvent::GraphicsSceneMouseRelease: + q->mouseReleaseEvent(e); + break; + case QEvent::GraphicsSceneMouseDoubleClick: + q->mouseDoubleClickEvent(e); + break; + } +} + +void QSGItemPrivate::deliverWheelEvent(QGraphicsSceneWheelEvent *e) +{ + Q_Q(QSGItem); + q->wheelEvent(e); +} + +void QSGItemPrivate::deliverTouchEvent(QTouchEvent *e) +{ + Q_Q(QSGItem); + q->touchEvent(e); +} + +void QSGItemPrivate::deliverHoverEvent(QGraphicsSceneHoverEvent *e) +{ + Q_Q(QSGItem); + switch(e->type()) { + default: + Q_ASSERT(!"Unknown event type"); + case QEvent::GraphicsSceneHoverEnter: + q->hoverEnterEvent(e); + break; + case QEvent::GraphicsSceneHoverLeave: + q->hoverLeaveEvent(e); + break; + case QEvent::GraphicsSceneHoverMove: + q->hoverMoveEvent(e); + break; + } +} + +void QSGItem::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_UNUSED(change); + Q_UNUSED(value); +} + +/*! \internal */ +// XXX todo - do we want/need this anymore? +QRectF QSGItem::boundingRect() const +{ + Q_D(const QSGItem); + return QRectF(0, 0, d->width, d->height); +} + +QSGItem::TransformOrigin QSGItem::transformOrigin() const +{ + Q_D(const QSGItem); + return d->origin; +} + +void QSGItem::setTransformOrigin(TransformOrigin origin) +{ + Q_D(QSGItem); + if (origin == d->origin) + return; + + d->origin = origin; + d->dirty(QSGItemPrivate::TransformOrigin); + + emit transformOriginChanged(d->origin); +} + +QPointF QSGItem::transformOriginPoint() const +{ + Q_D(const QSGItem); + return d->computeTransformOrigin(); +} + +qreal QSGItem::z() const +{ + Q_D(const QSGItem); + return d->z; +} + +void QSGItem::setZ(qreal v) +{ + Q_D(QSGItem); + if (d->z == v) + return; + + d->z = v; + + d->dirty(QSGItemPrivate::ZValue); + if (d->parentItem) + QSGItemPrivate::get(d->parentItem)->dirty(QSGItemPrivate::ChildrenStackingChanged); + + emit zChanged(); +} + +qreal QSGItem::rotation() const +{ + Q_D(const QSGItem); + return d->rotation; +} + +void QSGItem::setRotation(qreal r) +{ + Q_D(QSGItem); + if (d->rotation == r) + return; + + d->rotation = r; + + d->dirty(QSGItemPrivate::BasicTransform); + + d->itemChange(ItemRotationHasChanged, r); + + emit rotationChanged(); +} + +qreal QSGItem::scale() const +{ + Q_D(const QSGItem); + return d->scale; +} + +void QSGItem::setScale(qreal s) +{ + Q_D(QSGItem); + if (d->scale == s) + return; + + d->scale = s; + + d->dirty(QSGItemPrivate::BasicTransform); + + emit scaleChanged(); +} + +qreal QSGItem::opacity() const +{ + Q_D(const QSGItem); + return d->opacity; +} + +void QSGItem::setOpacity(qreal o) +{ + Q_D(QSGItem); + if (d->opacity == o) + return; + + d->opacity = o; + + d->dirty(QSGItemPrivate::OpacityValue); + + d->itemChange(ItemOpacityHasChanged, o); + + emit opacityChanged(); +} + +bool QSGItem::isVisible() const +{ + Q_D(const QSGItem); + return d->effectiveVisible; +} + +void QSGItem::setVisible(bool v) +{ + Q_D(QSGItem); + if (v == d->explicitVisible) + return; + + d->explicitVisible = v; + + d->setEffectiveVisibleRecur(d->calcEffectiveVisible()); +} + +bool QSGItem::isEnabled() const +{ + Q_D(const QSGItem); + return d->effectiveEnable; +} + +void QSGItem::setEnabled(bool e) +{ + Q_D(QSGItem); + if (e == d->explicitEnable) + return; + + d->explicitEnable = e; + + d->setEffectiveEnableRecur(d->calcEffectiveEnable()); +} + +bool QSGItemPrivate::calcEffectiveVisible() const +{ + // XXX todo - Should the effective visible of an element with no parent just be the current + // effective visible? This would prevent pointless re-processing in the case of an element + // moving to/from a no-parent situation, but it is different from what graphics view does. + return explicitVisible && (!parentItem || QSGItemPrivate::get(parentItem)->effectiveVisible); +} + +void QSGItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) +{ + Q_Q(QSGItem); + + if (newEffectiveVisible && !explicitVisible) { + // This item locally overrides visibility + return; + } + + if (newEffectiveVisible == effectiveVisible) { + // No change necessary + return; + } + + effectiveVisible = newEffectiveVisible; + dirty(Visible); + if (parentItem) QSGItemPrivate::get(parentItem)->dirty(ChildrenStackingChanged); + + if (canvas) { + QSGCanvasPrivate *canvasPriv = QSGCanvasPrivate::get(canvas); + if (canvasPriv->mouseGrabberItem == q) + q->ungrabMouse(); + } + + for (int ii = 0; ii < childItems.count(); ++ii) + QSGItemPrivate::get(childItems.at(ii))->setEffectiveVisibleRecur(newEffectiveVisible); + + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Visibility) + change.listener->itemVisibilityChanged(q); + } + + emit q->visibleChanged(); +} + +bool QSGItemPrivate::calcEffectiveEnable() const +{ + // XXX todo - Should the effective enable of an element with no parent just be the current + // effective enable? This would prevent pointless re-processing in the case of an element + // moving to/from a no-parent situation, but it is different from what graphics view does. + return explicitEnable && (!parentItem || QSGItemPrivate::get(parentItem)->effectiveEnable); +} + +void QSGItemPrivate::setEffectiveEnableRecur(bool newEffectiveEnable) +{ + Q_Q(QSGItem); + + // XXX todo - need to fixup focus + + if (newEffectiveEnable && !explicitEnable) { + // This item locally overrides enable + return; + } + + if (newEffectiveEnable == effectiveEnable) { + // No change necessary + return; + } + + effectiveEnable = newEffectiveEnable; + + if (canvas) { + QSGCanvasPrivate *canvasPriv = QSGCanvasPrivate::get(canvas); + if (canvasPriv->mouseGrabberItem == q) + q->ungrabMouse(); + } + + for (int ii = 0; ii < childItems.count(); ++ii) + QSGItemPrivate::get(childItems.at(ii))->setEffectiveEnableRecur(newEffectiveEnable); + + emit q->enabledChanged(); +} + +QString QSGItemPrivate::dirtyToString() const +{ +#define DIRTY_TO_STRING(value) if (dirtyAttributes & value) { \ + if (!rv.isEmpty()) \ + rv.append(QLatin1String("|")); \ + rv.append(QLatin1String(#value)); \ +} + +// QString rv = QLatin1String("0x") + QString::number(dirtyAttributes, 16); + QString rv; + + DIRTY_TO_STRING(TransformOrigin); + DIRTY_TO_STRING(Transform); + DIRTY_TO_STRING(BasicTransform); + DIRTY_TO_STRING(Position); + DIRTY_TO_STRING(Size); + DIRTY_TO_STRING(ZValue); + DIRTY_TO_STRING(Content); + DIRTY_TO_STRING(Smooth); + DIRTY_TO_STRING(OpacityValue); + DIRTY_TO_STRING(ChildrenChanged); + DIRTY_TO_STRING(ChildrenStackingChanged); + DIRTY_TO_STRING(ParentChanged); + DIRTY_TO_STRING(Clip); + DIRTY_TO_STRING(Canvas); + DIRTY_TO_STRING(EffectReference); + DIRTY_TO_STRING(Visible); + DIRTY_TO_STRING(HideReference); + + return rv; +} + +void QSGItemPrivate::dirty(DirtyType type) +{ + Q_Q(QSGItem); + if (type & (TransformOrigin | Transform | BasicTransform | Position | Size)) + transformChanged(); + + if (!(dirtyAttributes & type) || (canvas && !prevDirtyItem)) { + dirtyAttributes |= type; + if (canvas) { + addToDirtyList(); + QSGCanvasPrivate::get(canvas)->dirtyItem(q); + } + } +} + +void QSGItemPrivate::addToDirtyList() +{ + Q_Q(QSGItem); + + Q_ASSERT(canvas); + if (!prevDirtyItem) { + Q_ASSERT(!nextDirtyItem); + + QSGCanvasPrivate *p = QSGCanvasPrivate::get(canvas); + nextDirtyItem = p->dirtyItemList; + if (nextDirtyItem) QSGItemPrivate::get(nextDirtyItem)->prevDirtyItem = &nextDirtyItem; + prevDirtyItem = &p->dirtyItemList; + p->dirtyItemList = q; + p->dirtyItem(q); + } + Q_ASSERT(prevDirtyItem); +} + +void QSGItemPrivate::removeFromDirtyList() +{ + if (prevDirtyItem) { + if (nextDirtyItem) QSGItemPrivate::get(nextDirtyItem)->prevDirtyItem = prevDirtyItem; + *prevDirtyItem = nextDirtyItem; + prevDirtyItem = 0; + nextDirtyItem = 0; + } + Q_ASSERT(!prevDirtyItem); + Q_ASSERT(!nextDirtyItem); +} + +void QSGItemPrivate::refFromEffectItem(bool hide) +{ + ++effectRefCount; + if (1 == effectRefCount) { + dirty(EffectReference); + if (parentItem) QSGItemPrivate::get(parentItem)->dirty(ChildrenStackingChanged); + } + if (hide) { + if (++hideRefCount == 1) + dirty(HideReference); + } +} + +void QSGItemPrivate::derefFromEffectItem(bool unhide) +{ + Q_ASSERT(effectRefCount); + --effectRefCount; + if (0 == effectRefCount) { + dirty(EffectReference); + if (parentItem) QSGItemPrivate::get(parentItem)->dirty(ChildrenStackingChanged); + } + if (unhide) { + if (--hideRefCount == 0) + dirty(HideReference); + } +} + +void QSGItemPrivate::itemChange(QSGItem::ItemChange change, const QSGItem::ItemChangeData &data) +{ + Q_Q(QSGItem); + switch(change) { + case QSGItem::ItemChildAddedChange: + q->itemChange(change, data); + if (_contents && componentComplete) + _contents->childAdded(data.item); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Children) { + change.listener->itemChildAdded(q, data.item); + } + } + break; + case QSGItem::ItemChildRemovedChange: + q->itemChange(change, data); + if (_contents && componentComplete) + _contents->childRemoved(data.item); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Children) { + change.listener->itemChildRemoved(q, data.item); + } + } + break; + case QSGItem::ItemSceneChange: + q->itemChange(change, data); + break; + case QSGItem::ItemVisibleHasChanged: + q->itemChange(change, data); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Visibility) { + change.listener->itemVisibilityChanged(q); + } + } + break; + case QSGItem::ItemParentHasChanged: + q->itemChange(change, data); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Parent) { + change.listener->itemParentChanged(q, data.item); + } + } + break; + case QSGItem::ItemOpacityHasChanged: + q->itemChange(change, data); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Opacity) { + change.listener->itemOpacityChanged(q); + } + } + break; + case QSGItem::ItemActiveFocusHasChanged: + q->itemChange(change, data); + break; + case QSGItem::ItemRotationHasChanged: + q->itemChange(change, data); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QSGItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QSGItemPrivate::Rotation) { + change.listener->itemRotationChanged(q); + } + } + break; + } +} + +bool QSGItem::smooth() const +{ + Q_D(const QSGItem); + return d->smooth; +} + +void QSGItem::setSmooth(bool smooth) +{ + Q_D(QSGItem); + if (d->smooth == smooth) + return; + + d->smooth = smooth; + d->dirty(QSGItemPrivate::Smooth); + + emit smoothChanged(smooth); +} + +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)); + else + setFlags((Flags)(d->flags & ~(quint32)flag)); +} + +void QSGItem::setFlags(Flags flags) +{ + Q_D(QSGItem); + + if ((flags & ItemIsFocusScope) != (d->flags & ItemIsFocusScope)) { + if (flags & ItemIsFocusScope && !d->childItems.isEmpty() && d->canvas) { + qWarning("QSGItem: Cannot set FocusScope once item has children and is in a canvas."); + flags &= ~ItemIsFocusScope; + } else if (d->flags & ItemIsFocusScope) { + qWarning("QSGItem: Cannot unset FocusScope flag."); + flags |= ItemIsFocusScope; + } + } + + if ((flags & ItemClipsChildrenToShape ) != (d->flags & ItemClipsChildrenToShape)) + d->dirty(QSGItemPrivate::Clip); + + d->flags = flags; +} + +qreal QSGItem::x() const +{ + Q_D(const QSGItem); + return d->x; +} + +qreal QSGItem::y() const +{ + Q_D(const QSGItem); + return d->y; +} + +QPointF QSGItem::pos() const +{ + Q_D(const QSGItem); + return QPointF(d->x, d->y); +} + +void QSGItem::setX(qreal v) +{ + Q_D(QSGItem); + if (d->x == v) + return; + + qreal oldx = d->x; + d->x = v; + + d->dirty(QSGItemPrivate::Position); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(oldx, y(), width(), height())); +} + +void QSGItem::setY(qreal v) +{ + Q_D(QSGItem); + if (d->y == v) + return; + + qreal oldy = d->y; + d->y = v; + + d->dirty(QSGItemPrivate::Position); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), oldy, width(), height())); +} + +void QSGItem::setPos(const QPointF &pos) +{ + Q_D(QSGItem); + if (QPointF(d->x, d->y) == pos) + return; + + qreal oldx = d->x; + qreal oldy = d->y; + + d->x = pos.x(); + d->y = pos.y(); + + d->dirty(QSGItemPrivate::Position); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(oldx, oldy, width(), height())); +} + +qreal QSGItem::width() const +{ + Q_D(const QSGItem); + return d->width; +} + +void QSGItem::setWidth(qreal w) +{ + Q_D(QSGItem); + if (qIsNaN(w)) + return; + + d->widthValid = true; + if (d->width == w) + return; + + qreal oldWidth = d->width; + d->width = w; + + d->dirty(QSGItemPrivate::Size); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), oldWidth, height())); +} + +void QSGItem::resetWidth() +{ + Q_D(QSGItem); + d->widthValid = false; + setImplicitWidth(implicitWidth()); +} + +void QSGItemPrivate::implicitWidthChanged() +{ + Q_Q(QSGItem); + emit q->implicitWidthChanged(); +} + +qreal QSGItemPrivate::getImplicitWidth() const +{ + return implicitWidth; +} + +qreal QSGItem::implicitWidth() const +{ + Q_D(const QSGItem); + return d->getImplicitWidth(); +} + +void QSGItem::setImplicitWidth(qreal w) +{ + Q_D(QSGItem); + bool changed = w != d->implicitWidth; + d->implicitWidth = w; + if (d->width == w || widthValid()) { + if (changed) + d->implicitWidthChanged(); + return; + } + + qreal oldWidth = d->width; + d->width = w; + + d->dirty(QSGItemPrivate::Size); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), oldWidth, height())); + + if (changed) + d->implicitWidthChanged(); +} + +bool QSGItem::widthValid() const +{ + Q_D(const QSGItem); + return d->widthValid; +} + +qreal QSGItem::height() const +{ + Q_D(const QSGItem); + return d->height; +} + +void QSGItem::setHeight(qreal h) +{ + Q_D(QSGItem); + if (qIsNaN(h)) + return; + + d->heightValid = true; + if (d->height == h) + return; + + qreal oldHeight = d->height; + d->height = h; + + d->dirty(QSGItemPrivate::Size); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), width(), oldHeight)); +} + +void QSGItem::resetHeight() +{ + Q_D(QSGItem); + d->heightValid = false; + setImplicitHeight(implicitHeight()); +} + +void QSGItemPrivate::implicitHeightChanged() +{ + Q_Q(QSGItem); + emit q->implicitHeightChanged(); +} + +qreal QSGItemPrivate::getImplicitHeight() const +{ + return implicitHeight; +} + +qreal QSGItem::implicitHeight() const +{ + Q_D(const QSGItem); + return d->getImplicitHeight(); +} + +void QSGItem::setImplicitHeight(qreal h) +{ + Q_D(QSGItem); + bool changed = h != d->implicitHeight; + d->implicitHeight = h; + if (d->height == h || heightValid()) { + if (changed) + d->implicitHeightChanged(); + return; + } + + qreal oldHeight = d->height; + d->height = h; + + d->dirty(QSGItemPrivate::Size); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), width(), oldHeight)); + + if (changed) + d->implicitHeightChanged(); +} + +bool QSGItem::heightValid() const +{ + Q_D(const QSGItem); + return d->heightValid; +} + +void QSGItem::setSize(const QSizeF &size) +{ + Q_D(QSGItem); + d->heightValid = true; + d->widthValid = true; + + if (QSizeF(d->width, d->height) == size) + return; + + qreal oldHeight = d->height; + qreal oldWidth = d->width; + d->height = size.height(); + d->width = size.width(); + + d->dirty(QSGItemPrivate::Size); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), oldWidth, oldHeight)); +} + +bool QSGItem::hasActiveFocus() const +{ + Q_D(const QSGItem); + return d->activeFocus; +} + +bool QSGItem::hasFocus() const +{ + Q_D(const QSGItem); + return d->focus; +} + +void QSGItem::setFocus(bool focus) +{ + Q_D(QSGItem); + if (d->focus == focus) + return; + + if (d->canvas) { + // Need to find our nearest focus scope + QSGItem *scope = parentItem(); + while (scope && !scope->isFocusScope()) + scope = scope->parentItem(); + if (focus) + QSGCanvasPrivate::get(d->canvas)->setFocusInScope(scope, this); + else + QSGCanvasPrivate::get(d->canvas)->clearFocusInScope(scope, this); + } else { + d->focus = focus; + emit focusChanged(focus); + } +} + +bool QSGItem::isFocusScope() const +{ + return flags() & ItemIsFocusScope; +} + +QSGItem *QSGItem::scopedFocusItem() const +{ + Q_D(const QSGItem); + if (!isFocusScope()) + return 0; + else + return d->subFocusItem; +} + + +Qt::MouseButtons QSGItem::acceptedMouseButtons() const +{ + Q_D(const QSGItem); + return d->acceptedMouseButtons; +} + +void QSGItem::setAcceptedMouseButtons(Qt::MouseButtons buttons) +{ + Q_D(QSGItem); + d->acceptedMouseButtons = buttons; +} + +bool QSGItem::filtersChildMouseEvents() const +{ + Q_D(const QSGItem); + return d->filtersChildMouseEvents; +} + +void QSGItem::setFiltersChildMouseEvents(bool filter) +{ + Q_D(QSGItem); + d->filtersChildMouseEvents = filter; +} + +bool QSGItem::isUnderMouse() const +{ + Q_D(const QSGItem); + if (!d->canvas) + return false; + + QPoint cursorPos = QCursor::pos(); + if (QRectF(0, 0, width(), height()).contains(mapFromScene(d->canvas->mapFromGlobal(cursorPos)))) + return true; + return false; +} + +bool QSGItem::acceptHoverEvents() const +{ + Q_D(const QSGItem); + return d->hoverEnabled; +} + +void QSGItem::setAcceptHoverEvents(bool enabled) +{ + Q_D(QSGItem); + d->hoverEnabled = enabled; + + if (d->canvas && d->hoverEnabled && !d->canvas->hasMouseTracking()) + d->canvas->setMouseTracking(true); +} + +void QSGItem::grabMouse() +{ + Q_D(QSGItem); + if (!d->canvas) + return; + QSGCanvasPrivate *canvasPriv = QSGCanvasPrivate::get(d->canvas); + if (canvasPriv->mouseGrabberItem == this) + return; + + QSGItem *oldGrabber = canvasPriv->mouseGrabberItem; + canvasPriv->mouseGrabberItem = this; + if (oldGrabber) + oldGrabber->mouseUngrabEvent(); +} + +void QSGItem::ungrabMouse() +{ + Q_D(QSGItem); + if (!d->canvas) + return; + QSGCanvasPrivate *canvasPriv = QSGCanvasPrivate::get(d->canvas); + if (canvasPriv->mouseGrabberItem != this) { + qWarning("QSGItem::ungrabMouse(): Item is not the mouse grabber."); + return; + } + + canvasPriv->mouseGrabberItem = 0; + mouseUngrabEvent(); +} + +bool QSGItem::keepMouseGrab() const +{ + Q_D(const QSGItem); + return d->keepMouse; +} + +void QSGItem::setKeepMouseGrab(bool keep) +{ + Q_D(QSGItem); + d->keepMouse = keep; +} + +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 +{ + Q_D(const QSGItem); + return d->itemToCanvasTransform().map(point); +} + +QRectF QSGItem::mapRectToItem(const QSGItem *item, const QRectF &rect) const +{ + Q_D(const QSGItem); + QTransform t = d->itemToCanvasTransform(); + if (item) + t *= QSGItemPrivate::get(item)->canvasToItemTransform(); + return t.mapRect(rect); +} + +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 p = item?item->mapToScene(point):point; + return mapFromScene(p); +} + +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 +{ + Q_D(const QSGItem); + QTransform t = item?QSGItemPrivate::get(item)->itemToCanvasTransform():QTransform(); + t *= d->canvasToItemTransform(); + return t.mapRect(rect); +} + +QRectF QSGItem::mapRectFromScene(const QRectF &rect) const +{ + Q_D(const QSGItem); + return d->canvasToItemTransform().mapRect(rect); +} + +bool QSGItem::event(QEvent *ev) +{ + return QObject::event(ev); + +#if 0 + if (ev->type() == QEvent::PolishRequest) { + Q_D(QSGItem); + d->polishScheduled = false; + updatePolish(); + return true; + } else { + return QObject::event(ev); + } +#endif +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QSGItem *item) +{ + if (!item) { + debug << "QSGItem(0)"; + return debug; + } + + debug << item->metaObject()->className() << "(this =" << ((void*)item) + << ", name=" << item->objectName() + << ", parent =" << ((void*)item->parentItem()) + << ", geometry =" << QRectF(item->pos(), QSizeF(item->width(), item->height())) + << ", z =" << item->z() << ')'; + return debug; +} +#endif + +qint64 QSGItemPrivate::consistentTime = -1; +void QSGItemPrivate::setConsistentTime(qint64 t) +{ + consistentTime = t; +} + +class QElapsedTimerConsistentTimeHack +{ +public: + void start() { + t1 = QSGItemPrivate::consistentTime; + t2 = 0; + } + qint64 elapsed() { + return QSGItemPrivate::consistentTime - t1; + } + qint64 restart() { + qint64 val = QSGItemPrivate::consistentTime - t1; + t1 = QSGItemPrivate::consistentTime; + t2 = 0; + return val; + } + +private: + qint64 t1; + qint64 t2; +}; + +void QSGItemPrivate::start(QElapsedTimer &t) +{ + if (QSGItemPrivate::consistentTime == -1) + t.start(); + else + ((QElapsedTimerConsistentTimeHack*)&t)->start(); +} + +qint64 QSGItemPrivate::elapsed(QElapsedTimer &t) +{ + if (QSGItemPrivate::consistentTime == -1) + return t.elapsed(); + else + return ((QElapsedTimerConsistentTimeHack*)&t)->elapsed(); +} + +qint64 QSGItemPrivate::restart(QElapsedTimer &t) +{ + if (QSGItemPrivate::consistentTime == -1) + return t.restart(); + else + return ((QElapsedTimerConsistentTimeHack*)&t)->restart(); +} + +QT_END_NAMESPACE + +#include <moc_qsgitem.cpp> diff --git a/src/declarative/items/qsgitem.h b/src/declarative/items/qsgitem.h new file mode 100644 index 0000000000..564d819000 --- /dev/null +++ b/src/declarative/items/qsgitem.h @@ -0,0 +1,399 @@ +// Commit: 6f78a6080b84cc3ef96b73a4ff58d1b5a72f08f4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGITEM_H +#define QSGITEM_H + +#include <QtDeclarative/qdeclarative.h> +#include <QtDeclarative/qdeclarativecomponent.h> + +#include <QtCore/QObject> +#include <QtCore/QList> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qfont.h> +#include <QtGui/qaction.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGItem; +class QSGTransformPrivate; +class QSGTransform : public QObject +{ + Q_OBJECT +public: + QSGTransform(QObject *parent = 0); + ~QSGTransform(); + + void appendToItem(QSGItem *); + void prependToItem(QSGItem *); + + virtual void applyTo(QMatrix4x4 *matrix) const = 0; + +protected Q_SLOTS: + void update(); + +protected: + QSGTransform(QSGTransformPrivate &dd, QObject *parent); + +private: + Q_DECLARE_PRIVATE(QSGTransform); +}; + +class QDeclarativeState; +class QSGAnchorLine; +class QDeclarativeTransition; +class QSGKeyEvent; +class QSGAnchors; +class QSGItemPrivate; +class QSGCanvas; +class QSGEngine; +class QTouchEvent; +class QSGNode; +class QSGTransformNode; +class Q_DECLARATIVE_EXPORT QSGItem : public QObject, public QDeclarativeParserStatus +{ + Q_OBJECT + Q_INTERFACES(QDeclarativeParserStatus) + + Q_PROPERTY(QSGItem *parent READ parentItem WRITE setParentItem NOTIFY parentChanged DESIGNABLE false FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QDeclarativeListProperty<QObject> data READ data DESIGNABLE false) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QDeclarativeListProperty<QObject> resources READ resources DESIGNABLE false) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QDeclarativeListProperty<QSGItem> children READ children NOTIFY childrenChanged DESIGNABLE false) + + Q_PROPERTY(QPointF pos READ pos FINAL) + Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged FINAL) + Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged FINAL) + Q_PROPERTY(qreal z READ z WRITE setZ NOTIFY zChanged FINAL) + Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged RESET resetWidth FINAL) + Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY heightChanged RESET resetHeight FINAL) + + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged FINAL) + + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QDeclarativeListProperty<QDeclarativeState> states READ states DESIGNABLE false) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QDeclarativeListProperty<QDeclarativeTransition> transitions READ transitions DESIGNABLE false) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QString state READ state WRITE setState NOTIFY stateChanged) + Q_PROPERTY(QRectF childrenRect READ childrenRect NOTIFY childrenRectChanged DESIGNABLE false FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchors * anchors READ anchors DESIGNABLE false CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine left READ left CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine right READ right CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine horizontalCenter READ horizontalCenter CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine top READ top CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine bottom READ bottom CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine verticalCenter READ verticalCenter CONSTANT FINAL) + Q_PRIVATE_PROPERTY(QSGItem::d_func(), QSGAnchorLine baseline READ baseline CONSTANT FINAL) + Q_PROPERTY(qreal baselineOffset READ baselineOffset WRITE setBaselineOffset NOTIFY baselineOffsetChanged) + + Q_PROPERTY(bool clip READ clip WRITE setClip NOTIFY clipChanged) + + Q_PROPERTY(bool focus READ hasFocus WRITE setFocus NOTIFY focusChanged FINAL) + Q_PROPERTY(bool activeFocus READ hasActiveFocus NOTIFY activeFocusChanged FINAL) + + Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(qreal scale READ scale WRITE setScale NOTIFY scaleChanged) + Q_PROPERTY(TransformOrigin transformOrigin READ transformOrigin WRITE setTransformOrigin NOTIFY transformOriginChanged) + Q_PROPERTY(QPointF transformOriginPoint READ transformOriginPoint) // XXX todo - notify? + Q_PROPERTY(QDeclarativeListProperty<QSGTransform> transform READ transform DESIGNABLE false FINAL) + + Q_PROPERTY(bool smooth READ smooth WRITE setSmooth NOTIFY smoothChanged) + Q_PROPERTY(qreal implicitWidth READ implicitWidth WRITE setImplicitWidth NOTIFY implicitWidthChanged) + Q_PROPERTY(qreal implicitHeight READ implicitHeight WRITE setImplicitHeight NOTIFY implicitHeightChanged) + + Q_ENUMS(TransformOrigin) + Q_CLASSINFO("DefaultProperty", "data") + +public: + enum Flag { + ItemClipsChildrenToShape = 0x01, + ItemAcceptsInputMethod = 0x02, + ItemIsFocusScope = 0x04, + ItemHasContents = 0x08, + // Remember to increment the size of QSGItemPrivate::flags + }; + Q_DECLARE_FLAGS(Flags, Flag) + + enum ItemChange { + ItemChildAddedChange, // value.item + ItemChildRemovedChange, // value.item + ItemSceneChange, // value.canvas + ItemVisibleHasChanged, // value.realValue + ItemParentHasChanged, // value.item + ItemOpacityHasChanged, // value.realValue + ItemActiveFocusHasChanged, // value.boolValue + ItemRotationHasChanged, // value.realValue + }; + + union ItemChangeData { + ItemChangeData(QSGItem *v) : item(v) {} + ItemChangeData(QSGCanvas *v) : canvas(v) {} + ItemChangeData(qreal v) : realValue(v) {} + ItemChangeData(bool v) : boolValue(v) {} + + QSGItem *item; + QSGCanvas *canvas; + qreal realValue; + bool boolValue; + }; + + enum TransformOrigin { + TopLeft, Top, TopRight, + Left, Center, Right, + BottomLeft, Bottom, BottomRight + }; + + QSGItem(QSGItem *parent = 0); + virtual ~QSGItem(); + + QSGEngine *sceneGraphEngine() const; + + QSGCanvas *canvas() const; + QSGItem *parentItem() const; + void setParentItem(QSGItem *parent); + void stackBefore(const QSGItem *); + void stackAfter(const QSGItem *); + + QRectF childrenRect(); + QList<QSGItem *> childItems() const; + + bool clip() const; + void setClip(bool); + + qreal baselineOffset() const; + void setBaselineOffset(qreal); + + QDeclarativeListProperty<QSGTransform> transform(); + + qreal x() const; + qreal y() const; + QPointF pos() const; + void setX(qreal); + void setY(qreal); + void setPos(const QPointF &); + + qreal width() const; + void setWidth(qreal); + void resetWidth(); + qreal implicitWidth() const; + + qreal height() const; + void setHeight(qreal); + void resetHeight(); + qreal implicitHeight() const; + + void setSize(const QSizeF &size); + + TransformOrigin transformOrigin() const; + void setTransformOrigin(TransformOrigin); + QPointF transformOriginPoint() const; + + qreal z() const; + void setZ(qreal); + + qreal rotation() const; + void setRotation(qreal); + qreal scale() const; + void setScale(qreal); + + qreal opacity() const; + void setOpacity(qreal); + + bool isVisible() const; + void setVisible(bool); + + bool isEnabled() const; + void setEnabled(bool); + + bool smooth() const; + void setSmooth(bool); + + Flags flags() const; + void setFlag(Flag flag, bool enabled = true); + void setFlags(Flags flags); + + QRectF boundingRect() const; + + bool hasActiveFocus() const; + bool hasFocus() const; + void setFocus(bool); + bool isFocusScope() const; + QSGItem *scopedFocusItem() const; + + Qt::MouseButtons acceptedMouseButtons() const; + void setAcceptedMouseButtons(Qt::MouseButtons buttons); + bool acceptHoverEvents() const; + void setAcceptHoverEvents(bool enabled); + + bool isUnderMouse() const; + void grabMouse(); + void ungrabMouse(); + bool keepMouseGrab() const; + void setKeepMouseGrab(bool); + bool filtersChildMouseEvents() const; + void setFiltersChildMouseEvents(bool filter); + + QTransform itemTransform(QSGItem *, bool *) const; + QPointF mapToItem(const QSGItem *item, const QPointF &point) const; + QPointF mapToScene(const QPointF &point) const; + QRectF mapRectToItem(const QSGItem *item, const QRectF &rect) const; + QRectF mapRectToScene(const QRectF &rect) const; + QPointF mapFromItem(const QSGItem *item, const QPointF &point) const; + QPointF mapFromScene(const QPointF &point) const; + QRectF mapRectFromItem(const QSGItem *item, const QRectF &rect) const; + QRectF mapRectFromScene(const QRectF &rect) const; + + void polish(); + + Q_INVOKABLE QScriptValue mapFromItem(const QScriptValue &item, qreal x, qreal y) const; + Q_INVOKABLE QScriptValue mapToItem(const QScriptValue &item, qreal x, qreal y) const; + Q_INVOKABLE void forceActiveFocus(); + Q_INVOKABLE QSGItem *childAt(qreal x, qreal y) const; + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints(Qt::InputMethodHints hints); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + struct UpdatePaintNodeData { + QSGTransformNode *transformNode; + private: + friend class QSGCanvasPrivate; + UpdatePaintNodeData(); + }; + +public Q_SLOTS: + void update(); + void updateMicroFocus(); + +Q_SIGNALS: + void childrenRectChanged(const QRectF &); + void baselineOffsetChanged(qreal); + void stateChanged(const QString &); + void focusChanged(bool); + void activeFocusChanged(bool); + void parentChanged(QSGItem *); + void transformOriginChanged(TransformOrigin); + void smoothChanged(bool); + void clipChanged(bool); + + // XXX todo + void childrenChanged(); + void opacityChanged(); + void enabledChanged(); + void visibleChanged(); + void rotationChanged(); + void scaleChanged(); + + void xChanged(); + void yChanged(); + void widthChanged(); + void heightChanged(); + void zChanged(); + void implicitWidthChanged(); + void implicitHeightChanged(); + +protected: + virtual bool event(QEvent *); + + bool isComponentComplete() const; + virtual void itemChange(ItemChange, const ItemChangeData &); + + void setImplicitWidth(qreal); + bool widthValid() const; // ### better name? + void setImplicitHeight(qreal); + bool heightValid() const; // ### better name? + + virtual void classBegin(); + virtual void componentComplete(); + + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *); + virtual void focusInEvent(QFocusEvent *); + virtual void focusOutEvent(QFocusEvent *); + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseUngrabEvent(); // XXX todo - params? + virtual void wheelEvent(QGraphicsSceneWheelEvent *event); + virtual void touchEvent(QTouchEvent *event); + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual bool childMouseEventFilter(QSGItem *, QEvent *); + + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + virtual void updatePolish(); + +protected: + QSGItem(QSGItemPrivate &dd, QSGItem *parent = 0); + +private: + friend class QSGCanvas; + friend class QSGCanvasPrivate; + friend class QSGRenderer; + Q_DISABLE_COPY(QSGItem) + Q_DECLARE_PRIVATE(QSGItem) +}; + +// XXX todo +Q_DECLARE_OPERATORS_FOR_FLAGS(QSGItem::Flags) + +#ifndef QT_NO_DEBUG_STREAM +QDebug Q_DECLARATIVE_EXPORT operator<<(QDebug debug, QSGItem *item); +#endif + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGItem) +QML_DECLARE_TYPE(QSGTransform) + +QT_END_HEADER + +#endif // QSGITEM_H diff --git a/src/declarative/items/qsgitem_p.h b/src/declarative/items/qsgitem_p.h new file mode 100644 index 0000000000..c76eceb674 --- /dev/null +++ b/src/declarative/items/qsgitem_p.h @@ -0,0 +1,712 @@ +// Commit: 5c783d0a9a912816813945387903857a314040b5 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGITEM_P_H +#define QSGITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgitem.h" + +#include "qsganchors_p.h" +#include "qsganchors_p_p.h" +#include "qsgitemchangelistener_p.h" + +#include "qsgcanvas_p.h" + +#include "qsgnode.h" +#include "qsgclipnode_p.h" + +#include <private/qpodvector_p.h> +#include <private/qdeclarativestate_p.h> +#include <private/qdeclarativenullablevalue_p_p.h> +#include <private/qdeclarativenotifier_p.h> +#include <private/qdeclarativeglobal_p.h> + +#include <qdeclarative.h> +#include <qdeclarativecontext.h> + +#include <QtCore/qlist.h> +#include <QtCore/qdebug.h> +#include <QtCore/qelapsedtimer.h> + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QSGItemKeyFilter; +class QSGLayoutMirroringAttached; + +//### merge into private? +class QSGContents : public QObject, public QSGItemChangeListener +{ + Q_OBJECT +public: + QSGContents(QSGItem *item); + ~QSGContents(); + + QRectF rectF() const; + + void childRemoved(QSGItem *item); + void childAdded(QSGItem *item); + + void calcGeometry() { calcWidth(); calcHeight(); } + void complete(); + +Q_SIGNALS: + void rectChanged(QRectF); + +protected: + void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry); + void itemDestroyed(QSGItem *item); + //void itemVisibilityChanged(QSGItem *item) + +private: + void calcHeight(QSGItem *changed = 0); + void calcWidth(QSGItem *changed = 0); + + QSGItem *m_item; + qreal m_x; + qreal m_y; + qreal m_width; + qreal m_height; +}; + +class QSGTransformPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSGTransform); +public: + static QSGTransformPrivate* get(QSGTransform *transform) { return transform->d_func(); } + + QSGTransformPrivate(); + + QList<QSGItem *> items; +}; + +class Q_DECLARATIVE_EXPORT QSGItemPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSGItem) + +public: + static QSGItemPrivate* get(QSGItem *item) { return item->d_func(); } + static const QSGItemPrivate* get(const QSGItem *item) { return item->d_func(); } + + QSGItemPrivate(); + void init(QSGItem *parent); + + QDeclarativeListProperty<QObject> data(); + QDeclarativeListProperty<QObject> resources(); + QDeclarativeListProperty<QSGItem> children(); + + QDeclarativeListProperty<QDeclarativeState> states(); + QDeclarativeListProperty<QDeclarativeTransition> transitions(); + + QString state() const; + void setState(const QString &); + + QSGAnchorLine left() const; + QSGAnchorLine right() const; + QSGAnchorLine horizontalCenter() const; + QSGAnchorLine top() const; + QSGAnchorLine bottom() const; + QSGAnchorLine verticalCenter() const; + QSGAnchorLine baseline() const; + + // data property + static void data_append(QDeclarativeListProperty<QObject> *, QObject *); + static int data_count(QDeclarativeListProperty<QObject> *); + static QObject *data_at(QDeclarativeListProperty<QObject> *, int); + static void data_clear(QDeclarativeListProperty<QObject> *); + + // resources property + static QObject *resources_at(QDeclarativeListProperty<QObject> *, int); + static void resources_append(QDeclarativeListProperty<QObject> *, QObject *); + static int resources_count(QDeclarativeListProperty<QObject> *); + static void resources_clear(QDeclarativeListProperty<QObject> *); + + // children property + static void children_append(QDeclarativeListProperty<QSGItem> *, QSGItem *); + static int children_count(QDeclarativeListProperty<QSGItem> *); + static QSGItem *children_at(QDeclarativeListProperty<QSGItem> *, int); + static void children_clear(QDeclarativeListProperty<QSGItem> *); + + // transform property + static int transform_count(QDeclarativeListProperty<QSGTransform> *list); + static void transform_append(QDeclarativeListProperty<QSGTransform> *list, QSGTransform *); + static QSGTransform *transform_at(QDeclarativeListProperty<QSGTransform> *list, int); + static void transform_clear(QDeclarativeListProperty<QSGTransform> *list); + + QSGAnchors *anchors() const; + mutable QSGAnchors *_anchors; + QSGContents *_contents; + + QDeclarativeNullableValue<qreal> baselineOffset; + + struct AnchorLines { + AnchorLines(QSGItem *); + QSGAnchorLine left; + QSGAnchorLine right; + QSGAnchorLine hCenter; + QSGAnchorLine top; + QSGAnchorLine bottom; + QSGAnchorLine vCenter; + QSGAnchorLine baseline; + }; + mutable AnchorLines *_anchorLines; + AnchorLines *anchorLines() const; + + enum ChangeType { + Geometry = 0x01, + SiblingOrder = 0x02, + Visibility = 0x04, + Opacity = 0x08, + Destroyed = 0x10, + Parent = 0x20, + Children = 0x40, + Rotation = 0x80, + }; + + Q_DECLARE_FLAGS(ChangeTypes, ChangeType) + + struct ChangeListener { + ChangeListener(QSGItemChangeListener *l, QSGItemPrivate::ChangeTypes t) : listener(l), types(t) {} + QSGItemChangeListener *listener; + QSGItemPrivate::ChangeTypes types; + bool operator==(const ChangeListener &other) const { return listener == other.listener && types == other.types; } + }; + + void addItemChangeListener(QSGItemChangeListener *listener, ChangeTypes types) { + changeListeners.append(ChangeListener(listener, types)); + } + void removeItemChangeListener(QSGItemChangeListener *, ChangeTypes types); + QPODVector<ChangeListener,4> changeListeners; + + QDeclarativeStateGroup *_states(); + QDeclarativeStateGroup *_stateGroup; + + QSGItem::TransformOrigin origin:5; + quint32 flags:4; + bool widthValid:1; + bool heightValid:1; + bool componentComplete:1; + bool keepMouse:1; + bool hoverEnabled:1; + bool smooth:1; + bool focus:1; + bool activeFocus:1; + bool notifiedFocus:1; + bool notifiedActiveFocus:1; + bool filtersChildMouseEvents:1; + bool explicitVisible:1; + bool effectiveVisible:1; + bool explicitEnable:1; + bool effectiveEnable:1; + bool polishScheduled:1; + bool inheritedLayoutMirror:1; + bool effectiveLayoutMirror:1; + bool isMirrorImplicit:1; + bool inheritMirrorFromParent:1; + bool inheritMirrorFromItem:1; + bool childrenDoNotOverlap:1; + quint32 dummy:1; + + QSGCanvas *canvas; + QSGContext *sceneGraphContext() const { return static_cast<QSGCanvasPrivate *>(QObjectPrivate::get(canvas))->context; } + + QSGItem *parentItem; + QList<QSGItem *> childItems; + QList<QSGItem *> paintOrderChildItems() const; + void addChild(QSGItem *); + void removeChild(QSGItem *); + void siblingOrderChanged(); + + class InitializationState { + public: + QSGItem *getFocusScope(QSGItem *item); + void clear(); + void clear(QSGItem *focusScope); + private: + QSGItem *focusScope; + }; + void initCanvas(InitializationState *, QSGCanvas *); + + QSGItem *subFocusItem; + + QTransform canvasToItemTransform() const; + QTransform itemToCanvasTransform() const; + void itemToParentTransform(QTransform &) const; + + qreal x; + qreal y; + qreal width; + qreal height; + qreal implicitWidth; + qreal implicitHeight; + + qreal z; + qreal scale; + qreal rotation; + qreal opacity; + + QSGLayoutMirroringAttached* attachedLayoutDirection; + + Qt::MouseButtons acceptedMouseButtons; + Qt::InputMethodHints imHints; + + virtual qreal getImplicitWidth() const; + virtual qreal getImplicitHeight() const; + virtual void implicitWidthChanged(); + virtual void implicitHeightChanged(); + + void resolveLayoutMirror(); + void setImplicitLayoutMirror(bool mirror, bool inherit); + void setLayoutMirror(bool mirror); + bool isMirrored() const { + return effectiveLayoutMirror; + } + + QPointF computeTransformOrigin() const; + QList<QSGTransform *> transforms; + virtual void transformChanged(); + + QSGItemKeyFilter *keyHandler; + void deliverKeyEvent(QKeyEvent *); + void deliverInputMethodEvent(QInputMethodEvent *); + void deliverFocusEvent(QFocusEvent *); + void deliverMouseEvent(QGraphicsSceneMouseEvent *); + void deliverWheelEvent(QGraphicsSceneWheelEvent *); + void deliverTouchEvent(QTouchEvent *); + void deliverHoverEvent(QGraphicsSceneHoverEvent *); + + bool calcEffectiveVisible() const; + void setEffectiveVisibleRecur(bool); + bool calcEffectiveEnable() const; + void setEffectiveEnableRecur(bool); + + // XXX todo + enum DirtyType { + TransformOrigin = 0x00000001, + Transform = 0x00000002, + BasicTransform = 0x00000004, + Position = 0x00000008, + Size = 0x00000010, + + ZValue = 0x00000020, + Content = 0x00000040, + Smooth = 0x00000080, + OpacityValue = 0x00000100, + ChildrenChanged = 0x00000200, + ChildrenStackingChanged = 0x00000400, + ParentChanged = 0x00000800, + + Clip = 0x00001000, + Canvas = 0x00002000, + + EffectReference = 0x00008000, + Visible = 0x00010000, + HideReference = 0x00020000, + // When you add an attribute here, don't forget to update + // dirtyToString() + + TransformUpdateMask = TransformOrigin | Transform | BasicTransform | Position | Size | Canvas, + ComplexTransformUpdateMask = Transform | Canvas, + ContentUpdateMask = Size | Content | Smooth | Canvas, + ChildrenUpdateMask = ChildrenChanged | ChildrenStackingChanged | EffectReference | Canvas, + + }; + quint32 dirtyAttributes; + QString dirtyToString() const; + void dirty(DirtyType); + void addToDirtyList(); + void removeFromDirtyList(); + QSGItem *nextDirtyItem; + QSGItem**prevDirtyItem; + + inline QSGTransformNode *itemNode(); + inline QSGNode *childContainerNode(); + + /* + QSGNode order is: + - itemNode + - (opacityNode) + - (clipNode) + - (effectNode) + - groupNode + */ + + QSGTransformNode *itemNodeInstance; + QSGOpacityNode *opacityNode; + QSGDefaultClipNode *clipNode; + QSGRootNode *rootNode; + QSGNode *groupNode; + QSGNode *paintNode; + int paintNodeIndex; + + virtual QSGTransformNode *createTransformNode(); + + // A reference from an effect item means that this item is used by the effect, so + // it should insert a root node. + void refFromEffectItem(bool hide); + void derefFromEffectItem(bool unhide); + int effectRefCount; + int hideRefCount; + + void itemChange(QSGItem::ItemChange, const QSGItem::ItemChangeData &); + + virtual void mirrorChange() {} + + static qint64 consistentTime; + static void setConsistentTime(qint64 t); + static void start(QElapsedTimer &); + static qint64 elapsed(QElapsedTimer &); + static qint64 restart(QElapsedTimer &); +}; + +/* + Key filters can be installed on a QSGItem, but not removed. Currently they + are only used by attached objects (which are only destroyed on Item + destruction), so this isn't a problem. If in future this becomes any form + of public API, they will have to support removal too. +*/ +class QSGItemKeyFilter +{ +public: + QSGItemKeyFilter(QSGItem * = 0); + virtual ~QSGItemKeyFilter(); + + virtual void keyPressed(QKeyEvent *event, bool post); + virtual void keyReleased(QKeyEvent *event, bool post); + virtual void inputMethodEvent(QInputMethodEvent *event, bool post); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + virtual void componentComplete(); + + bool m_processPost; + +private: + QSGItemKeyFilter *m_next; +}; + +class QSGKeyNavigationAttachedPrivate : public QObjectPrivate +{ +public: + QSGKeyNavigationAttachedPrivate() + : QObjectPrivate(), + left(0), right(0), up(0), down(0), tab(0), backtab(0), + leftSet(false), rightSet(false), upSet(false), downSet(false), + tabSet(false), backtabSet(false) {} + + QSGItem *left; + QSGItem *right; + QSGItem *up; + QSGItem *down; + QSGItem *tab; + QSGItem *backtab; + bool leftSet : 1; + bool rightSet : 1; + bool upSet : 1; + bool downSet : 1; + bool tabSet : 1; + bool backtabSet : 1; +}; + +class QSGKeyNavigationAttached : public QObject, public QSGItemKeyFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGKeyNavigationAttached) + + Q_PROPERTY(QSGItem *left READ left WRITE setLeft NOTIFY leftChanged) + Q_PROPERTY(QSGItem *right READ right WRITE setRight NOTIFY rightChanged) + Q_PROPERTY(QSGItem *up READ up WRITE setUp NOTIFY upChanged) + Q_PROPERTY(QSGItem *down READ down WRITE setDown NOTIFY downChanged) + Q_PROPERTY(QSGItem *tab READ tab WRITE setTab NOTIFY tabChanged) + Q_PROPERTY(QSGItem *backtab READ backtab WRITE setBacktab NOTIFY backtabChanged) + Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) + + Q_ENUMS(Priority) + +public: + QSGKeyNavigationAttached(QObject * = 0); + + QSGItem *left() const; + void setLeft(QSGItem *); + QSGItem *right() const; + void setRight(QSGItem *); + QSGItem *up() const; + void setUp(QSGItem *); + QSGItem *down() const; + void setDown(QSGItem *); + QSGItem *tab() const; + void setTab(QSGItem *); + QSGItem *backtab() const; + void setBacktab(QSGItem *); + + enum Priority { BeforeItem, AfterItem }; + Priority priority() const; + void setPriority(Priority); + + static QSGKeyNavigationAttached *qmlAttachedProperties(QObject *); + +Q_SIGNALS: + void leftChanged(); + void rightChanged(); + void upChanged(); + void downChanged(); + void tabChanged(); + void backtabChanged(); + void priorityChanged(); + +private: + virtual void keyPressed(QKeyEvent *event, bool post); + virtual void keyReleased(QKeyEvent *event, bool post); + void setFocusNavigation(QSGItem *currentItem, const char *dir); +}; + +class QSGLayoutMirroringAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled RESET resetEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool childrenInherit READ childrenInherit WRITE setChildrenInherit NOTIFY childrenInheritChanged) + +public: + explicit QSGLayoutMirroringAttached(QObject *parent = 0); + + bool enabled() const; + void setEnabled(bool); + void resetEnabled(); + + bool childrenInherit() const; + void setChildrenInherit(bool); + + static QSGLayoutMirroringAttached *qmlAttachedProperties(QObject *); +Q_SIGNALS: + void enabledChanged(); + void childrenInheritChanged(); +private: + friend class QSGItemPrivate; + QSGItemPrivate *itemPrivate; +}; + +class QSGKeysAttachedPrivate : public QObjectPrivate +{ +public: + QSGKeysAttachedPrivate() + : QObjectPrivate(), inPress(false), inRelease(false) + , inIM(false), enabled(true), imeItem(0), item(0) + {} + + bool isConnected(const char *signalName); + + //loop detection + bool inPress:1; + bool inRelease:1; + bool inIM:1; + + bool enabled : 1; + + QSGItem *imeItem; + QList<QSGItem *> targets; + QSGItem *item; +}; + +class QSGKeysAttached : public QObject, public QSGItemKeyFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGKeysAttached) + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QDeclarativeListProperty<QSGItem> forwardTo READ forwardTo) + Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) + + Q_ENUMS(Priority) + +public: + QSGKeysAttached(QObject *parent=0); + ~QSGKeysAttached(); + + bool enabled() const { Q_D(const QSGKeysAttached); return d->enabled; } + void setEnabled(bool enabled) { + Q_D(QSGKeysAttached); + if (enabled != d->enabled) { + d->enabled = enabled; + emit enabledChanged(); + } + } + + enum Priority { BeforeItem, AfterItem}; + Priority priority() const; + void setPriority(Priority); + + QDeclarativeListProperty<QSGItem> forwardTo() { + Q_D(QSGKeysAttached); + return QDeclarativeListProperty<QSGItem>(this, d->targets); + } + + virtual void componentComplete(); + + static QSGKeysAttached *qmlAttachedProperties(QObject *); + +Q_SIGNALS: + void enabledChanged(); + void priorityChanged(); + void pressed(QSGKeyEvent *event); + void released(QSGKeyEvent *event); + void digit0Pressed(QSGKeyEvent *event); + void digit1Pressed(QSGKeyEvent *event); + void digit2Pressed(QSGKeyEvent *event); + void digit3Pressed(QSGKeyEvent *event); + void digit4Pressed(QSGKeyEvent *event); + void digit5Pressed(QSGKeyEvent *event); + void digit6Pressed(QSGKeyEvent *event); + void digit7Pressed(QSGKeyEvent *event); + void digit8Pressed(QSGKeyEvent *event); + void digit9Pressed(QSGKeyEvent *event); + + void leftPressed(QSGKeyEvent *event); + void rightPressed(QSGKeyEvent *event); + void upPressed(QSGKeyEvent *event); + void downPressed(QSGKeyEvent *event); + void tabPressed(QSGKeyEvent *event); + void backtabPressed(QSGKeyEvent *event); + + void asteriskPressed(QSGKeyEvent *event); + void numberSignPressed(QSGKeyEvent *event); + void escapePressed(QSGKeyEvent *event); + void returnPressed(QSGKeyEvent *event); + void enterPressed(QSGKeyEvent *event); + void deletePressed(QSGKeyEvent *event); + void spacePressed(QSGKeyEvent *event); + void backPressed(QSGKeyEvent *event); + void cancelPressed(QSGKeyEvent *event); + void selectPressed(QSGKeyEvent *event); + void yesPressed(QSGKeyEvent *event); + void noPressed(QSGKeyEvent *event); + void context1Pressed(QSGKeyEvent *event); + void context2Pressed(QSGKeyEvent *event); + void context3Pressed(QSGKeyEvent *event); + void context4Pressed(QSGKeyEvent *event); + void callPressed(QSGKeyEvent *event); + void hangupPressed(QSGKeyEvent *event); + void flipPressed(QSGKeyEvent *event); + void menuPressed(QSGKeyEvent *event); + void volumeUpPressed(QSGKeyEvent *event); + void volumeDownPressed(QSGKeyEvent *event); + +private: + virtual void keyPressed(QKeyEvent *event, bool post); + virtual void keyReleased(QKeyEvent *event, bool post); + virtual void inputMethodEvent(QInputMethodEvent *, bool post); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + + const QByteArray keyToSignal(int key) { + QByteArray keySignal; + if (key >= Qt::Key_0 && key <= Qt::Key_9) { + keySignal = "digit0Pressed"; + keySignal[5] = '0' + (key - Qt::Key_0); + } else { + int i = 0; + while (sigMap[i].key && sigMap[i].key != key) + ++i; + keySignal = sigMap[i].sig; + } + return keySignal; + } + + struct SigMap { + int key; + const char *sig; + }; + + static const SigMap sigMap[]; +}; + +QSGTransformNode *QSGItemPrivate::itemNode() +{ + if (!itemNodeInstance) { + itemNodeInstance = createTransformNode(); +#ifdef QML_RUNTIME_TESTING + Q_Q(QSGItem); + itemNodeInstance->description = QString::fromLatin1("QSGItem(%1)").arg(QString::fromLatin1(q->metaObject()->className())); +#endif + } + return itemNodeInstance; +} + +QSGNode *QSGItemPrivate::childContainerNode() +{ + if (!groupNode) { + groupNode = new QSGNode(); + if (rootNode) + rootNode->appendChildNode(groupNode); + else if (clipNode) + clipNode->appendChildNode(groupNode); + else if (opacityNode) + opacityNode->appendChildNode(groupNode); + else + itemNode()->appendChildNode(groupNode); + groupNode->setFlag(QSGNode::ChildrenDoNotOverlap, childrenDoNotOverlap); +#ifdef QML_RUNTIME_TESTING + groupNode->description = QLatin1String("group"); +#endif + } + return groupNode; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(QSGItemPrivate::ChangeTypes); + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGKeysAttached) +QML_DECLARE_TYPEINFO(QSGKeysAttached, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QSGKeyNavigationAttached) +QML_DECLARE_TYPEINFO(QSGKeyNavigationAttached, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QSGLayoutMirroringAttached) +QML_DECLARE_TYPEINFO(QSGLayoutMirroringAttached, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QSGITEM_P_H diff --git a/src/declarative/items/qsgitemchangelistener_p.h b/src/declarative/items/qsgitemchangelistener_p.h new file mode 100644 index 0000000000..3b4018a772 --- /dev/null +++ b/src/declarative/items/qsgitemchangelistener_p.h @@ -0,0 +1,82 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGITEMCHANGELISTENER_P_H +#define QSGITEMCHANGELISTENER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +class QRectF; +class QSGItem; +class QSGAnchorsPrivate; +class QSGItemChangeListener +{ +public: + virtual void itemGeometryChanged(QSGItem *, const QRectF &, const QRectF &) {} + virtual void itemSiblingOrderChanged(QSGItem *) {} + virtual void itemVisibilityChanged(QSGItem *) {} + virtual void itemOpacityChanged(QSGItem *) {} + virtual void itemDestroyed(QSGItem *) {} + virtual void itemChildAdded(QSGItem *, QSGItem *) {} + virtual void itemChildRemoved(QSGItem *, QSGItem *) {} + virtual void itemParentChanged(QSGItem *, QSGItem *) {} + virtual void itemRotationChanged(QSGItem *) {} + + virtual QSGAnchorsPrivate *anchorPrivate() { return 0; } +}; + +QT_END_NAMESPACE + +#endif // QSGITEMCHANGELISTENER_P_H diff --git a/src/declarative/items/qsgitemsmodule.cpp b/src/declarative/items/qsgitemsmodule.cpp new file mode 100644 index 0000000000..aa74ff6bc5 --- /dev/null +++ b/src/declarative/items/qsgitemsmodule.cpp @@ -0,0 +1,205 @@ +// Commit: 2c7cab4172f1acc86fd49345a2847417e162f2c3 +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgitemsmodule_p.h" + +#include "qsgitem.h" +#include "qsgitem_p.h" +#include "qsgevents_p_p.h" +#include "qsgrectangle_p.h" +#include "qsgfocusscope_p.h" +#include "qsgtext_p.h" +#include "qsgtextinput_p.h" +#include "qsgtextedit_p.h" +#include "qsgimage_p.h" +#include "qsgborderimage_p.h" +#include "qsgscalegrid_p_p.h" +#include "qsgmousearea_p.h" +#include "qsgpincharea_p.h" +#include "qsgflickable_p.h" +#include "qsgflickable_p_p.h" +#include "qsglistview_p.h" +#include "qsgvisualitemmodel_p.h" +#include "qsggridview_p.h" +#include "qsgpathview_p.h" +#include <private/qdeclarativepath_p.h> +#include "qsgpositioners_p.h" +#include "qsgrepeater_p.h" +#include "qsgloader_p.h" +#include "qsganimatedimage_p.h" +#include "qsgflipable_p.h" +#include "qsgtranslate_p.h" +#include "qsgstateoperations_p.h" +#include "qsganimation_p.h" +#include <private/qsgshadereffectitem_p.h> +#include <private/qsgshadereffectsource_p.h> +//#include "private/qsgpincharea_p.h" + +static QDeclarativePrivate::AutoParentResult qsgitem_autoParent(QObject *obj, QObject *parent) +{ + QSGItem *item = qobject_cast<QSGItem *>(obj); + if (!item) + return QDeclarativePrivate::IncompatibleObject; + + QSGItem *parentItem = qobject_cast<QSGItem *>(parent); + if (!parentItem) + return QDeclarativePrivate::IncompatibleParent; + + item->setParentItem(parentItem); + return QDeclarativePrivate::Parented; +} + +static void qt_sgitems_defineModule(const char *uri, int major, int minor) +{ + QDeclarativePrivate::RegisterAutoParent autoparent = { 0, &qsgitem_autoParent }; + QDeclarativePrivate::qmlregister(QDeclarativePrivate::AutoParentRegistration, &autoparent); + +#ifdef QT_NO_MOVIE + qmlRegisterTypeNotAvailable(uri,major,minor,"AnimatedImage", qApp->translate("QSGAnimatedImage","Qt was built without support for QMovie")); +#else + qmlRegisterType<QSGAnimatedImage>(uri,major,minor,"AnimatedImage"); +#endif + qmlRegisterType<QSGBorderImage>(uri,major,minor,"BorderImage"); + qmlRegisterType<QSGColumn>(uri,major,minor,"Column"); + qmlRegisterType<QSGDrag>(uri,major,minor,"Drag"); + qmlRegisterType<QSGFlickable>(uri,major,minor,"Flickable"); + qmlRegisterType<QSGFlipable>(uri,major,minor,"Flipable"); + qmlRegisterType<QSGFlow>(uri,major,minor,"Flow"); +// qmlRegisterType<QDeclarativeFocusPanel>(uri,major,minor,"FocusPanel"); + qmlRegisterType<QSGFocusScope>(uri,major,minor,"FocusScope"); + qmlRegisterType<QSGGradient>(uri,major,minor,"Gradient"); + qmlRegisterType<QSGGradientStop>(uri,major,minor,"GradientStop"); + qmlRegisterType<QSGGrid>(uri,major,minor,"Grid"); + qmlRegisterType<QSGGridView>(uri,major,minor,"GridView"); + qmlRegisterType<QSGImage>(uri,major,minor,"Image"); + qmlRegisterType<QSGItem>(uri,major,minor,"Item"); + qmlRegisterType<QSGListView>(uri,major,minor,"ListView"); + qmlRegisterType<QSGLoader>(uri,major,minor,"Loader"); + qmlRegisterType<QSGMouseArea>(uri,major,minor,"MouseArea"); + qmlRegisterType<QDeclarativePath>(uri,major,minor,"Path"); + qmlRegisterType<QDeclarativePathAttribute>(uri,major,minor,"PathAttribute"); + qmlRegisterType<QDeclarativePathCubic>(uri,major,minor,"PathCubic"); + qmlRegisterType<QDeclarativePathLine>(uri,major,minor,"PathLine"); + qmlRegisterType<QDeclarativePathPercent>(uri,major,minor,"PathPercent"); + qmlRegisterType<QDeclarativePathQuad>(uri,major,minor,"PathQuad"); + qmlRegisterType<QSGPathView>(uri,major,minor,"PathView"); +#ifndef QT_NO_VALIDATOR + qmlRegisterType<QIntValidator>(uri,major,minor,"IntValidator"); + qmlRegisterType<QDoubleValidator>(uri,major,minor,"DoubleValidator"); + qmlRegisterType<QRegExpValidator>(uri,major,minor,"RegExpValidator"); +#endif + qmlRegisterType<QSGRectangle>(uri,major,minor,"Rectangle"); + qmlRegisterType<QSGRepeater>(uri,major,minor,"Repeater"); + qmlRegisterType<QSGRow>(uri,major,minor,"Row"); + qmlRegisterType<QSGTranslate>(uri,major,minor,"Translate"); + qmlRegisterType<QSGRotation>(uri,major,minor,"Rotation"); + qmlRegisterType<QSGScale>(uri,major,minor,"Scale"); + qmlRegisterType<QSGText>(uri,major,minor,"Text"); + qmlRegisterType<QSGTextEdit>(uri,major,minor,"TextEdit"); + qmlRegisterType<QSGTextInput>(uri,major,minor,"TextInput"); + qmlRegisterType<QSGViewSection>(uri,major,minor,"ViewSection"); + qmlRegisterType<QSGVisualDataModel>(uri,major,minor,"VisualDataModel"); + qmlRegisterType<QSGVisualItemModel>(uri,major,minor,"VisualItemModel"); + + qmlRegisterType<QSGAnchors>(); + qmlRegisterType<QSGKeyEvent>(); + qmlRegisterType<QSGMouseEvent>(); + qmlRegisterType<QSGTransform>(); + qmlRegisterType<QDeclarativePathElement>(); + qmlRegisterType<QDeclarativeCurve>(); + qmlRegisterType<QSGScaleGrid>(); +#ifndef QT_NO_VALIDATOR + qmlRegisterType<QValidator>(); +#endif + qmlRegisterType<QSGVisualModel>(); +#ifndef QT_NO_ACTION + qmlRegisterType<QAction>(); +#endif + qmlRegisterType<QSGPen>(); + qmlRegisterType<QSGFlickableVisibleArea>(); + qRegisterMetaType<QSGAnchorLine>("QSGAnchorLine"); + + qmlRegisterUncreatableType<QSGKeyNavigationAttached>(uri,major,minor,"KeyNavigation",QSGKeyNavigationAttached::tr("KeyNavigation is only available via attached properties")); + qmlRegisterUncreatableType<QSGKeysAttached>(uri,major,minor,"Keys",QSGKeysAttached::tr("Keys is only available via attached properties")); + qmlRegisterUncreatableType<QSGLayoutMirroringAttached>(uri,major,minor,"LayoutMirroring", QSGLayoutMirroringAttached::tr("LayoutMirroring is only available via attached properties")); + + qmlRegisterType<QSGPinchArea>(uri,major,minor,"PinchArea"); + qmlRegisterType<QSGPinch>(uri,major,minor,"Pinch"); + qmlRegisterType<QSGPinchEvent>(); + + qmlRegisterType<QSGShaderEffectItem>("QtQuick", 2, 0, "ShaderEffectItem"); + qmlRegisterType<QSGShaderEffectSource>("QtQuick", 2, 0, "ShaderEffectSource"); + qmlRegisterUncreatableType<QSGShaderEffectMesh>("QtQuick", 2, 0, "ShaderEffectMesh", QSGShaderEffectMesh::tr("Cannot create instance of abstract class ShaderEffectMesh.")); + qmlRegisterType<QSGGridMesh>("QtQuick", 2, 0, "GridMesh"); + + qmlRegisterUncreatableType<QSGPaintedItem>("QtQuick", 2, 0, "PaintedItem", QSGPaintedItem::tr("Cannot create instance of abstract class PaintedItem")); + + qmlRegisterType<QSGParentChange>(uri, major, minor,"ParentChange"); + qmlRegisterType<QSGAnchorChanges>(uri, major, minor,"AnchorChanges"); + qmlRegisterType<QSGAnchorSet>(); + qmlRegisterType<QSGAnchorAnimation>(uri, major, minor,"AnchorAnimation"); + qmlRegisterType<QSGParentAnimation>(uri, major, minor,"ParentAnimation"); +} + +void QSGItemsModule::defineModule() +{ + static bool initialized = false; + if (initialized) + return; + initialized = true; + + // XXX todo - Remove before final integration... + QByteArray mode = qgetenv("QMLSCENE_IMPORT_NAME"); + QByteArray name = "QtQuick"; + int majorVersion = 2; + int minorVersion = 0; + if (mode == "quick1") { + majorVersion = 1; + } else if (mode == "qt") { + name = "Qt"; + majorVersion = 4; + minorVersion = 7; + } + + qt_sgitems_defineModule(name, majorVersion, minorVersion); +} + diff --git a/src/declarative/items/qsgitemsmodule_p.h b/src/declarative/items/qsgitemsmodule_p.h new file mode 100644 index 0000000000..2d8a971c22 --- /dev/null +++ b/src/declarative/items/qsgitemsmodule_p.h @@ -0,0 +1,65 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGITEMSMODULE_P_H +#define QSGITEMSMODULE_P_H + +#include <qdeclarative.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGItemsModule +{ +public: + static void defineModule(); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSGITEMSMODULE_P_H + diff --git a/src/declarative/items/qsglistview.cpp b/src/declarative/items/qsglistview.cpp new file mode 100644 index 0000000000..3bc9026f8b --- /dev/null +++ b/src/declarative/items/qsglistview.cpp @@ -0,0 +1,3057 @@ +// Commit: cce89db1e2555cbca8fc28072e1c6dd737cec6c4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsglistview_p.h" +#include "qsgflickable_p_p.h" +#include "qsgvisualitemmodel_p.h" + +#include <QtDeclarative/qdeclarativeexpression.h> +#include <QtDeclarative/qdeclarativeengine.h> +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qevent.h> +#include <QtCore/qmath.h> +#include <QtCore/qcoreapplication.h> + +#include <private/qdeclarativesmoothedanimation_p_p.h> +#include <private/qlistmodelinterface_p.h> + +QT_BEGIN_NAMESPACE + +void QSGViewSection::setProperty(const QString &property) +{ + if (property != m_property) { + m_property = property; + emit propertyChanged(); + } +} + +void QSGViewSection::setCriteria(QSGViewSection::SectionCriteria criteria) +{ + if (criteria != m_criteria) { + m_criteria = criteria; + emit criteriaChanged(); + } +} + +void QSGViewSection::setDelegate(QDeclarativeComponent *delegate) +{ + if (delegate != m_delegate) { + m_delegate = delegate; + emit delegateChanged(); + } +} + +QString QSGViewSection::sectionString(const QString &value) +{ + if (m_criteria == FirstCharacter) + return value.isEmpty() ? QString() : value.at(0); + else + return value; +} + +//---------------------------------------------------------------------------- + +class FxListItemSG +{ +public: + FxListItemSG(QSGItem *i, QSGListView *v) : item(i), section(0), view(v) { + attached = static_cast<QSGListViewAttached*>(qmlAttachedPropertiesObject<QSGListView>(item)); + if (attached) + attached->setView(view); + } + ~FxListItemSG() {} + qreal position() const { + if (section) { + if (view->orientation() == QSGListView::Vertical) + return section->y(); + else + return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -section->width()-section->x() : section->x()); + } else { + return itemPosition(); + } + } + qreal itemPosition() const { + if (view->orientation() == QSGListView::Vertical) + return item->y(); + else + return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -item->width()-item->x() : item->x()); + } + qreal size() const { + if (section) + return (view->orientation() == QSGListView::Vertical ? item->height()+section->height() : item->width()+section->width()); + else + return (view->orientation() == QSGListView::Vertical ? item->height() : item->width()); + } + qreal itemSize() const { + return (view->orientation() == QSGListView::Vertical ? item->height() : item->width()); + } + qreal sectionSize() const { + if (section) + return (view->orientation() == QSGListView::Vertical ? section->height() : section->width()); + return 0.0; + } + qreal endPosition() const { + if (view->orientation() == QSGListView::Vertical) { + return item->y() + (item->height() >= 1.0 ? item->height() : 1) - 1; + } else { + return (view->effectiveLayoutDirection() == Qt::RightToLeft + ? -item->width()-item->x() + (item->width() >= 1.0 ? item->width() : 1) + : item->x() + (item->width() >= 1.0 ? item->width() : 1)) - 1; + } + } + void setPosition(qreal pos) { + if (view->orientation() == QSGListView::Vertical) { + if (section) { + section->setY(pos); + pos += section->height(); + } + item->setY(pos); + } else { + if (view->effectiveLayoutDirection() == Qt::RightToLeft) { + if (section) { + section->setX(-section->width()-pos); + pos += section->width(); + } + item->setX(-item->width()-pos); + } else { + if (section) { + section->setX(pos); + pos += section->width(); + } + item->setX(pos); + } + } + } + void setSize(qreal size) { + if (view->orientation() == QSGListView::Vertical) + item->setHeight(size); + else + item->setWidth(size); + } + bool contains(qreal x, qreal y) const { + return (x >= item->x() && x < item->x() + item->width() && + y >= item->y() && y < item->y() + item->height()); + } + + QSGItem *item; + QSGItem *section; + QSGListView *view; + QSGListViewAttached *attached; + int index; +}; + +//---------------------------------------------------------------------------- + +class QSGListViewPrivate : public QSGFlickablePrivate +{ + Q_DECLARE_PUBLIC(QSGListView) + +public: + QSGListViewPrivate() + : currentItem(0), orient(QSGListView::Vertical), layoutDirection(Qt::LeftToRight) + , visiblePos(0), visibleIndex(0) + , averageSize(100.0), currentIndex(-1), requestedIndex(-1) + , itemCount(0), highlightRangeStart(0), highlightRangeEnd(0) + , highlightComponent(0), highlight(0), trackedItem(0) + , moveReason(Other), buffer(0), highlightPosAnimator(0), highlightSizeAnimator(0) + , sectionCriteria(0), spacing(0.0) + , highlightMoveSpeed(400), highlightMoveDuration(-1) + , highlightResizeSpeed(400), highlightResizeDuration(-1), highlightRange(QSGListView::NoHighlightRange) + , snapMode(QSGListView::NoSnap), overshootDist(0.0) + , footerComponent(0), footer(0), headerComponent(0), header(0) + , bufferMode(BufferBefore | BufferAfter) + , ownModel(false), wrap(false), autoHighlight(true), haveHighlightRange(false) + , correctFlick(false), inFlickCorrection(false), lazyRelease(false) + , deferredRelease(false), layoutScheduled(false), currentIndexCleared(false) + , inViewportMoved(false) + , highlightRangeStartValid(false), highlightRangeEndValid(false) + , minExtentDirty(true), maxExtentDirty(true) + {} + + void init(); + void clear(); + FxListItemSG *createItem(int modelIndex); + void releaseItem(FxListItemSG *item); + + FxListItemSG *visibleItem(int modelIndex) const { + if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) { + for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) { + FxListItemSG *item = visibleItems.at(i); + if (item->index == modelIndex) + return item; + } + } + return 0; + } + + FxListItemSG *firstVisibleItem() const { + const qreal pos = isRightToLeft() ? -position()-size() : position(); + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItemSG *item = visibleItems.at(i); + if (item->index != -1 && item->endPosition() > pos) + return item; + } + return visibleItems.count() ? visibleItems.first() : 0; + } + + FxListItemSG *nextVisibleItem() const { + const qreal pos = isRightToLeft() ? -position()-size() : position(); + bool foundFirst = false; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItemSG *item = visibleItems.at(i); + if (item->index != -1) { + if (foundFirst) + return item; + else if (item->position() < pos && item->endPosition() > pos) + foundFirst = true; + } + } + return 0; + } + + // Returns the item before modelIndex, if created. + // May return an item marked for removal. + FxListItemSG *itemBefore(int modelIndex) const { + if (modelIndex < visibleIndex) + return 0; + int idx = 1; + int lastIndex = -1; + while (idx < visibleItems.count()) { + FxListItemSG *item = visibleItems.at(idx); + if (item->index != -1) + lastIndex = item->index; + if (item->index == modelIndex) + return visibleItems.at(idx-1); + ++idx; + } + if (lastIndex == modelIndex-1) + return visibleItems.last(); + return 0; + } + + void regenerate() { + Q_Q(QSGListView); + if (q->isComponentComplete()) { + if (header) { + // XXX todo - the original did scene()->removeItem(). Why? + header->item->setParentItem(0); + header->item->deleteLater(); + delete header; + header = 0; + } + if (footer) { + // XXX todo - the original did scene()->removeItem(). Why? + footer->item->setParentItem(0); + footer->item->deleteLater(); + delete footer; + footer = 0; + } + updateHeader(); + updateFooter(); + clear(); + setPosition(0); + q->refill(); + updateCurrent(currentIndex); + } + } + + void mirrorChange() { + Q_Q(QSGListView); + regenerate(); + emit q->effectiveLayoutDirectionChanged(); + } + + bool isRightToLeft() const { + Q_Q(const QSGListView); + return orient == QSGListView::Horizontal && q->effectiveLayoutDirection() == Qt::RightToLeft; + } + + qreal position() const { + Q_Q(const QSGListView); + return orient == QSGListView::Vertical ? q->contentY() : q->contentX(); + } + void setPosition(qreal pos) { + Q_Q(QSGListView); + if (orient == QSGListView::Vertical) { + q->QSGFlickable::setContentY(pos); + } else { + if (isRightToLeft()) + q->QSGFlickable::setContentX(-pos-size()); + else + q->QSGFlickable::setContentX(pos); + } + } + qreal size() const { + Q_Q(const QSGListView); + return orient == QSGListView::Vertical ? q->height() : q->width(); + } + + qreal originPosition() const { + qreal pos = 0; + if (!visibleItems.isEmpty()) { + pos = (*visibleItems.constBegin())->position(); + if (visibleIndex > 0) + pos -= visibleIndex * (averageSize + spacing); + } + return pos; + } + + qreal lastPosition() const { + qreal pos = 0; + if (!visibleItems.isEmpty()) { + int invisibleCount = visibleItems.count() - visibleIndex; + for (int i = visibleItems.count()-1; i >= 0; --i) { + if (visibleItems.at(i)->index != -1) { + invisibleCount = model->count() - visibleItems.at(i)->index - 1; + break; + } + } + pos = (*(--visibleItems.constEnd()))->endPosition() + invisibleCount * (averageSize + spacing); + } else if (model && model->count()) { + pos = model->count() * averageSize + (model->count()-1) * spacing; + } + return pos; + } + + qreal startPosition() const { + return isRightToLeft() ? -lastPosition()-1 : originPosition(); + } + + qreal endPosition() const { + return isRightToLeft() ? -originPosition()-1 : lastPosition(); + } + + qreal positionAt(int modelIndex) const { + if (FxListItemSG *item = visibleItem(modelIndex)) + return item->position(); + if (!visibleItems.isEmpty()) { + if (modelIndex < visibleIndex) { + int count = visibleIndex - modelIndex; + qreal cs = 0; + if (modelIndex == currentIndex && currentItem) { + cs = currentItem->size() + spacing; + --count; + } + return (*visibleItems.constBegin())->position() - count * (averageSize + spacing) - cs; + } else { + int idx = visibleItems.count() - 1; + while (idx >= 0 && visibleItems.at(idx)->index == -1) + --idx; + if (idx < 0) + idx = visibleIndex; + else + idx = visibleItems.at(idx)->index; + int count = modelIndex - idx - 1; + return (*(--visibleItems.constEnd()))->endPosition() + spacing + count * (averageSize + spacing) + 1; + } + } + return 0; + } + + qreal endPositionAt(int modelIndex) const { + if (FxListItemSG *item = visibleItem(modelIndex)) + return item->endPosition(); + if (!visibleItems.isEmpty()) { + if (modelIndex < visibleIndex) { + int count = visibleIndex - modelIndex; + return (*visibleItems.constBegin())->position() - (count - 1) * (averageSize + spacing) - spacing - 1; + } else { + int idx = visibleItems.count() - 1; + while (idx >= 0 && visibleItems.at(idx)->index == -1) + --idx; + if (idx < 0) + idx = visibleIndex; + else + idx = visibleItems.at(idx)->index; + int count = modelIndex - idx - 1; + return (*(--visibleItems.constEnd()))->endPosition() + count * (averageSize + spacing); + } + } + return 0; + } + + QString sectionAt(int modelIndex) { + if (FxListItemSG *item = visibleItem(modelIndex)) + return item->attached->section(); + + QString section; + if (sectionCriteria) { + QString propValue = model->stringValue(modelIndex, sectionCriteria->property()); + section = sectionCriteria->sectionString(propValue); + } + + return section; + } + + bool isValid() const { + return model && model->count() && model->isValid(); + } + + qreal snapPosAt(qreal pos) { + if (FxListItemSG *snapItem = snapItemAt(pos)) + return snapItem->position(); + if (visibleItems.count()) { + qreal firstPos = visibleItems.first()->position(); + qreal endPos = visibleItems.last()->position(); + if (pos < firstPos) { + return firstPos - qRound((firstPos - pos) / averageSize) * averageSize; + } else if (pos > endPos) + return endPos + qRound((pos - endPos) / averageSize) * averageSize; + } + return qRound((pos - originPosition()) / averageSize) * averageSize + originPosition(); + } + + FxListItemSG *snapItemAt(qreal pos) { + FxListItemSG *snapItem = 0; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItemSG *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if (highlight && itemTop >= pos && item->endPosition() <= pos + highlight->size() - 1) + return item; + if (itemTop+item->size()/2 >= pos && itemTop-item->size()/2 < pos) + snapItem = item; + } + return snapItem; + } + + int lastVisibleIndex() const { + int lastIndex = -1; + for (int i = visibleItems.count()-1; i >= 0; --i) { + FxListItemSG *listItem = visibleItems.at(i); + if (listItem->index != -1) { + lastIndex = listItem->index; + break; + } + } + return lastIndex; + } + + // map a model index to visibleItems index. + int mapFromModel(int modelIndex) const { + if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.count()) + return -1; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItemSG *listItem = visibleItems.at(i); + if (listItem->index == modelIndex) + return i; + if (listItem->index > modelIndex) + return -1; + } + return -1; // Not in visibleList + } + + void updateViewport() { + Q_Q(QSGListView); + if (orient == QSGListView::Vertical) { + q->setContentHeight(endPosition() - startPosition() + 1); + } else { + q->setContentWidth(endPosition() - startPosition() + 1); + } + } + + void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) { + Q_Q(QSGListView); + QSGFlickablePrivate::itemGeometryChanged(item, newGeometry, oldGeometry); + if (!q->isComponentComplete()) + return; + if (item != contentItem && (!highlight || item != highlight->item)) { + if ((orient == QSGListView::Vertical && newGeometry.height() != oldGeometry.height()) + || (orient == QSGListView::Horizontal && newGeometry.width() != oldGeometry.width())) { + scheduleLayout(); + } + } + if ((header && header->item == item) || (footer && footer->item == item)) { + if (header) + updateHeader(); + if (footer) + updateFooter(); + } + if (currentItem && currentItem->item == item) + updateHighlight(); + if (trackedItem && trackedItem->item == item) + q->trackedPositionChanged(); + } + + // for debugging only + void checkVisible() const { + int skip = 0; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItemSG *listItem = visibleItems.at(i); + if (listItem->index == -1) { + ++skip; + } else if (listItem->index != visibleIndex + i - skip) { + qFatal("index %d %d %d", visibleIndex, i, listItem->index); + } + } + } + + void refill(qreal from, qreal to, bool doBuffer = false); + void scheduleLayout(); + void layout(); + void updateUnrequestedIndexes(); + void updateUnrequestedPositions(); + void updateTrackedItem(); + void createHighlight(); + void updateHighlight(); + void createSection(FxListItemSG *); + void updateSections(); + void updateCurrentSection(); + void updateCurrent(int); + void updateAverage(); + void updateHeader(); + void updateFooter(); + void fixupPosition(); + void positionViewAtIndex(int index, int mode); + virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent); + virtual void flick(QSGFlickablePrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity); + + QDeclarativeGuard<QSGVisualModel> model; + QVariant modelVariant; + QList<FxListItemSG*> visibleItems; + QHash<QSGItem*,int> unrequestedItems; + FxListItemSG *currentItem; + QSGListView::Orientation orient; + Qt::LayoutDirection layoutDirection; + qreal visiblePos; + int visibleIndex; + qreal averageSize; + int currentIndex; + int requestedIndex; + int itemCount; + qreal highlightRangeStart; + qreal highlightRangeEnd; + QDeclarativeComponent *highlightComponent; + FxListItemSG *highlight; + FxListItemSG *trackedItem; + enum MovementReason { Other, SetIndex, Mouse }; + MovementReason moveReason; + int buffer; + QSmoothedAnimation *highlightPosAnimator; + QSmoothedAnimation *highlightSizeAnimator; + QSGViewSection *sectionCriteria; + QString currentSection; + static const int sectionCacheSize = 4; + QSGItem *sectionCache[sectionCacheSize]; + qreal spacing; + qreal highlightMoveSpeed; + int highlightMoveDuration; + qreal highlightResizeSpeed; + int highlightResizeDuration; + QSGListView::HighlightRangeMode highlightRange; + QSGListView::SnapMode snapMode; + qreal overshootDist; + QDeclarativeComponent *footerComponent; + FxListItemSG *footer; + QDeclarativeComponent *headerComponent; + FxListItemSG *header; + enum BufferMode { NoBuffer = 0x00, BufferBefore = 0x01, BufferAfter = 0x02 }; + int bufferMode; + mutable qreal minExtent; + mutable qreal maxExtent; + + bool ownModel : 1; + bool wrap : 1; + bool autoHighlight : 1; + bool haveHighlightRange : 1; + bool correctFlick : 1; + bool inFlickCorrection : 1; + bool lazyRelease : 1; + bool deferredRelease : 1; + bool layoutScheduled : 1; + bool currentIndexCleared : 1; + bool inViewportMoved : 1; + bool highlightRangeStartValid : 1; + bool highlightRangeEndValid : 1; + mutable bool minExtentDirty : 1; + mutable bool maxExtentDirty : 1; +}; + +void QSGListViewPrivate::init() +{ + Q_Q(QSGListView); + QSGItemPrivate::get(contentItem)->childrenDoNotOverlap = true; + q->setFlag(QSGItem::ItemIsFocusScope); + addItemChangeListener(this, Geometry); + QObject::connect(q, SIGNAL(movementEnded()), q, SLOT(animStopped())); + q->setFlickableDirection(QSGFlickable::VerticalFlick); + ::memset(sectionCache, 0, sizeof(QSGItem*) * sectionCacheSize); +} + +void QSGListViewPrivate::clear() +{ + timeline.clear(); + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + for (int i = 0; i < sectionCacheSize; ++i) { + delete sectionCache[i]; + sectionCache[i] = 0; + } + visiblePos = header ? header->size() : 0; + visibleIndex = 0; + releaseItem(currentItem); + currentItem = 0; + createHighlight(); + trackedItem = 0; + minExtentDirty = true; + maxExtentDirty = true; + itemCount = 0; +} + +FxListItemSG *QSGListViewPrivate::createItem(int modelIndex) +{ + Q_Q(QSGListView); + // create object + requestedIndex = modelIndex; + FxListItemSG *listItem = 0; + if (QSGItem *item = model->item(modelIndex, false)) { + listItem = new FxListItemSG(item, q); + listItem->index = modelIndex; + // initialise attached properties + if (sectionCriteria) { + QString propValue = model->stringValue(modelIndex, sectionCriteria->property()); + listItem->attached->m_section = sectionCriteria->sectionString(propValue); + if (modelIndex > 0) { + if (FxListItemSG *item = itemBefore(modelIndex)) + listItem->attached->m_prevSection = item->attached->section(); + else + listItem->attached->m_prevSection = sectionAt(modelIndex-1); + } + if (modelIndex < model->count()-1) { + if (FxListItemSG *item = visibleItem(modelIndex+1)) + listItem->attached->m_nextSection = item->attached->section(); + else + listItem->attached->m_nextSection = sectionAt(modelIndex+1); + } + } + if (model->completePending()) { + // complete + listItem->item->setZ(1); + listItem->item->setParentItem(q->contentItem()); + model->completeItem(); + } else { + listItem->item->setParentItem(q->contentItem()); + } + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + if (sectionCriteria && sectionCriteria->delegate()) { + if (listItem->attached->m_prevSection != listItem->attached->m_section) + createSection(listItem); + } + unrequestedItems.remove(listItem->item); + } + requestedIndex = -1; + + return listItem; +} + +void QSGListViewPrivate::releaseItem(FxListItemSG *item) +{ + Q_Q(QSGListView); + if (!item || !model) + return; + if (trackedItem == item) + trackedItem = 0; + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item->item); + itemPrivate->removeItemChangeListener(this, QSGItemPrivate::Geometry); + if (model->release(item->item) == 0) { + // item was not destroyed, and we no longer reference it. + unrequestedItems.insert(item->item, model->indexOf(item->item, q)); + } + if (item->section) { + int i = 0; + do { + if (!sectionCache[i]) { + sectionCache[i] = item->section; + sectionCache[i]->setVisible(false); + item->section = 0; + break; + } + ++i; + } while (i < sectionCacheSize); + delete item->section; + } + delete item; +} + +void QSGListViewPrivate::refill(qreal from, qreal to, bool doBuffer) +{ + Q_Q(QSGListView); + if (!isValid() || !q->isComponentComplete()) + return; + itemCount = model->count(); + qreal bufferFrom = from - buffer; + qreal bufferTo = to + buffer; + qreal fillFrom = from; + qreal fillTo = to; + if (doBuffer && (bufferMode & BufferAfter)) + fillTo = bufferTo; + if (doBuffer && (bufferMode & BufferBefore)) + fillFrom = bufferFrom; + + int modelIndex = visibleIndex; + qreal itemEnd = visiblePos-1; + if (!visibleItems.isEmpty()) { + visiblePos = (*visibleItems.constBegin())->position(); + itemEnd = (*(--visibleItems.constEnd()))->endPosition() + spacing; + int i = visibleItems.count() - 1; + while (i > 0 && visibleItems.at(i)->index == -1) + --i; + if (visibleItems.at(i)->index != -1) + modelIndex = visibleItems.at(i)->index + 1; + } + + if (visibleItems.count() && (fillFrom > itemEnd+averageSize+spacing + || fillTo < visiblePos - averageSize - spacing)) { + // We've jumped more than a page. Estimate which items are now + // visible and fill from there. + int count = (fillFrom - itemEnd) / (averageSize + spacing); + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + modelIndex += count; + if (modelIndex >= model->count()) { + count -= modelIndex - model->count() + 1; + modelIndex = model->count() - 1; + } else if (modelIndex < 0) { + count -= modelIndex; + modelIndex = 0; + } + visibleIndex = modelIndex; + visiblePos = itemEnd + count * (averageSize + spacing) + 1; + itemEnd = visiblePos-1; + } + + bool changed = false; + FxListItemSG *item = 0; + qreal pos = itemEnd + 1; + while (modelIndex < model->count() && pos <= fillTo) { +// qDebug() << "refill: append item" << modelIndex << "pos" << pos; + if (!(item = createItem(modelIndex))) + break; + item->setPosition(pos); + pos += item->size() + spacing; + visibleItems.append(item); + ++modelIndex; + changed = true; + if (doBuffer) // never buffer more than one item per frame + break; + } + while (visibleIndex > 0 && visibleIndex <= model->count() && visiblePos-1 >= fillFrom) { +// qDebug() << "refill: prepend item" << visibleIndex-1 << "current top pos" << visiblePos; + if (!(item = createItem(visibleIndex-1))) + break; + --visibleIndex; + visiblePos -= item->size() + spacing; + item->setPosition(visiblePos); + visibleItems.prepend(item); + changed = true; + if (doBuffer) // never buffer more than one item per frame + break; + } + + if (!lazyRelease || !changed || deferredRelease) { // avoid destroying items in the same frame that we create + while (visibleItems.count() > 1 && (item = visibleItems.first()) && item->endPosition() < bufferFrom) { + if (item->attached->delayRemove()) + break; +// qDebug() << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition(); + if (item->index != -1) + visibleIndex++; + visibleItems.removeFirst(); + releaseItem(item); + changed = true; + } + while (visibleItems.count() > 1 && (item = visibleItems.last()) && item->position() > bufferTo) { + if (item->attached->delayRemove()) + break; +// qDebug() << "refill: remove last" << visibleIndex+visibleItems.count()-1 << item->position(); + visibleItems.removeLast(); + releaseItem(item); + changed = true; + } + deferredRelease = false; + } else { + deferredRelease = true; + } + if (changed) { + minExtentDirty = true; + maxExtentDirty = true; + if (visibleItems.count()) + visiblePos = (*visibleItems.constBegin())->position(); + updateAverage(); + if (currentIndex >= 0 && currentItem && !visibleItem(currentIndex)) { + currentItem->setPosition(positionAt(currentIndex)); + updateHighlight(); + } + + if (sectionCriteria) + updateCurrentSection(); + if (header) + updateHeader(); + if (footer) + updateFooter(); + updateViewport(); + updateUnrequestedPositions(); + } else if (!doBuffer && buffer && bufferMode != NoBuffer) { + refill(from, to, true); + } + lazyRelease = false; +} + +void QSGListViewPrivate::scheduleLayout() +{ + Q_Q(QSGListView); + if (!layoutScheduled) { + layoutScheduled = true; + q->polish(); + } +} + +void QSGListViewPrivate::layout() +{ + Q_Q(QSGListView); + layoutScheduled = false; + if (!isValid() && !visibleItems.count()) { + clear(); + setPosition(0); + return; + } + if (!visibleItems.isEmpty()) { + bool fixedCurrent = currentItem && visibleItems.first()->item == currentItem->item; + qreal sum = visibleItems.first()->size(); + qreal pos = visibleItems.first()->position() + visibleItems.first()->size() + spacing; + for (int i=1; i < visibleItems.count(); ++i) { + FxListItemSG *item = visibleItems.at(i); + item->setPosition(pos); + pos += item->size() + spacing; + sum += item->size(); + fixedCurrent = fixedCurrent || (currentItem && item->item == currentItem->item); + } + averageSize = qRound(sum / visibleItems.count()); + // move current item if it is not a visible item. + if (currentIndex >= 0 && currentItem && !fixedCurrent) + currentItem->setPosition(positionAt(currentIndex)); + } + q->refill(); + minExtentDirty = true; + maxExtentDirty = true; + updateHighlight(); + if (!q->isMoving() && !q->isFlicking()) { + fixupPosition(); + q->refill(); + } + if (header) + updateHeader(); + if (footer) + updateFooter(); + updateViewport(); +} + +void QSGListViewPrivate::updateUnrequestedIndexes() +{ + Q_Q(QSGListView); + QHash<QSGItem*,int>::iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) + *it = model->indexOf(it.key(), q); +} + +void QSGListViewPrivate::updateUnrequestedPositions() +{ + Q_Q(QSGListView); + if (unrequestedItems.count()) { + qreal pos = position(); + QHash<QSGItem*,int>::const_iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) { + QSGItem *item = it.key(); + if (orient == QSGListView::Vertical) { + if (item->y() + item->height() > pos && item->y() < pos + q->height()) + item->setY(positionAt(*it)); + } else { + if (item->x() + item->width() > pos && item->x() < pos + q->width()) { + if (isRightToLeft()) + item->setX(-positionAt(*it)-item->width()); + else + item->setX(positionAt(*it)); + } + } + } + } +} + +void QSGListViewPrivate::updateTrackedItem() +{ + Q_Q(QSGListView); + FxListItemSG *item = currentItem; + if (highlight) + item = highlight; + trackedItem = item; + if (trackedItem) + q->trackedPositionChanged(); +} + +void QSGListViewPrivate::createHighlight() +{ + Q_Q(QSGListView); + bool changed = false; + if (highlight) { + if (trackedItem == highlight) + trackedItem = 0; + delete highlight->item; + delete highlight; + highlight = 0; + delete highlightPosAnimator; + delete highlightSizeAnimator; + highlightPosAnimator = 0; + highlightSizeAnimator = 0; + changed = true; + } + + if (currentItem) { + QSGItem *item = 0; + if (highlightComponent) { + QDeclarativeContext *highlightContext = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = highlightComponent->create(highlightContext); + if (nobj) { + QDeclarative_setParent_noEvent(highlightContext, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete highlightContext; + } + } else { + item = new QSGItem; + } + if (item) { + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + highlight = new FxListItemSG(item, q); + if (currentItem && autoHighlight) { + if (orient == QSGListView::Vertical) { + highlight->item->setHeight(currentItem->item->height()); + } else { + highlight->item->setWidth(currentItem->item->width()); + } + highlight->setPosition(currentItem->itemPosition()); + } + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + const QLatin1String posProp(orient == QSGListView::Vertical ? "y" : "x"); + highlightPosAnimator = new QSmoothedAnimation(q); + highlightPosAnimator->target = QDeclarativeProperty(highlight->item, posProp); + highlightPosAnimator->velocity = highlightMoveSpeed; + highlightPosAnimator->userDuration = highlightMoveDuration; + const QLatin1String sizeProp(orient == QSGListView::Vertical ? "height" : "width"); + highlightSizeAnimator = new QSmoothedAnimation(q); + highlightSizeAnimator->velocity = highlightResizeSpeed; + highlightSizeAnimator->userDuration = highlightResizeDuration; + highlightSizeAnimator->target = QDeclarativeProperty(highlight->item, sizeProp); + if (autoHighlight) { + highlightPosAnimator->restart(); + highlightSizeAnimator->restart(); + } + changed = true; + } + } + if (changed) + emit q->highlightItemChanged(); +} + +void QSGListViewPrivate::updateHighlight() +{ + if ((!currentItem && highlight) || (currentItem && !highlight)) + createHighlight(); + if (currentItem && autoHighlight && highlight && !movingHorizontally && !movingVertically) { + // auto-update highlight + highlightPosAnimator->to = isRightToLeft() + ? -currentItem->itemPosition()-currentItem->itemSize() + : currentItem->itemPosition(); + highlightSizeAnimator->to = currentItem->itemSize(); + if (orient == QSGListView::Vertical) { + if (highlight->item->width() == 0) + highlight->item->setWidth(currentItem->item->width()); + } else { + if (highlight->item->height() == 0) + highlight->item->setHeight(currentItem->item->height()); + } + highlightPosAnimator->restart(); + highlightSizeAnimator->restart(); + } + updateTrackedItem(); +} + +void QSGListViewPrivate::createSection(FxListItemSG *listItem) +{ + Q_Q(QSGListView); + if (!sectionCriteria || !sectionCriteria->delegate()) + return; + if (listItem->attached->m_prevSection != listItem->attached->m_section) { + if (!listItem->section) { + qreal pos = listItem->position(); + int i = sectionCacheSize-1; + while (i >= 0 && !sectionCache[i]) + --i; + if (i >= 0) { + listItem->section = sectionCache[i]; + sectionCache[i] = 0; + listItem->section->setVisible(true); + QDeclarativeContext *context = QDeclarativeEngine::contextForObject(listItem->section)->parentContext(); + context->setContextProperty(QLatin1String("section"), listItem->attached->m_section); + } else { + QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); + context->setContextProperty(QLatin1String("section"), listItem->attached->m_section); + QObject *nobj = sectionCriteria->delegate()->beginCreate(context); + if (nobj) { + QDeclarative_setParent_noEvent(context, nobj); + listItem->section = qobject_cast<QSGItem *>(nobj); + if (!listItem->section) { + delete nobj; + } else { + listItem->section->setZ(1); + QDeclarative_setParent_noEvent(listItem->section, q->contentItem()); + listItem->section->setParentItem(q->contentItem()); + } + } else { + delete context; + } + sectionCriteria->delegate()->completeCreate(); + } + listItem->setPosition(pos); + } else { + QDeclarativeContext *context = QDeclarativeEngine::contextForObject(listItem->section)->parentContext(); + context->setContextProperty(QLatin1String("section"), listItem->attached->m_section); + } + } else if (listItem->section) { + qreal pos = listItem->position(); + int i = 0; + do { + if (!sectionCache[i]) { + sectionCache[i] = listItem->section; + sectionCache[i]->setVisible(false); + listItem->section = 0; + return; + } + ++i; + } while (i < sectionCacheSize); + delete listItem->section; + listItem->section = 0; + listItem->setPosition(pos); + } +} + +void QSGListViewPrivate::updateSections() +{ + if (sectionCriteria && !visibleItems.isEmpty()) { + QString prevSection; + if (visibleIndex > 0) + prevSection = sectionAt(visibleIndex-1); + QSGListViewAttached *prevAtt = 0; + int idx = -1; + for (int i = 0; i < visibleItems.count(); ++i) { + QSGListViewAttached *attached = visibleItems.at(i)->attached; + attached->setPrevSection(prevSection); + if (visibleItems.at(i)->index != -1) { + QString propValue = model->stringValue(visibleItems.at(i)->index, sectionCriteria->property()); + attached->setSection(sectionCriteria->sectionString(propValue)); + idx = visibleItems.at(i)->index; + } + createSection(visibleItems.at(i)); + if (prevAtt) + prevAtt->setNextSection(attached->section()); + prevSection = attached->section(); + prevAtt = attached; + } + if (prevAtt) { + if (idx > 0 && idx < model->count()-1) + prevAtt->setNextSection(sectionAt(idx+1)); + else + prevAtt->setNextSection(QString()); + } + } +} + +void QSGListViewPrivate::updateCurrentSection() +{ + Q_Q(QSGListView); + if (!sectionCriteria || visibleItems.isEmpty()) { + if (!currentSection.isEmpty()) { + currentSection.clear(); + emit q->currentSectionChanged(); + } + return; + } + int index = 0; + while (index < visibleItems.count() && visibleItems.at(index)->endPosition() < position()) + ++index; + + QString newSection = currentSection; + if (index < visibleItems.count()) + newSection = visibleItems.at(index)->attached->section(); + else + newSection = visibleItems.first()->attached->section(); + if (newSection != currentSection) { + currentSection = newSection; + emit q->currentSectionChanged(); + } +} + +void QSGListViewPrivate::updateCurrent(int modelIndex) +{ + Q_Q(QSGListView); + if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) { + if (currentItem) { + currentItem->attached->setIsCurrentItem(false); + releaseItem(currentItem); + currentItem = 0; + currentIndex = modelIndex; + emit q->currentIndexChanged(); + updateHighlight(); + } else if (currentIndex != modelIndex) { + currentIndex = modelIndex; + emit q->currentIndexChanged(); + } + return; + } + + if (currentItem && currentIndex == modelIndex) { + updateHighlight(); + return; + } + FxListItemSG *oldCurrentItem = currentItem; + currentIndex = modelIndex; + currentItem = createItem(modelIndex); + if (oldCurrentItem && (!currentItem || oldCurrentItem->item != currentItem->item)) + oldCurrentItem->attached->setIsCurrentItem(false); + if (currentItem) { + if (modelIndex == visibleIndex - 1 && visibleItems.count()) { + // We can calculate exact postion in this case + currentItem->setPosition(visibleItems.first()->position() - currentItem->size() - spacing); + } else { + // Create current item now and position as best we can. + // Its position will be corrected when it becomes visible. + currentItem->setPosition(positionAt(modelIndex)); + } + currentItem->item->setFocus(true); + currentItem->attached->setIsCurrentItem(true); + // Avoid showing section delegate twice. We still need the section heading so that + // currentItem positioning works correctly. + // This is slightly sub-optimal, but section heading caching minimizes the impact. + if (currentItem->section) + currentItem->section->setVisible(false); + if (visibleItems.isEmpty()) + averageSize = currentItem->size(); + } + updateHighlight(); + emit q->currentIndexChanged(); + // Release the old current item + releaseItem(oldCurrentItem); +} + +void QSGListViewPrivate::updateAverage() +{ + if (!visibleItems.count()) + return; + qreal sum = 0.0; + for (int i = 0; i < visibleItems.count(); ++i) + sum += visibleItems.at(i)->size(); + averageSize = qRound(sum / visibleItems.count()); +} + +void QSGListViewPrivate::updateFooter() +{ + Q_Q(QSGListView); + if (!footer && footerComponent) { + QSGItem *item = 0; + QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = footerComponent->create(context); + if (nobj) { + QDeclarative_setParent_noEvent(context, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete context; + } + if (item) { + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + item->setZ(1); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + footer = new FxListItemSG(item, q); + } + } + if (footer) { + if (visibleItems.count()) { + qreal endPos = lastPosition() + 1; + if (lastVisibleIndex() == model->count()-1) { + footer->setPosition(endPos); + } else { + qreal visiblePos = position() + q->height(); + if (endPos <= visiblePos || footer->position() < endPos) + footer->setPosition(endPos); + } + } else { + footer->setPosition(visiblePos); + } + } +} + +void QSGListViewPrivate::updateHeader() +{ + Q_Q(QSGListView); + if (!header && headerComponent) { + QSGItem *item = 0; + QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = headerComponent->create(context); + if (nobj) { + QDeclarative_setParent_noEvent(context, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete context; + } + if (item) { + QDeclarative_setParent_noEvent(item, q->contentItem()); + item->setParentItem(q->contentItem()); + item->setZ(1); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + header = new FxListItemSG(item, q); + } + } + if (header) { + if (visibleItems.count()) { + qreal startPos = originPosition(); + if (visibleIndex == 0) { + header->setPosition(startPos - header->size()); + } else { + if (position() <= startPos || header->position() > startPos - header->size()) + header->setPosition(startPos - header->size()); + } + } else { + if (itemCount == 0) + visiblePos = header->size(); + header->setPosition(0); + } + } +} + +void QSGListViewPrivate::fixupPosition() +{ + if ((haveHighlightRange && highlightRange == QSGListView::StrictlyEnforceRange) + || snapMode != QSGListView::NoSnap) + moveReason = Other; + if (orient == QSGListView::Vertical) + fixupY(); + else + fixupX(); +} + +void QSGListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent) +{ + if ((orient == QSGListView::Horizontal && &data == &vData) + || (orient == QSGListView::Vertical && &data == &hData)) + return; + + correctFlick = false; + fixupMode = moveReason == Mouse ? fixupMode : Immediate; + + qreal highlightStart; + qreal highlightEnd; + qreal viewPos; + if (isRightToLeft()) { + // Handle Right-To-Left exceptions + viewPos = -position()-size(); + highlightStart = highlightRangeStartValid ? size() - highlightRangeEnd : highlightRangeStart; + highlightEnd = highlightRangeEndValid ? size() - highlightRangeStart : highlightRangeEnd; + } else { + viewPos = position(); + highlightStart = highlightRangeStart; + highlightEnd = highlightRangeEnd; + } + + if (currentItem && haveHighlightRange && highlightRange == QSGListView::StrictlyEnforceRange + && moveReason != QSGListViewPrivate::SetIndex) { + updateHighlight(); + qreal pos = currentItem->itemPosition(); + if (viewPos < pos + currentItem->itemSize() - highlightEnd) + viewPos = pos + currentItem->itemSize() - highlightEnd; + if (viewPos > pos - highlightStart) + viewPos = pos - highlightStart; + if (isRightToLeft()) + viewPos = -viewPos-size(); + + timeline.reset(data.move); + if (viewPos != position()) { + if (fixupMode != Immediate) { + timeline.move(data.move, -viewPos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); + data.fixingUp = true; + } else { + timeline.set(data.move, -viewPos); + } + } + vTime = timeline.time(); + } else if (snapMode != QSGListView::NoSnap && moveReason != QSGListViewPrivate::SetIndex) { + qreal tempPosition = isRightToLeft() ? -position()-size() : position(); + FxListItemSG *topItem = snapItemAt(tempPosition+highlightStart); + FxListItemSG *bottomItem = snapItemAt(tempPosition+highlightEnd); + qreal pos; + bool isInBounds = -position() > maxExtent && -position() < minExtent; + if (topItem && isInBounds) { + if (topItem->index == 0 && header && tempPosition+highlightStart < header->position()+header->size()/2) { + pos = isRightToLeft() ? - header->position() + highlightStart - size() : header->position() - highlightStart; + } else { + if (isRightToLeft()) + pos = qMax(qMin(-topItem->position() + highlightStart - size(), -maxExtent), -minExtent); + else + pos = qMax(qMin(topItem->position() - highlightStart, -maxExtent), -minExtent); + } + } else if (bottomItem && isInBounds) { + if (isRightToLeft()) + pos = qMax(qMin(-bottomItem->position() + highlightStart - size(), -maxExtent), -minExtent); + else + pos = qMax(qMin(bottomItem->position() - highlightStart, -maxExtent), -minExtent); + } else { + QSGFlickablePrivate::fixup(data, minExtent, maxExtent); + return; + } + + qreal dist = qAbs(data.move + pos); + if (dist > 0) { + timeline.reset(data.move); + if (fixupMode != Immediate) { + timeline.move(data.move, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration/2); + data.fixingUp = true; + } else { + timeline.set(data.move, -pos); + } + vTime = timeline.time(); + } + } else { + QSGFlickablePrivate::fixup(data, minExtent, maxExtent); + } + data.inOvershoot = false; + fixupMode = Normal; +} + +void QSGListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, + QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity) +{ + Q_Q(QSGListView); + + data.fixingUp = false; + moveReason = Mouse; + if ((!haveHighlightRange || highlightRange != QSGListView::StrictlyEnforceRange) && snapMode == QSGListView::NoSnap) { + correctFlick = true; + QSGFlickablePrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, velocity); + return; + } + qreal maxDistance = 0; + qreal dataValue = isRightToLeft() ? -data.move.value()+size() : data.move.value(); + // -ve velocity means list is moving up/left + if (velocity > 0) { + if (data.move.value() < minExtent) { + if (snapMode == QSGListView::SnapOneItem) { + if (FxListItemSG *item = isRightToLeft() ? nextVisibleItem() : firstVisibleItem()) + maxDistance = qAbs(item->position() + dataValue); + } else { + maxDistance = qAbs(minExtent - data.move.value()); + } + } + if (snapMode == QSGListView::NoSnap && highlightRange != QSGListView::StrictlyEnforceRange) + data.flickTarget = minExtent; + } else { + if (data.move.value() > maxExtent) { + if (snapMode == QSGListView::SnapOneItem) { + if (FxListItemSG *item = isRightToLeft() ? firstVisibleItem() : nextVisibleItem()) + maxDistance = qAbs(item->position() + dataValue); + } else { + maxDistance = qAbs(maxExtent - data.move.value()); + } + } + if (snapMode == QSGListView::NoSnap && highlightRange != QSGListView::StrictlyEnforceRange) + data.flickTarget = maxExtent; + } + bool overShoot = boundsBehavior == QSGFlickable::DragAndOvershootBounds; + qreal highlightStart = isRightToLeft() && highlightRangeStartValid ? size()-highlightRangeEnd : highlightRangeStart; + if (maxDistance > 0 || overShoot) { + // These modes require the list to stop exactly on an item boundary. + // The initial flick will estimate the boundary to stop on. + // Since list items can have variable sizes, the boundary will be + // reevaluated and adjusted as we approach the boundary. + qreal v = velocity; + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { + if (v < 0) + v = -maxVelocity; + else + v = maxVelocity; + } + if (!flickingHorizontally && !flickingVertically) { + // the initial flick - estimate boundary + qreal accel = deceleration; + qreal v2 = v * v; + overshootDist = 0.0; + // + averageSize/4 to encourage moving at least one item in the flick direction + qreal dist = v2 / (accel * 2.0) + averageSize/4; + if (maxDistance > 0) + dist = qMin(dist, maxDistance); + if (v > 0) + dist = -dist; + if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QSGListView::SnapOneItem) { + qreal distTemp = isRightToLeft() ? -dist : dist; + data.flickTarget = -snapPosAt(-(dataValue - highlightStart) + distTemp) + highlightStart; + data.flickTarget = isRightToLeft() ? -data.flickTarget+size() : data.flickTarget; + if (overShoot) { + if (data.flickTarget >= minExtent) { + overshootDist = overShootDistance(vSize); + data.flickTarget += overshootDist; + } else if (data.flickTarget <= maxExtent) { + overshootDist = overShootDistance(vSize); + data.flickTarget -= overshootDist; + } + } + qreal adjDist = -data.flickTarget + data.move.value(); + if (qAbs(adjDist) > qAbs(dist)) { + // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration + qreal adjv2 = accel * 2.0f * qAbs(adjDist); + if (adjv2 > v2) { + v2 = adjv2; + v = qSqrt(v2); + if (dist > 0) + v = -v; + } + } + dist = adjDist; + accel = v2 / (2.0f * qAbs(dist)); + } else if (overShoot) { + data.flickTarget = data.move.value() - dist; + if (data.flickTarget >= minExtent) { + overshootDist = overShootDistance(vSize); + data.flickTarget += overshootDist; + } else if (data.flickTarget <= maxExtent) { + overshootDist = overShootDistance(vSize); + data.flickTarget -= overshootDist; + } + } + timeline.reset(data.move); + timeline.accel(data.move, v, accel, maxDistance + overshootDist); + timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this)); + if (!flickingHorizontally && q->xflick()) { + flickingHorizontally = true; + emit q->flickingChanged(); + emit q->flickingHorizontallyChanged(); + emit q->flickStarted(); + } + if (!flickingVertically && q->yflick()) { + flickingVertically = true; + emit q->flickingChanged(); + emit q->flickingVerticallyChanged(); + emit q->flickStarted(); + } + correctFlick = true; + } else { + // reevaluate the target boundary. + qreal newtarget = data.flickTarget; + if (snapMode != QSGListView::NoSnap || highlightRange == QSGListView::StrictlyEnforceRange) { + qreal tempFlickTarget = isRightToLeft() ? -data.flickTarget+size() : data.flickTarget; + newtarget = -snapPosAt(-(tempFlickTarget - highlightStart)) + highlightStart; + newtarget = isRightToLeft() ? -newtarget+size() : newtarget; + } + if (velocity < 0 && newtarget <= maxExtent) + newtarget = maxExtent - overshootDist; + else if (velocity > 0 && newtarget >= minExtent) + newtarget = minExtent + overshootDist; + if (newtarget == data.flickTarget) { // boundary unchanged - nothing to do + if (qAbs(velocity) < MinimumFlickVelocity) + correctFlick = false; + return; + } + data.flickTarget = newtarget; + qreal dist = -newtarget + data.move.value(); + if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) { + correctFlick = false; + timeline.reset(data.move); + fixup(data, minExtent, maxExtent); + return; + } + timeline.reset(data.move); + timeline.accelDistance(data.move, v, -dist); + timeline.callback(QDeclarativeTimeLineCallback(&data.move, fixupCallback, this)); + } + } else { + correctFlick = false; + timeline.reset(data.move); + fixup(data, minExtent, maxExtent); + } +} + +//---------------------------------------------------------------------------- + +QSGListView::QSGListView(QSGItem *parent) + : QSGFlickable(*(new QSGListViewPrivate), parent) +{ + Q_D(QSGListView); + d->init(); +} + +QSGListView::~QSGListView() +{ + Q_D(QSGListView); + d->clear(); + if (d->ownModel) + delete d->model; + delete d->header; + delete d->footer; +} + +QVariant QSGListView::model() const +{ + Q_D(const QSGListView); + return d->modelVariant; +} + +void QSGListView::setModel(const QVariant &model) +{ + Q_D(QSGListView); + if (d->modelVariant == model) + return; + if (d->model) { + disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + disconnect(d->model, SIGNAL(itemsChanged(int,int)), this, SLOT(itemsChanged(int,int))); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + disconnect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + disconnect(d->model, SIGNAL(destroyingItem(QSGItem*)), this, SLOT(destroyingItem(QSGItem*))); + } + d->clear(); + QSGVisualModel *oldModel = d->model; + d->model = 0; + d->setPosition(0); + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QSGVisualModel *vim = 0; + if (object && (vim = qobject_cast<QSGVisualModel *>(object))) { + if (d->ownModel) { + delete oldModel; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this), this); + d->ownModel = true; + } else { + d->model = oldModel; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + dataModel->setModel(model); + } + if (d->model) { + d->bufferMode = QSGListViewPrivate::BufferBefore | QSGListViewPrivate::BufferAfter; + if (isComponentComplete()) { + updateSections(); + refill(); + if ((d->currentIndex >= d->model->count() || d->currentIndex < 0) && !d->currentIndexCleared) { + setCurrentIndex(0); + } else { + d->moveReason = QSGListViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->position()); + d->updateTrackedItem(); + } + } + d->updateViewport(); + } + connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + connect(d->model, SIGNAL(itemsChanged(int,int)), this, SLOT(itemsChanged(int,int))); + connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + connect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + connect(d->model, SIGNAL(destroyingItem(QSGItem*)), this, SLOT(destroyingItem(QSGItem*))); + emit countChanged(); + } + emit modelChanged(); +} + +QDeclarativeComponent *QSGListView::delegate() const +{ + Q_D(const QSGListView); + if (d->model) { + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QSGListView::setDelegate(QDeclarativeComponent *delegate) +{ + Q_D(QSGListView); + if (delegate == this->delegate()) + return; + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) { + dataModel->setDelegate(delegate); + if (isComponentComplete()) { + for (int i = 0; i < d->visibleItems.count(); ++i) + d->releaseItem(d->visibleItems.at(i)); + d->visibleItems.clear(); + d->releaseItem(d->currentItem); + d->currentItem = 0; + updateSections(); + refill(); + d->moveReason = QSGListViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->position()); + d->updateTrackedItem(); + } + d->updateViewport(); + } + } + emit delegateChanged(); +} + +int QSGListView::currentIndex() const +{ + Q_D(const QSGListView); + return d->currentIndex; +} + +void QSGListView::setCurrentIndex(int index) +{ + Q_D(QSGListView); + if (d->requestedIndex >= 0) // currently creating item + return; + d->currentIndexCleared = (index == -1); + if (index == d->currentIndex) + return; + if (isComponentComplete() && d->isValid()) { + d->moveReason = QSGListViewPrivate::SetIndex; + d->updateCurrent(index); + } else if (d->currentIndex != index) { + d->currentIndex = index; + emit currentIndexChanged(); + } +} + +QSGItem *QSGListView::currentItem() +{ + Q_D(QSGListView); + if (!d->currentItem) + return 0; + return d->currentItem->item; +} + +QSGItem *QSGListView::highlightItem() +{ + Q_D(QSGListView); + if (!d->highlight) + return 0; + return d->highlight->item; +} + +int QSGListView::count() const +{ + Q_D(const QSGListView); + if (d->model) + return d->model->count(); + return 0; +} + +QDeclarativeComponent *QSGListView::highlight() const +{ + Q_D(const QSGListView); + return d->highlightComponent; +} + +void QSGListView::setHighlight(QDeclarativeComponent *highlight) +{ + Q_D(QSGListView); + if (highlight != d->highlightComponent) { + d->highlightComponent = highlight; + d->createHighlight(); + if (d->currentItem) + d->updateHighlight(); + emit highlightChanged(); + } +} + +bool QSGListView::highlightFollowsCurrentItem() const +{ + Q_D(const QSGListView); + return d->autoHighlight; +} + +void QSGListView::setHighlightFollowsCurrentItem(bool autoHighlight) +{ + Q_D(QSGListView); + if (d->autoHighlight != autoHighlight) { + d->autoHighlight = autoHighlight; + if (autoHighlight) { + d->updateHighlight(); + } else { + if (d->highlightPosAnimator) + d->highlightPosAnimator->stop(); + if (d->highlightSizeAnimator) + d->highlightSizeAnimator->stop(); + } + emit highlightFollowsCurrentItemChanged(); + } +} + +//###Possibly rename these properties, since they are very useful even without a highlight? +qreal QSGListView::preferredHighlightBegin() const +{ + Q_D(const QSGListView); + return d->highlightRangeStart; +} + +void QSGListView::setPreferredHighlightBegin(qreal start) +{ + Q_D(QSGListView); + d->highlightRangeStartValid = true; + if (d->highlightRangeStart == start) + return; + d->highlightRangeStart = start; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit preferredHighlightBeginChanged(); +} + +void QSGListView::resetPreferredHighlightBegin() +{ + Q_D(QSGListView); + d->highlightRangeStartValid = false; + if (d->highlightRangeStart == 0) + return; + d->highlightRangeStart = 0; + emit preferredHighlightBeginChanged(); +} + +qreal QSGListView::preferredHighlightEnd() const +{ + Q_D(const QSGListView); + return d->highlightRangeEnd; +} + +void QSGListView::setPreferredHighlightEnd(qreal end) +{ + Q_D(QSGListView); + d->highlightRangeEndValid = true; + if (d->highlightRangeEnd == end) + return; + d->highlightRangeEnd = end; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit preferredHighlightEndChanged(); +} + +void QSGListView::resetPreferredHighlightEnd() +{ + Q_D(QSGListView); + d->highlightRangeEndValid = false; + if (d->highlightRangeEnd == 0) + return; + d->highlightRangeEnd = 0; + emit preferredHighlightEndChanged(); +} + +QSGListView::HighlightRangeMode QSGListView::highlightRangeMode() const +{ + Q_D(const QSGListView); + return d->highlightRange; +} + +void QSGListView::setHighlightRangeMode(HighlightRangeMode mode) +{ + Q_D(QSGListView); + if (d->highlightRange == mode) + return; + d->highlightRange = mode; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit highlightRangeModeChanged(); +} + +qreal QSGListView::spacing() const +{ + Q_D(const QSGListView); + return d->spacing; +} + +void QSGListView::setSpacing(qreal spacing) +{ + Q_D(QSGListView); + if (spacing != d->spacing) { + d->spacing = spacing; + d->layout(); + emit spacingChanged(); + } +} + +QSGListView::Orientation QSGListView::orientation() const +{ + Q_D(const QSGListView); + return d->orient; +} + +void QSGListView::setOrientation(QSGListView::Orientation orientation) +{ + Q_D(QSGListView); + if (d->orient != orientation) { + d->orient = orientation; + if (d->orient == QSGListView::Vertical) { + setContentWidth(-1); + setFlickableDirection(VerticalFlick); + setContentX(0); + } else { + setContentHeight(-1); + setFlickableDirection(HorizontalFlick); + setContentY(0); + } + d->regenerate(); + emit orientationChanged(); + } +} + +Qt::LayoutDirection QSGListView::layoutDirection() const +{ + Q_D(const QSGListView); + return d->layoutDirection; +} + +void QSGListView::setLayoutDirection(Qt::LayoutDirection layoutDirection) +{ + Q_D(QSGListView); + if (d->layoutDirection != layoutDirection) { + d->layoutDirection = layoutDirection; + d->regenerate(); + emit layoutDirectionChanged(); + emit effectiveLayoutDirectionChanged(); + } +} + +Qt::LayoutDirection QSGListView::effectiveLayoutDirection() const +{ + Q_D(const QSGListView); + if (d->effectiveLayoutMirror) + return d->layoutDirection == Qt::RightToLeft ? Qt::LeftToRight : Qt::RightToLeft; + else + return d->layoutDirection; +} + +bool QSGListView::isWrapEnabled() const +{ + Q_D(const QSGListView); + return d->wrap; +} + +void QSGListView::setWrapEnabled(bool wrap) +{ + Q_D(QSGListView); + if (d->wrap == wrap) + return; + d->wrap = wrap; + emit keyNavigationWrapsChanged(); +} + +int QSGListView::cacheBuffer() const +{ + Q_D(const QSGListView); + return d->buffer; +} + +void QSGListView::setCacheBuffer(int b) +{ + Q_D(QSGListView); + if (d->buffer != b) { + d->buffer = b; + if (isComponentComplete()) { + d->bufferMode = QSGListViewPrivate::BufferBefore | QSGListViewPrivate::BufferAfter; + refill(); + } + emit cacheBufferChanged(); + } +} + +QSGViewSection *QSGListView::sectionCriteria() +{ + Q_D(QSGListView); + if (!d->sectionCriteria) { + d->sectionCriteria = new QSGViewSection(this); + connect(d->sectionCriteria, SIGNAL(propertyChanged()), this, SLOT(updateSections())); + } + return d->sectionCriteria; +} + +QString QSGListView::currentSection() const +{ + Q_D(const QSGListView); + return d->currentSection; +} + +qreal QSGListView::highlightMoveSpeed() const +{ + Q_D(const QSGListView);\ + return d->highlightMoveSpeed; +} + +void QSGListView::setHighlightMoveSpeed(qreal speed) +{ + Q_D(QSGListView);\ + if (d->highlightMoveSpeed != speed) { + d->highlightMoveSpeed = speed; + if (d->highlightPosAnimator) + d->highlightPosAnimator->velocity = d->highlightMoveSpeed; + emit highlightMoveSpeedChanged(); + } +} + +int QSGListView::highlightMoveDuration() const +{ + Q_D(const QSGListView); + return d->highlightMoveDuration; +} + +void QSGListView::setHighlightMoveDuration(int duration) +{ + Q_D(QSGListView);\ + if (d->highlightMoveDuration != duration) { + d->highlightMoveDuration = duration; + if (d->highlightPosAnimator) + d->highlightPosAnimator->userDuration = d->highlightMoveDuration; + emit highlightMoveDurationChanged(); + } +} + +qreal QSGListView::highlightResizeSpeed() const +{ + Q_D(const QSGListView);\ + return d->highlightResizeSpeed; +} + +void QSGListView::setHighlightResizeSpeed(qreal speed) +{ + Q_D(QSGListView);\ + if (d->highlightResizeSpeed != speed) { + d->highlightResizeSpeed = speed; + if (d->highlightSizeAnimator) + d->highlightSizeAnimator->velocity = d->highlightResizeSpeed; + emit highlightResizeSpeedChanged(); + } +} + +int QSGListView::highlightResizeDuration() const +{ + Q_D(const QSGListView); + return d->highlightResizeDuration; +} + +void QSGListView::setHighlightResizeDuration(int duration) +{ + Q_D(QSGListView);\ + if (d->highlightResizeDuration != duration) { + d->highlightResizeDuration = duration; + if (d->highlightSizeAnimator) + d->highlightSizeAnimator->userDuration = d->highlightResizeDuration; + emit highlightResizeDurationChanged(); + } +} + +QSGListView::SnapMode QSGListView::snapMode() const +{ + Q_D(const QSGListView); + return d->snapMode; +} + +void QSGListView::setSnapMode(SnapMode mode) +{ + Q_D(QSGListView); + if (d->snapMode != mode) { + d->snapMode = mode; + emit snapModeChanged(); + } +} + +QDeclarativeComponent *QSGListView::footer() const +{ + Q_D(const QSGListView); + return d->footerComponent; +} + +void QSGListView::setFooter(QDeclarativeComponent *footer) +{ + Q_D(QSGListView); + if (d->footerComponent != footer) { + if (d->footer) { + // XXX todo - the original did scene()->removeItem(). Why? + d->footer->item->setParentItem(0); + d->footer->item->deleteLater(); + delete d->footer; + d->footer = 0; + } + d->footerComponent = footer; + d->minExtentDirty = true; + d->maxExtentDirty = true; + if (isComponentComplete()) { + d->updateFooter(); + d->updateViewport(); + d->fixupPosition(); + } + emit footerChanged(); + } +} + +QDeclarativeComponent *QSGListView::header() const +{ + Q_D(const QSGListView); + return d->headerComponent; +} + +void QSGListView::setHeader(QDeclarativeComponent *header) +{ + Q_D(QSGListView); + if (d->headerComponent != header) { + if (d->header) { + // XXX todo - the original did scene()->removeItem(). Why? + d->header->item->setParentItem(0); + d->header->item->deleteLater(); + delete d->header; + d->header = 0; + } + d->headerComponent = header; + d->minExtentDirty = true; + d->maxExtentDirty = true; + if (isComponentComplete()) { + d->updateHeader(); + d->updateFooter(); + d->updateViewport(); + d->fixupPosition(); + } + emit headerChanged(); + } +} + +void QSGListView::setContentX(qreal pos) +{ + Q_D(QSGListView); + // Positioning the view manually should override any current movement state + d->moveReason = QSGListViewPrivate::Other; + QSGFlickable::setContentX(pos); +} + +void QSGListView::setContentY(qreal pos) +{ + Q_D(QSGListView); + // Positioning the view manually should override any current movement state + d->moveReason = QSGListViewPrivate::Other; + QSGFlickable::setContentY(pos); +} + +void QSGListView::updatePolish() +{ + Q_D(QSGListView); + QSGFlickable::updatePolish(); + d->layout(); +} + +void QSGListView::viewportMoved() +{ + Q_D(QSGListView); + QSGFlickable::viewportMoved(); + if (!d->itemCount) + return; + // Recursion can occur due to refill changing the content size. + if (d->inViewportMoved) + return; + d->inViewportMoved = true; + d->lazyRelease = true; + refill(); + if (d->flickingHorizontally || d->flickingVertically || d->movingHorizontally || d->movingVertically) + d->moveReason = QSGListViewPrivate::Mouse; + if (d->moveReason != QSGListViewPrivate::SetIndex) { + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { + // reposition highlight + qreal pos = d->highlight->position(); + qreal viewPos; + qreal highlightStart; + qreal highlightEnd; + if (d->isRightToLeft()) { + // Handle Right-To-Left exceptions + viewPos = -d->position()-d->size(); + highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart; + highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd; + } else { + viewPos = d->position(); + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + } + if (pos > viewPos + highlightEnd - d->highlight->size()) + pos = viewPos + highlightEnd - d->highlight->size(); + if (pos < viewPos + highlightStart) + pos = viewPos + highlightStart; + d->highlightPosAnimator->stop(); + d->highlight->setPosition(qRound(pos)); + + // update current index + if (FxListItemSG *snapItem = d->snapItemAt(d->highlight->position())) { + if (snapItem->index >= 0 && snapItem->index != d->currentIndex) + d->updateCurrent(snapItem->index); + } + } + } + + if ((d->flickingHorizontally || d->flickingVertically) && d->correctFlick && !d->inFlickCorrection) { + d->inFlickCorrection = true; + // Near an end and it seems that the extent has changed? + // Recalculate the flick so that we don't end up in an odd position. + if (yflick() && !d->vData.inOvershoot) { + if (d->vData.velocity > 0) { + const qreal minY = minYExtent(); + if ((minY - d->vData.move.value() < height()/2 || d->vData.flickTarget - d->vData.move.value() < height()/2) + && minY != d->vData.flickTarget) + d->flickY(-d->vData.smoothVelocity.value()); + d->bufferMode = QSGListViewPrivate::BufferBefore; + } else if (d->vData.velocity < 0) { + const qreal maxY = maxYExtent(); + if ((d->vData.move.value() - maxY < height()/2 || d->vData.move.value() - d->vData.flickTarget < height()/2) + && maxY != d->vData.flickTarget) + d->flickY(-d->vData.smoothVelocity.value()); + d->bufferMode = QSGListViewPrivate::BufferAfter; + } + } + + if (xflick() && !d->hData.inOvershoot) { + if (d->hData.velocity > 0) { + const qreal minX = minXExtent(); + if ((minX - d->hData.move.value() < width()/2 || d->hData.flickTarget - d->hData.move.value() < width()/2) + && minX != d->hData.flickTarget) + d->flickX(-d->hData.smoothVelocity.value()); + d->bufferMode = d->isRightToLeft() ? QSGListViewPrivate::BufferAfter : QSGListViewPrivate::BufferBefore; + } else if (d->hData.velocity < 0) { + const qreal maxX = maxXExtent(); + if ((d->hData.move.value() - maxX < width()/2 || d->hData.move.value() - d->hData.flickTarget < width()/2) + && maxX != d->hData.flickTarget) + d->flickX(-d->hData.smoothVelocity.value()); + d->bufferMode = d->isRightToLeft() ? QSGListViewPrivate::BufferBefore : QSGListViewPrivate::BufferAfter; + } + } + d->inFlickCorrection = false; + } + d->inViewportMoved = false; +} + +qreal QSGListView::minYExtent() const +{ + Q_D(const QSGListView); + if (d->orient == QSGListView::Horizontal) + return QSGFlickable::minYExtent(); + if (d->minExtentDirty) { + d->minExtent = -d->startPosition(); + if (d->header && d->visibleItems.count()) + d->minExtent += d->header->size(); + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + d->minExtent += d->highlightRangeStart; + if (d->sectionCriteria) { + if (d->visibleItem(0)) + d->minExtent -= d->visibleItem(0)->sectionSize(); + } + d->minExtent = qMax(d->minExtent, -(d->endPositionAt(0) - d->highlightRangeEnd + 1)); + } + d->minExtentDirty = false; + } + + return d->minExtent; +} + +qreal QSGListView::maxYExtent() const +{ + Q_D(const QSGListView); + if (d->orient == QSGListView::Horizontal) + return height(); + if (d->maxExtentDirty) { + if (!d->model || !d->model->count()) { + d->maxExtent = d->header ? -d->header->size() : 0; + d->maxExtent += height(); + } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + d->maxExtent = -(d->positionAt(d->model->count()-1) - d->highlightRangeStart); + if (d->highlightRangeEnd != d->highlightRangeStart) + d->maxExtent = qMin(d->maxExtent, -(d->endPosition() - d->highlightRangeEnd + 1)); + } else { + d->maxExtent = -(d->endPosition() - height() + 1); + } + if (d->footer) + d->maxExtent -= d->footer->size(); + qreal minY = minYExtent(); + if (d->maxExtent > minY) + d->maxExtent = minY; + d->maxExtentDirty = false; + } + return d->maxExtent; +} + +qreal QSGListView::minXExtent() const +{ + Q_D(const QSGListView); + if (d->orient == QSGListView::Vertical) + return QSGFlickable::minXExtent(); + if (d->minExtentDirty) { + d->minExtent = -d->startPosition(); + qreal highlightStart; + qreal highlightEnd; + qreal endPositionFirstItem = 0; + if (d->isRightToLeft()) { + if (d->model && d->model->count()) + endPositionFirstItem = d->positionAt(d->model->count()-1); + else if (d->header) + d->minExtent += d->header->size(); + highlightStart = d->highlightRangeStartValid + ? d->highlightRangeStart - (d->lastPosition()-endPositionFirstItem) + : d->size() - (d->lastPosition()-endPositionFirstItem); + highlightEnd = d->highlightRangeEndValid ? d->highlightRangeEnd : d->size(); + if (d->footer) + d->minExtent += d->footer->size(); + qreal maxX = maxXExtent(); + if (d->minExtent < maxX) + d->minExtent = maxX; + } else { + endPositionFirstItem = d->endPositionAt(0); + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + if (d->header && d->visibleItems.count()) + d->minExtent += d->header->size(); + } + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + d->minExtent += highlightStart; + d->minExtent = qMax(d->minExtent, -(endPositionFirstItem - highlightEnd + 1)); + } + d->minExtentDirty = false; + } + + return d->minExtent; +} + +qreal QSGListView::maxXExtent() const +{ + Q_D(const QSGListView); + if (d->orient == QSGListView::Vertical) + return width(); + if (d->maxExtentDirty) { + qreal highlightStart; + qreal highlightEnd; + qreal lastItemPosition = 0; + d->maxExtent = 0; + if (d->isRightToLeft()) { + highlightStart = d->highlightRangeStartValid ? d->highlightRangeEnd : d->size(); + highlightEnd = d->highlightRangeEndValid ? d->highlightRangeStart : d->size(); + lastItemPosition = d->endPosition(); + } else { + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + if (d->model && d->model->count()) + lastItemPosition = d->positionAt(d->model->count()-1); + } + if (!d->model || !d->model->count()) { + if (!d->isRightToLeft()) + d->maxExtent = d->header ? -d->header->size() : 0; + d->maxExtent += width(); + } else if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) { + d->maxExtent = -(lastItemPosition - highlightStart); + if (highlightEnd != highlightStart) { + d->maxExtent = d->isRightToLeft() + ? qMax(d->maxExtent, -(d->endPosition() - highlightEnd + 1)) + : qMin(d->maxExtent, -(d->endPosition() - highlightEnd + 1)); + } + } else { + d->maxExtent = -(d->endPosition() - width() + 1); + } + if (d->isRightToLeft()) { + if (d->header && d->visibleItems.count()) + d->maxExtent -= d->header->size(); + } else { + if (d->footer) + d->maxExtent -= d->footer->size(); + qreal minX = minXExtent(); + if (d->maxExtent > minX) + d->maxExtent = minX; + } + d->maxExtentDirty = false; + } + + return d->maxExtent; +} + +void QSGListView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QSGListView); + if (d->model && d->model->count() && d->interactive) { + if ((d->orient == QSGListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Left) + || (d->orient == QSGListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Right) + || (d->orient == QSGListView::Vertical && event->key() == Qt::Key_Up)) { + if (currentIndex() > 0 || (d->wrap && !event->isAutoRepeat())) { + decrementCurrentIndex(); + event->accept(); + return; + } else if (d->wrap) { + event->accept(); + return; + } + } else if ((d->orient == QSGListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Right) + || (d->orient == QSGListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Left) + || (d->orient == QSGListView::Vertical && event->key() == Qt::Key_Down)) { + if (currentIndex() < d->model->count() - 1 || (d->wrap && !event->isAutoRepeat())) { + incrementCurrentIndex(); + event->accept(); + return; + } else if (d->wrap) { + event->accept(); + return; + } + } + } + event->ignore(); + QSGFlickable::keyPressEvent(event); +} + +void QSGListView::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + Q_D(QSGListView); + d->maxExtentDirty = true; + d->minExtentDirty = true; + if (d->isRightToLeft() && d->orient == QSGListView::Horizontal) { + // maintain position relative to the right edge + int dx = newGeometry.width() - oldGeometry.width(); + setContentX(contentX() - dx); + } + QSGFlickable::geometryChanged(newGeometry, oldGeometry); +} + + +void QSGListView::incrementCurrentIndex() +{ + Q_D(QSGListView); + int count = d->model ? d->model->count() : 0; + if (count && (currentIndex() < count - 1 || d->wrap)) { + d->moveReason = QSGListViewPrivate::SetIndex; + int index = currentIndex()+1; + setCurrentIndex((index >= 0 && index < count) ? index : 0); + } +} + +void QSGListView::decrementCurrentIndex() +{ + Q_D(QSGListView); + int count = d->model ? d->model->count() : 0; + if (count && (currentIndex() > 0 || d->wrap)) { + d->moveReason = QSGListViewPrivate::SetIndex; + int index = currentIndex()-1; + setCurrentIndex((index >= 0 && index < count) ? index : count-1); + } +} + +void QSGListViewPrivate::positionViewAtIndex(int index, int mode) +{ + Q_Q(QSGListView); + if (!isValid()) + return; + if (mode < QSGListView::Beginning || mode > QSGListView::Contain) + return; + int idx = qMax(qMin(index, model->count()-1), 0); + + if (layoutScheduled) + layout(); + qreal pos = isRightToLeft() ? -position() - size() : position(); + FxListItemSG *item = visibleItem(idx); + qreal maxExtent; + if (orient == QSGListView::Vertical) + maxExtent = -q->maxYExtent(); + else + maxExtent = isRightToLeft() ? q->minXExtent()-size(): -q->maxXExtent(); + if (!item) { + int itemPos = positionAt(idx); + // save the currently visible items in case any of them end up visible again + QList<FxListItemSG*> oldVisible = visibleItems; + visibleItems.clear(); + visiblePos = itemPos; + visibleIndex = idx; + setPosition(qMin(qreal(itemPos), maxExtent)); + // now release the reference to all the old visible items. + for (int i = 0; i < oldVisible.count(); ++i) + releaseItem(oldVisible.at(i)); + item = visibleItem(idx); + } + if (item) { + const qreal itemPos = item->position(); + switch (mode) { + case QSGListView::Beginning: + pos = itemPos; + if (index < 0 && header) + pos -= header->size(); + break; + case QSGListView::Center: + pos = itemPos - (size() - item->size())/2; + break; + case QSGListView::End: + pos = itemPos - size() + item->size(); + if (index >= model->count() && footer) + pos += footer->size(); + break; + case QSGListView::Visible: + if (itemPos > pos + size()) + pos = itemPos - size() + item->size(); + else if (item->endPosition() < pos) + pos = itemPos; + break; + case QSGListView::Contain: + if (item->endPosition() > pos + size()) + pos = itemPos - size() + item->size(); + if (itemPos < pos) + pos = itemPos; + } + pos = qMin(pos, maxExtent); + qreal minExtent; + if (orient == QSGListView::Vertical) { + minExtent = -q->minYExtent(); + } else { + minExtent = isRightToLeft() ? q->maxXExtent()-size(): -q->minXExtent(); + } + pos = qMax(pos, minExtent); + moveReason = QSGListViewPrivate::Other; + q->cancelFlick(); + setPosition(pos); + if (highlight) { + if (autoHighlight) { + highlight->setPosition(currentItem->itemPosition()); + highlight->setSize(currentItem->itemSize()); + } + updateHighlight(); + } + } + fixupPosition(); +} + +void QSGListView::positionViewAtIndex(int index, int mode) +{ + Q_D(QSGListView); + if (!d->isValid() || index < 0 || index >= d->model->count()) + return; + d->positionViewAtIndex(index, mode); +} + +void QSGListView::positionViewAtBeginning() +{ + Q_D(QSGListView); + if (!d->isValid()) + return; + d->positionViewAtIndex(-1, Beginning); +} + +void QSGListView::positionViewAtEnd() +{ + Q_D(QSGListView); + if (!d->isValid()) + return; + d->positionViewAtIndex(d->model->count(), End); +} + +int QSGListView::indexAt(qreal x, qreal y) const +{ + Q_D(const QSGListView); + for (int i = 0; i < d->visibleItems.count(); ++i) { + const FxListItemSG *listItem = d->visibleItems.at(i); + if(listItem->contains(x, y)) + return listItem->index; + } + + return -1; +} + +void QSGListView::componentComplete() +{ + Q_D(QSGListView); + QSGFlickable::componentComplete(); + updateSections(); + d->updateHeader(); + d->updateFooter(); + if (d->isValid()) { + refill(); + d->moveReason = QSGListViewPrivate::SetIndex; + if (d->currentIndex < 0 && !d->currentIndexCleared) + d->updateCurrent(0); + else + d->updateCurrent(d->currentIndex); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->position()); + d->updateTrackedItem(); + } + d->moveReason = QSGListViewPrivate::Other; + d->fixupPosition(); + } +} + +void QSGListView::updateSections() +{ + Q_D(QSGListView); + if (isComponentComplete() && d->model) { + QList<QByteArray> roles; + if (d->sectionCriteria && !d->sectionCriteria->property().isEmpty()) + roles << d->sectionCriteria->property().toUtf8(); + d->model->setWatchedRoles(roles); + d->updateSections(); + if (d->itemCount) + d->layout(); + } +} + +void QSGListView::refill() +{ + Q_D(QSGListView); + if (d->isRightToLeft()) + d->refill(-d->position()-d->size()+1, -d->position()); + else + d->refill(d->position(), d->position()+d->size()-1); +} + +void QSGListView::trackedPositionChanged() +{ + Q_D(QSGListView); + if (!d->trackedItem || !d->currentItem) + return; + if (d->moveReason == QSGListViewPrivate::SetIndex) { + qreal trackedPos = qCeil(d->trackedItem->position()); + qreal trackedSize = d->trackedItem->size(); + if (d->trackedItem != d->currentItem) { + trackedPos -= d->currentItem->sectionSize(); + trackedSize += d->currentItem->sectionSize(); + } + qreal viewPos; + qreal highlightStart; + qreal highlightEnd; + if (d->isRightToLeft()) { + viewPos = -d->position()-d->size(); + highlightStart = d->highlightRangeStartValid ? d->size()-d->highlightRangeEnd : d->highlightRangeStart; + highlightEnd = d->highlightRangeEndValid ? d->size()-d->highlightRangeStart : d->highlightRangeEnd; + } else { + viewPos = d->position(); + highlightStart = d->highlightRangeStart; + highlightEnd = d->highlightRangeEnd; + } + qreal pos = viewPos; + if (d->haveHighlightRange) { + if (d->highlightRange == StrictlyEnforceRange) { + if (trackedPos > pos + highlightEnd - d->trackedItem->size()) + pos = trackedPos - highlightEnd + d->trackedItem->size(); + if (trackedPos < pos + highlightStart) + pos = trackedPos - highlightStart; + } else { + if (trackedPos < d->startPosition() + highlightStart) { + pos = d->startPosition(); + } else if (d->trackedItem->endPosition() > d->endPosition() - d->size() + highlightEnd) { + pos = d->endPosition() - d->size() + 1; + if (pos < d->startPosition()) + pos = d->startPosition(); + } else { + if (trackedPos < viewPos + highlightStart) { + pos = trackedPos - highlightStart; + } else if (trackedPos > viewPos + highlightEnd - trackedSize) { + pos = trackedPos - highlightEnd + trackedSize; + } + } + } + } else { + if (trackedPos < viewPos && d->currentItem->position() < viewPos) { + pos = d->currentItem->position() < trackedPos ? 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() + 1; + if (trackedSize > d->size()) + pos = trackedPos; + } else { + pos = d->currentItem->endPosition() - d->size() + 1; + if (d->currentItem->size() > d->size()) + pos = d->currentItem->position(); + } + } + } + if (viewPos != pos) { + cancelFlick(); + d->calcVelocity = true; + d->setPosition(pos); + d->calcVelocity = false; + } + } +} + +void QSGListView::itemsInserted(int modelIndex, int count) +{ + Q_D(QSGListView); + if (!isComponentComplete()) + return; + d->updateUnrequestedIndexes(); + d->moveReason = QSGListViewPrivate::Other; + + qreal tempPos = d->isRightToLeft() ? -d->position()-d->size() : d->position(); + int index = d->visibleItems.count() ? d->mapFromModel(modelIndex) : 0; + if (index < 0) { + int i = d->visibleItems.count() - 1; + while (i > 0 && d->visibleItems.at(i)->index == -1) + --i; + if (i == 0 && d->visibleItems.first()->index == -1) { + // there are no visible items except items marked for removal + index = d->visibleItems.count(); + } else if (d->visibleItems.at(i)->index + 1 == modelIndex + && d->visibleItems.at(i)->endPosition() < d->buffer+tempPos+d->size()-1) { + // Special case of appending an item to the model. + index = d->visibleItems.count(); + } else { + if (modelIndex < d->visibleIndex) { + // Insert before visible items + d->visibleIndex += count; + for (int i = 0; i < d->visibleItems.count(); ++i) { + FxListItemSG *listItem = d->visibleItems.at(i); + if (listItem->index != -1 && listItem->index >= modelIndex) + listItem->index += count; + } + } + if (d->currentIndex >= modelIndex) { + // adjust current item index + d->currentIndex += count; + if (d->currentItem) + d->currentItem->index = d->currentIndex; + emit currentIndexChanged(); + } + d->scheduleLayout(); + d->itemCount += count; + emit countChanged(); + return; + } + } + + // index can be the next item past the end of the visible items list (i.e. appended) + int pos = 0; + if (d->visibleItems.count()) { + pos = index < d->visibleItems.count() ? d->visibleItems.at(index)->position() + : d->visibleItems.last()->endPosition()+d->spacing+1; + } else if (d->itemCount == 0 && d->header) { + pos = d->header->size(); + } + + int initialPos = pos; + int diff = 0; + QList<FxListItemSG*> added; + bool addedVisible = false; + FxListItemSG *firstVisible = d->firstVisibleItem(); + if (firstVisible && pos < firstVisible->position()) { + // Insert items before the visible item. + int insertionIdx = index; + int i = 0; + int from = tempPos - d->buffer; + for (i = count-1; i >= 0 && pos > from; --i) { + if (!addedVisible) { + d->scheduleLayout(); + addedVisible = true; + } + FxListItemSG *item = d->createItem(modelIndex + i); + d->visibleItems.insert(insertionIdx, item); + pos -= item->size() + d->spacing; + item->setPosition(pos); + index++; + } + if (i >= 0) { + // If we didn't insert all our new items - anything + // before the current index is not visible - remove it. + while (insertionIdx--) { + FxListItemSG *item = d->visibleItems.takeFirst(); + if (item->index != -1) + d->visibleIndex++; + d->releaseItem(item); + } + } else { + // adjust pos of items before inserted items. + for (int i = insertionIdx-1; i >= 0; i--) { + FxListItemSG *listItem = d->visibleItems.at(i); + listItem->setPosition(listItem->position() - (initialPos - pos)); + } + } + } else { + int i = 0; + int to = d->buffer+tempPos+d->size()-1; + for (i = 0; i < count && pos <= to; ++i) { + if (!addedVisible) { + d->scheduleLayout(); + addedVisible = true; + } + FxListItemSG *item = d->createItem(modelIndex + i); + d->visibleItems.insert(index, item); + item->setPosition(pos); + added.append(item); + pos += item->size() + d->spacing; + ++index; + } + if (i != count) { + // We didn't insert all our new items, which means anything + // beyond the current index is not visible - remove it. + while (d->visibleItems.count() > index) + d->releaseItem(d->visibleItems.takeLast()); + } + diff = pos - initialPos; + } + if (d->itemCount && d->currentIndex >= modelIndex) { + // adjust current item index + d->currentIndex += count; + if (d->currentItem) { + d->currentItem->index = d->currentIndex; + d->currentItem->setPosition(d->currentItem->position() + diff); + } + emit currentIndexChanged(); + } else if (!d->itemCount && (!d->currentIndex || (d->currentIndex < 0 && !d->currentIndexCleared))) { + d->updateCurrent(0); + } + // Update the indexes of the following visible items. + for (; index < d->visibleItems.count(); ++index) { + FxListItemSG *listItem = d->visibleItems.at(index); + if (d->currentItem && listItem->item != d->currentItem->item) + listItem->setPosition(listItem->position() + diff); + if (listItem->index != -1) + listItem->index += count; + } + // everything is in order now - emit add() signal + for (int j = 0; j < added.count(); ++j) + added.at(j)->attached->emitAdd(); + + d->updateSections(); + d->itemCount += count; + emit countChanged(); +} + +void QSGListView::itemsRemoved(int modelIndex, int count) +{ + Q_D(QSGListView); + if (!isComponentComplete()) + return; + d->moveReason = QSGListViewPrivate::Other; + d->updateUnrequestedIndexes(); + d->itemCount -= count; + + FxListItemSG *firstVisible = d->firstVisibleItem(); + int preRemovedSize = 0; + bool removedVisible = false; + // Remove the items from the visible list, skipping anything already marked for removal + QList<FxListItemSG*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxListItemSG *item = *it; + if (item->index == -1 || item->index < modelIndex) { + // already removed, or before removed items + ++it; + } else if (item->index >= modelIndex + count) { + // after removed items + item->index -= count; + ++it; + } else { + // removed item + if (!removedVisible) { + d->scheduleLayout(); + removedVisible = true; + } + item->attached->emitRemove(); + if (item->attached->delayRemove()) { + item->index = -1; + connect(item->attached, SIGNAL(delayRemoveChanged()), this, SLOT(destroyRemoved()), Qt::QueuedConnection); + ++it; + } else { + if (item == firstVisible) + firstVisible = 0; + if (firstVisible && item->position() < firstVisible->position()) + preRemovedSize += item->size(); + it = d->visibleItems.erase(it); + d->releaseItem(item); + } + } + } + + if (firstVisible && d->visibleItems.first() != firstVisible) + d->visibleItems.first()->setPosition(d->visibleItems.first()->position() + preRemovedSize); + + // fix current + if (d->currentIndex >= modelIndex + count) { + d->currentIndex -= count; + if (d->currentItem) + d->currentItem->index -= count; + emit currentIndexChanged(); + } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) { + // current item has been removed. + if (d->currentItem) { + d->currentItem->attached->setIsCurrentItem(false); + d->releaseItem(d->currentItem); + d->currentItem = 0; + } + d->currentIndex = -1; + if (d->itemCount) + d->updateCurrent(qMin(modelIndex, d->itemCount-1)); + else + emit currentIndexChanged(); + } + + // update visibleIndex + bool haveVisibleIndex = false; + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + haveVisibleIndex = true; + break; + } + } + + if (removedVisible && !haveVisibleIndex) { + d->timeline.clear(); + if (d->itemCount == 0) { + d->visibleIndex = 0; + d->visiblePos = d->header ? d->header->size() : 0; + d->setPosition(0); + d->updateHeader(); + d->updateFooter(); + } else { + if (modelIndex < d->visibleIndex) + d->visibleIndex = modelIndex+1; + d->visibleIndex = qMax(qMin(d->visibleIndex, d->itemCount-1), 0); + } + } + + d->updateSections(); + emit countChanged(); +} + +void QSGListView::destroyRemoved() +{ + Q_D(QSGListView); + for (QList<FxListItemSG*>::Iterator it = d->visibleItems.begin(); + it != d->visibleItems.end();) { + FxListItemSG *listItem = *it; + if (listItem->index == -1 && listItem->attached->delayRemove() == false) { + d->releaseItem(listItem); + it = d->visibleItems.erase(it); + } else { + ++it; + } + } + + // Correct the positioning of the items + d->updateSections(); + d->layout(); +} + +void QSGListView::itemsMoved(int from, int to, int count) +{ + Q_D(QSGListView); + if (!isComponentComplete()) + return; + d->updateUnrequestedIndexes(); + + if (d->visibleItems.isEmpty()) { + refill(); + return; + } + + d->moveReason = QSGListViewPrivate::Other; + FxListItemSG *firstVisible = d->firstVisibleItem(); + qreal firstItemPos = firstVisible->position(); + QHash<int,FxListItemSG*> moved; + int moveBy = 0; + + QList<FxListItemSG*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxListItemSG *item = *it; + if (item->index >= from && item->index < from + count) { + // take the items that are moving + item->index += (to-from); + moved.insert(item->index, item); + if (item->position() < firstItemPos) + moveBy += item->size(); + it = d->visibleItems.erase(it); + } else { + // move everything after the moved items. + if (item->index > from && item->index != -1) + item->index -= count; + ++it; + } + } + + int remaining = count; + int endIndex = d->visibleIndex; + it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxListItemSG *item = *it; + if (remaining && item->index >= to && item->index < to + count) { + // place items in the target position, reusing any existing items + FxListItemSG *movedItem = moved.take(item->index); + if (!movedItem) + movedItem = d->createItem(item->index); + if (item->index <= firstVisible->index) + moveBy -= movedItem->size(); + it = d->visibleItems.insert(it, movedItem); + ++it; + --remaining; + } else { + if (item->index != -1) { + if (item->index >= to) { + // update everything after the moved items. + item->index += count; + } + endIndex = item->index; + } + ++it; + } + } + + // If we have moved items to the end of the visible items + // then add any existing moved items that we have + while (FxListItemSG *item = moved.take(endIndex+1)) { + d->visibleItems.append(item); + ++endIndex; + } + + // update visibleIndex + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + // Fix current index + if (d->currentIndex >= 0 && d->currentItem) { + int oldCurrent = d->currentIndex; + d->currentIndex = d->model->indexOf(d->currentItem->item, this); + if (oldCurrent != d->currentIndex) { + d->currentItem->index = d->currentIndex; + emit currentIndexChanged(); + } + } + + // Whatever moved items remain are no longer visible items. + while (moved.count()) { + int idx = moved.begin().key(); + FxListItemSG *item = moved.take(idx); + if (d->currentItem && item->item == d->currentItem->item) + item->setPosition(d->positionAt(idx)); + d->releaseItem(item); + } + + // Ensure we don't cause an ugly list scroll. + d->visibleItems.first()->setPosition(d->visibleItems.first()->position() + moveBy); + + d->updateSections(); + d->layout(); +} + +void QSGListView::itemsChanged(int, int) +{ + Q_D(QSGListView); + d->updateSections(); + d->layout(); +} + +void QSGListView::modelReset() +{ + Q_D(QSGListView); + d->moveReason = QSGListViewPrivate::SetIndex; + d->regenerate(); + if (d->highlight && d->currentItem) { + if (d->autoHighlight) + d->highlight->setPosition(d->currentItem->position()); + d->updateTrackedItem(); + } + d->moveReason = QSGListViewPrivate::Other; + emit countChanged(); +} + +void QSGListView::createdItem(int index, QSGItem *item) +{ + Q_D(QSGListView); + if (d->requestedIndex != index) { + item->setParentItem(contentItem()); + d->unrequestedItems.insert(item, index); + if (d->orient == QSGListView::Vertical) { + item->setY(d->positionAt(index)); + } else { + if (d->isRightToLeft()) + item->setX(-d->positionAt(index)-item->width()); + else + item->setX(d->positionAt(index)); + } + } +} + +void QSGListView::destroyingItem(QSGItem *item) +{ + Q_D(QSGListView); + d->unrequestedItems.remove(item); +} + +void QSGListView::animStopped() +{ + Q_D(QSGListView); + d->bufferMode = QSGListViewPrivate::NoBuffer; + if (d->haveHighlightRange && d->highlightRange == QSGListView::StrictlyEnforceRange) + d->updateHighlight(); +} + +QSGListViewAttached *QSGListView::qmlAttachedProperties(QObject *obj) +{ + return new QSGListViewAttached(obj); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsglistview_p.h b/src/declarative/items/qsglistview_p.h new file mode 100644 index 0000000000..2e3df2020f --- /dev/null +++ b/src/declarative/items/qsglistview_p.h @@ -0,0 +1,374 @@ +// Commit: 95814418f9d6adeba365c795462e8afb00138211 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGLISTVIEW_P_H +#define QSGLISTVIEW_P_H + +#include "qsgflickable_p.h" + +#include <private/qdeclarativeguard_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_AUTOTEST_EXPORT QSGViewSection : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged) + Q_PROPERTY(SectionCriteria criteria READ criteria WRITE setCriteria NOTIFY criteriaChanged) + Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_ENUMS(SectionCriteria) +public: + QSGViewSection(QObject *parent=0) : QObject(parent), m_criteria(FullString), m_delegate(0) {} + + QString property() const { return m_property; } + void setProperty(const QString &); + + enum SectionCriteria { FullString, FirstCharacter }; + SectionCriteria criteria() const { return m_criteria; } + void setCriteria(SectionCriteria); + + QDeclarativeComponent *delegate() const { return m_delegate; } + void setDelegate(QDeclarativeComponent *delegate); + + QString sectionString(const QString &value); + +Q_SIGNALS: + void propertyChanged(); + void criteriaChanged(); + void delegateChanged(); + +private: + QString m_property; + SectionCriteria m_criteria; + QDeclarativeComponent *m_delegate; +}; + + +class QSGVisualModel; +class QSGListViewAttached; +class QSGListViewPrivate; +class Q_AUTOTEST_EXPORT QSGListView : public QSGFlickable +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGListView) + + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QSGItem *currentItem READ currentItem NOTIFY currentIndexChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + + Q_PROPERTY(QDeclarativeComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged) + Q_PROPERTY(QSGItem *highlightItem READ highlightItem NOTIFY highlightItemChanged) + Q_PROPERTY(bool highlightFollowsCurrentItem READ highlightFollowsCurrentItem WRITE setHighlightFollowsCurrentItem NOTIFY highlightFollowsCurrentItemChanged) + Q_PROPERTY(qreal highlightMoveSpeed READ highlightMoveSpeed WRITE setHighlightMoveSpeed NOTIFY highlightMoveSpeedChanged) + Q_PROPERTY(int highlightMoveDuration READ highlightMoveDuration WRITE setHighlightMoveDuration NOTIFY highlightMoveDurationChanged) + Q_PROPERTY(qreal highlightResizeSpeed READ highlightResizeSpeed WRITE setHighlightResizeSpeed NOTIFY highlightResizeSpeedChanged) + Q_PROPERTY(int highlightResizeDuration READ highlightResizeDuration WRITE setHighlightResizeDuration NOTIFY highlightResizeDurationChanged) + + Q_PROPERTY(qreal preferredHighlightBegin READ preferredHighlightBegin WRITE setPreferredHighlightBegin NOTIFY preferredHighlightBeginChanged RESET resetPreferredHighlightBegin) + Q_PROPERTY(qreal preferredHighlightEnd READ preferredHighlightEnd WRITE setPreferredHighlightEnd NOTIFY preferredHighlightEndChanged RESET resetPreferredHighlightEnd) + Q_PROPERTY(HighlightRangeMode highlightRangeMode READ highlightRangeMode WRITE setHighlightRangeMode NOTIFY highlightRangeModeChanged) + + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY(Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged) + Q_PROPERTY(Qt::LayoutDirection effectiveLayoutDirection READ effectiveLayoutDirection NOTIFY effectiveLayoutDirectionChanged) + Q_PROPERTY(bool keyNavigationWraps READ isWrapEnabled WRITE setWrapEnabled NOTIFY keyNavigationWrapsChanged) + Q_PROPERTY(int cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged) + Q_PROPERTY(QSGViewSection *section READ sectionCriteria CONSTANT) + Q_PROPERTY(QString currentSection READ currentSection NOTIFY currentSectionChanged) + + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged) + + Q_PROPERTY(QDeclarativeComponent *header READ header WRITE setHeader NOTIFY headerChanged) + Q_PROPERTY(QDeclarativeComponent *footer READ footer WRITE setFooter NOTIFY footerChanged) + + Q_ENUMS(HighlightRangeMode) + Q_ENUMS(Orientation) + Q_ENUMS(SnapMode) + Q_ENUMS(PositionMode) + Q_CLASSINFO("DefaultProperty", "data") + +public: + QSGListView(QSGItem *parent=0); + ~QSGListView(); + + QVariant model() const; + void setModel(const QVariant &); + + QDeclarativeComponent *delegate() const; + void setDelegate(QDeclarativeComponent *); + + int currentIndex() const; + void setCurrentIndex(int idx); + + QSGItem *currentItem(); + QSGItem *highlightItem(); + int count() const; + + QDeclarativeComponent *highlight() const; + void setHighlight(QDeclarativeComponent *highlight); + + bool highlightFollowsCurrentItem() const; + void setHighlightFollowsCurrentItem(bool); + + enum HighlightRangeMode { NoHighlightRange, ApplyRange, StrictlyEnforceRange }; + HighlightRangeMode highlightRangeMode() const; + void setHighlightRangeMode(HighlightRangeMode mode); + + qreal preferredHighlightBegin() const; + void setPreferredHighlightBegin(qreal); + void resetPreferredHighlightBegin(); + + qreal preferredHighlightEnd() const; + void setPreferredHighlightEnd(qreal); + void resetPreferredHighlightEnd(); + + qreal spacing() const; + void setSpacing(qreal spacing); + + enum Orientation { Horizontal = Qt::Horizontal, Vertical = Qt::Vertical }; + Orientation orientation() const; + void setOrientation(Orientation); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection(Qt::LayoutDirection); + Qt::LayoutDirection effectiveLayoutDirection() const; + + bool isWrapEnabled() const; + void setWrapEnabled(bool); + + int cacheBuffer() const; + void setCacheBuffer(int); + + QSGViewSection *sectionCriteria(); + QString currentSection() const; + + qreal highlightMoveSpeed() const; + void setHighlightMoveSpeed(qreal); + + int highlightMoveDuration() const; + void setHighlightMoveDuration(int); + + qreal highlightResizeSpeed() const; + void setHighlightResizeSpeed(qreal); + + int highlightResizeDuration() const; + void setHighlightResizeDuration(int); + + enum SnapMode { NoSnap, SnapToItem, SnapOneItem }; + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + QDeclarativeComponent *footer() const; + void setFooter(QDeclarativeComponent *); + + QDeclarativeComponent *header() const; + void setHeader(QDeclarativeComponent *); + + virtual void setContentX(qreal pos); + virtual void setContentY(qreal pos); + + static QSGListViewAttached *qmlAttachedProperties(QObject *); + + enum PositionMode { Beginning, Center, End, Visible, Contain }; + + Q_INVOKABLE void positionViewAtIndex(int index, int mode); + Q_INVOKABLE int indexAt(qreal x, qreal y) const; + Q_INVOKABLE void positionViewAtBeginning(); + Q_INVOKABLE void positionViewAtEnd(); + +public Q_SLOTS: + void incrementCurrentIndex(); + void decrementCurrentIndex(); + +Q_SIGNALS: + void countChanged(); + void spacingChanged(); + void orientationChanged(); + void layoutDirectionChanged(); + void effectiveLayoutDirectionChanged(); + void currentIndexChanged(); + void currentSectionChanged(); + void highlightMoveSpeedChanged(); + void highlightMoveDurationChanged(); + void highlightResizeSpeedChanged(); + void highlightResizeDurationChanged(); + void highlightChanged(); + void highlightItemChanged(); + void modelChanged(); + void delegateChanged(); + void highlightFollowsCurrentItemChanged(); + void preferredHighlightBeginChanged(); + void preferredHighlightEndChanged(); + void highlightRangeModeChanged(); + void keyNavigationWrapsChanged(); + void cacheBufferChanged(); + void snapModeChanged(); + void headerChanged(); + void footerChanged(); + +protected: + virtual void updatePolish(); + virtual void viewportMoved(); + virtual qreal minYExtent() const; + virtual qreal maxYExtent() const; + virtual qreal minXExtent() const; + virtual qreal maxXExtent() const; + virtual void keyPressEvent(QKeyEvent *); + virtual void geometryChanged(const QRectF &newGeometry,const QRectF &oldGeometry); + virtual void componentComplete(); + +private Q_SLOTS: + void updateSections(); + void refill(); + void trackedPositionChanged(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int from, int to, int count); + void itemsChanged(int index, int count); + void modelReset(); + void destroyRemoved(); + void createdItem(int index, QSGItem *item); + void destroyingItem(QSGItem *item); + void animStopped(); +}; + +class QSGListViewAttached : public QObject +{ + Q_OBJECT +public: + QSGListViewAttached(QObject *parent) + : QObject(parent), m_view(0), m_isCurrent(false), m_delayRemove(false) {} + ~QSGListViewAttached() {} + + Q_PROPERTY(QSGListView *view READ view NOTIFY viewChanged) + QSGListView *view() { return m_view; } + void setView(QSGListView *view) { + if (view != m_view) { + m_view = view; + emit viewChanged(); + } + } + + Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged) + bool isCurrentItem() const { return m_isCurrent; } + void setIsCurrentItem(bool c) { + if (m_isCurrent != c) { + m_isCurrent = c; + emit currentItemChanged(); + } + } + + Q_PROPERTY(QString previousSection READ prevSection NOTIFY prevSectionChanged) + QString prevSection() const { return m_prevSection; } + void setPrevSection(const QString §) { + if (m_prevSection != sect) { + m_prevSection = sect; + emit prevSectionChanged(); + } + } + + Q_PROPERTY(QString nextSection READ nextSection NOTIFY nextSectionChanged) + QString nextSection() const { return m_nextSection; } + void setNextSection(const QString §) { + if (m_nextSection != sect) { + m_nextSection = sect; + emit nextSectionChanged(); + } + } + + Q_PROPERTY(QString section READ section NOTIFY sectionChanged) + QString section() const { return m_section; } + void setSection(const QString §) { + if (m_section != sect) { + m_section = sect; + emit sectionChanged(); + } + } + + Q_PROPERTY(bool delayRemove READ delayRemove WRITE setDelayRemove NOTIFY delayRemoveChanged) + bool delayRemove() const { return m_delayRemove; } + void setDelayRemove(bool delay) { + if (m_delayRemove != delay) { + m_delayRemove = delay; + emit delayRemoveChanged(); + } + } + + void emitAdd() { emit add(); } + void emitRemove() { emit remove(); } + +Q_SIGNALS: + void currentItemChanged(); + void sectionChanged(); + void prevSectionChanged(); + void nextSectionChanged(); + void delayRemoveChanged(); + void add(); + void remove(); + void viewChanged(); + +public: + QDeclarativeGuard<QSGListView> m_view; + mutable QString m_section; + QString m_prevSection; + QString m_nextSection; + bool m_isCurrent : 1; + bool m_delayRemove : 1; +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPEINFO(QSGListView, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QSGListView) +QML_DECLARE_TYPE(QSGViewSection) + +QT_END_HEADER + +#endif // QSGLISTVIEW_P_H diff --git a/src/declarative/items/qsgloader.cpp b/src/declarative/items/qsgloader.cpp new file mode 100644 index 0000000000..6717098506 --- /dev/null +++ b/src/declarative/items/qsgloader.cpp @@ -0,0 +1,340 @@ +// Commit: 501180c6fbed0857126da2bb0ff1f17ee35472c6 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgloader_p_p.h" + +#include <QtDeclarative/qdeclarativeinfo.h> + +#include <private/qdeclarativeengine_p.h> +#include <private/qdeclarativeglobal_p.h> + +QT_BEGIN_NAMESPACE + +QSGLoaderPrivate::QSGLoaderPrivate() + : item(0), component(0), ownComponent(false), updatingSize(false), + itemWidthValid(false), itemHeightValid(false) +{ +} + +QSGLoaderPrivate::~QSGLoaderPrivate() +{ +} + +void QSGLoaderPrivate::itemGeometryChanged(QSGItem *resizeItem, const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (resizeItem == item) { + if (!updatingSize && newGeometry.width() != oldGeometry.width()) + itemWidthValid = true; + if (!updatingSize && newGeometry.height() != oldGeometry.height()) + itemHeightValid = true; + _q_updateSize(false); + } + QSGItemChangeListener::itemGeometryChanged(resizeItem, newGeometry, oldGeometry); +} + +void QSGLoaderPrivate::clear() +{ + if (ownComponent) { + component->deleteLater(); + component = 0; + ownComponent = false; + } + source = QUrl(); + + if (item) { + QSGItemPrivate *p = QSGItemPrivate::get(item); + p->removeItemChangeListener(this, QSGItemPrivate::Geometry); + + // We can't delete immediately because our item may have triggered + // the Loader to load a different item. + item->setParentItem(0); + item->setVisible(false); + item->deleteLater(); + item = 0; + } +} + +void QSGLoaderPrivate::initResize() +{ + QSGItemPrivate *p = QSGItemPrivate::get(item); + p->addItemChangeListener(this, QSGItemPrivate::Geometry); + // We may override the item's size, so we need to remember + // whether the item provided its own valid size. + itemWidthValid = p->widthValid; + itemHeightValid = p->heightValid; + _q_updateSize(); +} + +QSGLoader::QSGLoader(QSGItem *parent) + : QSGImplicitSizeItem(*(new QSGLoaderPrivate), parent) +{ + setFlag(ItemIsFocusScope); +} + +QSGLoader::~QSGLoader() +{ + Q_D(QSGLoader); + if (d->item) { + QSGItemPrivate *p = QSGItemPrivate::get(d->item); + p->removeItemChangeListener(d, QSGItemPrivate::Geometry); + } +} + +QUrl QSGLoader::source() const +{ + Q_D(const QSGLoader); + return d->source; +} + +void QSGLoader::setSource(const QUrl &url) +{ + Q_D(QSGLoader); + if (d->source == url) + return; + + d->clear(); + + d->source = url; + if (d->source.isEmpty()) { + emit sourceChanged(); + emit statusChanged(); + emit progressChanged(); + emit itemChanged(); + return; + } + + d->component = new QDeclarativeComponent(qmlEngine(this), d->source, this); + d->ownComponent = true; + + if (isComponentComplete()) + d->load(); +} + +QDeclarativeComponent *QSGLoader::sourceComponent() const +{ + Q_D(const QSGLoader); + return d->component; +} + +void QSGLoader::setSourceComponent(QDeclarativeComponent *comp) +{ + Q_D(QSGLoader); + if (comp == d->component) + return; + + d->clear(); + + d->component = comp; + d->ownComponent = false; + if (!d->component) { + emit sourceChanged(); + emit statusChanged(); + emit progressChanged(); + emit itemChanged(); + return; + } + + if (isComponentComplete()) + d->load(); +} + +void QSGLoader::resetSourceComponent() +{ + setSourceComponent(0); +} + +void QSGLoaderPrivate::load() +{ + Q_Q(QSGLoader); + + if (!q->isComponentComplete() || !component) + return; + + if (!component->isLoading()) { + _q_sourceLoaded(); + } else { + QObject::connect(component, SIGNAL(statusChanged(QDeclarativeComponent::Status)), + q, SLOT(_q_sourceLoaded())); + QObject::connect(component, SIGNAL(progressChanged(qreal)), + q, SIGNAL(progressChanged())); + emit q->statusChanged(); + emit q->progressChanged(); + emit q->sourceChanged(); + emit q->itemChanged(); + } +} + +void QSGLoaderPrivate::_q_sourceLoaded() +{ + Q_Q(QSGLoader); + + if (component) { + if (!component->errors().isEmpty()) { + QDeclarativeEnginePrivate::warning(qmlEngine(q), component->errors()); + emit q->sourceChanged(); + emit q->statusChanged(); + emit q->progressChanged(); + return; + } + + QDeclarativeContext *creationContext = component->creationContext(); + if (!creationContext) creationContext = qmlContext(q); + QDeclarativeContext *ctxt = new QDeclarativeContext(creationContext); + ctxt->setContextObject(q); + + QDeclarativeGuard<QDeclarativeComponent> c = component; + QObject *obj = component->beginCreate(ctxt); + if (component != c) { + // component->create could trigger a change in source that causes + // component to be set to something else. In that case we just + // need to cleanup. + if (c) + c->completeCreate(); + delete obj; + delete ctxt; + return; + } + if (obj) { + item = qobject_cast<QSGItem *>(obj); + if (item) { + QDeclarative_setParent_noEvent(ctxt, obj); + QDeclarative_setParent_noEvent(item, q); + item->setParentItem(q); +// item->setFocus(true); + initResize(); + } else { + qmlInfo(q) << QSGLoader::tr("Loader does not support loading non-visual elements."); + delete obj; + delete ctxt; + } + } else { + if (!component->errors().isEmpty()) + QDeclarativeEnginePrivate::warning(qmlEngine(q), component->errors()); + delete obj; + delete ctxt; + source = QUrl(); + } + component->completeCreate(); + emit q->sourceChanged(); + emit q->statusChanged(); + emit q->progressChanged(); + emit q->itemChanged(); + emit q->loaded(); + } +} + +QSGLoader::Status QSGLoader::status() const +{ + Q_D(const QSGLoader); + + if (d->component) + return static_cast<QSGLoader::Status>(d->component->status()); + + if (d->item) + return Ready; + + return d->source.isEmpty() ? Null : Error; +} + +void QSGLoader::componentComplete() +{ + Q_D(QSGLoader); + QSGItem::componentComplete(); + d->load(); +} + +qreal QSGLoader::progress() const +{ + Q_D(const QSGLoader); + + if (d->item) + return 1.0; + + if (d->component) + return d->component->progress(); + + return 0.0; +} + +void QSGLoaderPrivate::_q_updateSize(bool loaderGeometryChanged) +{ + Q_Q(QSGLoader); + if (!item || updatingSize) + return; + + updatingSize = true; + + if (!itemWidthValid) + q->setImplicitWidth(item->implicitWidth()); + else + q->setImplicitWidth(item->width()); + if (loaderGeometryChanged && q->widthValid()) + item->setWidth(q->width()); + + if (!itemHeightValid) + q->setImplicitHeight(item->implicitHeight()); + else + q->setImplicitHeight(item->height()); + if (loaderGeometryChanged && q->heightValid()) + item->setHeight(q->height()); + + updatingSize = false; +} + +QSGItem *QSGLoader::item() const +{ + Q_D(const QSGLoader); + return d->item; +} + +void QSGLoader::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QSGLoader); + if (newGeometry != oldGeometry) { + d->_q_updateSize(); + } + QSGItem::geometryChanged(newGeometry, oldGeometry); +} + +#include <moc_qsgloader_p.cpp> + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgloader_p.h b/src/declarative/items/qsgloader_p.h new file mode 100644 index 0000000000..689971792c --- /dev/null +++ b/src/declarative/items/qsgloader_p.h @@ -0,0 +1,107 @@ +// Commit: 6f78a6080b84cc3ef96b73a4ff58d1b5a72f08f4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGLOADER_P_H +#define QSGLOADER_P_H + +#include "qsgimplicitsizeitem_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGLoaderPrivate; +class Q_AUTOTEST_EXPORT QSGLoader : public QSGImplicitSizeItem +{ + Q_OBJECT + Q_ENUMS(Status) + + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(QDeclarativeComponent *sourceComponent READ sourceComponent WRITE setSourceComponent RESET resetSourceComponent NOTIFY sourceChanged) + Q_PROPERTY(QSGItem *item READ item NOTIFY itemChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged) + +public: + QSGLoader(QSGItem *parent=0); + virtual ~QSGLoader(); + + QUrl source() const; + void setSource(const QUrl &); + + QDeclarativeComponent *sourceComponent() const; + void setSourceComponent(QDeclarativeComponent *); + void resetSourceComponent(); + + enum Status { Null, Ready, Loading, Error }; + Status status() const; + qreal progress() const; + + QSGItem *item() const; + +Q_SIGNALS: + void itemChanged(); + void sourceChanged(); + void statusChanged(); + void progressChanged(); + void loaded(); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + void componentComplete(); + +private: + Q_DISABLE_COPY(QSGLoader) + Q_DECLARE_PRIVATE(QSGLoader) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLoaded()) + Q_PRIVATE_SLOT(d_func(), void _q_updateSize()) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGLoader) + +QT_END_HEADER + +#endif // QSGLOADER_P_H diff --git a/src/declarative/items/qsgloader_p_p.h b/src/declarative/items/qsgloader_p_p.h new file mode 100644 index 0000000000..63da789dce --- /dev/null +++ b/src/declarative/items/qsgloader_p_p.h @@ -0,0 +1,91 @@ +// Commit: 5d2817cd668a705729df1727de49adf00713ac97 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGLOADER_P_P_H +#define QSGLOADER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgloader_p.h" +#include "qsgimplicitsizeitem_p_p.h" +#include "qsgitemchangelistener_p.h" + +QT_BEGIN_NAMESPACE + +class QDeclarativeContext; +class QSGLoaderPrivate : public QSGImplicitSizeItemPrivate, public QSGItemChangeListener +{ + Q_DECLARE_PUBLIC(QSGLoader) + +public: + QSGLoaderPrivate(); + ~QSGLoaderPrivate(); + + void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry); + void clear(); + void initResize(); + void load(); + + QUrl source; + QSGItem *item; + QDeclarativeComponent *component; + bool ownComponent : 1; + bool updatingSize: 1; + bool itemWidthValid : 1; + bool itemHeightValid : 1; + + void _q_sourceLoaded(); + void _q_updateSize(bool loaderGeometryChanged = true); +}; + +QT_END_NAMESPACE + +#endif // QSGLOADER_P_P_H diff --git a/src/declarative/items/qsgmousearea.cpp b/src/declarative/items/qsgmousearea.cpp new file mode 100644 index 0000000000..887d78a64d --- /dev/null +++ b/src/declarative/items/qsgmousearea.cpp @@ -0,0 +1,800 @@ +// Commit: e1ffbc04131dc6f76fa76821c297d08162e4b1ee +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgmousearea_p.h" +#include "qsgmousearea_p_p.h" +#include "qsgcanvas.h" +#include "qsgevents_p_p.h" + +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qapplication.h> + +#include <float.h> + +QT_BEGIN_NAMESPACE +static const int PressAndHoldDelay = 800; + +QSGDrag::QSGDrag(QObject *parent) +: QObject(parent), _target(0), _axis(XandYAxis), _xmin(-FLT_MAX), _xmax(FLT_MAX), _ymin(-FLT_MAX), _ymax(FLT_MAX), +_active(false), _filterChildren(false) +{ +} + +QSGDrag::~QSGDrag() +{ +} + +QSGItem *QSGDrag::target() const +{ + return _target; +} + +void QSGDrag::setTarget(QSGItem *t) +{ + if (_target == t) + return; + _target = t; + emit targetChanged(); +} + +void QSGDrag::resetTarget() +{ + if (!_target) + return; + _target = 0; + emit targetChanged(); +} + +QSGDrag::Axis QSGDrag::axis() const +{ + return _axis; +} + +void QSGDrag::setAxis(QSGDrag::Axis a) +{ + if (_axis == a) + return; + _axis = a; + emit axisChanged(); +} + +qreal QSGDrag::xmin() const +{ + return _xmin; +} + +void QSGDrag::setXmin(qreal m) +{ + if (_xmin == m) + return; + _xmin = m; + emit minimumXChanged(); +} + +qreal QSGDrag::xmax() const +{ + return _xmax; +} + +void QSGDrag::setXmax(qreal m) +{ + if (_xmax == m) + return; + _xmax = m; + emit maximumXChanged(); +} + +qreal QSGDrag::ymin() const +{ + return _ymin; +} + +void QSGDrag::setYmin(qreal m) +{ + if (_ymin == m) + return; + _ymin = m; + emit minimumYChanged(); +} + +qreal QSGDrag::ymax() const +{ + return _ymax; +} + +void QSGDrag::setYmax(qreal m) +{ + if (_ymax == m) + return; + _ymax = m; + emit maximumYChanged(); +} + +bool QSGDrag::active() const +{ + return _active; +} + +void QSGDrag::setActive(bool drag) +{ + if (_active == drag) + return; + _active = drag; + emit activeChanged(); +} + +bool QSGDrag::filterChildren() const +{ + return _filterChildren; +} + +void QSGDrag::setFilterChildren(bool filter) +{ + if (_filterChildren == filter) + return; + _filterChildren = filter; + emit filterChildrenChanged(); +} + +QSGMouseAreaPrivate::QSGMouseAreaPrivate() +: absorb(true), hovered(false), pressed(false), longPress(false), + moved(false), stealMouse(false), doubleClick(false), preventStealing(false), drag(0) +{ + Q_Q(QSGMouseArea); + forwardTo = QDeclarativeListProperty<QSGItem>(q, forwardToList); +} + +QSGMouseAreaPrivate::~QSGMouseAreaPrivate() +{ + delete drag; +} + +void QSGMouseAreaPrivate::init() +{ + Q_Q(QSGMouseArea); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFiltersChildMouseEvents(true); +} + +void QSGMouseAreaPrivate::saveEvent(QGraphicsSceneMouseEvent *event) +{ + lastPos = event->pos(); + lastScenePos = event->scenePos(); + lastButton = event->button(); + lastButtons = event->buttons(); + lastModifiers = event->modifiers(); +} + +void QSGMouseAreaPrivate::forwardEvent(QGraphicsSceneMouseEvent* event) +{ + Q_Q(QSGMouseArea); + for(int i=0; i < forwardToList.count(); i++){ + event->setPos(forwardToList[i]->mapFromScene(event->scenePos())); + forwardToList[i]->canvas()->sendEvent(forwardToList[i], event); + if(event->isAccepted()) + break; + } + event->setPos(q->mapFromScene(event->scenePos())); +} + +bool QSGMouseAreaPrivate::isPressAndHoldConnected() +{ + Q_Q(QSGMouseArea); + static int idx = QObjectPrivate::get(q)->signalIndex("pressAndHold(QSGMouseEvent*)"); + return QObjectPrivate::get(q)->isSignalConnected(idx); +} + +bool QSGMouseAreaPrivate::isDoubleClickConnected() +{ + Q_Q(QSGMouseArea); + static int idx = QObjectPrivate::get(q)->signalIndex("doubleClicked(QSGMouseEvent*)"); + return QObjectPrivate::get(q)->isSignalConnected(idx); +} + +bool QSGMouseAreaPrivate::isClickConnected() +{ + Q_Q(QSGMouseArea); + static int idx = QObjectPrivate::get(q)->signalIndex("clicked(QSGMouseEvent*)"); + return QObjectPrivate::get(q)->isSignalConnected(idx); +} + +void QSGMouseAreaPrivate::propagate(QSGMouseEvent* event, PropagateType t) +{ + Q_Q(QSGMouseArea); + QPointF scenePos = q->mapToScene(QPointF(event->x(), event->y())); + propagateHelper(event, canvas->rootItem(), scenePos, t); +} + +bool QSGMouseAreaPrivate::propagateHelper(QSGMouseEvent *ev, QSGItem *item,const QPointF &sp, PropagateType sig) +{ + //Based off of QSGCanvas::deliverInitialMousePressEvent + //But specific to MouseArea, so doesn't belong in canvas + Q_Q(const QSGMouseArea); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + if (itemPrivate->opacity == 0.0) + return false; + + if (itemPrivate->flags & QSGItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(sp); + if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + return false; + } + + QList<QSGItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QSGItem *child = children.at(ii); + if (!child->isVisible() || !child->isEnabled()) + continue; + if (propagateHelper(ev, child, sp, sig)) + return true; + } + + QSGMouseArea* ma = qobject_cast<QSGMouseArea*>(item); + if (ma && ma != q && itemPrivate->acceptedMouseButtons & ev->button()) { + switch(sig){ + case Click: + if (!ma->d_func()->isClickConnected()) + return false; + break; + case DoubleClick: + if (!ma->d_func()->isDoubleClickConnected()) + return false; + break; + case PressAndHold: + if (!ma->d_func()->isPressAndHoldConnected()) + return false; + break; + } + QPointF p = item->mapFromScene(sp); + if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + ev->setX(p.x()); + ev->setY(p.y()); + ev->setAccepted(true);//It is connected, they have to explicitly ignore to let it slide + switch(sig){ + case Click: emit ma->clicked(ev); break; + case DoubleClick: emit ma->doubleClicked(ev); break; + case PressAndHold: emit ma->pressAndHold(ev); break; + } + if (ev->isAccepted()) + return true; + } + } + return false; + +} + +/* + Behavioral Change in QtQuick 2.0 + + From QtQuick 2.0, the signals clicked, doubleClicked and pressAndHold have a different interaction + model with regards to the delivery of events to multiple overlapping MouseAreas. These signals will now propagate + to all MouseAreas in the area, in painting order, until accepted by one of them. A signal is accepted by + default if there is a signal handler for it, use mouse.accepted = false; to ignore. This propagation + can send the signal to MouseAreas other than the one which accepted the press event, although that MouseArea + will receive the signal first. + + 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. + */ +QSGMouseArea::QSGMouseArea(QSGItem *parent) + : QSGItem(*(new QSGMouseAreaPrivate), parent) +{ + Q_D(QSGMouseArea); + d->init(); +} + +QSGMouseArea::~QSGMouseArea() +{ +} + +qreal QSGMouseArea::mouseX() const +{ + Q_D(const QSGMouseArea); + return d->lastPos.x(); +} + +qreal QSGMouseArea::mouseY() const +{ + Q_D(const QSGMouseArea); + return d->lastPos.y(); +} + +bool QSGMouseArea::isEnabled() const +{ + Q_D(const QSGMouseArea); + return d->absorb; +} + +void QSGMouseArea::setEnabled(bool a) +{ + Q_D(QSGMouseArea); + if (a != d->absorb) { + d->absorb = a; + emit enabledChanged(); + } +} + +bool QSGMouseArea::preventStealing() const +{ + Q_D(const QSGMouseArea); + return d->preventStealing; +} + +void QSGMouseArea::setPreventStealing(bool prevent) +{ + Q_D(QSGMouseArea); + if (prevent != d->preventStealing) { + d->preventStealing = prevent; + setKeepMouseGrab(d->preventStealing && d->absorb); + emit preventStealingChanged(); + } +} + +Qt::MouseButtons QSGMouseArea::pressedButtons() const +{ + Q_D(const QSGMouseArea); + return d->lastButtons; +} + +void QSGMouseArea::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGMouseArea); + d->moved = false; + d->stealMouse = d->preventStealing; + if (!d->absorb) + QSGItem::mousePressEvent(event); + else { + d->longPress = false; + d->saveEvent(event); + if (d->drag) { + d->dragX = drag()->axis() & QSGDrag::XAxis; + d->dragY = drag()->axis() & QSGDrag::YAxis; + } + if (d->drag) + d->drag->setActive(false); + setHovered(true); + d->startScene = event->scenePos(); + d->pressAndHoldTimer.start(PressAndHoldDelay, this); + setKeepMouseGrab(d->stealMouse); + event->setAccepted(setPressed(true)); + + if(!event->isAccepted() && d->forwardToList.count()) + d->forwardEvent(event); + } +} + +void QSGMouseArea::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGMouseArea); + if (!d->absorb) { + QSGItem::mouseMoveEvent(event); + return; + } + + d->saveEvent(event); + + // ### we should skip this if these signals aren't used + // ### can GV handle this for us? + bool contains = boundingRect().contains(d->lastPos); + if (d->hovered && !contains) + setHovered(false); + else if (!d->hovered && contains) + setHovered(true); + + if (d->drag && d->drag->target()) { + if (!d->moved) { + d->startX = drag()->target()->x(); + d->startY = drag()->target()->y(); + } + + QPointF startLocalPos; + QPointF curLocalPos; + if (drag()->target()->parentItem()) { + startLocalPos = drag()->target()->parentItem()->mapFromScene(d->startScene); + curLocalPos = drag()->target()->parentItem()->mapFromScene(event->scenePos()); + } else { + startLocalPos = d->startScene; + curLocalPos = event->scenePos(); + } + + const int dragThreshold = QApplication::startDragDistance(); + qreal dx = qAbs(curLocalPos.x() - startLocalPos.x()); + qreal dy = qAbs(curLocalPos.y() - startLocalPos.y()); + + if (keepMouseGrab() && d->stealMouse) + d->drag->setActive(true); + + if (d->dragX && d->drag->active()) { + qreal x = (curLocalPos.x() - startLocalPos.x()) + d->startX; + if (x < drag()->xmin()) + x = drag()->xmin(); + else if (x > drag()->xmax()) + x = drag()->xmax(); + drag()->target()->setX(x); + } + if (d->dragY && d->drag->active()) { + qreal y = (curLocalPos.y() - startLocalPos.y()) + d->startY; + if (y < drag()->ymin()) + y = drag()->ymin(); + else if (y > drag()->ymax()) + y = drag()->ymax(); + drag()->target()->setY(y); + } + + if (!keepMouseGrab()) { + if ((!d->dragY && dy < dragThreshold && d->dragX && dx > dragThreshold) + || (!d->dragX && dx < dragThreshold && d->dragY && dy > dragThreshold) + || (d->dragX && d->dragY && (dx > dragThreshold || dy > dragThreshold))) { + setKeepMouseGrab(true); + d->stealMouse = true; + } + } + + d->moved = true; + } + QSGMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, false, d->longPress); + emit mousePositionChanged(&me); + me.setX(d->lastPos.x()); + me.setY(d->lastPos.y()); + emit positionChanged(&me); + + if(!event->isAccepted() && d->forwardToList.count()) + d->forwardEvent(event); +} + +void QSGMouseArea::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGMouseArea); + d->stealMouse = false; + if (!d->absorb) { + QSGItem::mouseReleaseEvent(event); + } else { + d->saveEvent(event); + setPressed(false); + if (d->drag) + d->drag->setActive(false); + // If we don't accept hover, we need to reset containsMouse. + if (!acceptHoverEvents()) + setHovered(false); + QSGCanvas *c = canvas(); + if (c && c->mouseGrabberItem() == this) + ungrabMouse(); + setKeepMouseGrab(false); + + if(!event->isAccepted() && d->forwardToList.count()) + d->forwardEvent(event); + } + d->doubleClick = false; +} + +void QSGMouseArea::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGMouseArea); + if (!d->absorb) { + QSGItem::mouseDoubleClickEvent(event); + } else { + d->saveEvent(event); + QSGMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, true, false); + me.setAccepted(d->isDoubleClickConnected()); + emit this->doubleClicked(&me); + if (!me.isAccepted()) + d->propagate(&me, QSGMouseAreaPrivate::DoubleClick); + d->doubleClick = d->isDoubleClickConnected() || me.isAccepted(); + QSGItem::mouseDoubleClickEvent(event); + } +} + +void QSGMouseArea::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QSGMouseArea); + if (!d->absorb) { + QSGItem::hoverEnterEvent(event); + } else { + d->lastPos = event->pos(); + setHovered(true); + QSGMouseEvent me(d->lastPos.x(), d->lastPos.y(), Qt::NoButton, Qt::NoButton, event->modifiers(), false, false); + emit mousePositionChanged(&me); + } +} + +void QSGMouseArea::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QSGMouseArea); + if (!d->absorb) { + QSGItem::hoverMoveEvent(event); + } else { + d->lastPos = event->pos(); + QSGMouseEvent me(d->lastPos.x(), d->lastPos.y(), Qt::NoButton, Qt::NoButton, event->modifiers(), false, false); + emit mousePositionChanged(&me); + me.setX(d->lastPos.x()); + me.setY(d->lastPos.y()); + emit positionChanged(&me); + } +} + +void QSGMouseArea::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QSGMouseArea); + if (!d->absorb) + QSGItem::hoverLeaveEvent(event); + else + setHovered(false); +} + +void QSGMouseArea::mouseUngrabEvent() +{ + Q_D(QSGMouseArea); + if (d->pressed) { + // if our mouse grab has been removed (probably by Flickable), fix our + // state + d->pressed = false; + d->stealMouse = false; + setKeepMouseGrab(false); + emit canceled(); + emit pressedChanged(); + if (d->hovered) { + d->hovered = false; + emit hoveredChanged(); + } + } +} + +bool QSGMouseArea::sendMouseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGMouseArea); + QGraphicsSceneMouseEvent mouseEvent(event->type()); + QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + + QSGCanvas *c = canvas(); + QSGItem *grabber = c ? c->mouseGrabberItem() : 0; + bool stealThisEvent = d->stealMouse; + if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) { + mouseEvent.setAccepted(false); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button))); + } + } + mouseEvent.setScenePos(event->scenePos()); + mouseEvent.setLastScenePos(event->lastScenePos()); + mouseEvent.setPos(mapFromScene(event->scenePos())); + mouseEvent.setLastPos(mapFromScene(event->lastScenePos())); + + switch(mouseEvent.type()) { + case QEvent::GraphicsSceneMouseMove: + mouseMoveEvent(&mouseEvent); + break; + case QEvent::GraphicsSceneMousePress: + mousePressEvent(&mouseEvent); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(&mouseEvent); + break; + default: + break; + } + grabber = c->mouseGrabberItem(); + if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) + grabMouse(); + + return stealThisEvent; + } + if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) { + if (d->pressed) { + d->pressed = false; + d->stealMouse = false; + if (c && c->mouseGrabberItem() == this) + ungrabMouse(); + emit canceled(); + emit pressedChanged(); + if (d->hovered) { + d->hovered = false; + emit hoveredChanged(); + } + } + } + return false; +} + +bool QSGMouseArea::childMouseEventFilter(QSGItem *i, QEvent *e) +{ + Q_D(QSGMouseArea); + if (!d->absorb || !isVisible() || !d->drag || !d->drag->filterChildren()) + return QSGItem::childMouseEventFilter(i, e); + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); + default: + break; + } + + return QSGItem::childMouseEventFilter(i, e); +} + +void QSGMouseArea::timerEvent(QTimerEvent *event) +{ + Q_D(QSGMouseArea); + if (event->timerId() == d->pressAndHoldTimer.timerId()) { + d->pressAndHoldTimer.stop(); + bool dragged = d->drag && d->drag->active(); + if (d->pressed && dragged == false && d->hovered == true) { + d->longPress = true; + QSGMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, false, d->longPress); + me.setAccepted(d->isPressAndHoldConnected()); + emit pressAndHold(&me); + if (!me.isAccepted()) + d->propagate(&me, QSGMouseAreaPrivate::PressAndHold); + } + } +} + +void QSGMouseArea::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + Q_D(QSGMouseArea); + QSGItem::geometryChanged(newGeometry, oldGeometry); + + if (d->lastScenePos.isNull) + d->lastScenePos = mapToScene(d->lastPos); + else if (newGeometry.x() != oldGeometry.x() || newGeometry.y() != oldGeometry.y()) + d->lastPos = mapFromScene(d->lastScenePos); +} + +void QSGMouseArea::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QSGMouseArea); + switch (change) { + case ItemVisibleHasChanged: + if (acceptHoverEvents() && d->hovered != (isVisible() && isUnderMouse())) + setHovered(!d->hovered); + break; + default: + break; + } + + QSGItem::itemChange(change, value); +} + +bool QSGMouseArea::hoverEnabled() const +{ + return acceptHoverEvents(); +} + +void QSGMouseArea::setHoverEnabled(bool h) +{ + Q_D(QSGMouseArea); + if (h == acceptHoverEvents()) + return; + + setAcceptHoverEvents(h); + emit hoverEnabledChanged(); + if (d->hovered != isUnderMouse()) + setHovered(!d->hovered); +} + +bool QSGMouseArea::hovered() const +{ + Q_D(const QSGMouseArea); + return d->hovered; +} + +bool QSGMouseArea::pressed() const +{ + Q_D(const QSGMouseArea); + return d->pressed; +} + +void QSGMouseArea::setHovered(bool h) +{ + Q_D(QSGMouseArea); + if (d->hovered != h) { + d->hovered = h; + emit hoveredChanged(); + d->hovered ? emit entered() : emit exited(); + } +} + +Qt::MouseButtons QSGMouseArea::acceptedButtons() const +{ + return acceptedMouseButtons(); +} + +void QSGMouseArea::setAcceptedButtons(Qt::MouseButtons buttons) +{ + if (buttons != acceptedMouseButtons()) { + setAcceptedMouseButtons(buttons); + emit acceptedButtonsChanged(); + } +} + +bool QSGMouseArea::setPressed(bool p) +{ + Q_D(QSGMouseArea); + bool dragged = d->drag && d->drag->active(); + bool isclick = d->pressed == true && p == false && dragged == false && d->hovered == true; + + if (d->pressed != p) { + d->pressed = p; + QSGMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, isclick, d->longPress); + if (d->pressed) { + if (!d->doubleClick) + emit pressed(&me); + me.setX(d->lastPos.x()); + me.setY(d->lastPos.y()); + emit mousePositionChanged(&me); + emit pressedChanged(); + } else { + emit released(&me); + me.setX(d->lastPos.x()); + me.setY(d->lastPos.y()); + emit pressedChanged(); + if (isclick && !d->longPress && !d->doubleClick){ + me.setAccepted(d->isClickConnected()); + emit clicked(&me); + if (!me.isAccepted()) + d->propagate(&me, QSGMouseAreaPrivate::Click); + } + } + + return me.isAccepted(); + } + return false; +} + +QSGDrag *QSGMouseArea::drag() +{ + Q_D(QSGMouseArea); + if (!d->drag) + d->drag = new QSGDrag; + return d->drag; +} + +QDeclarativeListProperty<QSGItem> QSGMouseArea::forwardTo() +{ + Q_D(QSGMouseArea); + return d->forwardTo; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgmousearea_p.h b/src/declarative/items/qsgmousearea_p.h new file mode 100644 index 0000000000..469b9f7168 --- /dev/null +++ b/src/declarative/items/qsgmousearea_p.h @@ -0,0 +1,219 @@ +// Commit: c6e6a35aeb8794d68a3ca0c4e27a3a1181c066b5 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGMOUSEAREA_P_H +#define QSGMOUSEAREA_P_H + +#include "qsgitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_AUTOTEST_EXPORT QSGDrag : public QObject +{ + Q_OBJECT + + Q_ENUMS(Axis) + Q_PROPERTY(QSGItem *target READ target WRITE setTarget NOTIFY targetChanged RESET resetTarget) + Q_PROPERTY(Axis axis READ axis WRITE setAxis NOTIFY axisChanged) + Q_PROPERTY(qreal minimumX READ xmin WRITE setXmin NOTIFY minimumXChanged) + Q_PROPERTY(qreal maximumX READ xmax WRITE setXmax NOTIFY maximumXChanged) + Q_PROPERTY(qreal minimumY READ ymin WRITE setYmin NOTIFY minimumYChanged) + Q_PROPERTY(qreal maximumY READ ymax WRITE setYmax NOTIFY maximumYChanged) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + Q_PROPERTY(bool filterChildren READ filterChildren WRITE setFilterChildren NOTIFY filterChildrenChanged) + //### consider drag and drop + +public: + QSGDrag(QObject *parent=0); + ~QSGDrag(); + + QSGItem *target() const; + void setTarget(QSGItem *); + void resetTarget(); + + enum Axis { XAxis=0x01, YAxis=0x02, XandYAxis=0x03 }; + Axis axis() const; + void setAxis(Axis); + + qreal xmin() const; + void setXmin(qreal); + qreal xmax() const; + void setXmax(qreal); + qreal ymin() const; + void setYmin(qreal); + qreal ymax() const; + void setYmax(qreal); + + bool active() const; + void setActive(bool); + + bool filterChildren() const; + void setFilterChildren(bool); + +Q_SIGNALS: + void targetChanged(); + void axisChanged(); + void minimumXChanged(); + void maximumXChanged(); + void minimumYChanged(); + void maximumYChanged(); + void activeChanged(); + void filterChildrenChanged(); + +private: + QSGItem *_target; + Axis _axis; + qreal _xmin; + qreal _xmax; + qreal _ymin; + qreal _ymax; + bool _active : 1; + bool _filterChildren: 1; + Q_DISABLE_COPY(QSGDrag) +}; + +class QSGMouseEvent; +class QSGMouseAreaPrivate; +class Q_AUTOTEST_EXPORT QSGMouseArea : public QSGItem +{ + Q_OBJECT + + Q_PROPERTY(qreal mouseX READ mouseX NOTIFY mousePositionChanged) + Q_PROPERTY(qreal mouseY READ mouseY NOTIFY mousePositionChanged) + Q_PROPERTY(bool containsMouse READ hovered NOTIFY hoveredChanged) + Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(Qt::MouseButtons pressedButtons READ pressedButtons NOTIFY pressedChanged) + Q_PROPERTY(Qt::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY acceptedButtonsChanged) + Q_PROPERTY(bool hoverEnabled READ hoverEnabled WRITE setHoverEnabled NOTIFY hoverEnabledChanged) + Q_PROPERTY(QSGDrag *drag READ drag CONSTANT) //### add flicking to QSGDrag or add a QDeclarativeFlick ??? + Q_PROPERTY(bool preventStealing READ preventStealing WRITE setPreventStealing NOTIFY preventStealingChanged) + Q_PROPERTY(QDeclarativeListProperty<QSGItem> forwardTo READ forwardTo); + +public: + QSGMouseArea(QSGItem *parent=0); + ~QSGMouseArea(); + + qreal mouseX() const; + qreal mouseY() const; + + bool isEnabled() const; + void setEnabled(bool); + + bool hovered() const; + bool pressed() const; + + Qt::MouseButtons pressedButtons() const; + + Qt::MouseButtons acceptedButtons() const; + void setAcceptedButtons(Qt::MouseButtons buttons); + + bool hoverEnabled() const; + void setHoverEnabled(bool h); + + QSGDrag *drag(); + + bool preventStealing() const; + void setPreventStealing(bool prevent); + + QDeclarativeListProperty<QSGItem> forwardTo(); + +Q_SIGNALS: + void hoveredChanged(); + void pressedChanged(); + void enabledChanged(); + void acceptedButtonsChanged(); + void hoverEnabledChanged(); + void positionChanged(QSGMouseEvent *mouse); + void mousePositionChanged(QSGMouseEvent *mouse); + void preventStealingChanged(); + + void pressed(QSGMouseEvent *mouse); + void pressAndHold(QSGMouseEvent *mouse); + void released(QSGMouseEvent *mouse); + void clicked(QSGMouseEvent *mouse); + void doubleClicked(QSGMouseEvent *mouse); + void entered(); + void exited(); + void canceled(); + +protected: + void setHovered(bool); + bool setPressed(bool); + bool sendMouseEvent(QGraphicsSceneMouseEvent *event); + + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseUngrabEvent(); + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + virtual bool childMouseEventFilter(QSGItem *i, QEvent *e); + virtual void timerEvent(QTimerEvent *event); + + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + virtual void itemChange(ItemChange change, const ItemChangeData& value); + +private: + void handlePress(); + void handleRelease(); + +private: + Q_DISABLE_COPY(QSGMouseArea) + Q_DECLARE_PRIVATE(QSGMouseArea) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGDrag) +QML_DECLARE_TYPE(QSGMouseArea) + +QT_END_HEADER + +#endif // QSGMOUSEAREA_P_H diff --git a/src/declarative/items/qsgmousearea_p_p.h b/src/declarative/items/qsgmousearea_p_p.h new file mode 100644 index 0000000000..e736c059a2 --- /dev/null +++ b/src/declarative/items/qsgmousearea_p_p.h @@ -0,0 +1,115 @@ +// Commit: c6e6a35aeb8794d68a3ca0c4e27a3a1181c066b5 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGMOUSEAREA_P_P_H +#define QSGMOUSEAREA_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgitem_p.h" + +#include <QtGui/qgraphicssceneevent.h> +#include <QtCore/qbasictimer.h> + +QT_BEGIN_NAMESPACE + +class QSGMouseEvent; +class QSGMouseArea; +class QSGMouseAreaPrivate : public QSGItemPrivate +{ + Q_DECLARE_PUBLIC(QSGMouseArea) + +public: + QSGMouseAreaPrivate(); + ~QSGMouseAreaPrivate(); + void init(); + + void saveEvent(QGraphicsSceneMouseEvent *event); + enum PropagateType{ + Click, + DoubleClick, + PressAndHold + }; + void propagate(QSGMouseEvent* event, PropagateType); + bool propagateHelper(QSGMouseEvent*, QSGItem*,const QPointF &, PropagateType); + void forwardEvent(QGraphicsSceneMouseEvent* event); + + bool isPressAndHoldConnected(); + bool isDoubleClickConnected(); + bool isClickConnected(); + + bool absorb : 1; + bool hovered : 1; + bool pressed : 1; + bool longPress : 1; + bool moved : 1; + bool dragX : 1; + bool dragY : 1; + bool stealMouse : 1; + bool doubleClick : 1; + bool preventStealing : 1; + QSGDrag *drag; + QPointF startScene; + qreal startX; + qreal startY; + QPointF lastPos; + QDeclarativeNullableValue<QPointF> lastScenePos; + Qt::MouseButton lastButton; + Qt::MouseButtons lastButtons; + Qt::KeyboardModifiers lastModifiers; + QBasicTimer pressAndHoldTimer; + QDeclarativeListProperty<QSGItem> forwardTo; + QList<QSGItem*> forwardToList; +}; + +QT_END_NAMESPACE + +#endif // QSGMOUSEAREA_P_P_H diff --git a/src/declarative/items/qsgninepatchnode.cpp b/src/declarative/items/qsgninepatchnode.cpp new file mode 100644 index 0000000000..045dd6c94f --- /dev/null +++ b/src/declarative/items/qsgninepatchnode.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgninepatchnode_p.h" +#include <private/qsgadaptationlayer_p.h> +#include <private/qmath_p.h> + +QSGNinePatchNode::QSGNinePatchNode() + : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0) + , m_horizontalTileMode(QSGBorderImage::Stretch) + , m_verticalTileMode(QSGBorderImage::Stretch) + , m_dirtyGeometry(false) +{ + setOpaqueMaterial(&m_material); + setMaterial(&m_materialO); + setGeometry(&m_geometry); + m_geometry.setDrawingMode(GL_TRIANGLES); +} + +void QSGNinePatchNode::setInnerRect(const QRectF &rect) +{ + if (m_innerRect == rect) + return; + m_innerRect = rect; + m_dirtyGeometry = true; +} + +void QSGNinePatchNode::setRect(const QRectF &rect) +{ + if (m_targetRect == rect) + return; + m_targetRect = rect; + m_dirtyGeometry = true; +} + +void QSGNinePatchNode::setHorzontalTileMode(QSGBorderImage::TileMode mode) +{ + if (mode == QSGBorderImage::TileMode(m_horizontalTileMode)) + return; + m_horizontalTileMode = mode; + m_dirtyGeometry = true; +} + + +void QSGNinePatchNode::setVerticalTileMode(QSGBorderImage::TileMode mode) +{ + if (mode == QSGBorderImage::TileMode(m_verticalTileMode)) + return; + m_verticalTileMode = mode; + m_dirtyGeometry = true; +} + + +void QSGNinePatchNode::setFiltering(QSGTexture::Filtering filtering) +{ + if (m_material.filtering() == filtering) + return; + + m_material.setFiltering(filtering); + m_materialO.setFiltering(filtering); + markDirty(DirtyMaterial); +} + +QSGTexture::Filtering QSGNinePatchNode::filtering() const +{ + return m_material.filtering(); +} + +void QSGNinePatchNode::setTexture(QSGTexture *texture) +{ + if (texture == m_material.texture()) + return; + m_material.setTexture(texture); + m_materialO.setTexture(texture); + markDirty(DirtyMaterial); +} + +QSGTexture *QSGNinePatchNode::texture() const +{ + return m_material.texture(); +} + +void QSGNinePatchNode::update() +{ + if (!m_dirtyGeometry) + return; + + // For stretch this algorithm could be simplified to use less vertices + // as more vertices could be reused then, but I doubt its where our main + // problem will lie. This way, we at least share the algorithm between all + + Q_ASSERT(m_material.texture()); + + float tw = m_material.texture()->textureSize().width(); + float th = m_material.texture()->textureSize().height(); + + float rightBorder = tw - m_innerRect.right(); + float bottomBorder = th - m_innerRect.bottom(); + +// qDebug() << m_innerRect << m_targetRect << m_horizontalTileMode << m_verticalTileMode; + + int xChunkCount = 0; // Number of chunks + float xChunkSize = 0; // Size of chunk in pixels + float xTexSize = m_innerRect.width(); // Size of the texture to stretch/tile + float xSize = m_targetRect.width() - m_innerRect.left() - rightBorder; // Size of area to fill with chunks + + if (m_horizontalTileMode == QSGBorderImage::Repeat) { + xChunkCount = qCeil(xSize / xTexSize); + xChunkSize = xTexSize; + } else if (m_horizontalTileMode == QSGBorderImage::Round) { + xChunkCount = qCeil(xSize / xTexSize); + qreal fullWidth = xChunkCount * xTexSize; + xChunkSize = xTexSize * xSize / fullWidth; + } else { + xChunkCount = 1; + xChunkSize = xSize; + } + + int yChunkCount = 0; + float yChunkSize = 0; // Relative to target rect. + float yTexSize = m_innerRect.height(); // Size of the texture to stretch/tile + float ySize = m_targetRect.height() - m_innerRect.top() - bottomBorder; + + if (m_verticalTileMode == QSGBorderImage::Repeat) { + yChunkCount = qCeil(ySize / yTexSize); + yChunkSize = yTexSize; + } else if (m_verticalTileMode == QSGBorderImage::Round) { + yChunkCount = qCeil(ySize / yTexSize); + qreal fullHeight = yChunkCount * yTexSize; + yChunkSize = yTexSize * ySize / fullHeight; + } else { + yChunkCount = 1; + yChunkSize = ySize; + } + + int xTotalChunkCount = xChunkCount + 2; + int yTotalChunkCount = yChunkCount + 2; + + int totalChunkCount = xTotalChunkCount * yTotalChunkCount; + int vertexCount = totalChunkCount * 4; + int indexCount = totalChunkCount * 6; + + if (vertexCount != m_geometry.vertexCount() || indexCount != m_geometry.indexCount()) + m_geometry.allocate(vertexCount, indexCount); + + QSGGeometry::TexturedPoint2D *v = m_geometry.vertexDataAsTexturedPoint2D(); + + + // Fill in the vertices.. The loop below is pretty much an exact replica + // of the one inside fillRow. + float yTexChunk1 = m_innerRect.top() / th; + float yTexChunk2 = m_innerRect.bottom() / th; + + fillRow(v, 0, 0, xChunkCount, xChunkSize); + fillRow(v, m_innerRect.y(), yTexChunk1, xChunkCount, xChunkSize); + + for (int yc=0; yc<yChunkCount; ++yc) { + float yy = m_innerRect.y() + yChunkSize * yc; + fillRow(v, yy, yTexChunk1, xChunkCount, xChunkSize); + + // Special case the last one + if (yc == yChunkCount - 1) { + float t = m_verticalTileMode == QSGBorderImage::Repeat + ? yTexChunk1 + (yTexChunk2 - yTexChunk1) * (m_targetRect.height() - bottomBorder - yy) / yChunkSize + : yTexChunk2; + fillRow(v, m_targetRect.height() - bottomBorder, t, xChunkCount, xChunkSize); + } else { + fillRow(v, yy + yChunkSize, yTexChunk2, xChunkCount, xChunkSize); + } + } + + fillRow(v, m_targetRect.height() - bottomBorder, yTexChunk2, xChunkCount, xChunkSize); + fillRow(v, m_targetRect.height(), 1, xChunkCount, xChunkSize); + + +// v = m_geometry.vertexDataAsTexturedPoint2D(); +// for (int i=0; i<m_geometry.vertexCount(); ++i) { +// printf("Vertex: %d: (%.3f, %.3f) - (%.3f, %.3f)\n", +// i, +// v->x, v->y, v->tx, v->ty); +// ++v; +// } + + quint16 *i = m_geometry.indexDataAsUShort(); + int row = xTotalChunkCount * 2; + for (int r=0; r<yTotalChunkCount; ++r) { + int offset = r * row * 2; + for (int c=0; c<xTotalChunkCount; ++c) { + *i++ = offset + c * 2; + *i++ = offset + c * 2 + 1; + *i++ = offset + c * 2 + row; + *i++ = offset + c * 2 + 1; + *i++ = offset + c * 2 + row + 1; + *i++ = offset + c * 2 + row; + } + } + +// i = m_geometry.indexDataAsUShort(); +// for (int idx=0; idx<m_geometry.indexCount(); idx+=6) { +// printf("%2d: ", idx / 6); +// for (int s=0; s<6; ++s) +// printf(" %d", i[idx + s]); +// printf("\n"); +// } + + markDirty(QSGNode::DirtyGeometry); +} + +void QSGNinePatchNode::fillRow(QSGGeometry::TexturedPoint2D *&v, float y, float ty, int xChunkCount, float xChunkSize) +{ + float tw = m_material.texture()->textureSize().width(); + float rightBorder = tw - m_innerRect.right(); + float xTexChunk1 = m_innerRect.left() / tw; + float xTexChunk2 = m_innerRect.right() / tw; + + v++->set(0, y, 0, ty); + v++->set(m_innerRect.x(), y, xTexChunk1, ty); + + for (int xc=0; xc<xChunkCount; ++xc) { + float xx = m_innerRect.x() + xChunkSize * xc; + v++->set(xx, y, xTexChunk1, ty); + + // Special case the last one + if (xc == xChunkCount - 1) { + float t = m_horizontalTileMode == QSGBorderImage::Repeat + ? xTexChunk1 + (xTexChunk2 - xTexChunk1) * (m_targetRect.width() - rightBorder - xx) / xChunkSize + : xTexChunk2; + v->set(m_targetRect.width() - rightBorder, y, t, ty); + } else { + v->set(xx + xChunkSize, y, xTexChunk2, ty); + } + ++v; + } + + v++->set(m_targetRect.width() - rightBorder, y, xTexChunk2, ty); + v++->set(m_targetRect.width(), y, 1, ty); +} diff --git a/src/declarative/items/qsgninepatchnode_p.h b/src/declarative/items/qsgninepatchnode_p.h new file mode 100644 index 0000000000..533495d3ce --- /dev/null +++ b/src/declarative/items/qsgninepatchnode_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGNINEPATCHNODE_H +#define QSGNINEPATCHNODE_H + +#include "qsgnode.h" +#include "qsgtexturematerial.h" +#include "qsgborderimage_p.h" + +class TextureReference; + +class QSGNinePatchNode : public QSGGeometryNode +{ +public: + QSGNinePatchNode(); + + void setTexture(QSGTexture *texture); + QSGTexture *texture() const; + + void setRect(const QRectF &rect); + QRectF rect() const { return m_targetRect; } + + void setInnerRect(const QRectF &rect); + QRectF innerRect() const { return m_innerRect; } + + void setFiltering(QSGTexture::Filtering filtering); + QSGTexture::Filtering filtering() const; + + void setHorzontalTileMode(QSGBorderImage::TileMode mode); + QSGBorderImage::TileMode horizontalTileMode() const { + return (QSGBorderImage::TileMode) m_horizontalTileMode; + } + + void setVerticalTileMode(QSGBorderImage::TileMode mode); + QSGBorderImage::TileMode verticalTileMode() const { + return (QSGBorderImage::TileMode) m_verticalTileMode; + } + + void update(); + +private: + void fillRow(QSGGeometry::TexturedPoint2D *&v, float y, float ty, int xChunkCount, float xChunkSize); + QRectF m_targetRect; + QRectF m_innerRect; + QSGTextureMaterial m_material; + QSGTextureMaterialWithOpacity m_materialO; + QSGGeometry m_geometry; + + uint m_horizontalTileMode : 2; + uint m_verticalTileMode : 2; + + uint m_dirtyGeometry : 1; +}; + +#endif // QSGNINEPATCHNODE_H diff --git a/src/declarative/items/qsgpainteditem.cpp b/src/declarative/items/qsgpainteditem.cpp new file mode 100644 index 0000000000..e9f4f73448 --- /dev/null +++ b/src/declarative/items/qsgpainteditem.cpp @@ -0,0 +1,363 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgpainteditem.h" +#include <private/qsgpainteditem_p.h> +#include <private/qsgpainternode_p.h> + +#include <private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QSGPaintedItem + \brief The QSGPaintedItem class provides a way to use the QPainter API in the + QML Scene Graph. + + The QSGPaintedItem makes it possible to use the QPainter API with the QML Scene Graph. + It sets up a textured rectangle in the Scene Graph and uses a QPainter to paint + onto the texture. The render target can be either a QImage or a QGLFramebufferObject. + When the render target is a QImage, QPainter first renders into the image then + the content is uploaded to the texture. + When a QGLFramebufferObject is used, QPainter paints directly onto the texture. + Call update() to trigger a repaint. + + Set the \l smooth property to true to enable QPainter to do anti-aliased rendering. + + QSGPaintedItem is meant to make it easier to port old code that is using the + QPainter API to the QML Scene Graph API and it should be used only for that purpose. + + To write your own painted item, you first create a subclass of QSGPaintedItem, and then + start by implementing its only pure virtual public function: paint(), which implements + the actual painting. To get the size of the area painted by the item, use + QSGItem::width() and QSGItem::height(). +*/ + +/*! + \enum QSGPaintedItem::RenderTarget + + This enum describes QSGPaintedItem's render targets. The render target is the + surface QPainter paints onto before the item is rendered on screen. + + \value Image The default; QPainter paints into a QImage using the raster paint engine. + The image's content needs to be uploaded to graphics memory afterward, this operation + can potentially be slow if the item is large. This render target allows high quality + anti-aliasing and fast item resizing. + + \value FramebufferObject QPainter paints into a QGLFramebufferObject using the GL + paint engine. Painting can be faster as no texture upload is required, but anti-aliasing + quality is not as good as if using an image. This render target allows faster rendering + in some cases, but you should avoid using it if the item is resized often. + + \sa setRenderTarget() +*/ + +/*! + \internal +*/ +QSGPaintedItemPrivate::QSGPaintedItemPrivate() + : QSGItemPrivate() + , fillColor(Qt::transparent) + , renderTarget(QSGPaintedItem::Image) + , geometryDirty(false) + , contentsDirty(false) + , opaquePainting(false) +{ +} + +/*! + Constructs a QSGPaintedItem with the given \a parent item. + */ +QSGPaintedItem::QSGPaintedItem(QSGItem *parent) + : QSGItem(*(new QSGPaintedItemPrivate), parent) +{ + setFlag(ItemHasContents); +} + +/*! + \internal +*/ +QSGPaintedItem::QSGPaintedItem(QSGPaintedItemPrivate &dd, QSGItem *parent) + : QSGItem(dd, parent) +{ + setFlag(ItemHasContents); +} + +/*! + Destroys the QSGPaintedItem. +*/ +QSGPaintedItem::~QSGPaintedItem() +{ +} + +/*! + Schedules a redraw of the area covered by \a rect in this item. You can call this function + whenever your item needs to be redrawn, such as if it changes appearance or size. + + This function does not cause an immediate paint; instead it schedules a paint request that + is processed by the QML Scene Graph when the next frame is rendered. The item will only be + redrawn if it is visible. + + Note that calling this function will trigger a repaint of the whole scene. + + \sa paint() +*/ +void QSGPaintedItem::update(const QRect &rect) +{ + Q_D(QSGPaintedItem); + d->contentsDirty = true; + if (rect.isNull() && !d->dirtyRect.isNull()) + d->dirtyRect = boundingRect().toAlignedRect(); + else + d->dirtyRect |= (boundingRect() & rect).toAlignedRect(); + QSGItem::update(); +} + +/*! + Returns true if this item is opaque; otherwise, false is returned. + + By default, painted items are not opaque. + + \sa setOpaquePainting() +*/ +bool QSGPaintedItem::opaquePainting() const +{ + Q_D(const QSGPaintedItem); + return d->opaquePainting; +} + +/*! + If \a opaque is true, the item is opaque; otherwise, it is considered as translucent. + + Opaque items are not blended with the rest of the scene, you should set this to true + if the content of the item is opaque to speed up rendering. + + By default, painted items are not opaque. + + \sa opaquePainting() +*/ +void QSGPaintedItem::setOpaquePainting(bool opaque) +{ + Q_D(QSGPaintedItem); + + if (d->opaquePainting == opaque) + return; + + d->opaquePainting = opaque; + QSGItem::update(); +} + +/*! + Returns true if antialiased painting is enabled; otherwise, false is returned. + + By default, antialiasing is not enabled. + + \sa setAntialiasing() +*/ +bool QSGPaintedItem::antialiasing() const +{ + Q_D(const QSGPaintedItem); + return d->antialiasing; +} + +/*! + If \a enable is true, antialiased painting is enabled. + + By default, antialiasing is not enabled. + + \sa antialiasing() +*/ +void QSGPaintedItem::setAntialiasing(bool enable) +{ + Q_D(QSGPaintedItem); + + if (d->antialiasing == enable) + return; + + d->antialiasing = enable; + update(); +} + +QSize QSGPaintedItem::contentsSize() const +{ + // XXX todo + return QSize(); +} + +void QSGPaintedItem::setContentsSize(const QSize &) +{ + // XXX todo +} + +void QSGPaintedItem::resetContentsSize() +{ + // XXX todo +} + +qreal QSGPaintedItem::contentsScale() const +{ + // XXX todo + return 1; +} + +void QSGPaintedItem::setContentsScale(qreal) +{ + // XXX todo +} + +/*! + \property QSGPaintedItem::fillColor + \brief The item's background fill color. + + By default, the fill color is set to Qt::transparent. +*/ +QColor QSGPaintedItem::fillColor() const +{ + Q_D(const QSGPaintedItem); + return d->fillColor; +} + +void QSGPaintedItem::setFillColor(const QColor &c) +{ + Q_D(QSGPaintedItem); + + if (d->fillColor == c) + return; + + d->fillColor = c; + update(); + + emit fillColorChanged(); +} + +/*! + \property QSGPaintedItem::renderTarget + \brief The item's render target. + + This property defines which render target the QPainter renders into, it can be either + QSGPaintedItem::Image or QSGPaintedItem::FramebufferObject. Both have certains benefits, + typically performance versus quality. Using a framebuffer object avoids a costly upload + of the image contents to the texture in graphics memory, while using an image enables + high quality anti-aliasing. + + \warning Resizing a framebuffer object is a costly operation, avoid using + the QSGPaintedItem::FramebufferObject render target if the item gets resized often. + + By default, the render target is QSGPaintedItem::Image. +*/ +QSGPaintedItem::RenderTarget QSGPaintedItem::renderTarget() const +{ + Q_D(const QSGPaintedItem); + return d->renderTarget; +} + +void QSGPaintedItem::setRenderTarget(RenderTarget target) +{ + Q_D(QSGPaintedItem); + + if (d->renderTarget == target) + return; + + d->renderTarget = target; + update(); + + emit renderTargetChanged(); +} + +/*! + \fn virtual void QSGPaintedItem::paint(QPainter *painter) = 0 + + This function, which is usually called by the QML Scene Graph, paints the + contents of an item in local coordinates. + + The function is called after the item has been filled with the fillColor. + + Reimplement this function in a QSGPaintedItem subclass to provide the + item's painting implementation, using \a painter. +*/ + +/*! + This function is called after the item's geometry has changed. +*/ +void QSGPaintedItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QSGPaintedItem); + d->geometryDirty = true; + QSGItem::geometryChanged(newGeometry, oldGeometry); +} + + +/*! + This function is called when the Scene Graph node associated to the item needs to + be updated. +*/ +QSGNode *QSGPaintedItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + Q_D(QSGPaintedItem); + + if (width() <= 0 || height() <= 0) { + delete oldNode; + return 0; + } + + QSGPainterNode *node = static_cast<QSGPainterNode *>(oldNode); + if (!node) + node = new QSGPainterNode(this); + + node->setPreferredRenderTarget(d->renderTarget); + node->setSize(QSize(d->width, d->height)); + node->setSmoothPainting(d->antialiasing); + node->setLinearFiltering(d->smooth); + node->setOpaquePainting(d->opaquePainting); + node->setFillColor(d->fillColor); + node->setDirty(d->contentsDirty || d->geometryDirty, d->dirtyRect); + node->update(); + + d->contentsDirty = false; + d->geometryDirty = false; + d->dirtyRect = QRect(); + + return node; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgpainteditem.h b/src/declarative/items/qsgpainteditem.h new file mode 100644 index 0000000000..8d4b466922 --- /dev/null +++ b/src/declarative/items/qsgpainteditem.h @@ -0,0 +1,114 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPAINTEDITEM_P_H +#define QSGPAINTEDITEM_P_H + +#include <qsgitem.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QSGPaintedItemPrivate; +class Q_DECLARATIVE_EXPORT QSGPaintedItem : public QSGItem +{ + Q_OBJECT + Q_ENUMS(RenderTarget) + + Q_PROPERTY(QSize contentsSize READ contentsSize WRITE setContentsSize NOTIFY contentsSizeChanged) + Q_PROPERTY(QColor fillColor READ fillColor WRITE setFillColor NOTIFY fillColorChanged) + Q_PROPERTY(qreal contentsScale READ contentsScale WRITE setContentsScale NOTIFY contentsScaleChanged) + Q_PROPERTY(RenderTarget renderTarget READ renderTarget WRITE setRenderTarget NOTIFY renderTargetChanged) +public: + QSGPaintedItem(QSGItem *parent = 0); + virtual ~QSGPaintedItem(); + + enum RenderTarget { + Image, + FramebufferObject + }; + + void update(const QRect &rect = QRect()); + + bool opaquePainting() const; + void setOpaquePainting(bool opaque); + + bool antialiasing() const; + void setAntialiasing(bool enable); + + QSize contentsSize() const; + void setContentsSize(const QSize &); + void resetContentsSize(); + + qreal contentsScale() const; + void setContentsScale(qreal); + + QColor fillColor() const; + void setFillColor(const QColor&); + + RenderTarget renderTarget() const; + void setRenderTarget(RenderTarget target); + + virtual void paint(QPainter *painter) = 0; + +Q_SIGNALS: + void fillColorChanged(); + void contentsSizeChanged(); + void contentsScaleChanged(); + void renderTargetChanged(); + +protected: + QSGPaintedItem(QSGPaintedItemPrivate &dd, QSGItem *parent = 0); + virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private: + Q_DISABLE_COPY(QSGPaintedItem); + Q_DECLARE_PRIVATE(QSGPaintedItem); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSGPAINTEDITEM_P_H diff --git a/src/declarative/items/qsgpainteditem_p.h b/src/declarative/items/qsgpainteditem_p.h new file mode 100644 index 0000000000..ee76319a92 --- /dev/null +++ b/src/declarative/items/qsgpainteditem_p.h @@ -0,0 +1,68 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPAINTEDITEM_P_P_H +#define QSGPAINTEDITEM_P_P_H + +#include "qsgitem_p.h" + +QT_BEGIN_NAMESPACE + +class QSGPaintedItemPrivate : public QSGItemPrivate +{ +public: + QSGPaintedItemPrivate(); + + QColor fillColor; + QSGPaintedItem::RenderTarget renderTarget; + + QRect dirtyRect; + + bool geometryDirty : 1; + bool contentsDirty : 1; + bool opaquePainting: 1; + bool antialiasing: 1; +}; + +QT_END_NAMESPACE + +#endif // QSGPAINTEDITEM_P_P_H diff --git a/src/declarative/items/qsgpathview.cpp b/src/declarative/items/qsgpathview.cpp new file mode 100644 index 0000000000..87e550b630 --- /dev/null +++ b/src/declarative/items/qsgpathview.cpp @@ -0,0 +1,1412 @@ +// Commit: 8878e2c53a0c9408d4b468e2dad485743c32f58b +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgpathview_p.h" +#include "qsgpathview_p_p.h" +#include "qsgcanvas.h" + +#include <private/qdeclarativestate_p.h> +#include <private/qdeclarativeopenmetaobject_p.h> +#include <private/qlistmodelinterface_p.h> + +#include <QtGui/qevent.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qapplication.h> +#include <QtCore/qmath.h> +#include <math.h> + +QT_BEGIN_NAMESPACE + +inline qreal qmlMod(qreal x, qreal y) +{ +#ifdef QT_USE_MATH_H_FLOATS + if(sizeof(qreal) == sizeof(float)) + return fmodf(float(x), float(y)); + else +#endif + return fmod(x, y); +} + +static QDeclarativeOpenMetaObjectType *qPathViewAttachedType = 0; + +QSGPathViewAttached::QSGPathViewAttached(QObject *parent) +: QObject(parent), m_percent(-1), m_view(0), m_onPath(false), m_isCurrent(false) +{ + if (qPathViewAttachedType) { + m_metaobject = new QDeclarativeOpenMetaObject(this, qPathViewAttachedType); + m_metaobject->setCached(true); + } else { + m_metaobject = new QDeclarativeOpenMetaObject(this); + } +} + +QSGPathViewAttached::~QSGPathViewAttached() +{ +} + +QVariant QSGPathViewAttached::value(const QByteArray &name) const +{ + return m_metaobject->value(name); +} +void QSGPathViewAttached::setValue(const QByteArray &name, const QVariant &val) +{ + m_metaobject->setValue(name, val); +} + + +void QSGPathViewPrivate::init() +{ + Q_Q(QSGPathView); + offset = 0; + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QSGItem::ItemIsFocusScope); + q->setFiltersChildMouseEvents(true); + q->connect(&tl, SIGNAL(updated()), q, SLOT(ticked())); + lastPosTime.invalidate(); + static int timelineCompletedIdx = -1; + static int movementEndingIdx = -1; + if (timelineCompletedIdx == -1) { + timelineCompletedIdx = QDeclarativeTimeLine::staticMetaObject.indexOfSignal("completed()"); + movementEndingIdx = QSGPathView::staticMetaObject.indexOfSlot("movementEnding()"); + } + QMetaObject::connect(&tl, timelineCompletedIdx, + q, movementEndingIdx, Qt::DirectConnection); +} + +QSGItem *QSGPathViewPrivate::getItem(int modelIndex) +{ + Q_Q(QSGPathView); + requestedIndex = modelIndex; + QSGItem *item = model->item(modelIndex, false); + if (item) { + if (!attType) { + // pre-create one metatype to share with all attached objects + attType = new QDeclarativeOpenMetaObjectType(&QSGPathViewAttached::staticMetaObject, qmlEngine(q)); + foreach(const QString &attr, path->attributes()) + attType->createProperty(attr.toUtf8()); + } + qPathViewAttachedType = attType; + QSGPathViewAttached *att = static_cast<QSGPathViewAttached *>(qmlAttachedPropertiesObject<QSGPathView>(item)); + qPathViewAttachedType = 0; + if (att) { + att->m_view = q; + att->setOnPath(true); + } + item->setParentItem(q); + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->addItemChangeListener(this, QSGItemPrivate::Geometry); + } + requestedIndex = -1; + return item; +} + +void QSGPathViewPrivate::releaseItem(QSGItem *item) +{ + if (!item || !model) + return; + QSGItemPrivate *itemPrivate = QSGItemPrivate::get(item); + itemPrivate->removeItemChangeListener(this, QSGItemPrivate::Geometry); + if (model->release(item) == 0) { + // item was not destroyed, and we no longer reference it. + if (QSGPathViewAttached *att = attached(item)) + att->setOnPath(false); + } +} + +QSGPathViewAttached *QSGPathViewPrivate::attached(QSGItem *item) +{ + return static_cast<QSGPathViewAttached *>(qmlAttachedPropertiesObject<QSGPathView>(item, false)); +} + +void QSGPathViewPrivate::clear() +{ + for (int i=0; i<items.count(); i++){ + QSGItem *p = items[i]; + releaseItem(p); + } + items.clear(); +} + +void QSGPathViewPrivate::updateMappedRange() +{ + if (model && pathItems != -1 && pathItems < modelCount) + mappedRange = qreal(pathItems)/modelCount; + else + mappedRange = 1.0; +} + +qreal QSGPathViewPrivate::positionOfIndex(qreal index) const +{ + qreal pos = -1.0; + + if (model && index >= 0 && index < modelCount) { + qreal start = 0.0; + if (haveHighlightRange && highlightRangeMode != QSGPathView::NoHighlightRange) + start = highlightRangeStart; + qreal globalPos = index + offset; + globalPos = qmlMod(globalPos, qreal(modelCount)) / modelCount; + if (pathItems != -1 && pathItems < modelCount) { + globalPos += start * mappedRange; + globalPos = qmlMod(globalPos, 1.0); + if (globalPos < mappedRange) + pos = globalPos / mappedRange; + } else { + pos = qmlMod(globalPos + start, 1.0); + } + } + + return pos; +} + +void QSGPathViewPrivate::createHighlight() +{ + Q_Q(QSGPathView); + if (!q->isComponentComplete()) + return; + + bool changed = false; + if (highlightItem) { + delete highlightItem; + highlightItem = 0; + changed = true; + } + + QSGItem *item = 0; + if (highlightComponent) { + QDeclarativeContext *highlightContext = new QDeclarativeContext(qmlContext(q)); + QObject *nobj = highlightComponent->create(highlightContext); + if (nobj) { + QDeclarative_setParent_noEvent(highlightContext, nobj); + item = qobject_cast<QSGItem *>(nobj); + if (!item) + delete nobj; + } else { + delete highlightContext; + } + } else { + item = new QSGItem; + } + if (item) { + QDeclarative_setParent_noEvent(item, q); + item->setParentItem(q); + highlightItem = item; + changed = true; + } + if (changed) + emit q->highlightItemChanged(); +} + +void QSGPathViewPrivate::updateHighlight() +{ + Q_Q(QSGPathView); + if (!q->isComponentComplete() || !isValid()) + return; + if (highlightItem) { + if (haveHighlightRange && highlightRangeMode == QSGPathView::StrictlyEnforceRange) { + updateItem(highlightItem, highlightRangeStart); + } else { + qreal target = currentIndex; + + offsetAdj = 0.0; + tl.reset(moveHighlight); + moveHighlight.setValue(highlightPosition); + + const int duration = highlightMoveDuration; + + if (target - highlightPosition > modelCount/2) { + highlightUp = false; + qreal distance = modelCount - target + highlightPosition; + tl.move(moveHighlight, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * highlightPosition / distance)); + tl.set(moveHighlight, modelCount-0.01); + tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * (modelCount-target) / distance)); + } else if (target - highlightPosition <= -modelCount/2) { + highlightUp = true; + qreal distance = modelCount - highlightPosition + target; + tl.move(moveHighlight, modelCount-0.01, QEasingCurve(QEasingCurve::InQuad), int(duration * (modelCount-highlightPosition) / distance)); + tl.set(moveHighlight, 0.0); + tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::OutQuad), int(duration * target / distance)); + } else { + highlightUp = highlightPosition - target < 0; + tl.move(moveHighlight, target, QEasingCurve(QEasingCurve::InOutQuad), duration); + } + } + } +} + +void QSGPathViewPrivate::setHighlightPosition(qreal pos) +{ + if (pos != highlightPosition) { + qreal start = 0.0; + qreal end = 1.0; + if (haveHighlightRange && highlightRangeMode != QSGPathView::NoHighlightRange) { + start = highlightRangeStart; + end = highlightRangeEnd; + } + + qreal range = qreal(modelCount); + // calc normalized position of highlight relative to offset + qreal relativeHighlight = qmlMod(pos + offset, range) / range; + + if (!highlightUp && relativeHighlight > end * mappedRange) { + qreal diff = 1.0 - relativeHighlight; + setOffset(offset + diff * range); + } else if (highlightUp && relativeHighlight >= (end - start) * mappedRange) { + qreal diff = relativeHighlight - (end - start) * mappedRange; + setOffset(offset - diff * range - 0.00001); + } + + highlightPosition = pos; + qreal pathPos = positionOfIndex(pos); + updateItem(highlightItem, pathPos); + if (QSGPathViewAttached *att = attached(highlightItem)) + att->setOnPath(pathPos != -1.0); + } +} + +void QSGPathView::pathUpdated() +{ + Q_D(QSGPathView); + QList<QSGItem*>::iterator it = d->items.begin(); + while (it != d->items.end()) { + QSGItem *item = *it; + if (QSGPathViewAttached *att = d->attached(item)) + att->m_percent = -1; + ++it; + } + refill(); +} + +void QSGPathViewPrivate::updateItem(QSGItem *item, qreal percent) +{ + if (QSGPathViewAttached *att = attached(item)) { + if (qFuzzyCompare(att->m_percent, percent)) + return; + att->m_percent = percent; + foreach(const QString &attr, path->attributes()) + att->setValue(attr.toUtf8(), path->attributeAt(attr, percent)); + } + QPointF pf = path->pointAt(percent); + item->setX(qRound(pf.x() - item->width()/2)); + item->setY(qRound(pf.y() - item->height()/2)); +} + +void QSGPathViewPrivate::regenerate() +{ + Q_Q(QSGPathView); + if (!q->isComponentComplete()) + return; + + clear(); + + if (!isValid()) + return; + + firstIndex = -1; + updateMappedRange(); + q->refill(); +} + +QSGPathView::QSGPathView(QSGItem *parent) + : QSGItem(*(new QSGPathViewPrivate), parent) +{ + Q_D(QSGPathView); + d->init(); +} + +QSGPathView::~QSGPathView() +{ + Q_D(QSGPathView); + d->clear(); + if (d->attType) + d->attType->release(); + if (d->ownModel) + delete d->model; +} + +QVariant QSGPathView::model() const +{ + Q_D(const QSGPathView); + return d->modelVariant; +} + +void QSGPathView::setModel(const QVariant &model) +{ + Q_D(QSGPathView); + if (d->modelVariant == model) + return; + + if (d->model) { + disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + disconnect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + for (int i=0; i<d->items.count(); i++){ + QSGItem *p = d->items[i]; + d->model->release(p); + } + d->items.clear(); + } + + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QSGVisualModel *vim = 0; + if (object && (vim = qobject_cast<QSGVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this), this); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + dataModel->setModel(model); + } + d->modelCount = 0; + if (d->model) { + connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + connect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + d->modelCount = d->model->count(); + if (d->model->count()) + d->offset = qmlMod(d->offset, qreal(d->model->count())); + if (d->offset < 0) + d->offset = d->model->count() + d->offset; +} + d->regenerate(); + d->fixOffset(); + emit countChanged(); + emit modelChanged(); +} + +int QSGPathView::count() const +{ + Q_D(const QSGPathView); + return d->model ? d->modelCount : 0; +} + +QDeclarativePath *QSGPathView::path() const +{ + Q_D(const QSGPathView); + return d->path; +} + +void QSGPathView::setPath(QDeclarativePath *path) +{ + Q_D(QSGPathView); + if (d->path == path) + return; + if (d->path) + disconnect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated())); + d->path = path; + connect(d->path, SIGNAL(changed()), this, SLOT(pathUpdated())); + if (d->isValid() && isComponentComplete()) { + d->clear(); + if (d->attType) { + d->attType->release(); + d->attType = 0; + } + d->regenerate(); + } + emit pathChanged(); +} + +int QSGPathView::currentIndex() const +{ + Q_D(const QSGPathView); + return d->currentIndex; +} + +void QSGPathView::setCurrentIndex(int idx) +{ + Q_D(QSGPathView); + 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); + } + } + } + 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->setFocus(true); + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setIsCurrentItem(true); + } + d->currentItemOffset = d->positionOfIndex(d->currentIndex); + d->updateHighlight(); + } + emit currentIndexChanged(); + } +} + +void QSGPathView::incrementCurrentIndex() +{ + Q_D(QSGPathView); + d->moveDirection = QSGPathViewPrivate::Positive; + setCurrentIndex(currentIndex()+1); +} + +void QSGPathView::decrementCurrentIndex() +{ + Q_D(QSGPathView); + if (d->model && d->modelCount) { + int idx = currentIndex()-1; + if (idx < 0) + idx = d->modelCount - 1; + d->moveDirection = QSGPathViewPrivate::Negative; + setCurrentIndex(idx); + } +} + +qreal QSGPathView::offset() const +{ + Q_D(const QSGPathView); + return d->offset; +} + +void QSGPathView::setOffset(qreal offset) +{ + Q_D(QSGPathView); + d->setOffset(offset); + d->updateCurrent(); +} + +void QSGPathViewPrivate::setOffset(qreal o) +{ + Q_Q(QSGPathView); + if (offset != o) { + if (isValid() && q->isComponentComplete()) { + offset = qmlMod(o, qreal(modelCount)); + if (offset < 0) + offset += qreal(modelCount); + q->refill(); + } else { + offset = o; + } + emit q->offsetChanged(); + } +} + +void QSGPathViewPrivate::setAdjustedOffset(qreal o) +{ + setOffset(o+offsetAdj); +} + +QDeclarativeComponent *QSGPathView::highlight() const +{ + Q_D(const QSGPathView); + return d->highlightComponent; +} + +void QSGPathView::setHighlight(QDeclarativeComponent *highlight) +{ + Q_D(QSGPathView); + if (highlight != d->highlightComponent) { + d->highlightComponent = highlight; + d->createHighlight(); + d->updateHighlight(); + emit highlightChanged(); + } +} + +QSGItem *QSGPathView::highlightItem() +{ + Q_D(const QSGPathView); + return d->highlightItem; +} + +qreal QSGPathView::preferredHighlightBegin() const +{ + Q_D(const QSGPathView); + return d->highlightRangeStart; +} + +void QSGPathView::setPreferredHighlightBegin(qreal start) +{ + Q_D(QSGPathView); + if (d->highlightRangeStart == start || start < 0 || start > 1.0) + return; + d->highlightRangeStart = start; + d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + refill(); + emit preferredHighlightBeginChanged(); +} + +qreal QSGPathView::preferredHighlightEnd() const +{ + Q_D(const QSGPathView); + return d->highlightRangeEnd; +} + +void QSGPathView::setPreferredHighlightEnd(qreal end) +{ + Q_D(QSGPathView); + if (d->highlightRangeEnd == end || end < 0 || end > 1.0) + return; + d->highlightRangeEnd = end; + d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + refill(); + emit preferredHighlightEndChanged(); +} + +QSGPathView::HighlightRangeMode QSGPathView::highlightRangeMode() const +{ + Q_D(const QSGPathView); + return d->highlightRangeMode; +} + +void QSGPathView::setHighlightRangeMode(HighlightRangeMode mode) +{ + Q_D(QSGPathView); + if (d->highlightRangeMode == mode) + return; + d->highlightRangeMode = mode; + d->haveHighlightRange = d->highlightRangeMode != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; + emit highlightRangeModeChanged(); +} + +int QSGPathView::highlightMoveDuration() const +{ + Q_D(const QSGPathView); + return d->highlightMoveDuration; +} + +void QSGPathView::setHighlightMoveDuration(int duration) +{ + Q_D(QSGPathView); + if (d->highlightMoveDuration == duration) + return; + d->highlightMoveDuration = duration; + emit highlightMoveDurationChanged(); +} + +qreal QSGPathView::dragMargin() const +{ + Q_D(const QSGPathView); + return d->dragMargin; +} + +void QSGPathView::setDragMargin(qreal dragMargin) +{ + Q_D(QSGPathView); + if (d->dragMargin == dragMargin) + return; + d->dragMargin = dragMargin; + emit dragMarginChanged(); +} + +qreal QSGPathView::flickDeceleration() const +{ + Q_D(const QSGPathView); + return d->deceleration; +} + +void QSGPathView::setFlickDeceleration(qreal dec) +{ + Q_D(QSGPathView); + if (d->deceleration == dec) + return; + d->deceleration = dec; + emit flickDecelerationChanged(); +} + +bool QSGPathView::isInteractive() const +{ + Q_D(const QSGPathView); + return d->interactive; +} + +void QSGPathView::setInteractive(bool interactive) +{ + Q_D(QSGPathView); + if (interactive != d->interactive) { + d->interactive = interactive; + if (!interactive) + d->tl.clear(); + emit interactiveChanged(); + } +} + +bool QSGPathView::isMoving() const +{ + Q_D(const QSGPathView); + return d->moving; +} + +bool QSGPathView::isFlicking() const +{ + Q_D(const QSGPathView); + return d->flicking; +} + +QDeclarativeComponent *QSGPathView::delegate() const +{ + Q_D(const QSGPathView); + if (d->model) { + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QSGPathView::setDelegate(QDeclarativeComponent *delegate) +{ + Q_D(QSGPathView); + if (delegate == this->delegate()) + return; + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) { + dataModel->setDelegate(delegate); + d->modelCount = dataModel->count(); + d->regenerate(); + emit delegateChanged(); + } +} + +int QSGPathView::pathItemCount() const +{ + Q_D(const QSGPathView); + return d->pathItems; +} + +void QSGPathView::setPathItemCount(int i) +{ + Q_D(QSGPathView); + if (i == d->pathItems) + return; + if (i < 1) + i = 1; + d->pathItems = i; + d->updateMappedRange(); + if (d->isValid() && isComponentComplete()) { + d->regenerate(); + } + emit pathItemCountChanged(); +} + +QPointF QSGPathViewPrivate::pointNear(const QPointF &point, qreal *nearPercent) const +{ + //XXX maybe do recursively at increasing resolution. + qreal mindist = 1e10; // big number + QPointF nearPoint = path->pointAt(0); + qreal nearPc = 0; + for (qreal i=1; i < 1000; i++) { + QPointF pt = path->pointAt(i/1000.0); + QPointF diff = pt - point; + qreal dist = diff.x()*diff.x() + diff.y()*diff.y(); + if (dist < mindist) { + nearPoint = pt; + nearPc = i; + mindist = dist; + } + } + + if (nearPercent) + *nearPercent = nearPc / 1000.0; + + return nearPoint; +} + +void QSGPathView::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPathView); + if (d->interactive) { + d->handleMousePressEvent(event); + event->accept(); + } else { + QSGItem::mousePressEvent(event); + } +} + +void QSGPathViewPrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGPathView); + if (!interactive || !items.count()) + return; + QPointF scenePoint = q->mapToScene(event->pos()); + int idx = 0; + for (; idx < items.count(); ++idx) { + QRectF rect = items.at(idx)->boundingRect(); + rect = items.at(idx)->mapRectToScene(rect); + if (rect.contains(scenePoint)) + break; + } + if (idx == items.count() && dragMargin == 0.) // didn't click on an item + return; + + startPoint = pointNear(event->pos(), &startPc); + if (idx == items.count()) { + qreal distance = qAbs(event->pos().x() - startPoint.x()) + qAbs(event->pos().y() - startPoint.y()); + if (distance > dragMargin) + return; + } + + if (tl.isActive() && flicking) + stealMouse = true; // If we've been flicked then steal the click. + else + stealMouse = false; + + lastElapsed = 0; + lastDist = 0; + QSGItemPrivate::start(lastPosTime); + tl.clear(); +} + +void QSGPathView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPathView); + if (d->interactive) { + d->handleMouseMoveEvent(event); + if (d->stealMouse) + setKeepMouseGrab(true); + event->accept(); + } else { + QSGItem::mouseMoveEvent(event); + } +} + +void QSGPathViewPrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QSGPathView); + if (!interactive || !lastPosTime.isValid()) + return; + + qreal newPc; + QPointF pathPoint = pointNear(event->pos(), &newPc); + if (!stealMouse) { + QPointF delta = pathPoint - startPoint; + if (qAbs(delta.x()) > QApplication::startDragDistance() || qAbs(delta.y()) > QApplication::startDragDistance()) { + stealMouse = true; + startPc = newPc; + } + } + + if (stealMouse) { + moveReason = QSGPathViewPrivate::Mouse; + qreal diff = (newPc - startPc)*modelCount*mappedRange; + if (diff) { + q->setOffset(offset + diff); + + if (diff > modelCount/2) + diff -= modelCount; + else if (diff < -modelCount/2) + diff += modelCount; + + lastElapsed = QSGItemPrivate::restart(lastPosTime); + lastDist = diff; + startPc = newPc; + } + if (!moving) { + moving = true; + emit q->movingChanged(); + emit q->movementStarted(); + } + } +} + +void QSGPathView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPathView); + if (d->interactive) { + d->handleMouseReleaseEvent(event); + event->accept(); + ungrabMouse(); + } else { + QSGItem::mouseReleaseEvent(event); + } +} + +void QSGPathViewPrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *) +{ + Q_Q(QSGPathView); + stealMouse = false; + q->setKeepMouseGrab(false); + if (!interactive || !lastPosTime.isValid()) + return; + + qreal elapsed = qreal(lastElapsed + QSGItemPrivate::elapsed(lastPosTime)) / 1000.; + qreal velocity = elapsed > 0. ? lastDist / elapsed : 0; + if (model && modelCount && qAbs(velocity) > 1.) { + qreal count = pathItems == -1 ? modelCount : pathItems; + if (qAbs(velocity) > count * 2) // limit velocity + velocity = (velocity > 0 ? count : -count) * 2; + // Calculate the distance to be travelled + qreal v2 = velocity*velocity; + qreal accel = deceleration/10; + // + 0.25 to encourage moving at least one item in the flick direction + qreal dist = qMin(qreal(modelCount-1), qreal(v2 / (accel * 2.0) + 0.25)); + if (haveHighlightRange && highlightRangeMode == QSGPathView::StrictlyEnforceRange) { + // round to nearest item. + if (velocity > 0.) + dist = qRound(dist + offset) - offset; + else + dist = qRound(dist - offset) + offset; + // Calculate accel required to stop on item boundary + if (dist <= 0.) { + dist = 0.; + accel = 0.; + } else { + accel = v2 / (2.0f * qAbs(dist)); + } + } + offsetAdj = 0.0; + moveOffset.setValue(offset); + tl.accel(moveOffset, velocity, accel, dist); + tl.callback(QDeclarativeTimeLineCallback(&moveOffset, fixOffsetCallback, this)); + if (!flicking) { + flicking = true; + emit q->flickingChanged(); + emit q->flickStarted(); + } + } else { + fixOffset(); + } + + lastPosTime.invalidate(); + if (!tl.isActive()) + q->movementEnding(); +} + +bool QSGPathView::sendMouseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPathView); + QGraphicsSceneMouseEvent mouseEvent(event->type()); + QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + QSGCanvas *c = canvas(); + QSGItem *grabber = c ? c->mouseGrabberItem() : 0; + bool stealThisEvent = d->stealMouse; + if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) { + mouseEvent.setAccepted(false); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button))); + } + } + mouseEvent.setScenePos(event->scenePos()); + mouseEvent.setLastScenePos(event->lastScenePos()); + mouseEvent.setPos(mapFromScene(event->scenePos())); + mouseEvent.setLastPos(mapFromScene(event->lastScenePos())); + + switch(mouseEvent.type()) { + case QEvent::GraphicsSceneMouseMove: + d->handleMouseMoveEvent(&mouseEvent); + break; + case QEvent::GraphicsSceneMousePress: + d->handleMousePressEvent(&mouseEvent); + stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above + break; + case QEvent::GraphicsSceneMouseRelease: + d->handleMouseReleaseEvent(&mouseEvent); + break; + default: + break; + } + grabber = c->mouseGrabberItem(); + if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) + grabMouse(); + + return d->stealMouse; + } else if (d->lastPosTime.isValid()) { + d->lastPosTime.invalidate(); + } + if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) + d->stealMouse = false; + return false; +} + +bool QSGPathView::childMouseEventFilter(QSGItem *i, QEvent *e) +{ + Q_D(QSGPathView); + if (!isVisible() || !d->interactive) + return QSGItem::childMouseEventFilter(i, e); + + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); + default: + break; + } + + return QSGItem::childMouseEventFilter(i, e); +} + +void QSGPathView::updatePolish() +{ + QSGItem::updatePolish(); + refill(); +} + +void QSGPathView::componentComplete() +{ + Q_D(QSGPathView); + QSGItem::componentComplete(); + d->createHighlight(); + // It is possible that a refill has already happended to to Path + // bindings being handled in the componentComplete(). If so + // don't do it again. + if (d->items.count() == 0 && d->model) { + d->modelCount = d->model->count(); + d->regenerate(); + } + d->updateHighlight(); +} + +void QSGPathView::refill() +{ + Q_D(QSGPathView); + if (!d->isValid() || !isComponentComplete()) + return; + + d->layoutScheduled = false; + bool currentVisible = false; + + // first move existing items and remove items off path + int idx = d->firstIndex; + QList<QSGItem*>::iterator it = d->items.begin(); + while (it != d->items.end()) { + qreal pos = d->positionOfIndex(idx); + QSGItem *item = *it; + if (pos >= 0.0) { + d->updateItem(item, pos); + if (idx == d->currentIndex) { + currentVisible = true; + d->currentItemOffset = pos; + } + ++it; + } else { +// qDebug() << "release"; + d->updateItem(item, 1.0); + d->releaseItem(item); + if (it == d->items.begin()) { + if (++d->firstIndex >= d->modelCount) + d->firstIndex = 0; + } + it = d->items.erase(it); + } + ++idx; + if (idx >= d->modelCount) + idx = 0; + } + if (!d->items.count()) + d->firstIndex = -1; + + if (d->modelCount) { + // add items to beginning and end + int count = d->pathItems == -1 ? d->modelCount : qMin(d->pathItems, d->modelCount); + if (d->items.count() < count) { + int idx = qRound(d->modelCount - d->offset) % d->modelCount; + qreal startPos = 0.0; + if (d->haveHighlightRange && d->highlightRangeMode != QSGPathView::NoHighlightRange) + startPos = d->highlightRangeStart; + if (d->firstIndex >= 0) { + startPos = d->positionOfIndex(d->firstIndex); + idx = (d->firstIndex + d->items.count()) % d->modelCount; + } + qreal pos = d->positionOfIndex(idx); + while ((pos > startPos || !d->items.count()) && d->items.count() < count) { + // qDebug() << "append" << idx; + QSGItem *item = d->getItem(idx); + 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; + d->items.append(item); + d->updateItem(item, pos); + if (d->model->completePending()) + d->model->completeItem(); + ++idx; + if (idx >= d->modelCount) + idx = 0; + pos = d->positionOfIndex(idx); + } + + idx = d->firstIndex - 1; + if (idx < 0) + idx = d->modelCount - 1; + pos = d->positionOfIndex(idx); + while (pos >= 0.0 && pos < startPos) { + // qDebug() << "prepend" << idx; + QSGItem *item = d->getItem(idx); + 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); + if (d->model->completePending()) + d->model->completeItem(); + d->firstIndex = idx; + idx = d->firstIndex - 1; + if (idx < 0) + idx = d->modelCount - 1; + pos = d->positionOfIndex(idx); + } + } + } + + if (!currentVisible) + d->currentItemOffset = 1.0; + + if (d->highlightItem && d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange) { + d->updateItem(d->highlightItem, d->highlightRangeStart); + if (QSGPathViewAttached *att = d->attached(d->highlightItem)) + att->setOnPath(true); + } else if (d->highlightItem && d->moveReason != QSGPathViewPrivate::SetIndex) { + d->updateItem(d->highlightItem, d->currentItemOffset); + if (QSGPathViewAttached *att = d->attached(d->highlightItem)) + att->setOnPath(currentVisible); + } + while (d->itemCache.count()) + d->releaseItem(d->itemCache.takeLast()); +} + +void QSGPathView::itemsInserted(int modelIndex, int count) +{ + //XXX support animated insertion + Q_D(QSGPathView); + if (!d->isValid() || !isComponentComplete()) + return; + + if (d->modelCount) { + d->itemCache += d->items; + d->items.clear(); + if (modelIndex <= d->currentIndex) { + d->currentIndex += count; + emit currentIndexChanged(); + } else if (d->offset != 0) { + d->offset += count; + d->offsetAdj += count; + } + } + + d->modelCount += count; + if (d->flicking || d->moving) { + d->regenerate(); + d->updateCurrent(); + } else { + d->firstIndex = -1; + d->updateMappedRange(); + d->scheduleLayout(); + } + emit countChanged(); +} + +void QSGPathView::itemsRemoved(int modelIndex, int count) +{ + //XXX support animated removal + Q_D(QSGPathView); + if (!d->model || !d->modelCount || !d->model->isValid() || !d->path || !isComponentComplete()) + return; + + // fix current + bool currentChanged = false; + if (d->currentIndex >= modelIndex + count) { + d->currentIndex -= count; + currentChanged = true; + } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) { + // current item has been removed. + d->currentIndex = qMin(modelIndex, d->modelCount-count-1); + if (d->currentItem) { + if (QSGPathViewAttached *att = d->attached(d->currentItem)) + att->setIsCurrentItem(true); + } + currentChanged = true; + } + + d->itemCache += d->items; + d->items.clear(); + + bool changedOffset = false; + if (modelIndex > d->currentIndex) { + if (d->offset >= count) { + changedOffset = true; + d->offset -= count; + d->offsetAdj -= count; + } + } + + d->modelCount -= count; + if (!d->modelCount) { + while (d->itemCache.count()) + d->releaseItem(d->itemCache.takeLast()); + d->offset = 0; + changedOffset = true; + d->tl.reset(d->moveOffset); + } else { + d->regenerate(); + d->updateCurrent(); + if (!d->flicking && !d->moving && d->haveHighlightRange && d->highlightRangeMode == QSGPathView::StrictlyEnforceRange) + d->snapToCurrent(); + } + if (changedOffset) + emit offsetChanged(); + if (currentChanged) + emit currentIndexChanged(); + emit countChanged(); +} + +void QSGPathView::itemsMoved(int /*from*/, int /*to*/, int /*count*/) +{ + Q_D(QSGPathView); + if (!d->isValid() || !isComponentComplete()) + return; + + QList<QSGItem *> removedItems = d->items; + d->items.clear(); + d->regenerate(); + while (removedItems.count()) + d->releaseItem(removedItems.takeLast()); + + // Fix current index + if (d->currentIndex >= 0 && d->currentItem) { + int oldCurrent = d->currentIndex; + d->currentIndex = d->model->indexOf(d->currentItem, this); + if (oldCurrent != d->currentIndex) + emit currentIndexChanged(); + } + d->updateCurrent(); +} + +void QSGPathView::modelReset() +{ + Q_D(QSGPathView); + d->modelCount = d->model->count(); + d->regenerate(); + emit countChanged(); +} + +void QSGPathView::createdItem(int index, QSGItem *item) +{ + Q_D(QSGPathView); + if (d->requestedIndex != index) { + if (!d->attType) { + // pre-create one metatype to share with all attached objects + d->attType = new QDeclarativeOpenMetaObjectType(&QSGPathViewAttached::staticMetaObject, qmlEngine(this)); + foreach(const QString &attr, d->path->attributes()) + d->attType->createProperty(attr.toUtf8()); + } + qPathViewAttachedType = d->attType; + QSGPathViewAttached *att = static_cast<QSGPathViewAttached *>(qmlAttachedPropertiesObject<QSGPathView>(item)); + qPathViewAttachedType = 0; + if (att) { + att->m_view = this; + att->setOnPath(false); + } + item->setParentItem(this); + d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0); + } +} + +void QSGPathView::destroyingItem(QSGItem *item) +{ + Q_UNUSED(item); +} + +void QSGPathView::ticked() +{ + Q_D(QSGPathView); + d->updateCurrent(); +} + +void QSGPathView::movementEnding() +{ + Q_D(QSGPathView); + if (d->flicking) { + d->flicking = false; + emit flickingChanged(); + emit flickEnded(); + } + if (d->moving && !d->stealMouse) { + d->moving = false; + emit movingChanged(); + emit movementEnded(); + } +} + +// find the item closest to the snap position +int QSGPathViewPrivate::calcCurrentIndex() +{ + int current = -1; + if (modelCount && model && items.count()) { + offset = qmlMod(offset, modelCount); + if (offset < 0) + offset += modelCount; + current = qRound(qAbs(qmlMod(modelCount - offset, modelCount))); + current = current % modelCount; + } + + return current; +} + +void QSGPathViewPrivate::updateCurrent() +{ + Q_Q(QSGPathView); + if (moveReason != Mouse) + return; + if (!modelCount || !haveHighlightRange || highlightRangeMode != QSGPathView::StrictlyEnforceRange) + return; + + 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); + } + } + currentIndex = idx; + currentItem = 0; + itemIndex = (idx - firstIndex + modelCount) % modelCount; + if (itemIndex < items.count()) { + currentItem = items.at(itemIndex); + currentItem->setFocus(true); + if (QSGPathViewAttached *att = attached(currentItem)) + att->setIsCurrentItem(true); + } + emit q->currentIndexChanged(); + } +} + +void QSGPathViewPrivate::fixOffsetCallback(void *d) +{ + ((QSGPathViewPrivate *)d)->fixOffset(); +} + +void QSGPathViewPrivate::fixOffset() +{ + Q_Q(QSGPathView); + if (model && items.count()) { + if (haveHighlightRange && highlightRangeMode == QSGPathView::StrictlyEnforceRange) { + int curr = calcCurrentIndex(); + if (curr != currentIndex) + q->setCurrentIndex(curr); + else + snapToCurrent(); + } + } +} + +void QSGPathViewPrivate::snapToCurrent() +{ + if (!model || modelCount <= 0) + return; + + qreal targetOffset = qmlMod(modelCount - currentIndex, modelCount); + + moveReason = Other; + offsetAdj = 0.0; + tl.reset(moveOffset); + moveOffset.setValue(offset); + + const int duration = highlightMoveDuration; + + if (moveDirection == Positive || (moveDirection == Shortest && targetOffset - offset > modelCount/2)) { + qreal distance = modelCount - targetOffset + offset; + if (targetOffset > moveOffset) { + tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::InQuad), int(duration * offset / distance)); + tl.set(moveOffset, modelCount); + tl.move(moveOffset, targetOffset, QEasingCurve(offset == 0.0 ? QEasingCurve::InOutQuad : QEasingCurve::OutQuad), int(duration * (modelCount-targetOffset) / distance)); + } else { + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration); + } + } else if (moveDirection == Negative || targetOffset - offset <= -modelCount/2) { + qreal distance = modelCount - offset + targetOffset; + if (targetOffset < moveOffset) { + tl.move(moveOffset, modelCount, QEasingCurve(targetOffset == 0 ? QEasingCurve::InOutQuad : QEasingCurve::InQuad), int(duration * (modelCount-offset) / distance)); + tl.set(moveOffset, 0.0); + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::OutQuad), int(duration * targetOffset / distance)); + } else { + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration); + } + } else { + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), duration); + } + moveDirection = Shortest; +} + +QSGPathViewAttached *QSGPathView::qmlAttachedProperties(QObject *obj) +{ + return new QSGPathViewAttached(obj); +} + +QT_END_NAMESPACE + diff --git a/src/declarative/items/qsgpathview_p.h b/src/declarative/items/qsgpathview_p.h new file mode 100644 index 0000000000..d75063395a --- /dev/null +++ b/src/declarative/items/qsgpathview_p.h @@ -0,0 +1,254 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPATHVIEW_P_H +#define QSGPATHVIEW_P_H + +#include "qsgitem.h" + +#include <private/qdeclarativepath_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGPathViewPrivate; +class QSGPathViewAttached; +class Q_AUTOTEST_EXPORT QSGPathView : public QSGItem +{ + Q_OBJECT + + 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(qreal offset READ offset WRITE setOffset NOTIFY offsetChanged) + + Q_PROPERTY(QDeclarativeComponent *highlight READ highlight WRITE setHighlight NOTIFY highlightChanged) + Q_PROPERTY(QSGItem *highlightItem READ highlightItem NOTIFY highlightItemChanged) + + Q_PROPERTY(qreal preferredHighlightBegin READ preferredHighlightBegin WRITE setPreferredHighlightBegin NOTIFY preferredHighlightBeginChanged) + Q_PROPERTY(qreal preferredHighlightEnd READ preferredHighlightEnd WRITE setPreferredHighlightEnd NOTIFY preferredHighlightEndChanged) + Q_PROPERTY(HighlightRangeMode highlightRangeMode READ highlightRangeMode WRITE setHighlightRangeMode NOTIFY highlightRangeModeChanged) + Q_PROPERTY(int highlightMoveDuration READ highlightMoveDuration WRITE setHighlightMoveDuration NOTIFY highlightMoveDurationChanged) + + Q_PROPERTY(qreal dragMargin READ dragMargin WRITE setDragMargin NOTIFY dragMarginChanged) + Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged) + + Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged) + Q_PROPERTY(bool flicking READ isFlicking NOTIFY flickingChanged) + + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(int pathItemCount READ pathItemCount WRITE setPathItemCount NOTIFY pathItemCountChanged) + + Q_ENUMS(HighlightRangeMode) + +public: + QSGPathView(QSGItem *parent=0); + virtual ~QSGPathView(); + + QVariant model() const; + void setModel(const QVariant &); + + QDeclarativePath *path() const; + void setPath(QDeclarativePath *); + + int currentIndex() const; + void setCurrentIndex(int idx); + + qreal offset() const; + void setOffset(qreal offset); + + QDeclarativeComponent *highlight() const; + void setHighlight(QDeclarativeComponent *highlight); + QSGItem *highlightItem(); + + enum HighlightRangeMode { NoHighlightRange, ApplyRange, StrictlyEnforceRange }; + HighlightRangeMode highlightRangeMode() const; + void setHighlightRangeMode(HighlightRangeMode mode); + + qreal preferredHighlightBegin() const; + void setPreferredHighlightBegin(qreal); + + qreal preferredHighlightEnd() const; + void setPreferredHighlightEnd(qreal); + + int highlightMoveDuration() const; + void setHighlightMoveDuration(int); + + qreal dragMargin() const; + void setDragMargin(qreal margin); + + qreal flickDeceleration() const; + void setFlickDeceleration(qreal dec); + + bool isInteractive() const; + void setInteractive(bool); + + bool isMoving() const; + bool isFlicking() const; + + int count() const; + + QDeclarativeComponent *delegate() const; + void setDelegate(QDeclarativeComponent *); + + int pathItemCount() const; + void setPathItemCount(int); + + static QSGPathViewAttached *qmlAttachedProperties(QObject *); + +public Q_SLOTS: + void incrementCurrentIndex(); + void decrementCurrentIndex(); + +Q_SIGNALS: + void currentIndexChanged(); + void offsetChanged(); + void modelChanged(); + void countChanged(); + void pathChanged(); + void preferredHighlightBeginChanged(); + void preferredHighlightEndChanged(); + void highlightRangeModeChanged(); + void dragMarginChanged(); + void snapPositionChanged(); + void delegateChanged(); + void pathItemCountChanged(); + void flickDecelerationChanged(); + void interactiveChanged(); + void movingChanged(); + void flickingChanged(); + void highlightChanged(); + void highlightItemChanged(); + void highlightMoveDurationChanged(); + void movementStarted(); + void movementEnded(); + void flickStarted(); + void flickEnded(); + +protected: + virtual void updatePolish(); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *); + bool sendMouseEvent(QGraphicsSceneMouseEvent *event); + bool childMouseEventFilter(QSGItem *, QEvent *); + void componentComplete(); + +private Q_SLOTS: + void refill(); + void ticked(); + void movementEnding(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int,int,int); + void modelReset(); + void createdItem(int index, QSGItem *item); + void destroyingItem(QSGItem *item); + void pathUpdated(); + +private: + friend class QSGPathViewAttached; + Q_DISABLE_COPY(QSGPathView) + Q_DECLARE_PRIVATE(QSGPathView) +}; + +class QDeclarativeOpenMetaObject; +class QSGPathViewAttached : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QSGPathView *view READ view CONSTANT) + Q_PROPERTY(bool isCurrentItem READ isCurrentItem NOTIFY currentItemChanged) + Q_PROPERTY(bool onPath READ isOnPath NOTIFY pathChanged) + +public: + QSGPathViewAttached(QObject *parent); + ~QSGPathViewAttached(); + + QSGPathView *view() { return m_view; } + + bool isCurrentItem() const { return m_isCurrent; } + void setIsCurrentItem(bool c) { + if (m_isCurrent != c) { + m_isCurrent = c; + emit currentItemChanged(); + } + } + + QVariant value(const QByteArray &name) const; + void setValue(const QByteArray &name, const QVariant &val); + + bool isOnPath() const { return m_onPath; } + void setOnPath(bool on) { + if (on != m_onPath) { + m_onPath = on; + emit pathChanged(); + } + } + qreal m_percent; + +Q_SIGNALS: + void currentItemChanged(); + void pathChanged(); + +private: + friend class QSGPathViewPrivate; + friend class QSGPathView; + QSGPathView *m_view; + QDeclarativeOpenMetaObject *m_metaobject; + bool m_onPath : 1; + bool m_isCurrent : 1; +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGPathView) +QML_DECLARE_TYPEINFO(QSGPathView, QML_HAS_ATTACHED_PROPERTIES) +QT_END_HEADER + +#endif // QSGPATHVIEW_P_H diff --git a/src/declarative/items/qsgpathview_p_p.h b/src/declarative/items/qsgpathview_p_p.h new file mode 100644 index 0000000000..ed54f29abe --- /dev/null +++ b/src/declarative/items/qsgpathview_p_p.h @@ -0,0 +1,193 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPATHVIEW_P_H +#define QDECLARATIVEPATHVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgpathview_p.h" +#include "qsgitem_p.h" +#include "qsgvisualitemmodel_p.h" + +#include <QtDeclarative/qdeclarative.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qcoreapplication.h> + +#include <private/qdeclarativeanimation_p_p.h> +#include <private/qdeclarativeguard_p.h> + +QT_BEGIN_NAMESPACE + +class QDeclarativeOpenMetaObjectType; +class QSGPathViewAttached; +class QSGPathViewPrivate : public QSGItemPrivate, public QSGItemChangeListener +{ + Q_DECLARE_PUBLIC(QSGPathView) + +public: + QSGPathViewPrivate() + : path(0), currentIndex(0), currentItemOffset(0.0), startPc(0), lastDist(0) + , lastElapsed(0), offset(0.0), offsetAdj(0.0), mappedRange(1.0) + , stealMouse(false), ownModel(false), interactive(true), haveHighlightRange(true) + , autoHighlight(true), highlightUp(false), layoutScheduled(false) + , moving(false), flicking(false) + , dragMargin(0), deceleration(100) + , moveOffset(this, &QSGPathViewPrivate::setAdjustedOffset) + , firstIndex(-1), pathItems(-1), requestedIndex(-1) + , moveReason(Other), moveDirection(Shortest), attType(0), highlightComponent(0), highlightItem(0) + , moveHighlight(this, &QSGPathViewPrivate::setHighlightPosition) + , highlightPosition(0) + , highlightRangeStart(0), highlightRangeEnd(0) + , highlightRangeMode(QSGPathView::StrictlyEnforceRange) + , highlightMoveDuration(300), modelCount(0) + { + } + + void init(); + + void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) { + if ((newGeometry.size() != oldGeometry.size()) + && (!highlightItem || item != highlightItem)) { + if (QSGPathViewAttached *att = attached(item)) + att->m_percent = -1; + scheduleLayout(); + } + } + + void scheduleLayout() { + Q_Q(QSGPathView); + if (!layoutScheduled) { + layoutScheduled = true; + q->polish(); + } + } + + QSGItem *getItem(int modelIndex); + void releaseItem(QSGItem *item); + QSGPathViewAttached *attached(QSGItem *item); + void clear(); + void updateMappedRange(); + qreal positionOfIndex(qreal index) const; + void createHighlight(); + void updateHighlight(); + void setHighlightPosition(qreal pos); + bool isValid() const { + return model && model->count() > 0 && model->isValid() && path; + } + + void handleMousePressEvent(QGraphicsSceneMouseEvent *event); + void handleMouseMoveEvent(QGraphicsSceneMouseEvent *event); + void handleMouseReleaseEvent(QGraphicsSceneMouseEvent *); + + int calcCurrentIndex(); + void updateCurrent(); + static void fixOffsetCallback(void*); + void fixOffset(); + void setOffset(qreal offset); + void setAdjustedOffset(qreal offset); + void regenerate(); + void updateItem(QSGItem *, qreal); + void snapToCurrent(); + QPointF pointNear(const QPointF &point, qreal *nearPercent=0) const; + + QDeclarativePath *path; + int currentIndex; + QDeclarativeGuard<QSGItem> currentItem; + qreal currentItemOffset; + qreal startPc; + QPointF startPoint; + qreal lastDist; + int lastElapsed; + qreal offset; + qreal offsetAdj; + qreal mappedRange; + bool stealMouse : 1; + bool ownModel : 1; + bool interactive : 1; + bool haveHighlightRange : 1; + bool autoHighlight : 1; + bool highlightUp : 1; + bool layoutScheduled : 1; + bool moving : 1; + bool flicking : 1; + QElapsedTimer lastPosTime; + QPointF lastPos; + qreal dragMargin; + qreal deceleration; + QDeclarativeTimeLine tl; + QDeclarativeTimeLineValueProxy<QSGPathViewPrivate> moveOffset; + int firstIndex; + int pathItems; + int requestedIndex; + QList<QSGItem *> items; + QList<QSGItem *> itemCache; + QDeclarativeGuard<QSGVisualModel> model; + QVariant modelVariant; + enum MovementReason { Other, SetIndex, Mouse }; + MovementReason moveReason; + enum MovementDirection { Shortest, Negative, Positive }; + MovementDirection moveDirection; + QDeclarativeOpenMetaObjectType *attType; + QDeclarativeComponent *highlightComponent; + QSGItem *highlightItem; + QDeclarativeTimeLineValueProxy<QSGPathViewPrivate> moveHighlight; + qreal highlightPosition; + qreal highlightRangeStart; + qreal highlightRangeEnd; + QSGPathView::HighlightRangeMode highlightRangeMode; + int highlightMoveDuration; + int modelCount; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/declarative/items/qsgpincharea.cpp b/src/declarative/items/qsgpincharea.cpp new file mode 100644 index 0000000000..f21d873e5e --- /dev/null +++ b/src/declarative/items/qsgpincharea.cpp @@ -0,0 +1,421 @@ +// Commit: f707672eb4c51ea82fbd98e1da16ece61a74c690 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSG 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgpincharea_p_p.h" +#include "qsgcanvas.h" + +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qapplication.h> + +#include <float.h> +#include <math.h> + +QT_BEGIN_NAMESPACE + +QSGPinch::QSGPinch() + : m_target(0), m_minScale(1.0), m_maxScale(1.0) + , m_minRotation(0.0), m_maxRotation(0.0) + , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX) + , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false) +{ +} + +QSGPinchAreaPrivate::~QSGPinchAreaPrivate() +{ + delete pinch; +} + +QSGPinchArea::QSGPinchArea(QSGItem *parent) + : QSGItem(*(new QSGPinchAreaPrivate), parent) +{ + Q_D(QSGPinchArea); + d->init(); +} + +QSGPinchArea::~QSGPinchArea() +{ +} + +bool QSGPinchArea::isEnabled() const +{ + Q_D(const QSGPinchArea); + return d->absorb; +} + +void QSGPinchArea::setEnabled(bool a) +{ + Q_D(QSGPinchArea); + if (a != d->absorb) { + d->absorb = a; + emit enabledChanged(); + } +} + +void QSGPinchArea::touchEvent(QTouchEvent *event) +{ + Q_D(QSGPinchArea); + if (!d->absorb || !isVisible()) { + QSGItem::event(event); + return; + } + + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + d->touchPoints.clear(); + for (int i = 0; i < event->touchPoints().count(); ++i) { + if (!(event->touchPoints().at(i).state() & Qt::TouchPointReleased)) { + d->touchPoints << event->touchPoints().at(i); + } + } + updatePinch(); + break; + case QEvent::TouchEnd: + d->touchPoints.clear(); + updatePinch(); + break; + default: + QSGItem::event(event); + } +} + +void QSGPinchArea::updatePinch() +{ + Q_D(QSGPinchArea); + if (d->touchPoints.count() == 0) { + if (d->inPinch) { + d->stealMouse = false; + setKeepMouseGrab(false); + d->inPinch = false; + QPointF pinchCenter = mapFromScene(d->sceneLastCenter); + QSGPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation); + pe.setStartCenter(d->pinchStartCenter); + pe.setPreviousCenter(pinchCenter); + pe.setPreviousAngle(d->pinchLastAngle); + pe.setPreviousScale(d->pinchLastScale); + pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); + pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); + pe.setPoint1(mapFromScene(d->lastPoint1)); + pe.setPoint2(mapFromScene(d->lastPoint2)); + emit pinchFinished(&pe); + d->pinchStartDist = 0; + d->pinchActivated = false; + if (d->pinch && d->pinch->target()) + d->pinch->setActive(false); + } + return; + } + QTouchEvent::TouchPoint touchPoint1 = d->touchPoints.at(0); + QTouchEvent::TouchPoint touchPoint2 = d->touchPoints.at(d->touchPoints. count() >= 2 ? 1 : 0); + if (d->touchPoints.count() == 2 + && (touchPoint1.state() & Qt::TouchPointPressed || touchPoint2.state() & Qt::TouchPointPressed)) { + d->id1 = touchPoint1.id(); + d->sceneStartPoint1 = touchPoint1.scenePos(); + d->sceneStartPoint2 = touchPoint2.scenePos(); + d->inPinch = false; + d->pinchRejected = false; + d->pinchActivated = true; + } else if (d->pinchActivated && !d->pinchRejected){ + const int dragThreshold = QApplication::startDragDistance(); + QPointF p1 = touchPoint1.scenePos(); + QPointF p2 = touchPoint2.scenePos(); + qreal dx = p1.x() - p2.x(); + qreal dy = p1.y() - p2.y(); + qreal dist = sqrt(dx*dx + dy*dy); + QPointF sceneCenter = (p1 + p2)/2; + qreal angle = QLineF(p1, p2).angle(); + if (d->touchPoints.count() == 1) { + // If we only have one point then just move the center + if (d->id1 == touchPoint1.id()) + sceneCenter = d->sceneLastCenter + touchPoint1.scenePos() - d->lastPoint1; + else + sceneCenter = d->sceneLastCenter + touchPoint2.scenePos() - d->lastPoint2; + angle = d->pinchLastAngle; + } + d->id1 = touchPoint1.id(); + if (angle > 180) + angle -= 360; + if (!d->inPinch) { + if (d->touchPoints.count() >= 2 + && (qAbs(p1.x()-d->sceneStartPoint1.x()) > dragThreshold + || qAbs(p1.y()-d->sceneStartPoint1.y()) > dragThreshold + || qAbs(p2.x()-d->sceneStartPoint2.x()) > dragThreshold + || qAbs(p2.y()-d->sceneStartPoint2.y()) > dragThreshold)) { + d->sceneStartCenter = sceneCenter; + d->sceneLastCenter = sceneCenter; + d->pinchStartCenter = mapFromScene(sceneCenter); + d->pinchStartDist = dist; + d->pinchStartAngle = angle; + d->pinchLastScale = 1.0; + d->pinchLastAngle = angle; + d->pinchRotation = 0.0; + d->lastPoint1 = p1; + d->lastPoint2 = p2; + QSGPinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0); + pe.setStartCenter(d->pinchStartCenter); + pe.setPreviousCenter(d->pinchStartCenter); + pe.setPreviousAngle(d->pinchLastAngle); + pe.setPreviousScale(d->pinchLastScale); + pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); + pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); + pe.setPoint1(mapFromScene(d->lastPoint1)); + pe.setPoint2(mapFromScene(d->lastPoint2)); + pe.setPointCount(d->touchPoints.count()); + emit pinchStarted(&pe); + if (pe.accepted()) { + d->inPinch = true; + d->stealMouse = true; + QSGCanvas *c = canvas(); + if (c && c->mouseGrabberItem() != this) + grabMouse(); + setKeepMouseGrab(true); + if (d->pinch && d->pinch->target()) { + d->pinchStartPos = pinch()->target()->pos(); + d->pinchStartScale = d->pinch->target()->scale(); + d->pinchStartRotation = d->pinch->target()->rotation(); + d->pinch->setActive(true); + } + } else { + d->pinchRejected = true; + } + } + } else if (d->pinchStartDist > 0) { + qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale; + qreal da = d->pinchLastAngle - angle; + if (da > 180) + da -= 360; + else if (da < -180) + da += 360; + d->pinchRotation += da; + QPointF pinchCenter = mapFromScene(sceneCenter); + QSGPinchEvent pe(pinchCenter, scale, angle, d->pinchRotation); + pe.setStartCenter(d->pinchStartCenter); + pe.setPreviousCenter(mapFromScene(d->sceneLastCenter)); + pe.setPreviousAngle(d->pinchLastAngle); + pe.setPreviousScale(d->pinchLastScale); + pe.setStartPoint1(mapFromScene(d->sceneStartPoint1)); + pe.setStartPoint2(mapFromScene(d->sceneStartPoint2)); + pe.setPoint1(touchPoint1.pos()); + pe.setPoint2(touchPoint2.pos()); + pe.setPointCount(d->touchPoints.count()); + d->pinchLastScale = scale; + d->sceneLastCenter = sceneCenter; + d->pinchLastAngle = angle; + d->lastPoint1 = touchPoint1.scenePos(); + d->lastPoint2 = touchPoint2.scenePos(); + emit pinchUpdated(&pe); + if (d->pinch && d->pinch->target()) { + qreal s = d->pinchStartScale * scale; + s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale()); + pinch()->target()->setScale(s); + QPointF pos = sceneCenter - d->sceneStartCenter + d->pinchStartPos; + if (pinch()->axis() & QSGPinch::XAxis) { + qreal x = pos.x(); + if (x < pinch()->xmin()) + x = pinch()->xmin(); + else if (x > pinch()->xmax()) + x = pinch()->xmax(); + pinch()->target()->setX(x); + } + if (pinch()->axis() & QSGPinch::YAxis) { + qreal y = pos.y(); + if (y < pinch()->ymin()) + y = pinch()->ymin(); + else if (y > pinch()->ymax()) + y = pinch()->ymax(); + pinch()->target()->setY(y); + } + if (d->pinchStartRotation >= pinch()->minimumRotation() + && d->pinchStartRotation <= pinch()->maximumRotation()) { + qreal r = d->pinchRotation + d->pinchStartRotation; + r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation()); + pinch()->target()->setRotation(r); + } + } + } + } +} + +void QSGPinchArea::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPinchArea); + d->stealMouse = false; + if (!d->absorb) + QSGItem::mousePressEvent(event); + else { + setKeepMouseGrab(false); + event->setAccepted(true); + } +} + +void QSGPinchArea::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPinchArea); + if (!d->absorb) { + QSGItem::mouseMoveEvent(event); + return; + } +} + +void QSGPinchArea::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPinchArea); + d->stealMouse = false; + if (!d->absorb) { + QSGItem::mouseReleaseEvent(event); + } else { + QSGCanvas *c = canvas(); + if (c && c->mouseGrabberItem() == this) + ungrabMouse(); + setKeepMouseGrab(false); + } +} + +void QSGPinchArea::mouseUngrabEvent() +{ + setKeepMouseGrab(false); +} + +bool QSGPinchArea::sendMouseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGPinchArea); + QGraphicsSceneMouseEvent mouseEvent(event->type()); + QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + + QSGCanvas *c = canvas(); + QSGItem *grabber = c ? c->mouseGrabberItem() : 0; + bool stealThisEvent = d->stealMouse; + if ((stealThisEvent || myRect.contains(event->scenePos().toPoint())) && (!grabber || !grabber->keepMouseGrab())) { + mouseEvent.setAccepted(false); + for (int i = 0x1; i <= 0x10; i <<= 1) { + if (event->buttons() & i) { + Qt::MouseButton button = Qt::MouseButton(i); + mouseEvent.setButtonDownPos(button, mapFromScene(event->buttonDownPos(button))); + } + } + mouseEvent.setScenePos(event->scenePos()); + mouseEvent.setLastScenePos(event->lastScenePos()); + mouseEvent.setPos(mapFromScene(event->scenePos())); + mouseEvent.setLastPos(mapFromScene(event->lastScenePos())); + + switch(mouseEvent.type()) { + case QEvent::GraphicsSceneMouseMove: + mouseMoveEvent(&mouseEvent); + break; + case QEvent::GraphicsSceneMousePress: + mousePressEvent(&mouseEvent); + break; + case QEvent::GraphicsSceneMouseRelease: + mouseReleaseEvent(&mouseEvent); + break; + default: + break; + } + grabber = c->mouseGrabberItem(); + if (grabber && stealThisEvent && !grabber->keepMouseGrab() && grabber != this) + grabMouse(); + + return stealThisEvent; + } + if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) { + d->stealMouse = false; + if (c && c->mouseGrabberItem() == this) + ungrabMouse(); + setKeepMouseGrab(false); + } + return false; +} + +bool QSGPinchArea::childMouseEventFilter(QSGItem *i, QEvent *e) +{ + Q_D(QSGPinchArea); + if (!d->absorb || !isVisible()) + return QSGItem::childMouseEventFilter(i, e); + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: { + QTouchEvent *touch = static_cast<QTouchEvent*>(e); + d->touchPoints.clear(); + for (int i = 0; i < touch->touchPoints().count(); ++i) + if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) + d->touchPoints << touch->touchPoints().at(i); + updatePinch(); + } + return d->inPinch; + case QEvent::TouchEnd: + d->touchPoints.clear(); + updatePinch(); + break; + default: + break; + } + + return QSGItem::childMouseEventFilter(i, e); +} + +void QSGPinchArea::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + QSGItem::geometryChanged(newGeometry, oldGeometry); +} + +void QSGPinchArea::itemChange(ItemChange change, const ItemChangeData &value) +{ + QSGItem::itemChange(change, value); +} + +QSGPinch *QSGPinchArea::pinch() +{ + Q_D(QSGPinchArea); + if (!d->pinch) + d->pinch = new QSGPinch; + return d->pinch; +} + + +QT_END_NAMESPACE + diff --git a/src/declarative/items/qsgpincharea_p.h b/src/declarative/items/qsgpincharea_p.h new file mode 100644 index 0000000000..4cba6367e1 --- /dev/null +++ b/src/declarative/items/qsgpincharea_p.h @@ -0,0 +1,315 @@ +// Commit: f707672eb4c51ea82fbd98e1da16ece61a74c690 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSG 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPINCHAREA_H +#define QSGPINCHAREA_H + +#include "qsgitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_AUTOTEST_EXPORT QSGPinch : public QObject +{ + Q_OBJECT + + Q_ENUMS(Axis) + Q_PROPERTY(QSGItem *target READ target WRITE setTarget RESET resetTarget) + Q_PROPERTY(qreal minimumScale READ minimumScale WRITE setMinimumScale NOTIFY minimumScaleChanged) + Q_PROPERTY(qreal maximumScale READ maximumScale WRITE setMaximumScale NOTIFY maximumScaleChanged) + Q_PROPERTY(qreal minimumRotation READ minimumRotation WRITE setMinimumRotation NOTIFY minimumRotationChanged) + Q_PROPERTY(qreal maximumRotation READ maximumRotation WRITE setMaximumRotation NOTIFY maximumRotationChanged) + Q_PROPERTY(Axis dragAxis READ axis WRITE setAxis NOTIFY dragAxisChanged) + Q_PROPERTY(qreal minimumX READ xmin WRITE setXmin NOTIFY minimumXChanged) + Q_PROPERTY(qreal maximumX READ xmax WRITE setXmax NOTIFY maximumXChanged) + Q_PROPERTY(qreal minimumY READ ymin WRITE setYmin NOTIFY minimumYChanged) + Q_PROPERTY(qreal maximumY READ ymax WRITE setYmax NOTIFY maximumYChanged) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + +public: + QSGPinch(); + + QSGItem *target() const { return m_target; } + void setTarget(QSGItem *target) { + if (target == m_target) + return; + m_target = target; + emit targetChanged(); + } + void resetTarget() { + if (!m_target) + return; + m_target = 0; + emit targetChanged(); + } + + qreal minimumScale() const { return m_minScale; } + void setMinimumScale(qreal s) { + if (s == m_minScale) + return; + m_minScale = s; + emit minimumScaleChanged(); + } + qreal maximumScale() const { return m_maxScale; } + void setMaximumScale(qreal s) { + if (s == m_maxScale) + return; + m_maxScale = s; + emit maximumScaleChanged(); + } + + qreal minimumRotation() const { return m_minRotation; } + void setMinimumRotation(qreal r) { + if (r == m_minRotation) + return; + m_minRotation = r; + emit minimumRotationChanged(); + } + qreal maximumRotation() const { return m_maxRotation; } + void setMaximumRotation(qreal r) { + if (r == m_maxRotation) + return; + m_maxRotation = r; + emit maximumRotationChanged(); + } + + enum Axis { NoDrag=0x00, XAxis=0x01, YAxis=0x02, XandYAxis=0x03 }; + Axis axis() const { return m_axis; } + void setAxis(Axis a) { + if (a == m_axis) + return; + m_axis = a; + emit dragAxisChanged(); + } + + qreal xmin() const { return m_xmin; } + void setXmin(qreal x) { + if (x == m_xmin) + return; + m_xmin = x; + emit minimumXChanged(); + } + qreal xmax() const { return m_xmax; } + void setXmax(qreal x) { + if (x == m_xmax) + return; + m_xmax = x; + emit maximumXChanged(); + } + qreal ymin() const { return m_ymin; } + void setYmin(qreal y) { + if (y == m_ymin) + return; + m_ymin = y; + emit minimumYChanged(); + } + qreal ymax() const { return m_ymax; } + void setYmax(qreal y) { + if (y == m_ymax) + return; + m_ymax = y; + emit maximumYChanged(); + } + + bool active() const { return m_active; } + void setActive(bool a) { + if (a == m_active) + return; + m_active = a; + emit activeChanged(); + } + +signals: + void targetChanged(); + void minimumScaleChanged(); + void maximumScaleChanged(); + void minimumRotationChanged(); + void maximumRotationChanged(); + void dragAxisChanged(); + void minimumXChanged(); + void maximumXChanged(); + void minimumYChanged(); + void maximumYChanged(); + void activeChanged(); + +private: + QSGItem *m_target; + qreal m_minScale; + qreal m_maxScale; + qreal m_minRotation; + qreal m_maxRotation; + Axis m_axis; + qreal m_xmin; + qreal m_xmax; + qreal m_ymin; + qreal m_ymax; + bool m_active; +}; + +class Q_AUTOTEST_EXPORT QSGPinchEvent : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPointF center READ center) + Q_PROPERTY(QPointF startCenter READ startCenter) + Q_PROPERTY(QPointF previousCenter READ previousCenter) + Q_PROPERTY(qreal scale READ scale) + Q_PROPERTY(qreal previousScale READ previousScale) + Q_PROPERTY(qreal angle READ angle) + Q_PROPERTY(qreal previousAngle READ previousAngle) + Q_PROPERTY(qreal rotation READ rotation) + Q_PROPERTY(QPointF point1 READ point1) + Q_PROPERTY(QPointF startPoint1 READ startPoint1) + Q_PROPERTY(QPointF point2 READ point2) + Q_PROPERTY(QPointF startPoint2 READ startPoint2) + Q_PROPERTY(int pointCount READ pointCount) + Q_PROPERTY(bool accepted READ accepted WRITE setAccepted) + +public: + QSGPinchEvent(QPointF c, qreal s, qreal a, qreal r) + : QObject(), m_center(c), m_scale(s), m_angle(a), m_rotation(r) + , m_pointCount(0), m_accepted(true) {} + + QPointF center() const { return m_center; } + QPointF startCenter() const { return m_startCenter; } + void setStartCenter(QPointF c) { m_startCenter = c; } + QPointF previousCenter() const { return m_lastCenter; } + void setPreviousCenter(QPointF c) { m_lastCenter = c; } + qreal scale() const { return m_scale; } + qreal previousScale() const { return m_lastScale; } + void setPreviousScale(qreal s) { m_lastScale = s; } + qreal angle() const { return m_angle; } + qreal previousAngle() const { return m_lastAngle; } + void setPreviousAngle(qreal a) { m_lastAngle = a; } + qreal rotation() const { return m_rotation; } + QPointF point1() const { return m_point1; } + void setPoint1(QPointF p) { m_point1 = p; } + QPointF startPoint1() const { return m_startPoint1; } + void setStartPoint1(QPointF p) { m_startPoint1 = p; } + QPointF point2() const { return m_point2; } + void setPoint2(QPointF p) { m_point2 = p; } + QPointF startPoint2() const { return m_startPoint2; } + void setStartPoint2(QPointF p) { m_startPoint2 = p; } + int pointCount() const { return m_pointCount; } + void setPointCount(int count) { m_pointCount = count; } + + bool accepted() const { return m_accepted; } + void setAccepted(bool a) { m_accepted = a; } + +private: + QPointF m_center; + QPointF m_startCenter; + QPointF m_lastCenter; + qreal m_scale; + qreal m_lastScale; + qreal m_angle; + qreal m_lastAngle; + qreal m_rotation; + QPointF m_point1; + QPointF m_point2; + QPointF m_startPoint1; + QPointF m_startPoint2; + int m_pointCount; + bool m_accepted; +}; + + +class QSGMouseEvent; +class QSGPinchAreaPrivate; +class Q_AUTOTEST_EXPORT QSGPinchArea : public QSGItem +{ + Q_OBJECT + + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QSGPinch *pinch READ pinch CONSTANT) + +public: + QSGPinchArea(QSGItem *parent=0); + ~QSGPinchArea(); + + bool isEnabled() const; + void setEnabled(bool); + + QSGPinch *pinch(); + +Q_SIGNALS: + void enabledChanged(); + void pinchStarted(QSGPinchEvent *pinch); + void pinchUpdated(QSGPinchEvent *pinch); + void pinchFinished(QSGPinchEvent *pinch); + +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + virtual void mouseUngrabEvent(); + virtual bool sendMouseEvent(QGraphicsSceneMouseEvent *event); + virtual bool childMouseEventFilter(QSGItem *i, QEvent *e); + virtual void touchEvent(QTouchEvent *event); + + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + virtual void itemChange(ItemChange change, const ItemChangeData& value); + +private: + void updatePinch(); + void handlePress(); + void handleRelease(); + +private: + Q_DISABLE_COPY(QSGPinchArea) + Q_DECLARE_PRIVATE(QSGPinchArea) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGPinch) +QML_DECLARE_TYPE(QSGPinchEvent) +QML_DECLARE_TYPE(QSGPinchArea) + +QT_END_HEADER + +#endif // QSGPINCHAREA_H + diff --git a/src/declarative/items/qsgpincharea_p_p.h b/src/declarative/items/qsgpincharea_p_p.h new file mode 100644 index 0000000000..b93d095e0e --- /dev/null +++ b/src/declarative/items/qsgpincharea_p_p.h @@ -0,0 +1,115 @@ +// Commit: f707672eb4c51ea82fbd98e1da16ece61a74c690 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtSG 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPINCHAREA_P_H +#define QSGPINCHAREA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qevent.h> + +#include "qsgitem_p.h" +#include "qsgpincharea_p.h" + +QT_BEGIN_NAMESPACE + +class QSGPinch; +class QSGPinchAreaPrivate : public QSGItemPrivate +{ + Q_DECLARE_PUBLIC(QSGPinchArea) +public: + QSGPinchAreaPrivate() + : absorb(true), stealMouse(false), inPinch(false) + , pinchRejected(false), pinchActivated(false) + , pinch(0), pinchStartDist(0), pinchStartScale(1.0) + , pinchLastScale(1.0), pinchStartRotation(0.0), pinchStartAngle(0.0) + , pinchLastAngle(0.0), pinchRotation(0.0) + { + } + + ~QSGPinchAreaPrivate(); + + void init() + { + Q_Q(QSGPinchArea); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFiltersChildMouseEvents(true); + } + + bool absorb : 1; + bool stealMouse : 1; + bool inPinch : 1; + bool pinchRejected : 1; + bool pinchActivated : 1; + QSGPinch *pinch; + QPointF sceneStartPoint1; + QPointF sceneStartPoint2; + QPointF lastPoint1; + QPointF lastPoint2; + qreal pinchStartDist; + qreal pinchStartScale; + qreal pinchLastScale; + qreal pinchStartRotation; + qreal pinchStartAngle; + qreal pinchLastAngle; + qreal pinchRotation; + QPointF sceneStartCenter; + QPointF pinchStartCenter; + QPointF sceneLastCenter; + QPointF pinchStartPos; + QList<QTouchEvent::TouchPoint> touchPoints; + int id1; +}; + +QT_END_NAMESPACE + +#endif // QSGPINCHAREA_P_H + diff --git a/src/declarative/items/qsgpositioners.cpp b/src/declarative/items/qsgpositioners.cpp new file mode 100644 index 0000000000..f174612af6 --- /dev/null +++ b/src/declarative/items/qsgpositioners.cpp @@ -0,0 +1,788 @@ +// Commit: 1f38d41854fa2daa51d938a4eb398752b1751e0b +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgpositioners_p.h" +#include "qsgpositioners_p_p.h" + +#include <QtDeclarative/qdeclarative.h> +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtCore/qmath.h> +#include <QtCore/qcoreapplication.h> + +#include <private/qdeclarativestate_p.h> +#include <private/qdeclarativestategroup_p.h> +#include <private/qdeclarativestateoperations_p.h> + +QT_BEGIN_NAMESPACE + +static const QSGItemPrivate::ChangeTypes watchedChanges + = QSGItemPrivate::Geometry + | QSGItemPrivate::SiblingOrder + | QSGItemPrivate::Visibility + | QSGItemPrivate::Opacity + | QSGItemPrivate::Destroyed; + +void QSGBasePositionerPrivate::watchChanges(QSGItem *other) +{ + QSGItemPrivate *otherPrivate = QSGItemPrivate::get(other); + otherPrivate->addItemChangeListener(this, watchedChanges); +} + +void QSGBasePositionerPrivate::unwatchChanges(QSGItem* other) +{ + QSGItemPrivate *otherPrivate = QSGItemPrivate::get(other); + otherPrivate->removeItemChangeListener(this, watchedChanges); +} + +QSGBasePositioner::QSGBasePositioner(PositionerType at, QSGItem *parent) + : QSGImplicitSizeItem(*(new QSGBasePositionerPrivate), parent) +{ + Q_D(QSGBasePositioner); + d->init(at); +} + +QSGBasePositioner::QSGBasePositioner(QSGBasePositionerPrivate &dd, PositionerType at, QSGItem *parent) + : QSGImplicitSizeItem(dd, parent) +{ + Q_D(QSGBasePositioner); + d->init(at); +} + +QSGBasePositioner::~QSGBasePositioner() +{ + Q_D(QSGBasePositioner); + for (int i = 0; i < positionedItems.count(); ++i) + d->unwatchChanges(positionedItems.at(i).item); + positionedItems.clear(); +} + +int QSGBasePositioner::spacing() const +{ + Q_D(const QSGBasePositioner); + return d->spacing; +} + +void QSGBasePositioner::setSpacing(int s) +{ + Q_D(QSGBasePositioner); + if (s==d->spacing) + return; + d->spacing = s; + prePositioning(); + emit spacingChanged(); +} + +QDeclarativeTransition *QSGBasePositioner::move() const +{ + Q_D(const QSGBasePositioner); + return d->moveTransition; +} + +void QSGBasePositioner::setMove(QDeclarativeTransition *mt) +{ + Q_D(QSGBasePositioner); + if (mt == d->moveTransition) + return; + d->moveTransition = mt; + emit moveChanged(); +} + +QDeclarativeTransition *QSGBasePositioner::add() const +{ + Q_D(const QSGBasePositioner); + return d->addTransition; +} + +void QSGBasePositioner::setAdd(QDeclarativeTransition *add) +{ + Q_D(QSGBasePositioner); + if (add == d->addTransition) + return; + + d->addTransition = add; + emit addChanged(); +} + +void QSGBasePositioner::componentComplete() +{ + QSGItem::componentComplete(); + positionedItems.reserve(childItems().count()); + prePositioning(); + reportConflictingAnchors(); +} + +void QSGBasePositioner::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QSGBasePositioner); + if (change == ItemChildAddedChange){ + prePositioning(); + } else if (change == ItemChildRemovedChange) { + QSGItem *child = value.item; + QSGBasePositioner::PositionedItem posItem(child); + int idx = positionedItems.find(posItem); + if (idx >= 0) { + d->unwatchChanges(child); + positionedItems.remove(idx); + } + prePositioning(); + } + + QSGItem::itemChange(change, value); +} + +void QSGBasePositioner::prePositioning() +{ + Q_D(QSGBasePositioner); + if (!isComponentComplete()) + return; + + if (d->doingPositioning) + return; + + d->queuedPositioning = false; + d->doingPositioning = true; + //Need to order children by creation order modified by stacking order + QList<QSGItem *> children = childItems(); + + QPODVector<PositionedItem,8> oldItems; + positionedItems.copyAndClear(oldItems); + for (int ii = 0; ii < children.count(); ++ii) { + QSGItem *child = children.at(ii); + QSGItemPrivate *childPrivate = QSGItemPrivate::get(child); + PositionedItem *item = 0; + PositionedItem posItem(child); + int wIdx = oldItems.find(posItem); + if (wIdx < 0) { + d->watchChanges(child); + positionedItems.append(posItem); + item = &positionedItems[positionedItems.count()-1]; + item->isNew = true; + if (child->opacity() <= 0.0 || !childPrivate->explicitVisible || !child->width() || !child->height()) + item->isVisible = false; + } else { + item = &oldItems[wIdx]; + // Items are only omitted from positioning if they are explicitly hidden + // i.e. their positioning is not affected if an ancestor is hidden. + if (child->opacity() <= 0.0 || !childPrivate->explicitVisible || !child->width() || !child->height()) { + item->isVisible = false; + } else if (!item->isVisible) { + item->isVisible = true; + item->isNew = true; + } else { + item->isNew = false; + } + positionedItems.append(*item); + } + } + QSizeF contentSize; + doPositioning(&contentSize); + if(d->addTransition || d->moveTransition) + finishApplyTransitions(); + d->doingPositioning = false; + //Set implicit size to the size of its children + setImplicitHeight(contentSize.height()); + setImplicitWidth(contentSize.width()); +} + +void QSGBasePositioner::positionX(int x, const PositionedItem &target) +{ + Q_D(QSGBasePositioner); + if(d->type == Horizontal || d->type == Both){ + if (target.isNew) { + if (!d->addTransition) + target.item->setX(x); + else + d->addActions << QDeclarativeAction(target.item, QLatin1String("x"), QVariant(x)); + } else if (x != target.item->x()) { + if (!d->moveTransition) + target.item->setX(x); + else + d->moveActions << QDeclarativeAction(target.item, QLatin1String("x"), QVariant(x)); + } + } +} + +void QSGBasePositioner::positionY(int y, const PositionedItem &target) +{ + Q_D(QSGBasePositioner); + if(d->type == Vertical || d->type == Both){ + if (target.isNew) { + if (!d->addTransition) + target.item->setY(y); + else + d->addActions << QDeclarativeAction(target.item, QLatin1String("y"), QVariant(y)); + } else if (y != target.item->y()) { + if (!d->moveTransition) + target.item->setY(y); + else + d->moveActions << QDeclarativeAction(target.item, QLatin1String("y"), QVariant(y)); + } + } +} + +void QSGBasePositioner::finishApplyTransitions() +{ + Q_D(QSGBasePositioner); + // Note that if a transition is not set the transition manager will + // apply the changes directly, in the case add/move aren't set + d->addTransitionManager.transition(d->addActions, d->addTransition); + d->moveTransitionManager.transition(d->moveActions, d->moveTransition); + d->addActions.clear(); + d->moveActions.clear(); +} + +QSGColumn::QSGColumn(QSGItem *parent) +: QSGBasePositioner(Vertical, parent) +{ +} + +void QSGColumn::doPositioning(QSizeF *contentSize) +{ + int voffset = 0; + + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (!child.item || !child.isVisible) + continue; + + if(child.item->y() != voffset) + positionY(voffset, child); + + contentSize->setWidth(qMax(contentSize->width(), child.item->width())); + + voffset += child.item->height(); + voffset += spacing(); + } + + contentSize->setHeight(voffset - spacing()); +} + +void QSGColumn::reportConflictingAnchors() +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate*>(QSGBasePositionerPrivate::get(this)); + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (child.item) { + QSGAnchors *anchors = QSGItemPrivate::get(static_cast<QSGItem *>(child.item))->_anchors; + if (anchors) { + QSGAnchors::Anchors usedAnchors = anchors->usedAnchors(); + if (usedAnchors & QSGAnchors::TopAnchor || + usedAnchors & QSGAnchors::BottomAnchor || + usedAnchors & QSGAnchors::VCenterAnchor || + anchors->fill() || anchors->centerIn()) { + d->anchorConflict = true; + break; + } + } + } + } + if (d->anchorConflict) { + qmlInfo(this) << "Cannot specify top, bottom, verticalCenter, fill or centerIn anchors for items inside Column"; + } +} + +QSGRow::QSGRow(QSGItem *parent) +: QSGBasePositioner(Horizontal, parent) +{ +} + +Qt::LayoutDirection QSGRow::layoutDirection() const +{ + return QSGBasePositionerPrivate::getLayoutDirection(this); +} + +void QSGRow::setLayoutDirection(Qt::LayoutDirection layoutDirection) +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate* >(QSGBasePositionerPrivate::get(this)); + if (d->layoutDirection != layoutDirection) { + d->layoutDirection = layoutDirection; + // For RTL layout the positioning changes when the width changes. + if (d->layoutDirection == Qt::RightToLeft) + d->addItemChangeListener(d, QSGItemPrivate::Geometry); + else + d->removeItemChangeListener(d, QSGItemPrivate::Geometry); + prePositioning(); + emit layoutDirectionChanged(); + emit effectiveLayoutDirectionChanged(); + } +} + +Qt::LayoutDirection QSGRow::effectiveLayoutDirection() const +{ + return QSGBasePositionerPrivate::getEffectiveLayoutDirection(this); +} + +void QSGRow::doPositioning(QSizeF *contentSize) +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate* >(QSGBasePositionerPrivate::get(this)); + int hoffset = 0; + + QList<int> hoffsets; + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (!child.item || !child.isVisible) + continue; + + if (d->isLeftToRight()) { + if (child.item->x() != hoffset) + positionX(hoffset, child); + } else { + hoffsets << hoffset; + } + + contentSize->setHeight(qMax(contentSize->height(), child.item->height())); + + hoffset += child.item->width(); + hoffset += spacing(); + } + + contentSize->setWidth(hoffset - spacing()); + + if (d->isLeftToRight()) + return; + + //Right to Left layout + int end = 0; + if (!widthValid()) + end = contentSize->width(); + else + end = width(); + + int acc = 0; + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (!child.item || !child.isVisible) + continue; + hoffset = end - hoffsets[acc++] - child.item->width(); + if (child.item->x() != hoffset) + positionX(hoffset, child); + } +} + +void QSGRow::reportConflictingAnchors() +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate*>(QSGBasePositionerPrivate::get(this)); + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (child.item) { + QSGAnchors *anchors = QSGItemPrivate::get(static_cast<QSGItem *>(child.item))->_anchors; + if (anchors) { + QSGAnchors::Anchors usedAnchors = anchors->usedAnchors(); + if (usedAnchors & QSGAnchors::LeftAnchor || + usedAnchors & QSGAnchors::RightAnchor || + usedAnchors & QSGAnchors::HCenterAnchor || + anchors->fill() || anchors->centerIn()) { + d->anchorConflict = true; + break; + } + } + } + } + if (d->anchorConflict) + qmlInfo(this) << "Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside Row"; +} + +QSGGrid::QSGGrid(QSGItem *parent) : + QSGBasePositioner(Both, parent), m_rows(-1), m_columns(-1), m_flow(LeftToRight) +{ +} + +void QSGGrid::setColumns(const int columns) +{ + if (columns == m_columns) + return; + m_columns = columns; + prePositioning(); + emit columnsChanged(); +} + +void QSGGrid::setRows(const int rows) +{ + if (rows == m_rows) + return; + m_rows = rows; + prePositioning(); + emit rowsChanged(); +} + +QSGGrid::Flow QSGGrid::flow() const +{ + return m_flow; +} + +void QSGGrid::setFlow(Flow flow) +{ + if (m_flow != flow) { + m_flow = flow; + prePositioning(); + emit flowChanged(); + } +} + +Qt::LayoutDirection QSGGrid::layoutDirection() const +{ + return QSGBasePositionerPrivate::getLayoutDirection(this); +} + +void QSGGrid::setLayoutDirection(Qt::LayoutDirection layoutDirection) +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate*>(QSGBasePositionerPrivate::get(this)); + if (d->layoutDirection != layoutDirection) { + d->layoutDirection = layoutDirection; + // For RTL layout the positioning changes when the width changes. + if (d->layoutDirection == Qt::RightToLeft) + d->addItemChangeListener(d, QSGItemPrivate::Geometry); + else + d->removeItemChangeListener(d, QSGItemPrivate::Geometry); + prePositioning(); + emit layoutDirectionChanged(); + emit effectiveLayoutDirectionChanged(); + } +} + +Qt::LayoutDirection QSGGrid::effectiveLayoutDirection() const +{ + return QSGBasePositionerPrivate::getEffectiveLayoutDirection(this); +} + +void QSGGrid::doPositioning(QSizeF *contentSize) +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate*>(QSGBasePositionerPrivate::get(this)); + int c = m_columns; + int r = m_rows; + //Is allocating the extra QPODVector too much overhead? + QPODVector<PositionedItem, 8> visibleItems;//we aren't concerned with invisible items + visibleItems.reserve(positionedItems.count()); + for(int i=0; i<positionedItems.count(); i++) + if(positionedItems[i].item && positionedItems[i].isVisible) + visibleItems.append(positionedItems[i]); + + int numVisible = visibleItems.count(); + if (m_columns <= 0 && m_rows <= 0){ + c = 4; + r = (numVisible+3)/4; + } else if (m_rows <= 0){ + r = (numVisible+(m_columns-1))/m_columns; + } else if (m_columns <= 0){ + c = (numVisible+(m_rows-1))/m_rows; + } + + if(r==0 || c==0) + return; //Nothing to do + + QList<int> maxColWidth; + QList<int> maxRowHeight; + int childIndex =0; + if (m_flow == LeftToRight) { + for (int i=0; i < r; i++){ + for (int j=0; j < c; j++){ + if (j==0) + maxRowHeight << 0; + if (i==0) + maxColWidth << 0; + + if (childIndex == visibleItems.count()) + break; + + const PositionedItem &child = visibleItems.at(childIndex++); + if (child.item->width() > maxColWidth[j]) + maxColWidth[j] = child.item->width(); + if (child.item->height() > maxRowHeight[i]) + maxRowHeight[i] = child.item->height(); + } + } + } else { + for (int j=0; j < c; j++){ + for (int i=0; i < r; i++){ + if (j==0) + maxRowHeight << 0; + if (i==0) + maxColWidth << 0; + + if (childIndex == visibleItems.count()) + break; + + const PositionedItem &child = visibleItems.at(childIndex++); + if (child.item->width() > maxColWidth[j]) + maxColWidth[j] = child.item->width(); + if (child.item->height() > maxRowHeight[i]) + maxRowHeight[i] = child.item->height(); + } + } + } + + int widthSum = 0; + for (int j=0; j < maxColWidth.size(); j++){ + if (j) + widthSum += spacing(); + widthSum += maxColWidth[j]; + } + + int heightSum = 0; + for (int i=0; i < maxRowHeight.size(); i++){ + if (i) + heightSum += spacing(); + heightSum += maxRowHeight[i]; + } + + contentSize->setHeight(heightSum); + contentSize->setWidth(widthSum); + + int end = 0; + if (widthValid()) + end = width(); + else + end = widthSum; + + int xoffset=0; + if (!d->isLeftToRight()) + xoffset = end; + int yoffset=0; + int curRow =0; + int curCol =0; + for (int i = 0; i < visibleItems.count(); ++i) { + const PositionedItem &child = visibleItems.at(i); + int childXOffset = xoffset; + if (!d->isLeftToRight()) + childXOffset -= child.item->width(); + if ((child.item->x() != childXOffset) || (child.item->y() != yoffset)){ + positionX(childXOffset, child); + positionY(yoffset, child); + } + + if (m_flow == LeftToRight) { + if (d->isLeftToRight()) + xoffset += maxColWidth[curCol]+spacing(); + else + xoffset -= maxColWidth[curCol]+spacing(); + curCol++; + curCol%=c; + if (!curCol){ + yoffset += maxRowHeight[curRow]+spacing(); + if (d->isLeftToRight()) + xoffset = 0; + else + xoffset = end; + curRow++; + if (curRow>=r) + break; + } + } else { + yoffset+=maxRowHeight[curRow]+spacing(); + curRow++; + curRow%=r; + if (!curRow){ + if (d->isLeftToRight()) + xoffset += maxColWidth[curCol]+spacing(); + else + xoffset -= maxColWidth[curCol]+spacing(); + yoffset=0; + curCol++; + if (curCol>=c) + break; + } + } + } +} + +void QSGGrid::reportConflictingAnchors() +{ + QSGBasePositionerPrivate *d = static_cast<QSGBasePositionerPrivate*>(QSGBasePositionerPrivate::get(this)); + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (child.item) { + QSGAnchors *anchors = QSGItemPrivate::get(static_cast<QSGItem *>(child.item))->_anchors; + if (anchors && (anchors->usedAnchors() || anchors->fill() || anchors->centerIn())) { + d->anchorConflict = true; + break; + } + } + } + if (d->anchorConflict) + qmlInfo(this) << "Cannot specify anchors for items inside Grid"; +} + +class QSGFlowPrivate : public QSGBasePositionerPrivate +{ + Q_DECLARE_PUBLIC(QSGFlow) + +public: + QSGFlowPrivate() + : QSGBasePositionerPrivate(), flow(QSGFlow::LeftToRight) + {} + + QSGFlow::Flow flow; +}; + +QSGFlow::QSGFlow(QSGItem *parent) +: QSGBasePositioner(*(new QSGFlowPrivate), Both, parent) +{ + Q_D(QSGFlow); + // Flow layout requires relayout if its own size changes too. + d->addItemChangeListener(d, QSGItemPrivate::Geometry); +} + +QSGFlow::Flow QSGFlow::flow() const +{ + Q_D(const QSGFlow); + return d->flow; +} + +void QSGFlow::setFlow(Flow flow) +{ + Q_D(QSGFlow); + if (d->flow != flow) { + d->flow = flow; + prePositioning(); + emit flowChanged(); + } +} + +Qt::LayoutDirection QSGFlow::layoutDirection() const +{ + Q_D(const QSGFlow); + return d->layoutDirection; +} + +void QSGFlow::setLayoutDirection(Qt::LayoutDirection layoutDirection) +{ + Q_D(QSGFlow); + if (d->layoutDirection != layoutDirection) { + d->layoutDirection = layoutDirection; + prePositioning(); + emit layoutDirectionChanged(); + emit effectiveLayoutDirectionChanged(); + } +} + +Qt::LayoutDirection QSGFlow::effectiveLayoutDirection() const +{ + return QSGBasePositionerPrivate::getEffectiveLayoutDirection(this); +} + +void QSGFlow::doPositioning(QSizeF *contentSize) +{ + Q_D(QSGFlow); + + int hoffset = 0; + int voffset = 0; + int linemax = 0; + QList<int> hoffsets; + + for (int i = 0; i < positionedItems.count(); ++i) { + const PositionedItem &child = positionedItems.at(i); + if (!child.item || !child.isVisible) + continue; + + if (d->flow == LeftToRight) { + if (widthValid() && hoffset && hoffset + child.item->width() > width()) { + hoffset = 0; + voffset += linemax + spacing(); + linemax = 0; + } + } else { + if (heightValid() && voffset && voffset + child.item->height() > height()) { + voffset = 0; + hoffset += linemax + spacing(); + linemax = 0; + } + } + + if (d->isLeftToRight()) { + if (child.item->x() != hoffset) + positionX(hoffset, child); + } else { + hoffsets << hoffset; + } + if (child.item->y() != voffset) + positionY(voffset, child); + + contentSize->setWidth(qMax(contentSize->width(), hoffset + child.item->width())); + contentSize->setHeight(qMax(contentSize->height(), voffset + child.item->height())); + + if (d->flow == LeftToRight) { + hoffset += child.item->width(); + hoffset += spacing(); + linemax = qMax(linemax, qCeil(child.item->height())); + } else { + voffset += child.item->height(); + voffset += spacing(); + linemax = qMax(linemax, qCeil(child.item->width())); + } + } + if (d->isLeftToRight()) + return; + + int end; + if (widthValid()) + end = width(); + else + end = contentSize->width(); + int acc = 0; + for (int i = 0; i < positionedItems.count(); ++i) { + const PositionedItem &child = positionedItems.at(i); + if (!child.item || !child.isVisible) + continue; + hoffset = end - hoffsets[acc++] - child.item->width(); + if (child.item->x() != hoffset) + positionX(hoffset, child); + } +} + +void QSGFlow::reportConflictingAnchors() +{ + Q_D(QSGFlow); + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (child.item) { + QSGAnchors *anchors = QSGItemPrivate::get(static_cast<QSGItem *>(child.item))->_anchors; + if (anchors && (anchors->usedAnchors() || anchors->fill() || anchors->centerIn())) { + d->anchorConflict = true; + break; + } + } + } + if (d->anchorConflict) + qmlInfo(this) << "Cannot specify anchors for items inside Flow"; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgpositioners_p.h b/src/declarative/items/qsgpositioners_p.h new file mode 100644 index 0000000000..eb0e73456b --- /dev/null +++ b/src/declarative/items/qsgpositioners_p.h @@ -0,0 +1,242 @@ +// Commit: 2c7cab4172f1acc86fd49345a2847417e162f2c3 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPOSITIONERS_P_H +#define QSGPOSITIONERS_P_H + +#include "qsgimplicitsizeitem_p.h" + +#include <private/qdeclarativestate_p.h> +#include <private/qpodvector_p.h> + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGBasePositionerPrivate; + +class Q_DECLARATIVE_PRIVATE_EXPORT QSGBasePositioner : public QSGImplicitSizeItem +{ + Q_OBJECT + + Q_PROPERTY(int spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY(QDeclarativeTransition *move READ move WRITE setMove NOTIFY moveChanged) + Q_PROPERTY(QDeclarativeTransition *add READ add WRITE setAdd NOTIFY addChanged) +public: + enum PositionerType { None = 0x0, Horizontal = 0x1, Vertical = 0x2, Both = 0x3 }; + QSGBasePositioner(PositionerType, QSGItem *parent); + ~QSGBasePositioner(); + + int spacing() const; + void setSpacing(int); + + QDeclarativeTransition *move() const; + void setMove(QDeclarativeTransition *); + + QDeclarativeTransition *add() const; + void setAdd(QDeclarativeTransition *); + +protected: + QSGBasePositioner(QSGBasePositionerPrivate &dd, PositionerType at, QSGItem *parent); + virtual void componentComplete(); + virtual void itemChange(ItemChange, const ItemChangeData &); + void finishApplyTransitions(); + +Q_SIGNALS: + void spacingChanged(); + void moveChanged(); + void addChanged(); + +protected Q_SLOTS: + void prePositioning(); + +protected: + virtual void doPositioning(QSizeF *contentSize)=0; + virtual void reportConflictingAnchors()=0; + class PositionedItem { + public : + PositionedItem(QSGItem *i) : item(i), isNew(false), isVisible(true) {} + bool operator==(const PositionedItem &other) const { return other.item == item; } + QSGItem *item; + bool isNew; + bool isVisible; + }; + + QPODVector<PositionedItem,8> positionedItems; + void positionX(int,const PositionedItem &target); + void positionY(int,const PositionedItem &target); + +private: + Q_DISABLE_COPY(QSGBasePositioner) + Q_DECLARE_PRIVATE(QSGBasePositioner) +}; + +class Q_AUTOTEST_EXPORT QSGColumn : public QSGBasePositioner +{ + Q_OBJECT +public: + QSGColumn(QSGItem *parent=0); +protected: + virtual void doPositioning(QSizeF *contentSize); + virtual void reportConflictingAnchors(); +private: + Q_DISABLE_COPY(QSGColumn) +}; + +class Q_AUTOTEST_EXPORT QSGRow: public QSGBasePositioner +{ + Q_OBJECT + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged) + Q_PROPERTY(Qt::LayoutDirection effectiveLayoutDirection READ effectiveLayoutDirection NOTIFY effectiveLayoutDirectionChanged) +public: + QSGRow(QSGItem *parent=0); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection (Qt::LayoutDirection); + Qt::LayoutDirection effectiveLayoutDirection() const; + +Q_SIGNALS: + void layoutDirectionChanged(); + void effectiveLayoutDirectionChanged(); + +protected: + virtual void doPositioning(QSizeF *contentSize); + virtual void reportConflictingAnchors(); +private: + Q_DISABLE_COPY(QSGRow) +}; + +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(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) + +public: + QSGGrid(QSGItem *parent=0); + + int rows() const {return m_rows;} + void setRows(const int rows); + + int columns() const {return m_columns;} + void setColumns(const int columns); + + Q_ENUMS(Flow) + enum Flow { LeftToRight, TopToBottom }; + Flow flow() const; + void setFlow(Flow); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection (Qt::LayoutDirection); + Qt::LayoutDirection effectiveLayoutDirection() const; + +Q_SIGNALS: + void rowsChanged(); + void columnsChanged(); + void flowChanged(); + void layoutDirectionChanged(); + void effectiveLayoutDirectionChanged(); + +protected: + virtual void doPositioning(QSizeF *contentSize); + virtual void reportConflictingAnchors(); + +private: + int m_rows; + int m_columns; + Flow m_flow; + Q_DISABLE_COPY(QSGGrid) +}; + +class QSGFlowPrivate; +class Q_AUTOTEST_EXPORT QSGFlow: public QSGBasePositioner +{ + Q_OBJECT + 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) +public: + QSGFlow(QSGItem *parent=0); + + Q_ENUMS(Flow) + enum Flow { LeftToRight, TopToBottom }; + Flow flow() const; + void setFlow(Flow); + + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection (Qt::LayoutDirection); + Qt::LayoutDirection effectiveLayoutDirection() const; + +Q_SIGNALS: + void flowChanged(); + void layoutDirectionChanged(); + void effectiveLayoutDirectionChanged(); + +protected: + virtual void doPositioning(QSizeF *contentSize); + virtual void reportConflictingAnchors(); +protected: + QSGFlow(QSGFlowPrivate &dd, QSGItem *parent); +private: + Q_DISABLE_COPY(QSGFlow) + Q_DECLARE_PRIVATE(QSGFlow) +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGColumn) +QML_DECLARE_TYPE(QSGRow) +QML_DECLARE_TYPE(QSGGrid) +QML_DECLARE_TYPE(QSGFlow) + +QT_END_HEADER + +#endif // QSGPOSITIONERS_P_H diff --git a/src/declarative/items/qsgpositioners_p_p.h b/src/declarative/items/qsgpositioners_p_p.h new file mode 100644 index 0000000000..49de12a1fd --- /dev/null +++ b/src/declarative/items/qsgpositioners_p_p.h @@ -0,0 +1,174 @@ +// Commit: 2c7cab4172f1acc86fd49345a2847417e162f2c3 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGPOSITIONERS_P_P_H +#define QSGPOSITIONERS_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgpositioners_p.h" +#include "qsgimplicitsizeitem_p_p.h" + +#include <private/qdeclarativestate_p.h> +#include <private/qdeclarativetransitionmanager_p_p.h> +#include <private/qdeclarativestateoperations_p.h> + +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> +#include <QtCore/qtimer.h> + +QT_BEGIN_NAMESPACE + +class QSGBasePositionerPrivate : public QSGImplicitSizeItemPrivate, public QSGItemChangeListener +{ + Q_DECLARE_PUBLIC(QSGBasePositioner) + +public: + QSGBasePositionerPrivate() + : spacing(0), type(QSGBasePositioner::None) + , moveTransition(0), addTransition(0), queuedPositioning(false) + , doingPositioning(false), anchorConflict(false), layoutDirection(Qt::LeftToRight) + { + } + + void init(QSGBasePositioner::PositionerType at) + { + type = at; + childrenDoNotOverlap = true; + } + + int spacing; + + QSGBasePositioner::PositionerType type; + QDeclarativeTransition *moveTransition; + QDeclarativeTransition *addTransition; + QDeclarativeStateOperation::ActionList addActions; + QDeclarativeStateOperation::ActionList moveActions; + QDeclarativeTransitionManager addTransitionManager; + QDeclarativeTransitionManager moveTransitionManager; + + void watchChanges(QSGItem *other); + void unwatchChanges(QSGItem* other); + bool queuedPositioning : 1; + bool doingPositioning : 1; + bool anchorConflict : 1; + + Qt::LayoutDirection layoutDirection; + + void schedulePositioning() + { + Q_Q(QSGBasePositioner); + if(!queuedPositioning){ + QTimer::singleShot(0,q,SLOT(prePositioning())); + queuedPositioning = true; + } + } + + void mirrorChange() { + Q_Q(QSGBasePositioner); + if (type != QSGBasePositioner::Vertical) + q->prePositioning(); + } + bool isLeftToRight() const { + if (type == QSGBasePositioner::Vertical) + return true; + else + return effectiveLayoutMirror ? layoutDirection == Qt::RightToLeft : layoutDirection == Qt::LeftToRight; + } + + virtual void itemSiblingOrderChanged(QSGItem* other) + { + Q_UNUSED(other); + //Delay is due to many children often being reordered at once + //And we only want to reposition them all once + schedulePositioning(); + } + + void itemGeometryChanged(QSGItem *, const QRectF &newGeometry, const QRectF &oldGeometry) + { + Q_Q(QSGBasePositioner); + if (newGeometry.size() != oldGeometry.size()) + q->prePositioning(); + } + + virtual void itemVisibilityChanged(QSGItem *) + { + schedulePositioning(); + } + virtual void itemOpacityChanged(QSGItem *) + { + Q_Q(QSGBasePositioner); + q->prePositioning(); + } + + void itemDestroyed(QSGItem *item) + { + Q_Q(QSGBasePositioner); + q->positionedItems.removeOne(QSGBasePositioner::PositionedItem(item)); + } + + static Qt::LayoutDirection getLayoutDirection(const QSGBasePositioner *positioner) + { + return positioner->d_func()->layoutDirection; + } + + static Qt::LayoutDirection getEffectiveLayoutDirection(const QSGBasePositioner *positioner) + { + if (positioner->d_func()->effectiveLayoutMirror) + return positioner->d_func()->layoutDirection == Qt::RightToLeft ? Qt::LeftToRight : Qt::RightToLeft; + else + return positioner->d_func()->layoutDirection; + } +}; + +QT_END_NAMESPACE + +#endif // QSGPOSITIONERS_P_P_H diff --git a/src/declarative/items/qsgrectangle.cpp b/src/declarative/items/qsgrectangle.cpp new file mode 100644 index 0000000000..cba6527759 --- /dev/null +++ b/src/declarative/items/qsgrectangle.cpp @@ -0,0 +1,290 @@ +// Commit: acc903853d5ac54d646d324b7386c998bc07d464 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgrectangle_p.h" +#include "qsgrectangle_p_p.h" + +#include <private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> + +#include <QtGui/qpixmapcache.h> +#include <QtCore/qstringbuilder.h> +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +// XXX todo - should we change rectangle to draw entirely within its width/height? + +QSGPen::QSGPen(QObject *parent) +: QObject(parent), _width(1), _color("#000000"), _valid(false) +{ +} + +QColor QSGPen::color() const +{ + return _color; +} + +void QSGPen::setColor(const QColor &c) +{ + _color = c; + _valid = (_color.alpha() && _width >= 1) ? true : false; + emit penChanged(); +} + +int QSGPen::width() const +{ + return _width; +} + +void QSGPen::setWidth(int w) +{ + if (_width == w && _valid) + return; + + _width = w; + _valid = (_color.alpha() && _width >= 1) ? true : false; + emit penChanged(); +} + +bool QSGPen::isValid() const +{ + return _valid; +} + +QSGGradientStop::QSGGradientStop(QObject *parent) +: QObject(parent) +{ +} + +qreal QSGGradientStop::position() const +{ + return m_position; +} + +void QSGGradientStop::setPosition(qreal position) +{ + m_position = position; updateGradient(); +} + +QColor QSGGradientStop::color() const +{ + return m_color; +} + +void QSGGradientStop::setColor(const QColor &color) +{ + m_color = color; updateGradient(); +} + +void QSGGradientStop::updateGradient() +{ + if (QSGGradient *grad = qobject_cast<QSGGradient*>(parent())) + grad->doUpdate(); +} + +QSGGradient::QSGGradient(QObject *parent) +: QObject(parent), m_gradient(0) +{ +} + +QSGGradient::~QSGGradient() +{ + delete m_gradient; +} + +QDeclarativeListProperty<QSGGradientStop> QSGGradient::stops() +{ + return QDeclarativeListProperty<QSGGradientStop>(this, m_stops); +} + +const QGradient *QSGGradient::gradient() const +{ + if (!m_gradient && !m_stops.isEmpty()) { + m_gradient = new QLinearGradient(0,0,0,1.0); + for (int i = 0; i < m_stops.count(); ++i) { + const QSGGradientStop *stop = m_stops.at(i); + m_gradient->setCoordinateMode(QGradient::ObjectBoundingMode); + m_gradient->setColorAt(stop->position(), stop->color()); + } + } + + return m_gradient; +} + +void QSGGradient::doUpdate() +{ + delete m_gradient; + m_gradient = 0; + emit updated(); +} + +int QSGRectanglePrivate::doUpdateSlotIdx = -1; + +QSGRectangle::QSGRectangle(QSGItem *parent) +: QSGItem(*(new QSGRectanglePrivate), parent) +{ + setFlag(ItemHasContents); +} + +void QSGRectangle::doUpdate() +{ + Q_D(QSGRectangle); + const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0; + d->setPaintMargin((pw+1)/2); + update(); +} + +QSGPen *QSGRectangle::border() +{ + Q_D(QSGRectangle); + return d->getPen(); +} + +QSGGradient *QSGRectangle::gradient() const +{ + Q_D(const QSGRectangle); + return d->gradient; +} + +void QSGRectangle::setGradient(QSGGradient *gradient) +{ + Q_D(QSGRectangle); + if (d->gradient == gradient) + return; + static int updatedSignalIdx = -1; + if (updatedSignalIdx < 0) + updatedSignalIdx = QSGGradient::staticMetaObject.indexOfSignal("updated()"); + if (d->doUpdateSlotIdx < 0) + d->doUpdateSlotIdx = QSGRectangle::staticMetaObject.indexOfSlot("doUpdate()"); + if (d->gradient) + QMetaObject::disconnect(d->gradient, updatedSignalIdx, this, d->doUpdateSlotIdx); + d->gradient = gradient; + if (d->gradient) + QMetaObject::connect(d->gradient, updatedSignalIdx, this, d->doUpdateSlotIdx); + update(); +} + +qreal QSGRectangle::radius() const +{ + Q_D(const QSGRectangle); + return d->radius; +} + +void QSGRectangle::setRadius(qreal radius) +{ + Q_D(QSGRectangle); + if (d->radius == radius) + return; + + d->radius = radius; + update(); + emit radiusChanged(); +} + +QColor QSGRectangle::color() const +{ + Q_D(const QSGRectangle); + return d->color; +} + +void QSGRectangle::setColor(const QColor &c) +{ + Q_D(QSGRectangle); + if (d->color == c) + return; + + d->color = c; + update(); + emit colorChanged(); +} + +QSGNode *QSGRectangle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + Q_D(QSGRectangle); + + if (width() <= 0 || height() <= 0) { + delete oldNode; + return 0; + } + + QSGRectangleNode *rectangle = static_cast<QSGRectangleNode *>(oldNode); + if (!rectangle) rectangle = d->sceneGraphContext()->createRectangleNode(); + + rectangle->setRect(QRectF(0, 0, width(), height())); + rectangle->setColor(d->color); + + if (d->pen && d->pen->isValid()) { + rectangle->setPenColor(d->pen->color()); + rectangle->setPenWidth(d->pen->width()); + } else { + rectangle->setPenColor(QColor()); + rectangle->setPenWidth(0); + } + + rectangle->setRadius(d->radius); + + QGradientStops stops; + if (d->gradient) { + QList<QSGGradientStop *> qxstops = d->gradient->m_stops; + for (int i = 0; i < qxstops.size(); ++i){ + int j = 0; + while (j < stops.size() && stops.at(j).first < qxstops[i]->position()) + j++; + stops.insert(j, QGradientStop(qxstops.at(i)->position(), qxstops.at(i)->color())); + } + } + rectangle->setGradientStops(stops); + + rectangle->update(); + + return rectangle; +} + +QRectF QSGRectangle::boundingRect() const +{ + Q_D(const QSGRectangle); + return QRectF(-d->paintmargin, -d->paintmargin, width()+d->paintmargin*2, height()+d->paintmargin*2); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgrectangle_p.h b/src/declarative/items/qsgrectangle_p.h new file mode 100644 index 0000000000..6cd5172f35 --- /dev/null +++ b/src/declarative/items/qsgrectangle_p.h @@ -0,0 +1,184 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGRECTANGLE_P_H +#define QSGRECTANGLE_P_H + +#include "qsgitem.h" + +#include <QtGui/qbrush.h> + +#include <private/qdeclarativeglobal_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class Q_DECLARATIVE_PRIVATE_EXPORT QSGPen : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int width READ width WRITE setWidth NOTIFY penChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY penChanged) +public: + QSGPen(QObject *parent=0); + + int width() const; + void setWidth(int w); + + QColor color() const; + void setColor(const QColor &c); + + bool isValid() const; + +Q_SIGNALS: + void penChanged(); + +private: + int _width; + QColor _color; + bool _valid; +}; + +class Q_AUTOTEST_EXPORT QSGGradientStop : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal position READ position WRITE setPosition) + Q_PROPERTY(QColor color READ color WRITE setColor) + +public: + QSGGradientStop(QObject *parent=0); + + qreal position() const; + void setPosition(qreal position); + + QColor color() const; + void setColor(const QColor &color); + +private: + void updateGradient(); + +private: + qreal m_position; + QColor m_color; +}; + +class Q_AUTOTEST_EXPORT QSGGradient : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativeListProperty<QSGGradientStop> stops READ stops) + Q_CLASSINFO("DefaultProperty", "stops") + +public: + QSGGradient(QObject *parent=0); + ~QSGGradient(); + + QDeclarativeListProperty<QSGGradientStop> stops(); + + const QGradient *gradient() const; + +Q_SIGNALS: + void updated(); + +private: + void doUpdate(); + +private: + QList<QSGGradientStop *> m_stops; + mutable QGradient *m_gradient; + friend class QSGRectangle; + friend class QSGGradientStop; +}; + +class QSGRectanglePrivate; +class Q_DECLARATIVE_PRIVATE_EXPORT QSGRectangle : public QSGItem +{ + Q_OBJECT + + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QSGGradient *gradient READ gradient WRITE setGradient) + Q_PROPERTY(QSGPen * border READ border CONSTANT) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) +public: + QSGRectangle(QSGItem *parent=0); + + QColor color() const; + void setColor(const QColor &); + + QSGPen *border(); + + QSGGradient *gradient() const; + void setGradient(QSGGradient *gradient); + + qreal radius() const; + void setRadius(qreal radius); + + QRectF boundingRect() const; + +Q_SIGNALS: + void colorChanged(); + void radiusChanged(); + +protected: + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private Q_SLOTS: + void doUpdate(); + +private: + Q_DISABLE_COPY(QSGRectangle) + Q_DECLARE_PRIVATE(QSGRectangle) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGPen) +QML_DECLARE_TYPE(QSGGradientStop) +QML_DECLARE_TYPE(QSGGradient) +QML_DECLARE_TYPE(QSGRectangle) + +QT_END_HEADER + +#endif // QSGRECTANGLE_P_H diff --git a/src/declarative/items/qsgrectangle_p_p.h b/src/declarative/items/qsgrectangle_p_p.h new file mode 100644 index 0000000000..15bbd1f032 --- /dev/null +++ b/src/declarative/items/qsgrectangle_p_p.h @@ -0,0 +1,109 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGRECTANGLE_P_P_H +#define QSGRECTANGLE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgitem_p.h" + +QT_BEGIN_NAMESPACE + +class QSGGradient; +class QSGRectangle; +class QSGRectanglePrivate : public QSGItemPrivate +{ + Q_DECLARE_PUBLIC(QSGRectangle) + +public: + QSGRectanglePrivate() : + color(Qt::white), gradient(0), pen(0), radius(0), paintmargin(0) + { + } + + ~QSGRectanglePrivate() + { + delete pen; + } + + QColor color; + QSGGradient *gradient; + QSGPen *pen; + qreal radius; + qreal paintmargin; + static int doUpdateSlotIdx; + + QSGPen *getPen() { + if (!pen) { + Q_Q(QSGRectangle); + pen = new QSGPen; + static int penChangedSignalIdx = -1; + if (penChangedSignalIdx < 0) + penChangedSignalIdx = QSGPen::staticMetaObject.indexOfSignal("penChanged()"); + if (doUpdateSlotIdx < 0) + doUpdateSlotIdx = QSGRectangle::staticMetaObject.indexOfSlot("doUpdate()"); + QMetaObject::connect(pen, penChangedSignalIdx, q, doUpdateSlotIdx); + } + return pen; + } + + void setPaintMargin(qreal margin) + { + if (margin == paintmargin) + return; + paintmargin = margin; + } +}; + +QT_END_NAMESPACE + +#endif // QSGRECTANGLE_P_P_H diff --git a/src/declarative/items/qsgrepeater.cpp b/src/declarative/items/qsgrepeater.cpp new file mode 100644 index 0000000000..ac6086fc62 --- /dev/null +++ b/src/declarative/items/qsgrepeater.cpp @@ -0,0 +1,294 @@ +// Commit: a9f1eaa6a368bf7a72b52c428728a3e3e0a76209 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgrepeater_p.h" +#include "qsgrepeater_p_p.h" +#include "qsgvisualitemmodel_p.h" + +#include <private/qdeclarativeglobal_p.h> +#include <private/qdeclarativelistaccessor_p.h> +#include <private/qlistmodelinterface_p.h> + +QT_BEGIN_NAMESPACE + +QSGRepeaterPrivate::QSGRepeaterPrivate() +: model(0), ownModel(false) +{ +} + +QSGRepeaterPrivate::~QSGRepeaterPrivate() +{ + if (ownModel) + delete model; +} + +QSGRepeater::QSGRepeater(QSGItem *parent) + : QSGItem(*(new QSGRepeaterPrivate), parent) +{ +} + +QSGRepeater::~QSGRepeater() +{ +} + +QVariant QSGRepeater::model() const +{ + Q_D(const QSGRepeater); + return d->dataSource; +} + +void QSGRepeater::setModel(const QVariant &model) +{ + Q_D(QSGRepeater); + if (d->dataSource == model) + return; + + clear(); + if (d->model) { + disconnect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + disconnect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + disconnect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + disconnect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + /* + disconnect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + disconnect(d->model, SIGNAL(destroyingItem(QSGItem*)), this, SLOT(destroyingItem(QSGItem*))); + */ + } + d->dataSource = model; + QObject *object = qvariant_cast<QObject*>(model); + QSGVisualModel *vim = 0; + if (object && (vim = qobject_cast<QSGVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + dataModel->setModel(model); + } + if (d->model) { + connect(d->model, SIGNAL(itemsInserted(int,int)), this, SLOT(itemsInserted(int,int))); + connect(d->model, SIGNAL(itemsRemoved(int,int)), this, SLOT(itemsRemoved(int,int))); + connect(d->model, SIGNAL(itemsMoved(int,int,int)), this, SLOT(itemsMoved(int,int,int))); + connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset())); + /* + connect(d->model, SIGNAL(createdItem(int,QSGItem*)), this, SLOT(createdItem(int,QSGItem*))); + connect(d->model, SIGNAL(destroyingItem(QSGItem*)), this, SLOT(destroyingItem(QSGItem*))); + */ + regenerate(); + } + emit modelChanged(); + emit countChanged(); +} + +QDeclarativeComponent *QSGRepeater::delegate() const +{ + Q_D(const QSGRepeater); + if (d->model) { + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QSGRepeater::setDelegate(QDeclarativeComponent *delegate) +{ + Q_D(QSGRepeater); + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) + if (delegate == dataModel->delegate()) + return; + + if (!d->ownModel) { + d->model = new QSGVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QSGVisualDataModel *dataModel = qobject_cast<QSGVisualDataModel*>(d->model)) { + dataModel->setDelegate(delegate); + regenerate(); + emit delegateChanged(); + } +} + +int QSGRepeater::count() const +{ + Q_D(const QSGRepeater); + if (d->model) + return d->model->count(); + return 0; +} + +QSGItem *QSGRepeater::itemAt(int index) const +{ + Q_D(const QSGRepeater); + if (index >= 0 && index < d->deletables.count()) + return d->deletables[index]; + return 0; +} + +void QSGRepeater::componentComplete() +{ + QSGItem::componentComplete(); + regenerate(); +} + +void QSGRepeater::itemChange(ItemChange change, const ItemChangeData &value) +{ + QSGItem::itemChange(change, value); + if (change == ItemParentHasChanged) { + regenerate(); + } +} + +void QSGRepeater::clear() +{ + Q_D(QSGRepeater); + bool complete = isComponentComplete(); + + if (d->model) { + while (d->deletables.count() > 0) { + QSGItem *item = d->deletables.takeLast(); + if (complete) + emit itemRemoved(d->deletables.count()-1, item); + d->model->release(item); + } + } + d->deletables.clear(); +} + +void QSGRepeater::regenerate() +{ + Q_D(QSGRepeater); + if (!isComponentComplete()) + return; + + clear(); + + if (!d->model || !d->model->count() || !d->model->isValid() || !parentItem() || !isComponentComplete()) + return; + + for (int ii = 0; ii < count(); ++ii) { + QSGItem *item = d->model->item(ii); + if (item) { + QDeclarative_setParent_noEvent(item, parentItem()); + item->setParentItem(parentItem()); + item->stackBefore(this); + d->deletables << item; + emit itemAdded(ii, item); + } + } +} + +void QSGRepeater::itemsInserted(int index, int count) +{ + Q_D(QSGRepeater); + if (!isComponentComplete()) + return; + for (int i = 0; i < count; ++i) { + int modelIndex = index + i; + QSGItem *item = d->model->item(modelIndex); + if (item) { + QDeclarative_setParent_noEvent(item, parentItem()); + item->setParentItem(parentItem()); + if (modelIndex < d->deletables.count()) + item->stackBefore(d->deletables.at(modelIndex)); + else + item->stackBefore(this); + d->deletables.insert(modelIndex, item); + emit itemAdded(modelIndex, item); + } + } + emit countChanged(); +} + +void QSGRepeater::itemsRemoved(int index, int count) +{ + Q_D(QSGRepeater); + if (!isComponentComplete() || count <= 0) + return; + while (count--) { + QSGItem *item = d->deletables.takeAt(index); + emit itemRemoved(index, item); + if (item) + d->model->release(item); + else + break; + } + emit countChanged(); +} + +void QSGRepeater::itemsMoved(int from, int to, int count) +{ + Q_D(QSGRepeater); + if (!isComponentComplete() || count <= 0) + return; + if (from + count > d->deletables.count()) { + regenerate(); + return; + } + QList<QSGItem*> removed; + int removedCount = count; + while (removedCount--) + removed << d->deletables.takeAt(from); + for (int i = 0; i < count; ++i) + d->deletables.insert(to + i, removed.at(i)); + d->deletables.last()->stackBefore(this); + for (int i = d->model->count()-1; i > 0; --i) { + QSGItem *item = d->deletables.at(i-1); + item->stackBefore(d->deletables.at(i)); + } +} + +void QSGRepeater::modelReset() +{ + if (!isComponentComplete()) + return; + regenerate(); + emit countChanged(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgrepeater_p.h b/src/declarative/items/qsgrepeater_p.h new file mode 100644 index 0000000000..3a53fa4177 --- /dev/null +++ b/src/declarative/items/qsgrepeater_p.h @@ -0,0 +1,111 @@ +// Commit: ebd4bc73c46c2962742a682b6a391fb68c482aec +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGREPEATER_P_H +#define QSGREPEATER_P_H + +#include "qsgitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGRepeaterPrivate; +class Q_AUTOTEST_EXPORT QSGRepeater : public QSGItem +{ + Q_OBJECT + + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_CLASSINFO("DefaultProperty", "delegate") + +public: + QSGRepeater(QSGItem *parent=0); + virtual ~QSGRepeater(); + + QVariant model() const; + void setModel(const QVariant &); + + QDeclarativeComponent *delegate() const; + void setDelegate(QDeclarativeComponent *); + + int count() const; + + Q_INVOKABLE QSGItem *itemAt(int index) const; + +Q_SIGNALS: + void modelChanged(); + void delegateChanged(); + void countChanged(); + + void itemAdded(int index, QSGItem *item); + void itemRemoved(int index, QSGItem *item); + +private: + void clear(); + void regenerate(); + +protected: + virtual void componentComplete(); + void itemChange(ItemChange change, const ItemChangeData &value); + +private Q_SLOTS: + void itemsInserted(int,int); + void itemsRemoved(int,int); + void itemsMoved(int,int,int); + void modelReset(); + +private: + Q_DISABLE_COPY(QSGRepeater) + Q_DECLARE_PRIVATE(QSGRepeater) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGRepeater) + +QT_END_HEADER + +#endif // QSGREPEATER_P_H diff --git a/src/declarative/items/qsgrepeater_p_p.h b/src/declarative/items/qsgrepeater_p_p.h new file mode 100644 index 0000000000..155f1b8c6d --- /dev/null +++ b/src/declarative/items/qsgrepeater_p_p.h @@ -0,0 +1,83 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGREPEATER_P_P_H +#define QSGREPEATER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgrepeater_p.h" +#include "qsgitem_p.h" + +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE + +class QDeclarativeContext; +class QSGVisualModel; +class QSGRepeaterPrivate : public QSGItemPrivate +{ + Q_DECLARE_PUBLIC(QSGRepeater) + +public: + QSGRepeaterPrivate(); + ~QSGRepeaterPrivate(); + + QSGVisualModel *model; + QVariant dataSource; + bool ownModel; + + QList<QPointer<QSGItem> > deletables; +}; + +QT_END_NAMESPACE + +#endif // QSGREPEATER_P_P_H diff --git a/src/declarative/items/qsgscalegrid.cpp b/src/declarative/items/qsgscalegrid.cpp new file mode 100644 index 0000000000..4a73801159 --- /dev/null +++ b/src/declarative/items/qsgscalegrid.cpp @@ -0,0 +1,213 @@ +// Commit: 7ddec9f3179bfd854ae53e23ab292de1f9a26377 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgscalegrid_p_p.h" + +#include <QtDeclarative/qdeclarative.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QSGScaleGrid + \brief The QSGScaleGrid class allows you to specify a 3x3 grid to use in scaling an image. +*/ + +QSGScaleGrid::QSGScaleGrid(QObject *parent) : QObject(parent), _left(0), _top(0), _right(0), _bottom(0) +{ +} + +QSGScaleGrid::~QSGScaleGrid() +{ +} + +bool QSGScaleGrid::isNull() const +{ + return !_left && !_top && !_right && !_bottom; +} + +void QSGScaleGrid::setLeft(int pos) +{ + if (_left != pos) { + _left = pos; + emit borderChanged(); + } +} + +void QSGScaleGrid::setTop(int pos) +{ + if (_top != pos) { + _top = pos; + emit borderChanged(); + } +} + +void QSGScaleGrid::setRight(int pos) +{ + if (_right != pos) { + _right = pos; + emit borderChanged(); + } +} + +void QSGScaleGrid::setBottom(int pos) +{ + if (_bottom != pos) { + _bottom = pos; + emit borderChanged(); + } +} + +QSGGridScaledImage::QSGGridScaledImage() +: _l(-1), _r(-1), _t(-1), _b(-1), + _h(QSGBorderImage::Stretch), _v(QSGBorderImage::Stretch) +{ +} + +QSGGridScaledImage::QSGGridScaledImage(const QSGGridScaledImage &o) +: _l(o._l), _r(o._r), _t(o._t), _b(o._b), _h(o._h), _v(o._v), _pix(o._pix) +{ +} + +QSGGridScaledImage &QSGGridScaledImage::operator=(const QSGGridScaledImage &o) +{ + _l = o._l; + _r = o._r; + _t = o._t; + _b = o._b; + _h = o._h; + _v = o._v; + _pix = o._pix; + return *this; +} + +QSGGridScaledImage::QSGGridScaledImage(QIODevice *data) +: _l(-1), _r(-1), _t(-1), _b(-1), _h(QSGBorderImage::Stretch), _v(QSGBorderImage::Stretch) +{ + int l = -1; + int r = -1; + int t = -1; + int b = -1; + QString imgFile; + + QByteArray raw; + while(raw = data->readLine(), !raw.isEmpty()) { + QString line = QString::fromUtf8(raw.trimmed()); + if (line.isEmpty() || line.startsWith(QLatin1Char('#'))) + continue; + + int colonId = line.indexOf(QLatin1Char(':')); + if (colonId <= 0) + return; + + QStringList list; + list.append(line.left(colonId).trimmed()); + list.append(line.mid(colonId+1).trimmed()); + + if (list[0] == QLatin1String("border.left")) + l = list[1].toInt(); + else if (list[0] == QLatin1String("border.right")) + r = list[1].toInt(); + else if (list[0] == QLatin1String("border.top")) + t = list[1].toInt(); + else if (list[0] == QLatin1String("border.bottom")) + b = list[1].toInt(); + else if (list[0] == QLatin1String("source")) + imgFile = list[1]; + else if (list[0] == QLatin1String("horizontalTileRule")) + _h = stringToRule(list[1]); + else if (list[0] == QLatin1String("verticalTileRule")) + _v = stringToRule(list[1]); + } + + if (l < 0 || r < 0 || t < 0 || b < 0 || imgFile.isEmpty()) + return; + + _l = l; _r = r; _t = t; _b = b; + + _pix = imgFile; +} + +QSGBorderImage::TileMode QSGGridScaledImage::stringToRule(const QString &s) +{ + if (s == QLatin1String("Stretch")) + return QSGBorderImage::Stretch; + if (s == QLatin1String("Repeat")) + return QSGBorderImage::Repeat; + if (s == QLatin1String("Round")) + return QSGBorderImage::Round; + + qWarning("QSGGridScaledImage: Invalid tile rule specified. Using Stretch."); + return QSGBorderImage::Stretch; +} + +bool QSGGridScaledImage::isValid() const +{ + return _l >= 0; +} + +int QSGGridScaledImage::gridLeft() const +{ + return _l; +} + +int QSGGridScaledImage::gridRight() const +{ + return _r; +} + +int QSGGridScaledImage::gridTop() const +{ + return _t; +} + +int QSGGridScaledImage::gridBottom() const +{ + return _b; +} + +QString QSGGridScaledImage::pixmapUrl() const +{ + return _pix; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgscalegrid_p_p.h b/src/declarative/items/qsgscalegrid_p_p.h new file mode 100644 index 0000000000..57beb4b3b0 --- /dev/null +++ b/src/declarative/items/qsgscalegrid_p_p.h @@ -0,0 +1,134 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGSCALEGRID_P_P_H +#define QSGSCALEGRID_P_P_H + +#include "qsgborderimage_p.h" + +#include <QtDeclarative/qdeclarative.h> +#include <QtCore/qobject.h> + +#include <private/qdeclarativepixmapcache_p.h> +#include <private/qdeclarativeglobal_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_PRIVATE_EXPORT QSGScaleGrid : public QObject +{ + Q_OBJECT + Q_ENUMS(TileRule) + + Q_PROPERTY(int left READ left WRITE setLeft NOTIFY borderChanged) + Q_PROPERTY(int top READ top WRITE setTop NOTIFY borderChanged) + Q_PROPERTY(int right READ right WRITE setRight NOTIFY borderChanged) + Q_PROPERTY(int bottom READ bottom WRITE setBottom NOTIFY borderChanged) + +public: + QSGScaleGrid(QObject *parent=0); + ~QSGScaleGrid(); + + bool isNull() const; + + int left() const { return _left; } + void setLeft(int); + + int top() const { return _top; } + void setTop(int); + + int right() const { return _right; } + void setRight(int); + + int bottom() const { return _bottom; } + void setBottom(int); + +Q_SIGNALS: + void borderChanged(); + +private: + int _left; + int _top; + int _right; + int _bottom; +}; + +class Q_DECLARATIVE_PRIVATE_EXPORT QSGGridScaledImage +{ +public: + QSGGridScaledImage(); + QSGGridScaledImage(const QSGGridScaledImage &); + QSGGridScaledImage(QIODevice*); + QSGGridScaledImage &operator=(const QSGGridScaledImage &); + bool isValid() const; + int gridLeft() const; + int gridRight() const; + int gridTop() const; + int gridBottom() const; + QSGBorderImage::TileMode horizontalTileRule() const { return _h; } + QSGBorderImage::TileMode verticalTileRule() const { return _v; } + + QString pixmapUrl() const; + +private: + static QSGBorderImage::TileMode stringToRule(const QString &); + +private: + int _l; + int _r; + int _t; + int _b; + QSGBorderImage::TileMode _h; + QSGBorderImage::TileMode _v; + QString _pix; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGScaleGrid) + +QT_END_HEADER + +#endif // QSGSCALEGRID_P_P_H diff --git a/src/declarative/items/qsgshadereffectitem.cpp b/src/declarative/items/qsgshadereffectitem.cpp new file mode 100644 index 0000000000..40ec25c2cc --- /dev/null +++ b/src/declarative/items/qsgshadereffectitem.cpp @@ -0,0 +1,449 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qsgshadereffectitem_p.h> +#include <private/qsgshadereffectnode_p.h> + +#include "qsgmaterial.h" +#include "qsgitem_p.h" + +#include <private/qsgcontext_p.h> +#include <private/qsgtextureprovider_p.h> +#include "qsgcanvas.h" + +#include <QtCore/qsignalmapper.h> +#include <QtOpenGL/qglframebufferobject.h> + +QT_BEGIN_NAMESPACE + +static const char qt_default_vertex_code[] = + "uniform highp mat4 qt_ModelViewProjectionMatrix; \n" + "attribute highp vec4 qt_Vertex; \n" + "attribute highp vec2 qt_MultiTexCoord0; \n" + "varying highp vec2 qt_TexCoord0; \n" + "void main() { \n" + " qt_TexCoord0 = qt_MultiTexCoord0; \n" + " gl_Position = qt_ModelViewProjectionMatrix * qt_Vertex; \n" + "}"; + +static const char qt_default_fragment_code[] = + "varying highp vec2 qt_TexCoord0; \n" + "uniform sampler2D source; \n" + "uniform lowp float qt_Opacity; \n" + "void main() { \n" + " gl_FragColor = texture2D(source, qt_TexCoord0) * qt_Opacity; \n" + "}"; + +static const char qt_position_attribute_name[] = "qt_Vertex"; +static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0"; + +const char *qtPositionAttributeName() +{ + return qt_position_attribute_name; +} + +const char *qtTexCoordAttributeName() +{ + return qt_texcoord_attribute_name; +} + +QSGShaderEffectItem::QSGShaderEffectItem(QSGItem *parent) + : QSGItem(parent) + , m_mesh(0) + , m_cullMode(NoCulling) + , m_blending(true) + , m_dirtyData(true) + , m_programDirty(true) + , m_dirtyMesh(true) + , m_dirtyGeometry(true) +{ + setFlag(QSGItem::ItemHasContents); +} + +QSGShaderEffectItem::~QSGShaderEffectItem() +{ + reset(); +} + +void QSGShaderEffectItem::componentComplete() +{ + updateProperties(); + QSGItem::componentComplete(); +} + +void QSGShaderEffectItem::setFragmentShader(const QByteArray &code) +{ + if (m_source.fragmentCode.constData() == code.constData()) + return; + m_source.fragmentCode = code; + if (isComponentComplete()) { + reset(); + updateProperties(); + } + emit fragmentShaderChanged(); +} + +void QSGShaderEffectItem::setVertexShader(const QByteArray &code) +{ + if (m_source.vertexCode.constData() == code.constData()) + return; + m_source.vertexCode = code; + if (isComponentComplete()) { + reset(); + updateProperties(); + } + emit vertexShaderChanged(); +} + +void QSGShaderEffectItem::setBlending(bool enable) +{ + if (blending() == enable) + return; + + m_blending = enable; + update(); + + emit blendingChanged(); +} + +void QSGShaderEffectItem::setMesh(QSGShaderEffectMesh *mesh) +{ + if (mesh == m_mesh) + return; + if (m_mesh) + disconnect(m_mesh, SIGNAL(geometryChanged()), this, 0); + m_mesh = mesh; + if (m_mesh) + connect(m_mesh, SIGNAL(geometryChanged()), this, SLOT(updateGeometry())); + m_dirtyMesh = true; + update(); + emit meshChanged(); +} + +void QSGShaderEffectItem::setCullMode(CullMode face) +{ + if (face == m_cullMode) + return; + m_cullMode = face; + update(); + emit cullModeChanged(); +} + +void QSGShaderEffectItem::changeSource(int index) +{ + Q_ASSERT(index >= 0 && index < m_sources.size()); + QVariant v = property(m_sources.at(index).name.constData()); + setSource(v, index); +} + +void QSGShaderEffectItem::updateData() +{ + m_dirtyData = true; + update(); +} + +void QSGShaderEffectItem::updateGeometry() +{ + m_dirtyGeometry = true; + update(); +} + +void QSGShaderEffectItem::setSource(const QVariant &var, int index) +{ + Q_ASSERT(index >= 0 && index < m_sources.size()); + + SourceData &source = m_sources[index]; + + source.item = 0; + if (var.isNull()) { + return; + } else if (!qVariantCanConvert<QObject *>(var)) { + qWarning("Could not assign source of type '%s' to property '%s'.", var.typeName(), source.name.constData()); + return; + } + + QObject *obj = qVariantValue<QObject *>(var); + + QSGTextureProvider *int3rface = QSGTextureProvider::from(obj); + if (!int3rface) { + qWarning("Could not assign property '%s', did not implement QSGTextureProvider.", source.name.constData()); + } + + source.item = qobject_cast<QSGItem *>(obj); + + // TODO: Find better solution. + // 'source.item' needs a canvas to get a scenegraph node. + // The easiest way to make sure it gets a canvas is to + // make it a part of the same item tree as 'this'. + if (source.item && source.item->parentItem() == 0) { + source.item->setParentItem(this); + source.item->setVisible(false); + } +} + +void QSGShaderEffectItem::disconnectPropertySignals() +{ + disconnect(this, 0, this, SLOT(updateData())); + for (int i = 0; i < m_sources.size(); ++i) { + SourceData &source = m_sources[i]; + disconnect(this, 0, source.mapper, 0); + disconnect(source.mapper, 0, this, 0); + } +} + +void QSGShaderEffectItem::connectPropertySignals() +{ + QSet<QByteArray>::const_iterator it; + for (it = m_source.uniformNames.begin(); it != m_source.uniformNames.end(); ++it) { + int pi = metaObject()->indexOfProperty(it->constData()); + if (pi >= 0) { + QMetaProperty mp = metaObject()->property(pi); + if (!mp.hasNotifySignal()) + qWarning("QSGShaderEffectItem: property '%s' does not have notification method!", it->constData()); + QByteArray signalName("2"); + signalName.append(mp.notifySignal().signature()); + connect(this, signalName, this, SLOT(updateData())); + } else { + qWarning("QSGShaderEffectItem: '%s' does not have a matching property!", it->constData()); + } + } + for (int i = 0; i < m_sources.size(); ++i) { + SourceData &source = m_sources[i]; + int pi = metaObject()->indexOfProperty(source.name.constData()); + if (pi >= 0) { + QMetaProperty mp = metaObject()->property(pi); + QByteArray signalName("2"); + signalName.append(mp.notifySignal().signature()); + connect(this, signalName, source.mapper, SLOT(map())); + source.mapper->setMapping(this, i); + connect(source.mapper, SIGNAL(mapped(int)), this, SLOT(changeSource(int))); + } else { + qWarning("QSGShaderEffectItem: '%s' does not have a matching source!", source.name.constData()); + } + } +} + +void QSGShaderEffectItem::reset() +{ + disconnectPropertySignals(); + + m_source.attributeNames.clear(); + m_source.uniformNames.clear(); + m_source.respectsOpacity = false; + m_source.respectsMatrix = false; + m_source.className = metaObject()->className(); + + for (int i = 0; i < m_sources.size(); ++i) { + const SourceData &source = m_sources.at(i); + delete source.mapper; + if (source.item && source.item->parentItem() == this) + source.item->setParentItem(0); + } + m_sources.clear(); + + m_programDirty = true; + m_dirtyMesh = true; +} + +void QSGShaderEffectItem::updateProperties() +{ + QByteArray vertexCode = m_source.vertexCode; + QByteArray fragmentCode = m_source.fragmentCode; + if (vertexCode.isEmpty()) + vertexCode = qt_default_vertex_code; + if (fragmentCode.isEmpty()) + fragmentCode = qt_default_fragment_code; + + lookThroughShaderCode(vertexCode); + lookThroughShaderCode(fragmentCode); + + if (!m_mesh && !m_source.attributeNames.contains(qt_position_attribute_name)) + qWarning("QSGShaderEffectItem: Missing reference to \'%s\'.", qt_position_attribute_name); + if (!m_mesh && !m_source.attributeNames.contains(qt_texcoord_attribute_name)) + qWarning("QSGShaderEffectItem: Missing reference to \'%s\'.", qt_texcoord_attribute_name); + if (!m_source.respectsMatrix) + qWarning("QSGShaderEffectItem: Missing reference to \'qt_ModelViewProjectionMatrix\'."); + if (!m_source.respectsOpacity) + qWarning("QSGShaderEffectItem: Missing reference to \'qt_Opacity\'."); + + for (int i = 0; i < m_sources.size(); ++i) { + QVariant v = property(m_sources.at(i).name); + setSource(v, i); + } + + connectPropertySignals(); +} + +void QSGShaderEffectItem::lookThroughShaderCode(const QByteArray &code) +{ + // Regexp for matching attributes and uniforms. + // In human readable form: attribute|uniform [lowp|mediump|highp] <type> <name> + static QRegExp re(QLatin1String("\\b(attribute|uniform)\\b\\s*\\b(?:lowp|mediump|highp)?\\b\\s*\\b(\\w+)\\b\\s*\\b(\\w+)")); + Q_ASSERT(re.isValid()); + + int pos = -1; + + QString wideCode = QString::fromLatin1(code.constData(), code.size()); + + while ((pos = re.indexIn(wideCode, pos + 1)) != -1) { + QByteArray decl = re.cap(1).toLatin1(); // uniform or attribute + QByteArray type = re.cap(2).toLatin1(); // type + QByteArray name = re.cap(3).toLatin1(); // variable name + + if (decl == "attribute") { + m_source.attributeNames.append(name); + } else { + Q_ASSERT(decl == "uniform"); + + if (name == "qt_ModelViewProjectionMatrix") { + m_source.respectsMatrix = true; + } else if (name == "qt_Opacity") { + m_source.respectsOpacity = true; + } else { + m_source.uniformNames.insert(name); + if (type == "sampler2D") { + SourceData d; + d.mapper = new QSignalMapper; + d.name = name; + d.item = 0; + m_sources.append(d); + } + } + } + } +} + +void QSGShaderEffectItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + m_dirtyGeometry = true; + QSGItem::geometryChanged(newGeometry, oldGeometry); +} + +QSGNode *QSGShaderEffectItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode); + + if (!node) { + node = new QSGShaderEffectNode; + node->setMaterial(&m_material); + m_programDirty = true; + m_dirtyData = true; + m_dirtyGeometry = true; + } + + if (m_dirtyMesh) { + node->setGeometry(0); + m_dirtyMesh = false; + m_dirtyGeometry = true; + } + + if (m_dirtyGeometry) { + node->setFlag(QSGNode::OwnsGeometry, false); + QSGGeometry *geometry = node->geometry(); + QRectF rect(0, 0, width(), height()); + QSGShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh; + + geometry = mesh->updateGeometry(geometry, m_source.attributeNames, rect); + if (!geometry) { + delete node; + return 0; + } + + node->setGeometry(geometry); + node->setFlag(QSGNode::OwnsGeometry, true); + + m_dirtyGeometry = false; + } + + if (m_programDirty) { + QSGShaderEffectProgram s = m_source; + if (s.fragmentCode.isEmpty()) + s.fragmentCode = qt_default_fragment_code; + if (s.vertexCode.isEmpty()) + s.vertexCode = qt_default_vertex_code; + + m_material.setProgramSource(s); + node->markDirty(QSGNode::DirtyMaterial); + m_programDirty = false; + } + + // Update blending + if (bool(m_material.flags() & QSGMaterial::Blending) != m_blending) { + m_material.setFlag(QSGMaterial::Blending, m_blending); + node->markDirty(QSGNode::DirtyMaterial); + } + + if (int(m_material.cullMode()) != int(m_cullMode)) { + m_material.setCullMode(QSGShaderEffectMaterial::CullMode(m_cullMode)); + node->markDirty(QSGNode::DirtyMaterial); + } + + if (m_dirtyData) { + QVector<QPair<QByteArray, QVariant> > values; + QVector<QPair<QByteArray, QPointer<QSGItem> > > textures; + const QVector<QPair<QByteArray, QPointer<QSGItem> > > &oldTextures = m_material.textureProviders(); + + for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin(); + it != m_source.uniformNames.end(); ++it) { + values.append(qMakePair(*it, property(*it))); + } + for (int i = 0; i < oldTextures.size(); ++i) { + QSGTextureProvider *oldSource = QSGTextureProvider::from(oldTextures.at(i).second); + if (oldSource && oldSource->textureChangedSignal()) + disconnect(oldTextures.at(i).second, oldSource->textureChangedSignal(), node, SLOT(markDirtyTexture())); + } + for (int i = 0; i < m_sources.size(); ++i) { + const SourceData &source = m_sources.at(i); + textures.append(qMakePair(source.name, source.item)); + QSGTextureProvider *t = QSGTextureProvider::from(source.item); + if (t && t->textureChangedSignal()) + connect(source.item, t->textureChangedSignal(), node, SLOT(markDirtyTexture()), Qt::DirectConnection); + } + m_material.setUniforms(values); + m_material.setTextureProviders(textures); + node->markDirty(QSGNode::DirtyMaterial); + m_dirtyData = false; + } + + return node; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgshadereffectitem_p.h b/src/declarative/items/qsgshadereffectitem_p.h new file mode 100644 index 0000000000..84f80492aa --- /dev/null +++ b/src/declarative/items/qsgshadereffectitem_p.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SHADEREFFECTITEM_H +#define SHADEREFFECTITEM_H + +#include "qsgitem.h" + +#include "qsgmaterial.h" +#include <private/qsgadaptationlayer_p.h> +#include <private/qsgshadereffectnode_p.h> +#include "qsgshadereffectmesh_p.h" + +#include <QtCore/qpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +const char *qtPositionAttributeName(); +const char *qtTexCoordAttributeName(); + +class QSGContext; +class QSignalMapper; +class QSGCustomMaterialShader; + +class QSGShaderEffectItem : public QSGItem +{ + Q_OBJECT + Q_PROPERTY(QByteArray fragmentShader READ fragmentShader WRITE setFragmentShader NOTIFY fragmentShaderChanged) + Q_PROPERTY(QByteArray vertexShader READ vertexShader WRITE setVertexShader NOTIFY vertexShaderChanged) + Q_PROPERTY(bool blending READ blending WRITE setBlending NOTIFY blendingChanged) + Q_PROPERTY(QSGShaderEffectMesh *mesh READ mesh WRITE setMesh NOTIFY meshChanged) + Q_PROPERTY(CullMode culling READ cullMode WRITE setCullMode NOTIFY cullModeChanged) + Q_ENUMS(CullMode) + +public: + enum CullMode + { + NoCulling = QSGShaderEffectMaterial::NoCulling, + BackFaceCulling = QSGShaderEffectMaterial::BackFaceCulling, + FrontFaceCulling = QSGShaderEffectMaterial::FrontFaceCulling + }; + + QSGShaderEffectItem(QSGItem *parent = 0); + ~QSGShaderEffectItem(); + + virtual void componentComplete(); + + QByteArray fragmentShader() const { return m_source.fragmentCode; } + void setFragmentShader(const QByteArray &code); + + QByteArray vertexShader() const { return m_source.vertexCode; } + void setVertexShader(const QByteArray &code); + + bool blending() const { return m_blending; } + void setBlending(bool enable); + + QSGShaderEffectMesh *mesh() const { return m_mesh; } + void setMesh(QSGShaderEffectMesh *mesh); + + CullMode cullMode() const { return m_cullMode; } + void setCullMode(CullMode face); + +Q_SIGNALS: + void fragmentShaderChanged(); + void vertexShaderChanged(); + void blendingChanged(); + void marginsChanged(); + void meshChanged(); + void cullModeChanged(); + +protected: + virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private Q_SLOTS: + void changeSource(int index); + void updateData(); + void updateGeometry(); + +private: + friend class QSGCustomMaterialShader; + friend class QSGShaderEffectNode; + + void setSource(const QVariant &var, int index); + void disconnectPropertySignals(); + void connectPropertySignals(); + void reset(); + void updateProperties(); + void lookThroughShaderCode(const QByteArray &code); + + QSGShaderEffectProgram m_source; + QSGShaderEffectMesh *m_mesh; + QSGGridMesh m_defaultMesh; + CullMode m_cullMode; + + struct SourceData + { + QSignalMapper *mapper; + QPointer<QSGItem> item; + QByteArray name; + }; + QVector<SourceData> m_sources; + QSGShaderEffectMaterial m_material; + + uint m_blending : 1; + uint m_dirtyData : 1; + + uint m_programDirty : 1; + uint m_dirtyMesh : 1; + uint m_dirtyGeometry : 1; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // SHADEREFFECTITEM_H diff --git a/src/declarative/items/qsgshadereffectmesh.cpp b/src/declarative/items/qsgshadereffectmesh.cpp new file mode 100644 index 0000000000..192e95c164 --- /dev/null +++ b/src/declarative/items/qsgshadereffectmesh.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgshadereffectmesh_p.h" +#include "qsggeometry.h" +#include "qsgshadereffectitem_p.h" + +QT_BEGIN_NAMESPACE + +QSGShaderEffectMesh::QSGShaderEffectMesh(QObject *parent) + : QObject(parent) +{ +} + + +QSGGridMesh::QSGGridMesh(QObject *parent) + : QSGShaderEffectMesh(parent) + , m_resolution(1, 1) +{ + connect(this, SIGNAL(resolutionChanged()), this, SIGNAL(geometryChanged())); +} + +QSGGeometry *QSGGridMesh::updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &dstRect) const +{ + int vmesh = m_resolution.height(); + int hmesh = m_resolution.width(); + int attrCount = attributes.count(); + + if (!geometry) { + bool error = true; + switch (attrCount) { + case 0: + qWarning("QSGGridMesh:: No attributes specified."); + break; + case 1: + if (attributes.at(0) == qtPositionAttributeName()) { + error = false; + break; + } + qWarning("QSGGridMesh:: Missing \'%s\' attribute.", + qtPositionAttributeName()); + break; + case 2: + if (attributes.contains(qtPositionAttributeName()) + && attributes.contains(qtTexCoordAttributeName())) + { + error = false; + break; + } + qWarning("QSGGridMesh:: Missing \'%s\' or \'%s\' attribute.", + qtPositionAttributeName(), qtTexCoordAttributeName()); + break; + default: + qWarning("QSGGridMesh:: Too many attributes specified."); + break;; + } + + if (error) { + delete geometry; + return 0; + } + + geometry = new QSGGeometry(attrCount == 1 + ? QSGGeometry::defaultAttributes_Point2D() + : QSGGeometry::defaultAttributes_TexturedPoint2D(), + (vmesh + 1) * (hmesh + 1), vmesh * 2 * (hmesh + 2), + GL_UNSIGNED_SHORT); + + } else { + geometry->allocate((vmesh + 1) * (hmesh + 1), vmesh * 2 * (hmesh + 2)); + } + + QSGGeometry::Point2D *vdata = static_cast<QSGGeometry::Point2D *>(geometry->vertexData()); + + bool positionFirst = attributes.at(0) == qtPositionAttributeName(); + + QRectF srcRect(0, 0, 1, 1); + for (int iy = 0; iy <= vmesh; ++iy) { + float fy = iy / float(vmesh); + float y = float(dstRect.top()) + fy * float(dstRect.height()); + float ty = float(srcRect.top()) + fy * float(srcRect.height()); + for (int ix = 0; ix <= hmesh; ++ix) { + float fx = ix / float(hmesh); + for (int ia = 0; ia < attrCount; ++ia) { + if (positionFirst == (ia == 0)) { + vdata->x = float(dstRect.left()) + fx * float(dstRect.width()); + vdata->y = y; + ++vdata; + } else { + vdata->x = float(srcRect.left()) + fx * float(srcRect.width()); + vdata->y = ty; + ++vdata; + } + } + } + } + + quint16 *indices = (quint16 *)geometry->indexDataAsUShort(); + int i = 0; + for (int iy = 0; iy < vmesh; ++iy) { + *(indices++) = i + hmesh + 1; + for (int ix = 0; ix <= hmesh; ++ix, ++i) { + *(indices++) = i + hmesh + 1; + *(indices++) = i; + } + *(indices++) = i - 1; + } + + return geometry; +} + +void QSGGridMesh::setResolution(const QSize &res) +{ + if (res == m_resolution) + return; + if (res.width() < 1 || res.height() < 1) { + qWarning("QSGGridMesh: Resolution must be at least 1x1"); + return; + } + m_resolution = res; + emit resolutionChanged(); +} + +QSize QSGGridMesh::resolution() const +{ + return m_resolution; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgshadereffectmesh_p.h b/src/declarative/items/qsgshadereffectmesh_p.h new file mode 100644 index 0000000000..88198b5c40 --- /dev/null +++ b/src/declarative/items/qsgshadereffectmesh_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativeparserstatus.h" + +#include <QtCore/qobject.h> +#include <QtCore/qsize.h> +#include <QtCore/qvariant.h> +#include <QtOpenGL/qglfunctions.h> + +#ifndef SHADEREFFECTMESH_H +#define SHADEREFFECTMESH_H + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGGeometry; +class QRectF; + +class QSGShaderEffectMesh : public QObject +{ + Q_OBJECT +public: + QSGShaderEffectMesh(QObject *parent = 0); + // If 'geometry' != 0, 'attributes' is the same as last time the function was called. + virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &rect) const = 0; + +Q_SIGNALS: + // Emitted when the geometry needs to be updated. + void geometryChanged(); +}; + +class QSGGridMesh : public QSGShaderEffectMesh +{ + Q_OBJECT + Q_PROPERTY(QSize resolution READ resolution WRITE setResolution NOTIFY resolutionChanged) +public: + QSGGridMesh(QObject *parent = 0); + virtual QSGGeometry *updateGeometry(QSGGeometry *geometry, const QVector<QByteArray> &attributes, const QRectF &rect) const; + + void setResolution(const QSize &res); + QSize resolution() const; + +Q_SIGNALS: + void resolutionChanged(); + +private: + QSize m_resolution; +}; + +inline QColor qt_premultiply_color(const QColor &c) +{ + return QColor::fromRgbF(c.redF() * c.alphaF(), c.greenF() * c.alphaF(), c.blueF() * c.alphaF(), c.alphaF()); +} + + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // SHADEREFFECTITEM_H diff --git a/src/declarative/items/qsgshadereffectnode.cpp b/src/declarative/items/qsgshadereffectnode.cpp new file mode 100644 index 0000000000..d1c8fbca9e --- /dev/null +++ b/src/declarative/items/qsgshadereffectnode.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qsgshadereffectnode_p.h> + +#include "qsgshadereffectmesh_p.h" +#include <private/qsgtextureprovider_p.h> +#include <private/qsgrenderer_p.h> + +QT_BEGIN_NAMESPACE + +class QSGCustomMaterialShader : public QSGMaterialShader +{ +public: + QSGCustomMaterialShader(const QSGShaderEffectMaterialKey &key, const QVector<QByteArray> &attributes); + virtual void deactivate(); + virtual void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect); + virtual char const *const *attributeNames() const; + +protected: + friend class QSGShaderEffectNode; + + virtual void initialize(); + virtual const char *vertexShader() const; + virtual const char *fragmentShader() const; + + const QSGShaderEffectMaterialKey m_key; + QVector<const char *> m_attributeNames; + const QVector<QByteArray> m_attributes; + + QVector<int> m_uniformLocs; + int m_opacityLoc; + int m_matrixLoc; + uint m_textureIndicesSet; +}; + +QSGCustomMaterialShader::QSGCustomMaterialShader(const QSGShaderEffectMaterialKey &key, const QVector<QByteArray> &attributes) + : m_key(key) + , m_attributes(attributes) + , m_textureIndicesSet(false) +{ + for (int i = 0; i < attributes.count(); ++i) + m_attributeNames.append(attributes.at(i).constData()); + m_attributeNames.append(0); +} + +void QSGCustomMaterialShader::deactivate() +{ + glDisable(GL_CULL_FACE); +} + +void QSGCustomMaterialShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) +{ + Q_ASSERT(newEffect != 0); + + const QSGShaderEffectMaterial *material = static_cast<const QSGShaderEffectMaterial *>(newEffect); + + if (!m_textureIndicesSet) { + for (int i = 0; i < material->m_textures.size(); ++i) + m_program.setUniformValue(material->m_textures.at(i).first.constData(), i); + m_textureIndicesSet = true; + } + + if (m_uniformLocs.size() != material->m_uniformValues.size()) { + m_uniformLocs.reserve(material->m_uniformValues.size()); + for (int i = 0; i < material->m_uniformValues.size(); ++i) { + const QByteArray &name = material->m_uniformValues.at(i).first; + m_uniformLocs.append(m_program.uniformLocation(name.constData())); + } + } + + QGLFunctions *functions = state.context()->functions(); + for (int i = material->m_textures.size() - 1; i >= 0; --i) { + QPointer<QSGItem> source = material->m_textures.at(i).second; + QSGTextureProvider *provider = QSGTextureProvider::from(source); + QSGTexture *texture = provider ? provider->texture() : 0; + if (!source || !provider || !texture) { + qWarning("ShaderEffectItem: source or provider missing when binding textures"); + continue; + } + functions->glActiveTexture(GL_TEXTURE0 + i); + provider->texture()->bind(); + } + + if (material->m_source.respectsOpacity) + m_program.setUniformValue(m_opacityLoc, state.opacity()); + + for (int i = 0; i < material->m_uniformValues.count(); ++i) { + const QVariant &v = material->m_uniformValues.at(i).second; + + switch (v.type()) { + case QVariant::Color: + m_program.setUniformValue(m_uniformLocs.at(i), qt_premultiply_color(qvariant_cast<QColor>(v))); + break; + case QVariant::Double: + m_program.setUniformValue(m_uniformLocs.at(i), (float) qvariant_cast<double>(v)); + break; + case QVariant::Transform: + m_program.setUniformValue(m_uniformLocs.at(i), qvariant_cast<QTransform>(v)); + break; + case QVariant::Int: + m_program.setUniformValue(m_uniformLocs.at(i), v.toInt()); + break; + case QVariant::Bool: + m_program.setUniformValue(m_uniformLocs.at(i), GLint(v.toBool())); + break; + case QVariant::Size: + case QVariant::SizeF: + m_program.setUniformValue(m_uniformLocs.at(i), v.toSizeF()); + break; + case QVariant::Point: + case QVariant::PointF: + m_program.setUniformValue(m_uniformLocs.at(i), v.toPointF()); + break; + case QVariant::Rect: + case QVariant::RectF: + { + QRectF r = v.toRectF(); + m_program.setUniformValue(m_uniformLocs.at(i), r.x(), r.y(), r.width(), r.height()); + } + break; + case QVariant::Vector3D: + m_program.setUniformValue(m_uniformLocs.at(i), qvariant_cast<QVector3D>(v)); + break; + default: + break; + } + } + + const QSGShaderEffectMaterial *oldMaterial = static_cast<const QSGShaderEffectMaterial *>(oldEffect); + if (oldEffect == 0 || material->cullMode() != oldMaterial->cullMode()) { + switch (material->cullMode()) { + case QSGShaderEffectMaterial::FrontFaceCulling: + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + break; + case QSGShaderEffectMaterial::BackFaceCulling: + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + break; + default: + glDisable(GL_CULL_FACE); + break; + } + } + + if ((state.isMatrixDirty()) && material->m_source.respectsMatrix) + m_program.setUniformValue(m_matrixLoc, state.combinedMatrix()); +} + +char const *const *QSGCustomMaterialShader::attributeNames() const +{ + return m_attributeNames.constData(); +} + +void QSGCustomMaterialShader::initialize() +{ + m_opacityLoc = m_program.uniformLocation("qt_Opacity"); + m_matrixLoc = m_program.uniformLocation("qt_ModelViewProjectionMatrix"); +} + +const char *QSGCustomMaterialShader::vertexShader() const +{ + return m_key.vertexCode.constData(); +} + +const char *QSGCustomMaterialShader::fragmentShader() const +{ + return m_key.fragmentCode.constData(); +} + + +bool QSGShaderEffectMaterialKey::operator == (const QSGShaderEffectMaterialKey &other) const +{ + return vertexCode == other.vertexCode && fragmentCode == other.fragmentCode && className == other.className; +} + +uint qHash(const QSGShaderEffectMaterialKey &key) +{ + return qHash(qMakePair(qMakePair(key.vertexCode, key.fragmentCode), key.className)); +} + + +QHash<QSGShaderEffectMaterialKey, QSharedPointer<QSGMaterialType> > QSGShaderEffectMaterial::materialMap; + +QSGShaderEffectMaterial::QSGShaderEffectMaterial() + : m_cullMode(NoCulling) +{ + setFlag(Blending, true); +} + +QSGMaterialType *QSGShaderEffectMaterial::type() const +{ + return m_type.data(); +} + +QSGMaterialShader *QSGShaderEffectMaterial::createShader() const +{ + return new QSGCustomMaterialShader(m_source, m_source.attributeNames); +} + +int QSGShaderEffectMaterial::compare(const QSGMaterial *other) const +{ + return this - static_cast<const QSGShaderEffectMaterial *>(other); +} + +void QSGShaderEffectMaterial::setCullMode(QSGShaderEffectMaterial::CullMode face) +{ + m_cullMode = face; +} + +QSGShaderEffectMaterial::CullMode QSGShaderEffectMaterial::cullMode() const +{ + return m_cullMode; +} + +void QSGShaderEffectMaterial::setProgramSource(const QSGShaderEffectProgram &source) +{ + m_source = source; + m_type = materialMap.value(m_source); + if (m_type.isNull()) { + m_type = QSharedPointer<QSGMaterialType>(new QSGMaterialType); + materialMap.insert(m_source, m_type); + } +} + +void QSGShaderEffectMaterial::setUniforms(const QVector<QPair<QByteArray, QVariant> > &uniformValues) +{ + m_uniformValues = uniformValues; +} + +void QSGShaderEffectMaterial::setTextureProviders(const QVector<QPair<QByteArray, QPointer<QSGItem> > > &textures) +{ + m_textures = textures; +} + +const QVector<QPair<QByteArray, QPointer<QSGItem> > > &QSGShaderEffectMaterial::textureProviders() const +{ + return m_textures; +} + +void QSGShaderEffectMaterial::updateTextures() const +{ + for (int i = 0; i < m_textures.size(); ++i) { + QSGItem *item = m_textures.at(i).second; + if (item) { + QSGTextureProvider *provider = QSGTextureProvider::from(item); + if (provider) { + QSGTexture *texture = provider->texture(); + if (!texture) { + qWarning("QSGShaderEffectMaterial: no texture from %s [%s]", + qPrintable(item->objectName()), + item->metaObject()->className()); + } + if (QSGDynamicTexture *t = qobject_cast<QSGDynamicTexture *>(provider->texture())) { + t->updateTexture(); + } + } + } + } +} + + +QSGShaderEffectNode::QSGShaderEffectNode() +{ + QSGNode::setFlag(UsePreprocess, true); +} + +QSGShaderEffectNode::~QSGShaderEffectNode() +{ +} + +void QSGShaderEffectNode::markDirtyTexture() +{ + markDirty(DirtyMaterial); +} + +void QSGShaderEffectNode::preprocess() +{ + Q_ASSERT(material()); + static_cast<QSGShaderEffectMaterial *>(material())->updateTextures(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgshadereffectnode_p.h b/src/declarative/items/qsgshadereffectnode_p.h new file mode 100644 index 0000000000..0be4b36294 --- /dev/null +++ b/src/declarative/items/qsgshadereffectnode_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SHADEREFFECTNODE_H +#define SHADEREFFECTNODE_H + +#include "qsgnode.h" +#include "qsgmaterial.h" +#include <private/qsgtextureprovider_p.h> +#include <qsgitem.h> + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +struct QSGShaderEffectMaterialKey { + QByteArray vertexCode; + QByteArray fragmentCode; + const char *className; + + bool operator == (const QSGShaderEffectMaterialKey &other) const; +}; + +uint qHash(const QSGShaderEffectMaterialKey &key); + +// TODO: Implement support for multisampling. +struct QSGShaderEffectProgram : public QSGShaderEffectMaterialKey +{ + QSGShaderEffectProgram() : respectsOpacity(false), respectsMatrix(false) {} + + QVector<QByteArray> attributeNames; + QSet<QByteArray> uniformNames; + + uint respectsOpacity : 1; + uint respectsMatrix : 1; +}; + + +class QSGCustomMaterialShader; +class QSGShaderEffectMaterial : public QSGMaterial +{ +public: + enum CullMode + { + NoCulling, + BackFaceCulling, + FrontFaceCulling + }; + + QSGShaderEffectMaterial(); + virtual QSGMaterialType *type() const; + virtual QSGMaterialShader *createShader() const; + virtual int compare(const QSGMaterial *other) const; + + void setCullMode(CullMode face); + CullMode cullMode() const; + + void setProgramSource(const QSGShaderEffectProgram &); + void setUniforms(const QVector<QPair<QByteArray, QVariant> > &uniformValues); + void setTextureProviders(const QVector<QPair<QByteArray, QPointer<QSGItem> > > &textures); + const QVector<QPair<QByteArray, QPointer<QSGItem> > > &textureProviders() const; + void updateTextures() const; + +protected: + friend class QSGShaderEffectItem; + friend class QSGCustomMaterialShader; + + // The type pointer needs to be unique. It is not safe to let the type object be part of the + // QSGShaderEffectMaterial, since it can be deleted and a new one constructed on top of the old + // one. The new QSGShaderEffectMaterial would then get the same type pointer as the old one, and + // CustomMaterialShaders based on the old one would incorrectly be used together with the new + // one. To guarantee that the type pointer is unique, the type object must live as long as + // there are any CustomMaterialShaders of that type. + QSharedPointer<QSGMaterialType> m_type; + + QSGShaderEffectProgram m_source; + QVector<QPair<QByteArray, QVariant> > m_uniformValues; + QVector<QPair<QByteArray, QPointer<QSGItem> > > m_textures; + CullMode m_cullMode; + + static QHash<QSGShaderEffectMaterialKey, QSharedPointer<QSGMaterialType> > materialMap; +}; + + +class QSGShaderEffectMesh; + +class QSGShaderEffectNode : public QObject, public QSGGeometryNode +{ + Q_OBJECT +public: + QSGShaderEffectNode(); + virtual ~QSGShaderEffectNode(); + + virtual void preprocess(); + +private Q_SLOTS: + void markDirtyTexture(); + +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // SHADEREFFECTNODE_H diff --git a/src/declarative/items/qsgshadereffectsource.cpp b/src/declarative/items/qsgshadereffectsource.cpp new file mode 100644 index 0000000000..e7e248299d --- /dev/null +++ b/src/declarative/items/qsgshadereffectsource.cpp @@ -0,0 +1,530 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgshadereffectsource_p.h" + +#include "qsgitem_p.h" +#include "qsgcanvas_p.h" +#include <private/qsgadaptationlayer_p.h> +#include <private/qsgrenderer_p.h> + +#include "qglframebufferobject.h" +#include "qmath.h" +#include <private/qsgtexture_p.h> + +QT_BEGIN_NAMESPACE + +DEFINE_BOOL_CONFIG_OPTION(qmlFboOverlay, QML_FBO_OVERLAY) + +QSGShaderEffectTexture::QSGShaderEffectTexture(QSGItem *shaderSource) + : QSGDynamicTexture() + , m_item(0) + , m_format(GL_RGBA) + , m_shaderSource(shaderSource) + , m_renderer(0) + , m_fbo(0) + , m_multisampledFbo(0) +#ifdef QSG_DEBUG_FBO_OVERLAY + , m_debugOverlay(0) +#endif + , m_mipmap(false) + , m_live(true) + , m_dirtyTexture(true) + , m_multisamplingSupportChecked(false) + , m_multisampling(false) +{ +} + +QSGShaderEffectTexture::~QSGShaderEffectTexture() +{ + delete m_renderer; + delete m_fbo; + delete m_multisampledFbo; +#ifdef QSG_DEBUG_FBO_OVERLAY + delete m_debugOverlay; +#endif +} + + +int QSGShaderEffectTexture::textureId() const +{ + return m_fbo->texture(); +} + +bool QSGShaderEffectTexture::hasAlphaChannel() const +{ + return m_format != GL_RGB; +} + +bool QSGShaderEffectTexture::hasMipmaps() const +{ + return m_mipmap; +} + + +void QSGShaderEffectTexture::bind() +{ + glBindTexture(GL_TEXTURE_2D, m_fbo->texture()); + updateBindOptions(); +} + +bool QSGShaderEffectTexture::updateTexture() +{ + if (m_dirtyTexture) { + grab(); + return true; + } + return false; +} + +void QSGShaderEffectTexture::setHasMipmaps(bool mipmap) +{ + if (mipmap == m_mipmap) + return; + m_mipmap = mipmap; + if (m_mipmap && m_fbo && !m_fbo->format().mipmap()) + markDirtyTexture(); +} + + +void QSGShaderEffectTexture::setItem(QSGNode *item) +{ + if (item == m_item) + return; + m_item = item; + markDirtyTexture(); +} + +void QSGShaderEffectTexture::setRect(const QRectF &rect) +{ + if (rect == m_rect) + return; + m_rect = rect; + markDirtyTexture(); +} + +void QSGShaderEffectTexture::setSize(const QSize &size) +{ + if (size == m_size) + return; + m_size = size; + markDirtyTexture(); +} + +void QSGShaderEffectTexture::setFormat(GLenum format) +{ + if (format == m_format) + return; + m_format = format; + markDirtyTexture(); +} + +void QSGShaderEffectTexture::setLive(bool live) +{ + if (live == m_live) + return; + m_live = live; + markDirtyTexture(); +} + +void QSGShaderEffectTexture::markDirtyTexture() +{ + if (m_live) { + m_dirtyTexture = true; + emit textureChanged(); + } +} + +void QSGShaderEffectTexture::grab() +{ + Q_ASSERT(m_item); + QSGNode *root = m_item; + while (root->childCount() && root->type() != QSGNode::RootNodeType) + root = root->childAtIndex(0); + if (root->type() != QSGNode::RootNodeType) + return; + + if (m_size.isEmpty()) { + delete m_fbo; + delete m_multisampledFbo; + m_multisampledFbo = m_fbo = 0; + return; + } + + QSGContext *context = QSGItemPrivate::get(m_shaderSource)->sceneGraphContext(); + + if (!m_renderer) { + m_renderer = context->createRenderer(); + connect(m_renderer, SIGNAL(sceneGraphChanged()), this, SLOT(markDirtyTexture()), Qt::DirectConnection); + } + m_renderer->setRootNode(static_cast<QSGRootNode *>(root)); + + if (!m_fbo || m_fbo->size() != m_size || m_fbo->format().internalTextureFormat() != m_format + || (!m_fbo->format().mipmap() && m_mipmap)) + { + if (!m_multisamplingSupportChecked) { + QList<QByteArray> extensions = QByteArray((const char *)glGetString(GL_EXTENSIONS)).split(' '); + m_multisampling = extensions.contains("GL_EXT_framebuffer_multisample") + && extensions.contains("GL_EXT_framebuffer_blit"); + m_multisamplingSupportChecked = true; + } + if (m_multisampling) { + delete m_fbo; + delete m_multisampledFbo; + QGLFramebufferObjectFormat format; + + format.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + format.setInternalTextureFormat(m_format); + format.setSamples(8); + m_multisampledFbo = new QGLFramebufferObject(m_size, format); + + format.setAttachment(QGLFramebufferObject::NoAttachment); + format.setMipmap(m_mipmap); + format.setSamples(0); + m_fbo = new QGLFramebufferObject(m_size, format); + + } else { + delete m_fbo; + QGLFramebufferObjectFormat format; + format.setAttachment(QGLFramebufferObject::CombinedDepthStencil); + format.setInternalTextureFormat(m_format); + format.setMipmap(m_mipmap); + m_fbo = new QGLFramebufferObject(m_size, format); + } + } + + // Render texture. + QSGNode::DirtyFlags dirty = root->dirtyFlags(); + root->markDirty(QSGNode::DirtyNodeAdded); // Force matrix and clip update. + m_renderer->nodeChanged(root, QSGNode::DirtyNodeAdded); // Force render list update. + +#ifdef QSG_DEBUG_FBO_OVERLAY + if (qmlFboOverlay()) { + if (!m_debugOverlay) + m_debugOverlay = context->createRectangleNode(); + m_debugOverlay->setRect(QRectF(0, 0, m_size.width(), m_size.height())); + m_debugOverlay->setColor(QColor(0xff, 0x00, 0x80, 0x40)); + m_debugOverlay->setPenColor(QColor()); + m_debugOverlay->setPenWidth(0); + m_debugOverlay->setRadius(0); + m_debugOverlay->update(); + root->appendChildNode(m_debugOverlay); + } +#endif + + m_dirtyTexture = false; + + const QGLContext *ctx = QGLContext::currentContext(); + m_renderer->setDeviceRect(m_size); + m_renderer->setViewportRect(m_size); + QRectF mirrored(m_rect.left(), m_rect.bottom(), m_rect.width(), -m_rect.height()); + m_renderer->setProjectMatrixToRect(mirrored); + m_renderer->setClearColor(Qt::transparent); + + if (m_multisampling) { + m_renderer->renderScene(BindableFbo(m_multisampledFbo)); + QRect r(0, 0, m_fbo->width(), m_fbo->height()); + QGLFramebufferObject::blitFramebuffer(m_fbo, r, m_multisampledFbo, r); + } else { + m_renderer->renderScene(BindableFbo(m_fbo)); + } + + if (m_mipmap) { + glBindTexture(GL_TEXTURE_2D, textureId()); + ctx->functions()->glGenerateMipmap(GL_TEXTURE_2D); + } + + root->markDirty(dirty | QSGNode::DirtyNodeAdded); // Force matrix, clip and render list update. + +#ifdef QSG_DEBUG_FBO_OVERLAY + if (qmlFboOverlay()) + root->removeChildNode(m_debugOverlay); +#endif +} + + +QSGShaderEffectSource::QSGShaderEffectSource(QSGItem *parent) + : QSGItem(parent) + , m_wrapMode(ClampToEdge) + , m_sourceItem(0) + , m_textureSize(0, 0) + , m_format(RGBA) + , m_live(true) + , m_hideSource(false) + , m_mipmap(false) +{ + setFlag(ItemHasContents); + m_texture = new QSGShaderEffectTexture(this); + connect(m_texture, SIGNAL(textureChanged()), this, SIGNAL(textureChanged()), Qt::DirectConnection); +} + +QSGShaderEffectSource::~QSGShaderEffectSource() +{ + delete m_texture; + if (m_sourceItem) + QSGItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource); +} + +QSGShaderEffectSource::WrapMode QSGShaderEffectSource::wrapMode() const +{ + return m_wrapMode; +} + +void QSGShaderEffectSource::setWrapMode(WrapMode mode) +{ + if (mode == m_wrapMode) + return; + m_wrapMode = mode; + update(); + emit wrapModeChanged(); +} + +QSGItem *QSGShaderEffectSource::sourceItem() const +{ + return m_sourceItem; +} + +void QSGShaderEffectSource::setSourceItem(QSGItem *item) +{ + if (item == m_sourceItem) + return; + if (m_sourceItem) + QSGItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource); + m_sourceItem = item; + if (m_sourceItem) { + // TODO: Find better solution. + // 'm_sourceItem' needs a canvas to get a scenegraph node. + // The easiest way to make sure it gets a canvas is to + // make it a part of the same item tree as 'this'. + if (m_sourceItem->parentItem() == 0) { + m_sourceItem->setParentItem(this); + m_sourceItem->setVisible(false); + } + QSGItemPrivate::get(m_sourceItem)->refFromEffectItem(m_hideSource); + } + update(); + emit sourceItemChanged(); +} + +QRectF QSGShaderEffectSource::sourceRect() const +{ + return m_sourceRect; +} + +void QSGShaderEffectSource::setSourceRect(const QRectF &rect) +{ + if (rect == m_sourceRect) + return; + m_sourceRect = rect; + update(); + emit sourceRectChanged(); +} + +QSize QSGShaderEffectSource::textureSize() const +{ + return m_textureSize; +} + +void QSGShaderEffectSource::setTextureSize(const QSize &size) +{ + if (size == m_textureSize) + return; + m_textureSize = size; + update(); + emit textureSizeChanged(); +} + +QSGShaderEffectSource::Format QSGShaderEffectSource::format() const +{ + return m_format; +} + +void QSGShaderEffectSource::setFormat(QSGShaderEffectSource::Format format) +{ + if (format == m_format) + return; + m_format = format; + update(); + emit formatChanged(); +} + +bool QSGShaderEffectSource::live() const +{ + return m_live; +} + +void QSGShaderEffectSource::setLive(bool live) +{ + if (live == m_live) + return; + m_live = live; + update(); + emit liveChanged(); +} + +bool QSGShaderEffectSource::hideSource() const +{ + return m_hideSource; +} + +void QSGShaderEffectSource::setHideSource(bool hide) +{ + if (hide == m_hideSource) + return; + if (m_sourceItem) { + QSGItemPrivate::get(m_sourceItem)->refFromEffectItem(hide); + QSGItemPrivate::get(m_sourceItem)->derefFromEffectItem(m_hideSource); + } + m_hideSource = hide; + update(); + emit hideSourceChanged(); +} + +bool QSGShaderEffectSource::mipmap() const +{ + return m_mipmap; +} + +void QSGShaderEffectSource::setMipmap(bool enabled) +{ + if (enabled == m_mipmap) + return; + printf("setting mipmap to: %d\n", enabled); + m_mipmap = enabled; + update(); + emit mipmapChanged(); +} + +void QSGShaderEffectSource::grab() +{ + if (!m_sourceItem) + return; + QSGCanvas *canvas = m_sourceItem->canvas(); + if (!canvas) + return; + QSGCanvasPrivate::get(canvas)->updateDirtyNodes(); + QGLContext *glctx = const_cast<QGLContext *>(canvas->context()); + glctx->makeCurrent(); + qobject_cast<QSGShaderEffectTexture *>(m_texture)->grab(); +} + +static void get_wrap_mode(QSGShaderEffectSource::WrapMode mode, QSGTexture::WrapMode *hWrap, QSGTexture::WrapMode *vWrap) +{ + switch (mode) { + case QSGShaderEffectSource::RepeatHorizontally: + *hWrap = QSGTexture::Repeat; + *vWrap = QSGTexture::ClampToEdge; + break; + case QSGShaderEffectSource::RepeatVertically: + *vWrap = QSGTexture::Repeat; + *hWrap = QSGTexture::ClampToEdge; + break; + case QSGShaderEffectSource::Repeat: + *hWrap = *vWrap = QSGTexture::Repeat; + break; + default: + // QSGShaderEffectSource::ClampToEdge + *hWrap = *vWrap = QSGTexture::ClampToEdge; + break; + } +} + + +QSGTexture *QSGShaderEffectSource::texture() const +{ + m_texture->setMipmapFiltering(m_mipmap ? QSGTexture::Linear : QSGTexture::None); + m_texture->setFiltering(QSGItemPrivate::get(this)->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + QSGTexture::WrapMode h, v; + get_wrap_mode(m_wrapMode, &h, &v); + m_texture->setHorizontalWrapMode(h); + m_texture->setVerticalWrapMode(v); + return m_texture; +} + +QSGNode *QSGShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + if (!m_sourceItem) { + delete oldNode; + return 0; + } + + QSGImageNode *node = static_cast<QSGImageNode *>(oldNode); + if (!node) { + node = QSGItemPrivate::get(this)->sceneGraphContext()->createImageNode(); + node->setFlag(QSGNode::UsePreprocess, true); + node->setTexture(m_texture); + } + + QSGShaderEffectTexture *tex = qobject_cast<QSGShaderEffectTexture *>(m_texture); + + tex->setItem(QSGItemPrivate::get(m_sourceItem)->itemNode()); + QRectF sourceRect = m_sourceRect.isNull() + ? 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; + tex->setSize(textureSize); + tex->setLive(m_live); + tex->setFormat(GLenum(m_format)); + tex->setHasMipmaps(m_mipmap); + + QSGTexture::Filtering filtering = QSGItemPrivate::get(this)->smooth + ? QSGTexture::Linear + : QSGTexture::Nearest; + QSGTexture::Filtering mmFiltering = m_mipmap ? filtering : QSGTexture::None; + node->setMipmapFiltering(mmFiltering); + node->setFiltering(filtering); + + QSGTexture::WrapMode hWrap, vWrap; + get_wrap_mode(m_wrapMode, &hWrap, &vWrap); + + node->setHorizontalWrapMode(hWrap); + node->setVerticalWrapMode(vWrap); + node->setTargetRect(QRectF(0, 0, width(), height())); + node->setSourceRect(QRectF(0, 0, 1, 1)); + node->update(); + + return node; +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgshadereffectsource_p.h b/src/declarative/items/qsgshadereffectsource_p.h new file mode 100644 index 0000000000..5d571ed42d --- /dev/null +++ b/src/declarative/items/qsgshadereffectsource_p.h @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SHADEREFFECTSOURCE_H +#define SHADEREFFECTSOURCE_H + +#include "qsgitem.h" +#include <private/qsgtextureprovider_p.h> +#include <private/qsgadaptationlayer_p.h> +#include <private/qsgcontext_p.h> + +#include "qpointer.h" +#include "qsize.h" +#include "qrect.h" + +#define QSG_DEBUG_FBO_OVERLAY + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGNode; +class UpdatePaintNodeData; +class QGLFramebufferObject; + +class QSGShaderEffectTexture : public QSGDynamicTexture +{ + Q_OBJECT +public: + QSGShaderEffectTexture(QSGItem *shaderSource); + ~QSGShaderEffectTexture(); + + virtual bool updateTexture(); + + // The item's "paint node", not effect node. + QSGNode *item() const { return m_item; } + void setItem(QSGNode *item); + + QRectF rect() const { return m_rect; } + void setRect(const QRectF &rect); + + QSize size() const { return m_size; } + void setSize(const QSize &size); + + void setHasMipmaps(bool mipmap); + + void bind(); + + bool hasAlphaChannel() const; + bool hasMipmaps() const; + int textureId() const; + QSize textureSize() const { return m_size; } + + GLenum format() const { return m_format; } + void setFormat(GLenum format); + + bool live() const { return bool(m_live); } + void setLive(bool live); + + void grab(); + +Q_SIGNALS: + void textureChanged(); + +public Q_SLOTS: + void markDirtyTexture(); + +private: + QSGNode *m_item; + QRectF m_rect; + QSize m_size; + GLenum m_format; + + QSGItem *m_shaderSource; + QSGRenderer *m_renderer; + QGLFramebufferObject *m_fbo; + QGLFramebufferObject *m_multisampledFbo; + +#ifdef QSG_DEBUG_FBO_OVERLAY + QSGRectangleNode *m_debugOverlay; +#endif + + uint m_mipmap : 1; + uint m_live : 1; + uint m_dirtyTexture : 1; + uint m_multisamplingSupportChecked : 1; + uint m_multisampling : 1; +}; + +class QSGShaderEffectSource : public QSGItem, public QSGTextureProvider +{ + Q_OBJECT + Q_PROPERTY(WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged) + Q_PROPERTY(QSGItem *sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged) + Q_PROPERTY(QRectF sourceRect READ sourceRect WRITE setSourceRect NOTIFY sourceRectChanged) + Q_PROPERTY(QSize textureSize READ textureSize WRITE setTextureSize NOTIFY textureSizeChanged) + Q_PROPERTY(Format format READ format WRITE setFormat NOTIFY formatChanged) + Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged) + Q_PROPERTY(bool hideSource READ hideSource WRITE setHideSource NOTIFY hideSourceChanged) + Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged) + Q_INTERFACES(QSGTextureProvider) + Q_ENUMS(Format WrapMode) +public: + enum WrapMode { + ClampToEdge, + RepeatHorizontally, + RepeatVertically, + Repeat + }; + + enum Format { + Alpha = GL_ALPHA, + RGB = GL_RGB, + RGBA = GL_RGBA + }; + + QSGShaderEffectSource(QSGItem *parent = 0); + ~QSGShaderEffectSource(); + + WrapMode wrapMode() const; + void setWrapMode(WrapMode mode); + + QSGItem *sourceItem() const; + void setSourceItem(QSGItem *item); + + QRectF sourceRect() const; + void setSourceRect(const QRectF &rect); + + QSize textureSize() const; + void setTextureSize(const QSize &size); + + Format format() const; + void setFormat(Format format); + + bool live() const; + void setLive(bool live); + + bool hideSource() const; + void setHideSource(bool hide); + + bool mipmap() const; + void setMipmap(bool enabled); + + QSGTexture *texture() const; + const char *textureChangedSignal() const { return SIGNAL(textureChanged()); } + + Q_INVOKABLE void grab(); + +Q_SIGNALS: + void wrapModeChanged(); + void sourceItemChanged(); + void sourceRectChanged(); + void textureSizeChanged(); + void formatChanged(); + void liveChanged(); + void hideSourceChanged(); + void mipmapChanged(); + + void textureChanged(); + +protected: + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private: + QSGTexture *m_texture; + WrapMode m_wrapMode; + QPointer<QSGItem> m_sourceItem; + QRectF m_sourceRect; + QSize m_textureSize; + Format m_format; + uint m_live : 1; + uint m_hideSource : 1; + uint m_mipmap : 1; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // SHADEREFFECTSOURCE_H diff --git a/src/declarative/items/qsgstateoperations.cpp b/src/declarative/items/qsgstateoperations.cpp new file mode 100644 index 0000000000..5390440e39 --- /dev/null +++ b/src/declarative/items/qsgstateoperations.cpp @@ -0,0 +1,1347 @@ +// Commit: 726a8b16c52fe4608c89d740b47361a2b073ce01 +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgstateoperations_p.h" +#include "qsgitem_p.h" + +#include <private/qdeclarativestate_p_p.h> + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +class QSGParentChangePrivate : public QDeclarativeStateOperationPrivate +{ + Q_DECLARE_PUBLIC(QSGParentChange) +public: + QSGParentChangePrivate() : target(0), parent(0), origParent(0), origStackBefore(0), + rewindParent(0), rewindStackBefore(0) {} + + QSGItem *target; + QDeclarativeGuard<QSGItem> parent; + QDeclarativeGuard<QSGItem> origParent; + QDeclarativeGuard<QSGItem> origStackBefore; + QSGItem *rewindParent; + QSGItem *rewindStackBefore; + + QDeclarativeNullableValue<QDeclarativeScriptString> xString; + QDeclarativeNullableValue<QDeclarativeScriptString> yString; + QDeclarativeNullableValue<QDeclarativeScriptString> widthString; + QDeclarativeNullableValue<QDeclarativeScriptString> heightString; + QDeclarativeNullableValue<QDeclarativeScriptString> scaleString; + QDeclarativeNullableValue<QDeclarativeScriptString> rotationString; + + void doChange(QSGItem *targetParent, QSGItem *stackBefore = 0); +}; + +void QSGParentChangePrivate::doChange(QSGItem *targetParent, QSGItem *stackBefore) +{ + if (targetParent && target && target->parentItem()) { + Q_Q(QSGParentChange); + bool ok; + const QTransform &transform = target->parentItem()->itemTransform(targetParent, &ok); + if (transform.type() >= QTransform::TxShear || !ok) { + qmlInfo(q) << QSGParentChange::tr("Unable to preserve appearance under complex transform"); + ok = false; + } + + qreal scale = 1; + qreal rotation = 0; + bool isRotate = (transform.type() == QTransform::TxRotate) || (transform.m11() < 0); + if (ok && !isRotate) { + if (transform.m11() == transform.m22()) + scale = transform.m11(); + else { + qmlInfo(q) << QSGParentChange::tr("Unable to preserve appearance under non-uniform scale"); + ok = false; + } + } else if (ok && isRotate) { + if (transform.m11() == transform.m22()) + scale = qSqrt(transform.m11()*transform.m11() + transform.m12()*transform.m12()); + else { + qmlInfo(q) << QSGParentChange::tr("Unable to preserve appearance under non-uniform scale"); + ok = false; + } + + if (scale != 0) + rotation = atan2(transform.m12()/scale, transform.m11()/scale) * 180/M_PI; + else { + qmlInfo(q) << QSGParentChange::tr("Unable to preserve appearance under scale of 0"); + ok = false; + } + } + + const QPointF &point = transform.map(QPointF(target->x(),target->y())); + qreal x = point.x(); + qreal y = point.y(); + + // setParentItem will update the transformOriginPoint if needed + target->setParentItem(targetParent); + + if (ok && target->transformOrigin() != QSGItem::TopLeft) { + qreal tempxt = target->transformOriginPoint().x(); + qreal tempyt = target->transformOriginPoint().y(); + QTransform t; + t.translate(-tempxt, -tempyt); + t.rotate(rotation); + t.scale(scale, scale); + t.translate(tempxt, tempyt); + const QPointF &offset = t.map(QPointF(0,0)); + x += offset.x(); + y += offset.y(); + } + + if (ok) { + //qDebug() << x << y << rotation << scale; + target->setX(x); + target->setY(y); + target->setRotation(target->rotation() + rotation); + target->setScale(target->scale() * scale); + } + } else if (target) { + target->setParentItem(targetParent); + } + + //restore the original stack position. + //### if stackBefore has also been reparented this won't work + if (stackBefore) + target->stackBefore(stackBefore); +} + +QSGParentChange::QSGParentChange(QObject *parent) + : QDeclarativeStateOperation(*(new QSGParentChangePrivate), parent) +{ +} + +QSGParentChange::~QSGParentChange() +{ +} + +QDeclarativeScriptString QSGParentChange::x() const +{ + Q_D(const QSGParentChange); + return d->xString.value; +} + +void QSGParentChange::setX(QDeclarativeScriptString x) +{ + Q_D(QSGParentChange); + d->xString = x; +} + +bool QSGParentChange::xIsSet() const +{ + Q_D(const QSGParentChange); + return d->xString.isValid(); +} + +QDeclarativeScriptString QSGParentChange::y() const +{ + Q_D(const QSGParentChange); + return d->yString.value; +} + +void QSGParentChange::setY(QDeclarativeScriptString y) +{ + Q_D(QSGParentChange); + d->yString = y; +} + +bool QSGParentChange::yIsSet() const +{ + Q_D(const QSGParentChange); + return d->yString.isValid(); +} + +QDeclarativeScriptString QSGParentChange::width() const +{ + Q_D(const QSGParentChange); + return d->widthString.value; +} + +void QSGParentChange::setWidth(QDeclarativeScriptString width) +{ + Q_D(QSGParentChange); + d->widthString = width; +} + +bool QSGParentChange::widthIsSet() const +{ + Q_D(const QSGParentChange); + return d->widthString.isValid(); +} + +QDeclarativeScriptString QSGParentChange::height() const +{ + Q_D(const QSGParentChange); + return d->heightString.value; +} + +void QSGParentChange::setHeight(QDeclarativeScriptString height) +{ + Q_D(QSGParentChange); + d->heightString = height; +} + +bool QSGParentChange::heightIsSet() const +{ + Q_D(const QSGParentChange); + return d->heightString.isValid(); +} + +QDeclarativeScriptString QSGParentChange::scale() const +{ + Q_D(const QSGParentChange); + return d->scaleString.value; +} + +void QSGParentChange::setScale(QDeclarativeScriptString scale) +{ + Q_D(QSGParentChange); + d->scaleString = scale; +} + +bool QSGParentChange::scaleIsSet() const +{ + Q_D(const QSGParentChange); + return d->scaleString.isValid(); +} + +QDeclarativeScriptString QSGParentChange::rotation() const +{ + Q_D(const QSGParentChange); + return d->rotationString.value; +} + +void QSGParentChange::setRotation(QDeclarativeScriptString rotation) +{ + Q_D(QSGParentChange); + d->rotationString = rotation; +} + +bool QSGParentChange::rotationIsSet() const +{ + Q_D(const QSGParentChange); + return d->rotationString.isValid(); +} + +QSGItem *QSGParentChange::originalParent() const +{ + Q_D(const QSGParentChange); + return d->origParent; +} + +QSGItem *QSGParentChange::object() const +{ + Q_D(const QSGParentChange); + return d->target; +} + +void QSGParentChange::setObject(QSGItem *target) +{ + Q_D(QSGParentChange); + d->target = target; +} + +QSGItem *QSGParentChange::parent() const +{ + Q_D(const QSGParentChange); + return d->parent; +} + +void QSGParentChange::setParent(QSGItem *parent) +{ + Q_D(QSGParentChange); + d->parent = parent; +} + +QDeclarativeStateOperation::ActionList QSGParentChange::actions() +{ + Q_D(QSGParentChange); + if (!d->target || !d->parent) + return ActionList(); + + ActionList actions; + + QDeclarativeAction a; + a.event = this; + actions << a; + + if (d->xString.isValid()) { + bool ok = false; + QString script = d->xString.value.script(); + qreal x = script.toFloat(&ok); + if (ok) { + QDeclarativeAction xa(d->target, QLatin1String("x"), x); + actions << xa; + } else { + QDeclarativeBinding *newBinding = new QDeclarativeBinding(script, d->target, qmlContext(this)); + newBinding->setTarget(QDeclarativeProperty(d->target, QLatin1String("x"))); + QDeclarativeAction xa; + xa.property = newBinding->property(); + xa.toBinding = newBinding; + xa.fromValue = xa.property.read(); + xa.deletableToBinding = true; + actions << xa; + } + } + + if (d->yString.isValid()) { + bool ok = false; + QString script = d->yString.value.script(); + qreal y = script.toFloat(&ok); + if (ok) { + QDeclarativeAction ya(d->target, QLatin1String("y"), y); + actions << ya; + } else { + QDeclarativeBinding *newBinding = new QDeclarativeBinding(script, d->target, qmlContext(this)); + newBinding->setTarget(QDeclarativeProperty(d->target, QLatin1String("y"))); + QDeclarativeAction ya; + ya.property = newBinding->property(); + ya.toBinding = newBinding; + ya.fromValue = ya.property.read(); + ya.deletableToBinding = true; + actions << ya; + } + } + + if (d->scaleString.isValid()) { + bool ok = false; + QString script = d->scaleString.value.script(); + qreal scale = script.toFloat(&ok); + if (ok) { + QDeclarativeAction sa(d->target, QLatin1String("scale"), scale); + actions << sa; + } else { + QDeclarativeBinding *newBinding = new QDeclarativeBinding(script, d->target, qmlContext(this)); + newBinding->setTarget(QDeclarativeProperty(d->target, QLatin1String("scale"))); + QDeclarativeAction sa; + sa.property = newBinding->property(); + sa.toBinding = newBinding; + sa.fromValue = sa.property.read(); + sa.deletableToBinding = true; + actions << sa; + } + } + + if (d->rotationString.isValid()) { + bool ok = false; + QString script = d->rotationString.value.script(); + qreal rotation = script.toFloat(&ok); + if (ok) { + QDeclarativeAction ra(d->target, QLatin1String("rotation"), rotation); + actions << ra; + } else { + QDeclarativeBinding *newBinding = new QDeclarativeBinding(script, d->target, qmlContext(this)); + newBinding->setTarget(QDeclarativeProperty(d->target, QLatin1String("rotation"))); + QDeclarativeAction ra; + ra.property = newBinding->property(); + ra.toBinding = newBinding; + ra.fromValue = ra.property.read(); + ra.deletableToBinding = true; + actions << ra; + } + } + + if (d->widthString.isValid()) { + bool ok = false; + QString script = d->widthString.value.script(); + qreal width = script.toFloat(&ok); + if (ok) { + QDeclarativeAction wa(d->target, QLatin1String("width"), width); + actions << wa; + } else { + QDeclarativeBinding *newBinding = new QDeclarativeBinding(script, d->target, qmlContext(this)); + newBinding->setTarget(QDeclarativeProperty(d->target, QLatin1String("width"))); + QDeclarativeAction wa; + wa.property = newBinding->property(); + wa.toBinding = newBinding; + wa.fromValue = wa.property.read(); + wa.deletableToBinding = true; + actions << wa; + } + } + + if (d->heightString.isValid()) { + bool ok = false; + QString script = d->heightString.value.script(); + qreal height = script.toFloat(&ok); + if (ok) { + QDeclarativeAction ha(d->target, QLatin1String("height"), height); + actions << ha; + } else { + QDeclarativeBinding *newBinding = new QDeclarativeBinding(script, d->target, qmlContext(this)); + newBinding->setTarget(QDeclarativeProperty(d->target, QLatin1String("height"))); + QDeclarativeAction ha; + ha.property = newBinding->property(); + ha.toBinding = newBinding; + ha.fromValue = ha.property.read(); + ha.deletableToBinding = true; + actions << ha; + } + } + + return actions; +} + +void QSGParentChange::saveOriginals() +{ + Q_D(QSGParentChange); + saveCurrentValues(); + d->origParent = d->rewindParent; + d->origStackBefore = d->rewindStackBefore; +} + +/*void QSGParentChange::copyOriginals(QDeclarativeActionEvent *other) +{ + Q_D(QSGParentChange); + QSGParentChange *pc = static_cast<QSGParentChange*>(other); + + d->origParent = pc->d_func()->rewindParent; + d->origStackBefore = pc->d_func()->rewindStackBefore; + + saveCurrentValues(); +}*/ + +void QSGParentChange::execute(Reason) +{ + Q_D(QSGParentChange); + d->doChange(d->parent); +} + +bool QSGParentChange::isReversable() +{ + return true; +} + +void QSGParentChange::reverse(Reason) +{ + Q_D(QSGParentChange); + d->doChange(d->origParent, d->origStackBefore); +} + +QString QSGParentChange::typeName() const +{ + return QLatin1String("ParentChange"); +} + +bool QSGParentChange::override(QDeclarativeActionEvent*other) +{ + Q_D(QSGParentChange); + if (other->typeName() != QLatin1String("ParentChange")) + return false; + if (QSGParentChange *otherPC = static_cast<QSGParentChange*>(other)) + return (d->target == otherPC->object()); + return false; +} + +void QSGParentChange::saveCurrentValues() +{ + Q_D(QSGParentChange); + if (!d->target) { + d->rewindParent = 0; + d->rewindStackBefore = 0; + return; + } + + d->rewindParent = d->target->parentItem(); + d->rewindStackBefore = 0; + + if (!d->rewindParent) + return; + + QList<QSGItem *> children = d->rewindParent->childItems(); + for (int ii = 0; ii < children.count() - 1; ++ii) { + if (children.at(ii) == d->target) { + d->rewindStackBefore = children.at(ii + 1); + break; + } + } +} + +void QSGParentChange::rewind() +{ + Q_D(QSGParentChange); + d->doChange(d->rewindParent, d->rewindStackBefore); +} + +class QSGAnchorSetPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSGAnchorSet) +public: + QSGAnchorSetPrivate() + : usedAnchors(0), resetAnchors(0), fill(0), + centerIn(0)/*, leftMargin(0), rightMargin(0), topMargin(0), bottomMargin(0), + margins(0), vCenterOffset(0), hCenterOffset(0), baselineOffset(0)*/ + { + } + + QSGAnchors::Anchors usedAnchors; + QSGAnchors::Anchors resetAnchors; + + QSGItem *fill; + QSGItem *centerIn; + + QDeclarativeScriptString leftScript; + QDeclarativeScriptString rightScript; + QDeclarativeScriptString topScript; + QDeclarativeScriptString bottomScript; + QDeclarativeScriptString hCenterScript; + QDeclarativeScriptString vCenterScript; + QDeclarativeScriptString baselineScript; + + /*qreal leftMargin; + qreal rightMargin; + qreal topMargin; + qreal bottomMargin; + qreal margins; + qreal vCenterOffset; + qreal hCenterOffset; + qreal baselineOffset;*/ +}; + +QSGAnchorSet::QSGAnchorSet(QObject *parent) + : QObject(*new QSGAnchorSetPrivate, parent) +{ +} + +QSGAnchorSet::~QSGAnchorSet() +{ +} + +QDeclarativeScriptString QSGAnchorSet::top() const +{ + Q_D(const QSGAnchorSet); + return d->topScript; +} + +void QSGAnchorSet::setTop(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::TopAnchor; + d->topScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetTop(); +} + +void QSGAnchorSet::resetTop() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::TopAnchor; + d->topScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::TopAnchor; +} + +QDeclarativeScriptString QSGAnchorSet::bottom() const +{ + Q_D(const QSGAnchorSet); + return d->bottomScript; +} + +void QSGAnchorSet::setBottom(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::BottomAnchor; + d->bottomScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetBottom(); +} + +void QSGAnchorSet::resetBottom() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::BottomAnchor; + d->bottomScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::BottomAnchor; +} + +QDeclarativeScriptString QSGAnchorSet::verticalCenter() const +{ + Q_D(const QSGAnchorSet); + return d->vCenterScript; +} + +void QSGAnchorSet::setVerticalCenter(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::VCenterAnchor; + d->vCenterScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetVerticalCenter(); +} + +void QSGAnchorSet::resetVerticalCenter() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::VCenterAnchor; + d->vCenterScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::VCenterAnchor; +} + +QDeclarativeScriptString QSGAnchorSet::baseline() const +{ + Q_D(const QSGAnchorSet); + return d->baselineScript; +} + +void QSGAnchorSet::setBaseline(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::BaselineAnchor; + d->baselineScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetBaseline(); +} + +void QSGAnchorSet::resetBaseline() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::BaselineAnchor; + d->baselineScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::BaselineAnchor; +} + +QDeclarativeScriptString QSGAnchorSet::left() const +{ + Q_D(const QSGAnchorSet); + return d->leftScript; +} + +void QSGAnchorSet::setLeft(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::LeftAnchor; + d->leftScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetLeft(); +} + +void QSGAnchorSet::resetLeft() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::LeftAnchor; + d->leftScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::LeftAnchor; +} + +QDeclarativeScriptString QSGAnchorSet::right() const +{ + Q_D(const QSGAnchorSet); + return d->rightScript; +} + +void QSGAnchorSet::setRight(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::RightAnchor; + d->rightScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetRight(); +} + +void QSGAnchorSet::resetRight() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::RightAnchor; + d->rightScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::RightAnchor; +} + +QDeclarativeScriptString QSGAnchorSet::horizontalCenter() const +{ + Q_D(const QSGAnchorSet); + return d->hCenterScript; +} + +void QSGAnchorSet::setHorizontalCenter(const QDeclarativeScriptString &edge) +{ + Q_D(QSGAnchorSet); + d->usedAnchors |= QSGAnchors::HCenterAnchor; + d->hCenterScript = edge; + if (edge.script() == QLatin1String("undefined")) + resetHorizontalCenter(); +} + +void QSGAnchorSet::resetHorizontalCenter() +{ + Q_D(QSGAnchorSet); + d->usedAnchors &= ~QSGAnchors::HCenterAnchor; + d->hCenterScript = QDeclarativeScriptString(); + d->resetAnchors |= QSGAnchors::HCenterAnchor; +} + +QSGItem *QSGAnchorSet::fill() const +{ + Q_D(const QSGAnchorSet); + return d->fill; +} + +void QSGAnchorSet::setFill(QSGItem *f) +{ + Q_D(QSGAnchorSet); + d->fill = f; +} + +void QSGAnchorSet::resetFill() +{ + setFill(0); +} + +QSGItem *QSGAnchorSet::centerIn() const +{ + Q_D(const QSGAnchorSet); + return d->centerIn; +} + +void QSGAnchorSet::setCenterIn(QSGItem* c) +{ + Q_D(QSGAnchorSet); + d->centerIn = c; +} + +void QSGAnchorSet::resetCenterIn() +{ + setCenterIn(0); +} + + +class QSGAnchorChangesPrivate : public QDeclarativeStateOperationPrivate +{ +public: + QSGAnchorChangesPrivate() + : target(0), anchorSet(new QSGAnchorSet), + leftBinding(0), rightBinding(0), hCenterBinding(0), + topBinding(0), bottomBinding(0), vCenterBinding(0), baselineBinding(0), + origLeftBinding(0), origRightBinding(0), origHCenterBinding(0), + origTopBinding(0), origBottomBinding(0), origVCenterBinding(0), + origBaselineBinding(0) + { + + } + ~QSGAnchorChangesPrivate() { delete anchorSet; } + + QSGItem *target; + QSGAnchorSet *anchorSet; + + QDeclarativeBinding *leftBinding; + QDeclarativeBinding *rightBinding; + QDeclarativeBinding *hCenterBinding; + QDeclarativeBinding *topBinding; + QDeclarativeBinding *bottomBinding; + QDeclarativeBinding *vCenterBinding; + QDeclarativeBinding *baselineBinding; + + QDeclarativeAbstractBinding *origLeftBinding; + QDeclarativeAbstractBinding *origRightBinding; + QDeclarativeAbstractBinding *origHCenterBinding; + QDeclarativeAbstractBinding *origTopBinding; + QDeclarativeAbstractBinding *origBottomBinding; + QDeclarativeAbstractBinding *origVCenterBinding; + QDeclarativeAbstractBinding *origBaselineBinding; + + QSGAnchorLine rewindLeft; + QSGAnchorLine rewindRight; + QSGAnchorLine rewindHCenter; + QSGAnchorLine rewindTop; + QSGAnchorLine rewindBottom; + QSGAnchorLine rewindVCenter; + QSGAnchorLine rewindBaseline; + + qreal fromX; + qreal fromY; + qreal fromWidth; + qreal fromHeight; + + qreal toX; + qreal toY; + qreal toWidth; + qreal toHeight; + + qreal rewindX; + qreal rewindY; + qreal rewindWidth; + qreal rewindHeight; + + bool applyOrigLeft; + bool applyOrigRight; + bool applyOrigHCenter; + bool applyOrigTop; + bool applyOrigBottom; + bool applyOrigVCenter; + bool applyOrigBaseline; + + QDeclarativeNullableValue<qreal> origWidth; + QDeclarativeNullableValue<qreal> origHeight; + qreal origX; + qreal origY; + + QList<QDeclarativeAbstractBinding*> oldBindings; + + QDeclarativeProperty leftProp; + QDeclarativeProperty rightProp; + QDeclarativeProperty hCenterProp; + QDeclarativeProperty topProp; + QDeclarativeProperty bottomProp; + QDeclarativeProperty vCenterProp; + QDeclarativeProperty baselineProp; +}; + +QSGAnchorChanges::QSGAnchorChanges(QObject *parent) + : QDeclarativeStateOperation(*(new QSGAnchorChangesPrivate), parent) +{ +} + +QSGAnchorChanges::~QSGAnchorChanges() +{ +} + +QSGAnchorChanges::ActionList QSGAnchorChanges::actions() +{ + Q_D(QSGAnchorChanges); + d->leftBinding = d->rightBinding = d->hCenterBinding = d->topBinding + = d->bottomBinding = d->vCenterBinding = d->baselineBinding = 0; + + d->leftProp = QDeclarativeProperty(d->target, QLatin1String("anchors.left")); + d->rightProp = QDeclarativeProperty(d->target, QLatin1String("anchors.right")); + d->hCenterProp = QDeclarativeProperty(d->target, QLatin1String("anchors.horizontalCenter")); + d->topProp = QDeclarativeProperty(d->target, QLatin1String("anchors.top")); + d->bottomProp = QDeclarativeProperty(d->target, QLatin1String("anchors.bottom")); + d->vCenterProp = QDeclarativeProperty(d->target, QLatin1String("anchors.verticalCenter")); + d->baselineProp = QDeclarativeProperty(d->target, QLatin1String("anchors.baseline")); + + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::LeftAnchor) { + d->leftBinding = new QDeclarativeBinding(d->anchorSet->d_func()->leftScript.script(), d->target, qmlContext(this)); + d->leftBinding->setTarget(d->leftProp); + } + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::RightAnchor) { + d->rightBinding = new QDeclarativeBinding(d->anchorSet->d_func()->rightScript.script(), d->target, qmlContext(this)); + d->rightBinding->setTarget(d->rightProp); + } + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::HCenterAnchor) { + d->hCenterBinding = new QDeclarativeBinding(d->anchorSet->d_func()->hCenterScript.script(), d->target, qmlContext(this)); + d->hCenterBinding->setTarget(d->hCenterProp); + } + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::TopAnchor) { + d->topBinding = new QDeclarativeBinding(d->anchorSet->d_func()->topScript.script(), d->target, qmlContext(this)); + d->topBinding->setTarget(d->topProp); + } + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::BottomAnchor) { + d->bottomBinding = new QDeclarativeBinding(d->anchorSet->d_func()->bottomScript.script(), d->target, qmlContext(this)); + d->bottomBinding->setTarget(d->bottomProp); + } + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::VCenterAnchor) { + d->vCenterBinding = new QDeclarativeBinding(d->anchorSet->d_func()->vCenterScript.script(), d->target, qmlContext(this)); + d->vCenterBinding->setTarget(d->vCenterProp); + } + if (d->anchorSet->d_func()->usedAnchors & QSGAnchors::BaselineAnchor) { + d->baselineBinding = new QDeclarativeBinding(d->anchorSet->d_func()->baselineScript.script(), d->target, qmlContext(this)); + d->baselineBinding->setTarget(d->baselineProp); + } + + QDeclarativeAction a; + a.event = this; + return ActionList() << a; +} + +QSGAnchorSet *QSGAnchorChanges::anchors() +{ + Q_D(QSGAnchorChanges); + return d->anchorSet; +} + +QSGItem *QSGAnchorChanges::object() const +{ + Q_D(const QSGAnchorChanges); + return d->target; +} + +void QSGAnchorChanges::setObject(QSGItem *target) +{ + Q_D(QSGAnchorChanges); + d->target = target; +} + +void QSGAnchorChanges::execute(Reason reason) +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(d->target); + //incorporate any needed "reverts" + if (d->applyOrigLeft) { + if (!d->origLeftBinding) + targetPrivate->anchors()->resetLeft(); + QDeclarativePropertyPrivate::setBinding(d->leftProp, d->origLeftBinding); + } + if (d->applyOrigRight) { + if (!d->origRightBinding) + targetPrivate->anchors()->resetRight(); + QDeclarativePropertyPrivate::setBinding(d->rightProp, d->origRightBinding); + } + if (d->applyOrigHCenter) { + if (!d->origHCenterBinding) + targetPrivate->anchors()->resetHorizontalCenter(); + QDeclarativePropertyPrivate::setBinding(d->hCenterProp, d->origHCenterBinding); + } + if (d->applyOrigTop) { + if (!d->origTopBinding) + targetPrivate->anchors()->resetTop(); + QDeclarativePropertyPrivate::setBinding(d->topProp, d->origTopBinding); + } + if (d->applyOrigBottom) { + if (!d->origBottomBinding) + targetPrivate->anchors()->resetBottom(); + QDeclarativePropertyPrivate::setBinding(d->bottomProp, d->origBottomBinding); + } + if (d->applyOrigVCenter) { + if (!d->origVCenterBinding) + targetPrivate->anchors()->resetVerticalCenter(); + QDeclarativePropertyPrivate::setBinding(d->vCenterProp, d->origVCenterBinding); + } + if (d->applyOrigBaseline) { + if (!d->origBaselineBinding) + targetPrivate->anchors()->resetBaseline(); + QDeclarativePropertyPrivate::setBinding(d->baselineProp, d->origBaselineBinding); + } + + //destroy old bindings + if (reason == ActualChange) { + for (int i = 0; i < d->oldBindings.size(); ++i) { + QDeclarativeAbstractBinding *binding = d->oldBindings.at(i); + if (binding) + binding->destroy(); + } + d->oldBindings.clear(); + } + + //reset any anchors that have been specified as "undefined" + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::LeftAnchor) { + targetPrivate->anchors()->resetLeft(); + QDeclarativePropertyPrivate::setBinding(d->leftProp, 0); + } + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::RightAnchor) { + targetPrivate->anchors()->resetRight(); + QDeclarativePropertyPrivate::setBinding(d->rightProp, 0); + } + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::HCenterAnchor) { + targetPrivate->anchors()->resetHorizontalCenter(); + QDeclarativePropertyPrivate::setBinding(d->hCenterProp, 0); + } + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::TopAnchor) { + targetPrivate->anchors()->resetTop(); + QDeclarativePropertyPrivate::setBinding(d->topProp, 0); + } + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::BottomAnchor) { + targetPrivate->anchors()->resetBottom(); + QDeclarativePropertyPrivate::setBinding(d->bottomProp, 0); + } + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::VCenterAnchor) { + targetPrivate->anchors()->resetVerticalCenter(); + QDeclarativePropertyPrivate::setBinding(d->vCenterProp, 0); + } + if (d->anchorSet->d_func()->resetAnchors & QSGAnchors::BaselineAnchor) { + targetPrivate->anchors()->resetBaseline(); + QDeclarativePropertyPrivate::setBinding(d->baselineProp, 0); + } + + //set any anchors that have been specified + if (d->leftBinding) + QDeclarativePropertyPrivate::setBinding(d->leftBinding->property(), d->leftBinding); + if (d->rightBinding) + QDeclarativePropertyPrivate::setBinding(d->rightBinding->property(), d->rightBinding); + if (d->hCenterBinding) + QDeclarativePropertyPrivate::setBinding(d->hCenterBinding->property(), d->hCenterBinding); + if (d->topBinding) + QDeclarativePropertyPrivate::setBinding(d->topBinding->property(), d->topBinding); + if (d->bottomBinding) + QDeclarativePropertyPrivate::setBinding(d->bottomBinding->property(), d->bottomBinding); + if (d->vCenterBinding) + QDeclarativePropertyPrivate::setBinding(d->vCenterBinding->property(), d->vCenterBinding); + if (d->baselineBinding) + QDeclarativePropertyPrivate::setBinding(d->baselineBinding->property(), d->baselineBinding); +} + +bool QSGAnchorChanges::isReversable() +{ + return true; +} + +void QSGAnchorChanges::reverse(Reason reason) +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(d->target); + //reset any anchors set by the state + if (d->leftBinding) { + targetPrivate->anchors()->resetLeft(); + QDeclarativePropertyPrivate::setBinding(d->leftBinding->property(), 0); + if (reason == ActualChange) { + d->leftBinding->destroy(); d->leftBinding = 0; + } + } + if (d->rightBinding) { + targetPrivate->anchors()->resetRight(); + QDeclarativePropertyPrivate::setBinding(d->rightBinding->property(), 0); + if (reason == ActualChange) { + d->rightBinding->destroy(); d->rightBinding = 0; + } + } + if (d->hCenterBinding) { + targetPrivate->anchors()->resetHorizontalCenter(); + QDeclarativePropertyPrivate::setBinding(d->hCenterBinding->property(), 0); + if (reason == ActualChange) { + d->hCenterBinding->destroy(); d->hCenterBinding = 0; + } + } + if (d->topBinding) { + targetPrivate->anchors()->resetTop(); + QDeclarativePropertyPrivate::setBinding(d->topBinding->property(), 0); + if (reason == ActualChange) { + d->topBinding->destroy(); d->topBinding = 0; + } + } + if (d->bottomBinding) { + targetPrivate->anchors()->resetBottom(); + QDeclarativePropertyPrivate::setBinding(d->bottomBinding->property(), 0); + if (reason == ActualChange) { + d->bottomBinding->destroy(); d->bottomBinding = 0; + } + } + if (d->vCenterBinding) { + targetPrivate->anchors()->resetVerticalCenter(); + QDeclarativePropertyPrivate::setBinding(d->vCenterBinding->property(), 0); + if (reason == ActualChange) { + d->vCenterBinding->destroy(); d->vCenterBinding = 0; + } + } + if (d->baselineBinding) { + targetPrivate->anchors()->resetBaseline(); + QDeclarativePropertyPrivate::setBinding(d->baselineBinding->property(), 0); + if (reason == ActualChange) { + d->baselineBinding->destroy(); d->baselineBinding = 0; + } + } + + //restore previous anchors + if (d->origLeftBinding) + QDeclarativePropertyPrivate::setBinding(d->leftProp, d->origLeftBinding); + if (d->origRightBinding) + QDeclarativePropertyPrivate::setBinding(d->rightProp, d->origRightBinding); + if (d->origHCenterBinding) + QDeclarativePropertyPrivate::setBinding(d->hCenterProp, d->origHCenterBinding); + if (d->origTopBinding) + QDeclarativePropertyPrivate::setBinding(d->topProp, d->origTopBinding); + if (d->origBottomBinding) + QDeclarativePropertyPrivate::setBinding(d->bottomProp, d->origBottomBinding); + if (d->origVCenterBinding) + QDeclarativePropertyPrivate::setBinding(d->vCenterProp, d->origVCenterBinding); + if (d->origBaselineBinding) + QDeclarativePropertyPrivate::setBinding(d->baselineProp, d->origBaselineBinding); + + //restore any absolute geometry changed by the state's anchors + QSGAnchors::Anchors stateVAnchors = d->anchorSet->d_func()->usedAnchors & QSGAnchors::Vertical_Mask; + QSGAnchors::Anchors origVAnchors = targetPrivate->anchors()->usedAnchors() & QSGAnchors::Vertical_Mask; + QSGAnchors::Anchors stateHAnchors = d->anchorSet->d_func()->usedAnchors & QSGAnchors::Horizontal_Mask; + QSGAnchors::Anchors origHAnchors = targetPrivate->anchors()->usedAnchors() & QSGAnchors::Horizontal_Mask; + + bool stateSetWidth = (stateHAnchors && + stateHAnchors != QSGAnchors::LeftAnchor && + stateHAnchors != QSGAnchors::RightAnchor && + stateHAnchors != QSGAnchors::HCenterAnchor); + bool origSetWidth = (origHAnchors && + origHAnchors != QSGAnchors::LeftAnchor && + origHAnchors != QSGAnchors::RightAnchor && + origHAnchors != QSGAnchors::HCenterAnchor); + if (d->origWidth.isValid() && stateSetWidth && !origSetWidth) + d->target->setWidth(d->origWidth.value); + + bool stateSetHeight = (stateVAnchors && + stateVAnchors != QSGAnchors::TopAnchor && + stateVAnchors != QSGAnchors::BottomAnchor && + stateVAnchors != QSGAnchors::VCenterAnchor && + stateVAnchors != QSGAnchors::BaselineAnchor); + bool origSetHeight = (origVAnchors && + origVAnchors != QSGAnchors::TopAnchor && + origVAnchors != QSGAnchors::BottomAnchor && + origVAnchors != QSGAnchors::VCenterAnchor && + origVAnchors != QSGAnchors::BaselineAnchor); + if (d->origHeight.isValid() && stateSetHeight && !origSetHeight) + d->target->setHeight(d->origHeight.value); + + if (stateHAnchors && !origHAnchors) + d->target->setX(d->origX); + + if (stateVAnchors && !origVAnchors) + d->target->setY(d->origY); +} + +QString QSGAnchorChanges::typeName() const +{ + return QLatin1String("AnchorChanges"); +} + +QList<QDeclarativeAction> QSGAnchorChanges::additionalActions() +{ + Q_D(QSGAnchorChanges); + QList<QDeclarativeAction> extra; + + QSGAnchors::Anchors combined = d->anchorSet->d_func()->usedAnchors | d->anchorSet->d_func()->resetAnchors; + bool hChange = combined & QSGAnchors::Horizontal_Mask; + bool vChange = combined & QSGAnchors::Vertical_Mask; + + if (d->target) { + QDeclarativeAction a; + if (hChange && d->fromX != d->toX) { + a.property = QDeclarativeProperty(d->target, QLatin1String("x")); + a.toValue = d->toX; + extra << a; + } + if (vChange && d->fromY != d->toY) { + a.property = QDeclarativeProperty(d->target, QLatin1String("y")); + a.toValue = d->toY; + extra << a; + } + if (hChange && d->fromWidth != d->toWidth) { + a.property = QDeclarativeProperty(d->target, QLatin1String("width")); + a.toValue = d->toWidth; + extra << a; + } + if (vChange && d->fromHeight != d->toHeight) { + a.property = QDeclarativeProperty(d->target, QLatin1String("height")); + a.toValue = d->toHeight; + extra << a; + } + } + + return extra; +} + +bool QSGAnchorChanges::changesBindings() +{ + return true; +} + +void QSGAnchorChanges::saveOriginals() +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + d->origLeftBinding = QDeclarativePropertyPrivate::binding(d->leftProp); + d->origRightBinding = QDeclarativePropertyPrivate::binding(d->rightProp); + d->origHCenterBinding = QDeclarativePropertyPrivate::binding(d->hCenterProp); + d->origTopBinding = QDeclarativePropertyPrivate::binding(d->topProp); + d->origBottomBinding = QDeclarativePropertyPrivate::binding(d->bottomProp); + d->origVCenterBinding = QDeclarativePropertyPrivate::binding(d->vCenterProp); + d->origBaselineBinding = QDeclarativePropertyPrivate::binding(d->baselineProp); + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(d->target); + if (targetPrivate->widthValid) + d->origWidth = d->target->width(); + if (targetPrivate->heightValid) + d->origHeight = d->target->height(); + d->origX = d->target->x(); + d->origY = d->target->y(); + + d->applyOrigLeft = d->applyOrigRight = d->applyOrigHCenter = d->applyOrigTop + = d->applyOrigBottom = d->applyOrigVCenter = d->applyOrigBaseline = false; + + saveCurrentValues(); +} + +void QSGAnchorChanges::copyOriginals(QDeclarativeActionEvent *other) +{ + Q_D(QSGAnchorChanges); + QSGAnchorChanges *ac = static_cast<QSGAnchorChanges*>(other); + QSGAnchorChangesPrivate *acp = ac->d_func(); + + QSGAnchors::Anchors combined = acp->anchorSet->d_func()->usedAnchors | + acp->anchorSet->d_func()->resetAnchors; + + //probably also need to revert some things + d->applyOrigLeft = (combined & QSGAnchors::LeftAnchor); + d->applyOrigRight = (combined & QSGAnchors::RightAnchor); + d->applyOrigHCenter = (combined & QSGAnchors::HCenterAnchor); + d->applyOrigTop = (combined & QSGAnchors::TopAnchor); + d->applyOrigBottom = (combined & QSGAnchors::BottomAnchor); + d->applyOrigVCenter = (combined & QSGAnchors::VCenterAnchor); + d->applyOrigBaseline = (combined & QSGAnchors::BaselineAnchor); + + d->origLeftBinding = acp->origLeftBinding; + d->origRightBinding = acp->origRightBinding; + d->origHCenterBinding = acp->origHCenterBinding; + d->origTopBinding = acp->origTopBinding; + d->origBottomBinding = acp->origBottomBinding; + d->origVCenterBinding = acp->origVCenterBinding; + d->origBaselineBinding = acp->origBaselineBinding; + + d->origWidth = acp->origWidth; + d->origHeight = acp->origHeight; + d->origX = acp->origX; + d->origY = acp->origY; + + d->oldBindings.clear(); + d->oldBindings << acp->leftBinding << acp->rightBinding << acp->hCenterBinding + << acp->topBinding << acp->bottomBinding << acp->baselineBinding; + + saveCurrentValues(); +} + +void QSGAnchorChanges::clearBindings() +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + //### should this (saving "from" values) be moved to saveCurrentValues()? + d->fromX = d->target->x(); + d->fromY = d->target->y(); + d->fromWidth = d->target->width(); + d->fromHeight = d->target->height(); + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(d->target); + //reset any anchors with corresponding reverts + //reset any anchors that have been specified as "undefined" + //reset any anchors that we'll be setting in the state + QSGAnchors::Anchors combined = d->anchorSet->d_func()->resetAnchors | + d->anchorSet->d_func()->usedAnchors; + if (d->applyOrigLeft || (combined & QSGAnchors::LeftAnchor)) { + targetPrivate->anchors()->resetLeft(); + QDeclarativePropertyPrivate::setBinding(d->leftProp, 0); + } + if (d->applyOrigRight || (combined & QSGAnchors::RightAnchor)) { + targetPrivate->anchors()->resetRight(); + QDeclarativePropertyPrivate::setBinding(d->rightProp, 0); + } + if (d->applyOrigHCenter || (combined & QSGAnchors::HCenterAnchor)) { + targetPrivate->anchors()->resetHorizontalCenter(); + QDeclarativePropertyPrivate::setBinding(d->hCenterProp, 0); + } + if (d->applyOrigTop || (combined & QSGAnchors::TopAnchor)) { + targetPrivate->anchors()->resetTop(); + QDeclarativePropertyPrivate::setBinding(d->topProp, 0); + } + if (d->applyOrigBottom || (combined & QSGAnchors::BottomAnchor)) { + targetPrivate->anchors()->resetBottom(); + QDeclarativePropertyPrivate::setBinding(d->bottomProp, 0); + } + if (d->applyOrigVCenter || (combined & QSGAnchors::VCenterAnchor)) { + targetPrivate->anchors()->resetVerticalCenter(); + QDeclarativePropertyPrivate::setBinding(d->vCenterProp, 0); + } + if (d->applyOrigBaseline || (combined & QSGAnchors::BaselineAnchor)) { + targetPrivate->anchors()->resetBaseline(); + QDeclarativePropertyPrivate::setBinding(d->baselineProp, 0); + } +} + +bool QSGAnchorChanges::override(QDeclarativeActionEvent*other) +{ + if (other->typeName() != QLatin1String("AnchorChanges")) + return false; + if (static_cast<QDeclarativeActionEvent*>(this) == other) + return true; + if (static_cast<QSGAnchorChanges*>(other)->object() == object()) + return true; + return false; +} + +void QSGAnchorChanges::rewind() +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(d->target); + + //restore previous values (but not previous bindings, i.e. anchors) + d->target->setX(d->rewindX); + d->target->setY(d->rewindY); + if (targetPrivate->widthValid) { + d->target->setWidth(d->rewindWidth); + } + if (targetPrivate->heightValid) { + d->target->setHeight(d->rewindHeight); + } +} + +void QSGAnchorChanges::saveCurrentValues() +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + QSGItemPrivate *targetPrivate = QSGItemPrivate::get(d->target); + d->rewindLeft = targetPrivate->anchors()->left(); + d->rewindRight = targetPrivate->anchors()->right(); + d->rewindHCenter = targetPrivate->anchors()->horizontalCenter(); + d->rewindTop = targetPrivate->anchors()->top(); + d->rewindBottom = targetPrivate->anchors()->bottom(); + d->rewindVCenter = targetPrivate->anchors()->verticalCenter(); + d->rewindBaseline = targetPrivate->anchors()->baseline(); + + d->rewindX = d->target->x(); + d->rewindY = d->target->y(); + d->rewindWidth = d->target->width(); + d->rewindHeight = d->target->height(); +} + +void QSGAnchorChanges::saveTargetValues() +{ + Q_D(QSGAnchorChanges); + if (!d->target) + return; + + d->toX = d->target->x(); + d->toY = d->target->y(); + d->toWidth = d->target->width(); + d->toHeight = d->target->height(); +} + +#include <moc_qsgstateoperations_p.cpp> + +QT_END_NAMESPACE + diff --git a/src/declarative/items/qsgstateoperations_p.h b/src/declarative/items/qsgstateoperations_p.h new file mode 100644 index 0000000000..f816e36b82 --- /dev/null +++ b/src/declarative/items/qsgstateoperations_p.h @@ -0,0 +1,275 @@ +// Commit: 84c47bbb133304d7ef35642fa1fbb17619d4a43d +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGSTATEOPERATIONS_H +#define QSGSTATEOPERATIONS_H + +#include "qsgitem.h" +#include "qsganchors_p.h" + +#include <private/qdeclarativestate_p.h> + +#include <QtDeclarative/qdeclarativescriptstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGParentChangePrivate; +class Q_AUTOTEST_EXPORT QSGParentChange : public QDeclarativeStateOperation, public QDeclarativeActionEvent +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGParentChange) + + Q_PROPERTY(QSGItem *target READ object WRITE setObject) + Q_PROPERTY(QSGItem *parent READ parent WRITE setParent) + Q_PROPERTY(QDeclarativeScriptString x READ x WRITE setX) + Q_PROPERTY(QDeclarativeScriptString y READ y WRITE setY) + Q_PROPERTY(QDeclarativeScriptString width READ width WRITE setWidth) + Q_PROPERTY(QDeclarativeScriptString height READ height WRITE setHeight) + Q_PROPERTY(QDeclarativeScriptString scale READ scale WRITE setScale) + Q_PROPERTY(QDeclarativeScriptString rotation READ rotation WRITE setRotation) +public: + QSGParentChange(QObject *parent=0); + ~QSGParentChange(); + + QSGItem *object() const; + void setObject(QSGItem *); + + QSGItem *parent() const; + void setParent(QSGItem *); + + QSGItem *originalParent() const; + + QDeclarativeScriptString x() const; + void setX(QDeclarativeScriptString x); + bool xIsSet() const; + + QDeclarativeScriptString y() const; + void setY(QDeclarativeScriptString y); + bool yIsSet() const; + + QDeclarativeScriptString width() const; + void setWidth(QDeclarativeScriptString width); + bool widthIsSet() const; + + QDeclarativeScriptString height() const; + void setHeight(QDeclarativeScriptString height); + bool heightIsSet() const; + + QDeclarativeScriptString scale() const; + void setScale(QDeclarativeScriptString scale); + bool scaleIsSet() const; + + QDeclarativeScriptString rotation() const; + void setRotation(QDeclarativeScriptString rotation); + bool rotationIsSet() const; + + virtual ActionList actions(); + + virtual void saveOriginals(); + //virtual void copyOriginals(QDeclarativeActionEvent*); + virtual void execute(Reason reason = ActualChange); + virtual bool isReversable(); + virtual void reverse(Reason reason = ActualChange); + virtual QString typeName() const; + virtual bool override(QDeclarativeActionEvent*other); + virtual void rewind(); + virtual void saveCurrentValues(); +}; + +class QSGAnchorChanges; +class QSGAnchorSetPrivate; +class Q_AUTOTEST_EXPORT QSGAnchorSet : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativeScriptString left READ left WRITE setLeft RESET resetLeft) + Q_PROPERTY(QDeclarativeScriptString right READ right WRITE setRight RESET resetRight) + Q_PROPERTY(QDeclarativeScriptString horizontalCenter READ horizontalCenter WRITE setHorizontalCenter RESET resetHorizontalCenter) + Q_PROPERTY(QDeclarativeScriptString top READ top WRITE setTop RESET resetTop) + Q_PROPERTY(QDeclarativeScriptString bottom READ bottom WRITE setBottom RESET resetBottom) + Q_PROPERTY(QDeclarativeScriptString verticalCenter READ verticalCenter WRITE setVerticalCenter RESET resetVerticalCenter) + Q_PROPERTY(QDeclarativeScriptString baseline READ baseline WRITE setBaseline RESET resetBaseline) + //Q_PROPERTY(QSGItem *fill READ fill WRITE setFill RESET resetFill) + //Q_PROPERTY(QSGItem *centerIn READ centerIn WRITE setCenterIn RESET resetCenterIn) + + /*Q_PROPERTY(qreal margins READ margins WRITE setMargins NOTIFY marginsChanged) + Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged) + Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged) + Q_PROPERTY(qreal horizontalCenterOffset READ horizontalCenterOffset WRITE setHorizontalCenterOffset NOTIFY horizontalCenterOffsetChanged()) + Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged) + Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged) + Q_PROPERTY(qreal verticalCenterOffset READ verticalCenterOffset WRITE setVerticalCenterOffset NOTIFY verticalCenterOffsetChanged()) + Q_PROPERTY(qreal baselineOffset READ baselineOffset WRITE setBaselineOffset NOTIFY baselineOffsetChanged())*/ + +public: + QSGAnchorSet(QObject *parent=0); + virtual ~QSGAnchorSet(); + + QDeclarativeScriptString left() const; + void setLeft(const QDeclarativeScriptString &edge); + void resetLeft(); + + QDeclarativeScriptString right() const; + void setRight(const QDeclarativeScriptString &edge); + void resetRight(); + + QDeclarativeScriptString horizontalCenter() const; + void setHorizontalCenter(const QDeclarativeScriptString &edge); + void resetHorizontalCenter(); + + QDeclarativeScriptString top() const; + void setTop(const QDeclarativeScriptString &edge); + void resetTop(); + + QDeclarativeScriptString bottom() const; + void setBottom(const QDeclarativeScriptString &edge); + void resetBottom(); + + QDeclarativeScriptString verticalCenter() const; + void setVerticalCenter(const QDeclarativeScriptString &edge); + void resetVerticalCenter(); + + QDeclarativeScriptString baseline() const; + void setBaseline(const QDeclarativeScriptString &edge); + void resetBaseline(); + + QSGItem *fill() const; + void setFill(QSGItem *); + void resetFill(); + + QSGItem *centerIn() const; + void setCenterIn(QSGItem *); + void resetCenterIn(); + + /*qreal leftMargin() const; + void setLeftMargin(qreal); + + qreal rightMargin() const; + void setRightMargin(qreal); + + qreal horizontalCenterOffset() const; + void setHorizontalCenterOffset(qreal); + + qreal topMargin() const; + void setTopMargin(qreal); + + qreal bottomMargin() const; + void setBottomMargin(qreal); + + qreal margins() const; + void setMargins(qreal); + + qreal verticalCenterOffset() const; + void setVerticalCenterOffset(qreal); + + qreal baselineOffset() const; + void setBaselineOffset(qreal);*/ + + QSGAnchors::Anchors usedAnchors() const; + +/*Q_SIGNALS: + void leftMarginChanged(); + void rightMarginChanged(); + void topMarginChanged(); + void bottomMarginChanged(); + void marginsChanged(); + void verticalCenterOffsetChanged(); + void horizontalCenterOffsetChanged(); + void baselineOffsetChanged();*/ + +private: + friend class QSGAnchorChanges; + Q_DISABLE_COPY(QSGAnchorSet) + Q_DECLARE_PRIVATE(QSGAnchorSet) +}; + +class QSGAnchorChangesPrivate; +class Q_AUTOTEST_EXPORT QSGAnchorChanges : public QDeclarativeStateOperation, public QDeclarativeActionEvent +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGAnchorChanges) + + Q_PROPERTY(QSGItem *target READ object WRITE setObject) + Q_PROPERTY(QSGAnchorSet *anchors READ anchors CONSTANT) + +public: + QSGAnchorChanges(QObject *parent=0); + ~QSGAnchorChanges(); + + virtual ActionList actions(); + + QSGAnchorSet *anchors(); + + QSGItem *object() const; + void setObject(QSGItem *); + + virtual void execute(Reason reason = ActualChange); + virtual bool isReversable(); + virtual void reverse(Reason reason = ActualChange); + virtual QString typeName() const; + virtual bool override(QDeclarativeActionEvent*other); + virtual bool changesBindings(); + virtual void saveOriginals(); + virtual bool needsCopy() { return true; } + virtual void copyOriginals(QDeclarativeActionEvent*); + virtual void clearBindings(); + virtual void rewind(); + virtual void saveCurrentValues(); + + QList<QDeclarativeAction> additionalActions(); + virtual void saveTargetValues(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGParentChange) +QML_DECLARE_TYPE(QSGAnchorSet) +QML_DECLARE_TYPE(QSGAnchorChanges) + +QT_END_HEADER + +#endif // QSGSTATEOPERATIONS_H + diff --git a/src/declarative/items/qsgtext.cpp b/src/declarative/items/qsgtext.cpp new file mode 100644 index 0000000000..f2ec7b21fa --- /dev/null +++ b/src/declarative/items/qsgtext.cpp @@ -0,0 +1,1249 @@ +// Commit: cce89db1e2555cbca8fc28072e1c6dd737cec6c4 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtext_p.h" +#include "qsgtext_p_p.h" + +#include <private/qsgdistancefieldglyphcache_p.h> +#include <private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> +#include "qsgtextnode_p.h" +#include "qsgimage_p_p.h" +#include <private/qsgtexture_p.h> + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qabstracttextdocumentlayout.h> +#include <QtGui/qpainter.h> +#include <QtGui/qtextdocument.h> +#include <QtGui/qtextobject.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qapplication.h> + +#include <private/qdeclarativestyledtext_p.h> +#include <private/qdeclarativepixmapcache_p.h> + +#include <qmath.h> +#include <limits.h> + +QT_BEGIN_NAMESPACE + +extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled; + +class QSGTextDocumentWithImageResources : public QTextDocument { + Q_OBJECT + +public: + QSGTextDocumentWithImageResources(QSGText *parent); + virtual ~QSGTextDocumentWithImageResources(); + + void setText(const QString &); + int resourcesLoading() const { return outstanding; } + +protected: + QVariant loadResource(int type, const QUrl &name); + +private slots: + void requestFinished(); + +private: + QHash<QUrl, QDeclarativePixmap *> m_resources; + + int outstanding; + static QSet<QUrl> errors; +}; + +DEFINE_BOOL_CONFIG_OPTION(enableImageCache, QML_ENABLE_TEXT_IMAGE_CACHE); + +QString QSGTextPrivate::elideChar = QString(0x2026); + +QSGTextPrivate::QSGTextPrivate() +: color((QRgb)0), style(QSGText::Normal), hAlign(QSGText::AlignLeft), + vAlign(QSGText::AlignTop), elideMode(QSGText::ElideNone), + format(QSGText::AutoText), wrapMode(QSGText::NoWrap), lineHeight(1), + lineHeightMode(QSGText::ProportionalHeight), lineCount(1), maximumLineCount(INT_MAX), + maximumLineCountValid(false), + texture(0), + imageCacheDirty(true), updateOnComponentComplete(true), + richText(false), singleline(false), cacheAllTextAsImage(true), internalWidthUpdate(false), + requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false), + layoutTextElided(false), naturalWidth(0), doc(0), nodeType(NodeIsNull) +{ + cacheAllTextAsImage = enableImageCache(); +} + +void QSGTextPrivate::init() +{ + Q_Q(QSGText); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QSGItem::ItemHasContents); +} + +QSGTextDocumentWithImageResources::QSGTextDocumentWithImageResources(QSGText *parent) +: QTextDocument(parent), outstanding(0) +{ + setUndoRedoEnabled(false); +} + +QSGTextDocumentWithImageResources::~QSGTextDocumentWithImageResources() +{ + if (!m_resources.isEmpty()) + qDeleteAll(m_resources); +} + +QVariant QSGTextDocumentWithImageResources::loadResource(int type, const QUrl &name) +{ + QDeclarativeContext *context = qmlContext(parent()); + QUrl url = context->resolvedUrl(name); + + if (type == QTextDocument::ImageResource) { + QHash<QUrl, QDeclarativePixmap *>::Iterator iter = m_resources.find(url); + + if (iter == m_resources.end()) { + QDeclarativePixmap *p = new QDeclarativePixmap(context->engine(), url); + iter = m_resources.insert(name, p); + + if (p->isLoading()) { + p->connectFinished(this, SLOT(requestFinished())); + outstanding++; + } + } + + QDeclarativePixmap *p = *iter; + if (p->isReady()) { + return p->pixmap(); + } else if (p->isError()) { + if (!errors.contains(url)) { + errors.insert(url); + qmlInfo(parent()) << p->error(); + } + } + } + + return QTextDocument::loadResource(type,url); // The *resolved* URL +} + +void QSGTextDocumentWithImageResources::requestFinished() +{ + outstanding--; + if (outstanding == 0) { + QSGText *textItem = static_cast<QSGText*>(parent()); + QString text = textItem->text(); +#ifndef QT_NO_TEXTHTMLPARSER + setHtml(text); +#else + setPlainText(text); +#endif + QSGTextPrivate *d = QSGTextPrivate::get(textItem); + d->updateLayout(); + } +} + +void QSGTextDocumentWithImageResources::setText(const QString &text) +{ + if (!m_resources.isEmpty()) { + qDeleteAll(m_resources); + m_resources.clear(); + outstanding = 0; + } + +#ifndef QT_NO_TEXTHTMLPARSER + setHtml(text); +#else + setPlainText(text); +#endif +} + +QSet<QUrl> QSGTextDocumentWithImageResources::errors; + +QSGTextPrivate::~QSGTextPrivate() +{ +} + +qreal QSGTextPrivate::getImplicitWidth() const +{ + if (!requireImplicitWidth) { + // We don't calculate implicitWidth unless it is required. + // We need to force a size update now to ensure implicitWidth is calculated + QSGTextPrivate *me = const_cast<QSGTextPrivate*>(this); + me->requireImplicitWidth = true; + me->updateSize(); + } + return implicitWidth; +} + +void QSGTextPrivate::updateLayout() +{ + Q_Q(QSGText); + if (!q->isComponentComplete()) { + updateOnComponentComplete = true; + return; + } + + layoutTextElided = false; + // Setup instance of QTextLayout for all cases other than richtext + if (!richText) { + layout.clearLayout(); + layout.setFont(font); + if (format != QSGText::StyledText) { + QString tmp = text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + singleline = !tmp.contains(QChar::LineSeparator); + if (singleline && !maximumLineCountValid && elideMode != QSGText::ElideNone && q->widthValid()) { + QFontMetrics fm(font); + tmp = fm.elidedText(tmp,(Qt::TextElideMode)elideMode,q->width()); + if (tmp != text) { + layoutTextElided = true; + if (!truncated) { + truncated = true; + emit q->truncatedChanged(); + } + } + } + layout.setText(tmp); + } else { + singleline = false; + QDeclarativeStyledText::parse(text, layout); + } + } else { + ensureDoc(); + QTextBlockFormat::LineHeightTypes type; + type = lineHeightMode == QSGText::FixedHeight ? QTextBlockFormat::FixedHeight : QTextBlockFormat::ProportionalHeight; + QTextBlockFormat blockFormat; + blockFormat.setLineHeight((lineHeightMode == QSGText::FixedHeight ? lineHeight : lineHeight * 100), type); + for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) { + QTextCursor cursor(it); + cursor.setBlockFormat(blockFormat); + } + } + + updateSize(); +} + +void QSGTextPrivate::updateSize() +{ + Q_Q(QSGText); + + if (!q->isComponentComplete()) { + updateOnComponentComplete = true; + return; + } + + if (!requireImplicitWidth) { + emit q->implicitWidthChanged(); + // if the implicitWidth is used, then updateSize() has already been called (recursively) + if (requireImplicitWidth) + return; + } + + invalidateImageCache(); + + QFontMetrics fm(font); + if (text.isEmpty()) { + q->setImplicitWidth(0); + q->setImplicitHeight(fm.height()); + paintedSize = QSize(0, fm.height()); + emit q->paintedSizeChanged(); + q->update(); + return; + } + + int dy = q->height(); + QSize size(0, 0); + + //setup instance of QTextLayout for all cases other than richtext + if (!richText) { + QRect textRect = setupTextLayout(); + layedOutTextRect = textRect; + size = textRect.size(); + dy -= size.height(); + } else { + singleline = false; // richtext can't elide or be optimized for single-line case + ensureDoc(); + doc->setDefaultFont(font); + QSGText::HAlignment horizontalAlignment = q->effectiveHAlign(); + if (rightToLeftText) { + if (horizontalAlignment == QSGText::AlignLeft) + horizontalAlignment = QSGText::AlignRight; + else if (horizontalAlignment == QSGText::AlignRight) + horizontalAlignment = QSGText::AlignLeft; + } + QTextOption option; + option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign)); + option.setWrapMode(QTextOption::WrapMode(wrapMode)); + doc->setDefaultTextOption(option); + if (requireImplicitWidth && q->widthValid()) { + doc->setTextWidth(-1); + naturalWidth = doc->idealWidth(); + } + if (wrapMode != QSGText::NoWrap && q->widthValid()) + doc->setTextWidth(q->width()); + else + doc->setTextWidth(doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug) + dy -= (int)doc->size().height(); + QSize dsize = doc->size().toSize(); + layedOutTextRect = QRect(QPoint(0,0), dsize); + size = QSize(int(doc->idealWidth()),dsize.height()); + } + int yoff = 0; + + if (q->heightValid()) { + if (vAlign == QSGText::AlignBottom) + yoff = dy; + else if (vAlign == QSGText::AlignVCenter) + yoff = dy/2; + } + q->setBaselineOffset(fm.ascent() + yoff); + + //### need to comfirm cost of always setting these for richText + internalWidthUpdate = true; + if (!q->widthValid()) + q->setImplicitWidth(size.width()); + else if (requireImplicitWidth) + q->setImplicitWidth(naturalWidth); + internalWidthUpdate = false; + + q->setImplicitHeight(size.height()); + if (paintedSize != size) { + paintedSize = size; + emit q->paintedSizeChanged(); + } + q->update(); +} + +/*! + Lays out the QSGTextPrivate::layout QTextLayout in the constraints of the QSGText. + + Returns the size of the final text. This can be used to position the text vertically (the text is + already absolutely positioned horizontally). +*/ +QRect QSGTextPrivate::setupTextLayout() +{ + // ### text layout handling should be profiled and optimized as needed + // what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine); + Q_Q(QSGText); + layout.setCacheEnabled(true); + + qreal lineWidth = 0; + int visibleCount = 0; + + //set manual width + if (q->widthValid()) + lineWidth = q->width(); + + QTextOption textOption = layout.textOption(); + textOption.setAlignment(Qt::Alignment(q->effectiveHAlign())); + textOption.setWrapMode(QTextOption::WrapMode(wrapMode)); + layout.setTextOption(textOption); + + bool elideText = false; + bool truncate = false; + + QFontMetrics fm(layout.font()); + elidePos = QPointF(); + + if (requireImplicitWidth && q->widthValid()) { + // requires an extra layout + QString elidedText; + if (layoutTextElided) { + // We have provided elided text to the layout, but we must calculate unelided width. + elidedText = layout.text(); + layout.setText(text); + } + layout.beginLayout(); + forever { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + } + layout.endLayout(); + QRectF br; + for (int i = 0; i < layout.lineCount(); ++i) { + QTextLine line = layout.lineAt(i); + br = br.united(line.naturalTextRect()); + } + naturalWidth = br.width(); + if (layoutTextElided) + layout.setText(elidedText); + } + + if (maximumLineCountValid) { + layout.beginLayout(); + if (!lineWidth) + lineWidth = INT_MAX; + int linesLeft = maximumLineCount; + int visibleTextLength = 0; + while (linesLeft > 0) { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + + visibleCount++; + if (lineWidth) + line.setLineWidth(lineWidth); + visibleTextLength += line.textLength(); + + if (--linesLeft == 0) { + if (visibleTextLength < text.length()) { + truncate = true; + if (elideMode==QSGText::ElideRight && q->widthValid()) { + qreal elideWidth = fm.width(elideChar); + // Need to correct for alignment + line.setLineWidth(lineWidth-elideWidth); + if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) { + line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y())); + elidePos.setX(line.naturalTextRect().left() - elideWidth); + } else { + elidePos.setX(line.naturalTextRect().right()); + } + elideText = true; + } + } + } + } + layout.endLayout(); + + //Update truncated + if (truncated != truncate) { + truncated = truncate; + emit q->truncatedChanged(); + } + } else { + layout.beginLayout(); + forever { + QTextLine line = layout.createLine(); + if (!line.isValid()) + break; + visibleCount++; + if (lineWidth) + line.setLineWidth(lineWidth); + } + layout.endLayout(); + } + + qreal height = 0; + QRectF br; + for (int i = 0; i < layout.lineCount(); ++i) { + QTextLine line = layout.lineAt(i); + // set line spacing + line.setPosition(QPointF(line.position().x(), height)); + if (elideText && i == layout.lineCount()-1) { + elidePos.setY(height + fm.ascent()); + br = br.united(QRectF(elidePos, QSizeF(fm.width(elideChar), fm.ascent()))); + } + br = br.united(line.naturalTextRect()); + height += (lineHeightMode == QSGText::FixedHeight) ? lineHeight : line.height() * lineHeight; + } + br.setHeight(height); + + if (!q->widthValid()) + naturalWidth = br.width(); + + //Update the number of visible lines + if (lineCount != visibleCount) { + lineCount = visibleCount; + emit q->lineCountChanged(); + } + + return QRect(qRound(br.x()), qRound(br.y()), qCeil(br.width()), qCeil(br.height())); +} + +/*! + Returns a painted version of the QSGTextPrivate::layout QTextLayout. + If \a drawStyle is true, the style color overrides all colors in the document. +*/ +QPixmap QSGTextPrivate::textLayoutImage(bool drawStyle) +{ + QSize size = layedOutTextRect.size(); + + //paint text + QPixmap img(size); + if (!size.isEmpty()) { + img.fill(Qt::transparent); +#ifdef Q_WS_MAC + bool oldSmooth = qt_applefontsmoothing_enabled; + qt_applefontsmoothing_enabled = false; +#endif + QPainter p(&img); +#ifdef Q_WS_MAC + qt_applefontsmoothing_enabled = oldSmooth; +#endif + drawTextLayout(&p, QPointF(-layedOutTextRect.x(),0), drawStyle); + } + return img; +} + +/*! + Paints the QSGTextPrivate::layout QTextLayout into \a painter at \a pos. If + \a drawStyle is true, the style color overrides all colors in the document. +*/ +void QSGTextPrivate::drawTextLayout(QPainter *painter, const QPointF &pos, bool drawStyle) +{ + if (drawStyle) + painter->setPen(styleColor); + else + painter->setPen(color); + painter->setFont(font); + layout.draw(painter, pos); + if (!elidePos.isNull()) + painter->drawText(pos + elidePos, elideChar); +} + +/*! + Returns a painted version of the QSGTextPrivate::doc QTextDocument. + If \a drawStyle is true, the style color overrides all colors in the document. +*/ +QPixmap QSGTextPrivate::textDocumentImage(bool drawStyle) +{ + QSize size = doc->size().toSize(); + + //paint text + QPixmap img(size); + img.fill(Qt::transparent); +#ifdef Q_WS_MAC + bool oldSmooth = qt_applefontsmoothing_enabled; + qt_applefontsmoothing_enabled = false; +#endif + QPainter p(&img); +#ifdef Q_WS_MAC + qt_applefontsmoothing_enabled = oldSmooth; +#endif + + QAbstractTextDocumentLayout::PaintContext context; + + QTextOption oldOption(doc->defaultTextOption()); + if (drawStyle) { + context.palette.setColor(QPalette::Text, styleColor); + QTextOption colorOption(doc->defaultTextOption()); + colorOption.setFlags(QTextOption::SuppressColors); + doc->setDefaultTextOption(colorOption); + } else { + context.palette.setColor(QPalette::Text, color); + } + doc->documentLayout()->draw(&p, context); + if (drawStyle) + doc->setDefaultTextOption(oldOption); + return img; +} + +/*! + Mark the image cache as dirty. +*/ +void QSGTextPrivate::invalidateImageCache() +{ + Q_Q(QSGText); + + if(cacheAllTextAsImage || (!QSGDistanceFieldGlyphCache::distanceFieldEnabled() && style != QSGText::Normal)){//If actually using the image cache + if (imageCacheDirty) + return; + + imageCacheDirty = true; + imageCache = QPixmap(); + } + if (q->isComponentComplete()) + q->update(); +} + +/*! + Tests if the image cache is dirty, and repaints it if it is. +*/ +void QSGTextPrivate::checkImageCache() +{ + if (!imageCacheDirty) + return; + + if (text.isEmpty()) { + + imageCache = QPixmap(); + + } else { + + QPixmap textImage; + QPixmap styledImage; + + if (richText) { + textImage = textDocumentImage(false); + if (style != QSGText::Normal) + styledImage = textDocumentImage(true); //### should use styleColor + } else { + textImage = textLayoutImage(false); + if (style != QSGText::Normal) + styledImage = textLayoutImage(true); //### should use styleColor + } + + switch (style) { + case QSGText::Outline: + imageCache = drawOutline(textImage, styledImage); + break; + case QSGText::Sunken: + imageCache = drawOutline(textImage, styledImage, -1); + break; + case QSGText::Raised: + imageCache = drawOutline(textImage, styledImage, 1); + break; + default: + imageCache = textImage; + break; + } + + } + + imageCacheDirty = false; +} + +/*! + Ensures the QSGTextPrivate::doc variable is set to a valid text document +*/ +void QSGTextPrivate::ensureDoc() +{ + if (!doc) { + Q_Q(QSGText); + doc = new QSGTextDocumentWithImageResources(q); + doc->setDocumentMargin(0); + } +} + +/*! + Draw \a styleSource as an outline around \a source and return the new image. +*/ +QPixmap QSGTextPrivate::drawOutline(const QPixmap &source, const QPixmap &styleSource) +{ + QPixmap img = QPixmap(styleSource.width() + 2, styleSource.height() + 2); + img.fill(Qt::transparent); + + QPainter ppm(&img); + + QPoint pos(0, 0); + pos += QPoint(-1, 0); + ppm.drawPixmap(pos, styleSource); + pos += QPoint(2, 0); + ppm.drawPixmap(pos, styleSource); + pos += QPoint(-1, -1); + ppm.drawPixmap(pos, styleSource); + pos += QPoint(0, 2); + ppm.drawPixmap(pos, styleSource); + + pos += QPoint(0, -1); + ppm.drawPixmap(pos, source); + ppm.end(); + + return img; +} + +/*! + Draw \a styleSource below \a source at \a yOffset and return the new image. +*/ +QPixmap QSGTextPrivate::drawOutline(const QPixmap &source, const QPixmap &styleSource, int yOffset) +{ + QPixmap img = QPixmap(styleSource.width() + 2, styleSource.height() + 2); + img.fill(Qt::transparent); + + QPainter ppm(&img); + + ppm.drawPixmap(QPoint(0, yOffset), styleSource); + ppm.drawPixmap(0, 0, source); + + ppm.end(); + + return img; +} + +QSGText::QSGText(QSGItem *parent) +: QSGImplicitSizeItem(*(new QSGTextPrivate), parent) +{ + Q_D(QSGText); + d->init(); +} + +QSGText::~QSGText() +{ +} + +QFont QSGText::font() const +{ + Q_D(const QSGText); + return d->sourceFont; +} + +void QSGText::setFont(const QFont &font) +{ + Q_D(QSGText); + if (d->sourceFont == font) + return; + + 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 + qreal size = qRound(d->font.pointSizeF()*2.0); + d->font.setPointSizeF(size/2.0); + } + + if (oldFont != d->font) + d->updateLayout(); + + emit fontChanged(d->sourceFont); +} + +QString QSGText::text() const +{ + Q_D(const QSGText); + return d->text; +} + +void QSGText::setText(const QString &n) +{ + Q_D(QSGText); + if (d->text == n) + return; + + d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(n)); + d->text = n; + if (isComponentComplete()) { + if (d->richText) { + d->ensureDoc(); + d->doc->setText(n); + d->rightToLeftText = d->doc->toPlainText().isRightToLeft(); + } else { + d->rightToLeftText = d->text.isRightToLeft(); + } + d->determineHorizontalAlignment(); + } + d->updateLayout(); + emit textChanged(d->text); +} + +QColor QSGText::color() const +{ + Q_D(const QSGText); + return d->color; +} + +void QSGText::setColor(const QColor &color) +{ + Q_D(QSGText); + if (d->color == color) + return; + + d->color = color; + d->invalidateImageCache(); + emit colorChanged(d->color); +} + +QSGText::TextStyle QSGText::style() const +{ + Q_D(const QSGText); + return d->style; +} + +void QSGText::setStyle(QSGText::TextStyle style) +{ + Q_D(QSGText); + if (d->style == style) + return; + + // changing to/from Normal requires the boundingRect() to change + if (isComponentComplete() && (d->style == Normal || style == Normal)) + update(); + d->style = style; + d->invalidateImageCache(); + emit styleChanged(d->style); +} + +QColor QSGText::styleColor() const +{ + Q_D(const QSGText); + return d->styleColor; +} + +void QSGText::setStyleColor(const QColor &color) +{ + Q_D(QSGText); + if (d->styleColor == color) + return; + + d->styleColor = color; + d->invalidateImageCache(); + emit styleColorChanged(d->styleColor); +} + +QSGText::HAlignment QSGText::hAlign() const +{ + Q_D(const QSGText); + return d->hAlign; +} + +void QSGText::setHAlign(HAlignment align) +{ + Q_D(QSGText); + bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; + d->hAlignImplicit = false; + if (d->setHAlign(align, forceAlign) && isComponentComplete()) + d->updateLayout(); +} + +void QSGText::resetHAlign() +{ + Q_D(QSGText); + d->hAlignImplicit = true; + if (d->determineHorizontalAlignment() && isComponentComplete()) + d->updateLayout(); +} + +QSGText::HAlignment QSGText::effectiveHAlign() const +{ + Q_D(const QSGText); + QSGText::HAlignment effectiveAlignment = d->hAlign; + if (!d->hAlignImplicit && d->effectiveLayoutMirror) { + switch (d->hAlign) { + case QSGText::AlignLeft: + effectiveAlignment = QSGText::AlignRight; + break; + case QSGText::AlignRight: + effectiveAlignment = QSGText::AlignLeft; + break; + default: + break; + } + } + return effectiveAlignment; +} + +bool QSGTextPrivate::setHAlign(QSGText::HAlignment alignment, bool forceAlign) +{ + Q_Q(QSGText); + if (hAlign != alignment || forceAlign) { + QSGText::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); + hAlign = alignment; + + emit q->horizontalAlignmentChanged(hAlign); + if (oldEffectiveHAlign != q->effectiveHAlign()) + emit q->effectiveHorizontalAlignmentChanged(); + return true; + } + return false; +} + +bool QSGTextPrivate::determineHorizontalAlignment() +{ + Q_Q(QSGText); + if (hAlignImplicit && q->isComponentComplete()) { + bool alignToRight = text.isEmpty() ? QApplication::keyboardInputDirection() == Qt::RightToLeft : rightToLeftText; + return setHAlign(alignToRight ? QSGText::AlignRight : QSGText::AlignLeft); + } + return false; +} + +void QSGTextPrivate::mirrorChange() +{ + Q_Q(QSGText); + if (q->isComponentComplete()) { + if (!hAlignImplicit && (hAlign == QSGText::AlignRight || hAlign == QSGText::AlignLeft)) { + updateLayout(); + emit q->effectiveHorizontalAlignmentChanged(); + } + } +} + +QTextDocument *QSGTextPrivate::textDocument() +{ + return doc; +} + +QSGText::VAlignment QSGText::vAlign() const +{ + Q_D(const QSGText); + return d->vAlign; +} + +void QSGText::setVAlign(VAlignment align) +{ + Q_D(QSGText); + if (d->vAlign == align) + return; + + d->vAlign = align; + emit verticalAlignmentChanged(align); +} + +QSGText::WrapMode QSGText::wrapMode() const +{ + Q_D(const QSGText); + return d->wrapMode; +} + +void QSGText::setWrapMode(WrapMode mode) +{ + Q_D(QSGText); + if (mode == d->wrapMode) + return; + + d->wrapMode = mode; + d->updateLayout(); + + emit wrapModeChanged(); +} + +int QSGText::lineCount() const +{ + Q_D(const QSGText); + return d->lineCount; +} + +bool QSGText::truncated() const +{ + Q_D(const QSGText); + return d->truncated; +} + +int QSGText::maximumLineCount() const +{ + Q_D(const QSGText); + return d->maximumLineCount; +} + +void QSGText::setMaximumLineCount(int lines) +{ + Q_D(QSGText); + + d->maximumLineCountValid = lines==INT_MAX ? false : true; + if (d->maximumLineCount != lines) { + d->maximumLineCount = lines; + d->updateLayout(); + emit maximumLineCountChanged(); + } +} + +void QSGText::resetMaximumLineCount() +{ + Q_D(QSGText); + setMaximumLineCount(INT_MAX); + d->elidePos = QPointF(); + if (d->truncated != false) { + d->truncated = false; + emit truncatedChanged(); + } +} + +QSGText::TextFormat QSGText::textFormat() const +{ + Q_D(const QSGText); + return d->format; +} + +void QSGText::setTextFormat(TextFormat format) +{ + Q_D(QSGText); + if (format == d->format) + return; + d->format = format; + bool wasRich = d->richText; + d->richText = format == RichText || (format == AutoText && Qt::mightBeRichText(d->text)); + + if (!wasRich && d->richText && isComponentComplete()) { + d->ensureDoc(); + d->doc->setText(d->text); + } + + d->updateLayout(); + + emit textFormatChanged(d->format); +} + +QSGText::TextElideMode QSGText::elideMode() const +{ + Q_D(const QSGText); + return d->elideMode; +} + +void QSGText::setElideMode(QSGText::TextElideMode mode) +{ + Q_D(QSGText); + if (mode == d->elideMode) + return; + + d->elideMode = mode; + d->updateLayout(); + + emit elideModeChanged(d->elideMode); +} + +/*! \internal */ +QRectF QSGText::boundingRect() const +{ + Q_D(const QSGText); + + QRect rect = d->layedOutTextRect; + if (d->style != Normal) + rect.adjust(-1, 0, 1, 2); + + // Could include font max left/right bearings to either side of rectangle. + + int h = height(); + switch (d->vAlign) { + case AlignTop: + break; + case AlignBottom: + rect.moveTop(h - rect.height()); + break; + case AlignVCenter: + rect.moveTop((h - rect.height()) / 2); + break; + } + + return QRectF(rect); +} + +/*! \internal */ +void QSGText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QSGText); + if ((!d->internalWidthUpdate && newGeometry.width() != oldGeometry.width()) + && (d->wrapMode != QSGText::NoWrap + || d->elideMode != QSGText::ElideNone + || d->hAlign != QSGText::AlignLeft)) { + if ((d->singleline || d->maximumLineCountValid) && d->elideMode != QSGText::ElideNone && widthValid()) { + // We need to re-elide + d->updateLayout(); + } else { + // We just need to re-layout + d->updateSize(); + } + } + + QSGItem::geometryChanged(newGeometry, oldGeometry); +} + +QSGNode *QSGText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + Q_D(QSGText); + + bool richTextAsImage = false; + if (d->richText) { + d->ensureDoc(); + richTextAsImage = QSGTextNode::isComplexRichText(d->doc); + } + + QRectF bounds = boundingRect(); + + // XXX todo - some styled text can be done by the QSGTextNode + if (richTextAsImage || d->cacheAllTextAsImage || (!QSGDistanceFieldGlyphCache::distanceFieldEnabled() && d->style != Normal)) { + bool wasDirty = d->imageCacheDirty; + + d->checkImageCache(); + + if (d->imageCache.isNull()) { + delete oldNode; + return 0; + } + + QSGImageNode *node = 0; + if (!oldNode || d->nodeType != QSGTextPrivate::NodeIsTexture) { + delete oldNode; + node = QSGItemPrivate::get(this)->sceneGraphContext()->createImageNode(); + d->texture = new QSGPlainTexture(); + wasDirty = true; + d->nodeType = QSGTextPrivate::NodeIsTexture; + } else { + node = static_cast<QSGImageNode *>(oldNode); + Q_ASSERT(d->texture); + } + + if (wasDirty) { + qobject_cast<QSGPlainTexture *>(d->texture)->setImage(d->imageCache.toImage()); + node->setTexture(0); + node->setTexture(d->texture); + } + + node->setTargetRect(QRectF(bounds.x(), bounds.y(), d->imageCache.width(), d->imageCache.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(); + + return node; + + } else { + QSGTextNode *node = 0; + if (!oldNode || d->nodeType != QSGTextPrivate::NodeIsText) { + delete oldNode; + node = new QSGTextNode(QSGItemPrivate::get(this)->sceneGraphContext()); + d->nodeType = QSGTextPrivate::NodeIsText; + } else { + node = static_cast<QSGTextNode *>(oldNode); + } + + node->deleteContent(); + node->setMatrix(QMatrix4x4()); + + if (d->richText) { + + d->ensureDoc(); + node->addTextDocument(bounds.topLeft(), d->doc, QColor(), d->style, d->styleColor); + + } else { + node->addTextLayout(QPoint(0, bounds.y()), &d->layout, d->color, d->style, d->styleColor); + } + + return node; + } +} + +qreal QSGText::paintedWidth() const +{ + Q_D(const QSGText); + return d->paintedSize.width(); +} + +qreal QSGText::paintedHeight() const +{ + Q_D(const QSGText); + return d->paintedSize.height(); +} + +qreal QSGText::lineHeight() const +{ + Q_D(const QSGText); + return d->lineHeight; +} + +void QSGText::setLineHeight(qreal lineHeight) +{ + Q_D(QSGText); + + if ((d->lineHeight == lineHeight) || (lineHeight < 0.0)) + return; + + d->lineHeight = lineHeight; + d->updateLayout(); + emit lineHeightChanged(lineHeight); +} + +QSGText::LineHeightMode QSGText::lineHeightMode() const +{ + Q_D(const QSGText); + return d->lineHeightMode; +} + +void QSGText::setLineHeightMode(LineHeightMode mode) +{ + Q_D(QSGText); + if (mode == d->lineHeightMode) + return; + + d->lineHeightMode = mode; + d->updateLayout(); + + emit lineHeightModeChanged(mode); +} + +/*! + Returns the number of resources (images) that are being loaded asynchronously. +*/ +int QSGText::resourcesLoading() const +{ + Q_D(const QSGText); + return d->doc ? d->doc->resourcesLoading() : 0; +} + +/*! \internal */ +void QSGText::componentComplete() +{ + Q_D(QSGText); + QSGItem::componentComplete(); + if (d->updateOnComponentComplete) { + d->updateOnComponentComplete = false; + if (d->richText) { + d->ensureDoc(); + d->doc->setText(d->text); + d->rightToLeftText = d->doc->toPlainText().isRightToLeft(); + } else { + d->rightToLeftText = d->text.isRightToLeft(); + } + d->determineHorizontalAlignment(); + d->updateLayout(); + } +} + +/*! \internal */ +void QSGText::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGText); + + if (!d->richText || !d->doc || d->doc->documentLayout()->anchorAt(event->pos()).isEmpty()) { + event->setAccepted(false); + d->activeLink.clear(); + } else { + d->activeLink = d->doc->documentLayout()->anchorAt(event->pos()); + } + + // ### may malfunction if two of the same links are clicked & dragged onto each other) + + if (!event->isAccepted()) + QSGItem::mousePressEvent(event); + +} + +/*! \internal */ +void QSGText::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGText); + + // ### confirm the link, and send a signal out + if (d->richText && d->doc && d->activeLink == d->doc->documentLayout()->anchorAt(event->pos())) + emit linkActivated(d->activeLink); + else + event->setAccepted(false); + + if (!event->isAccepted()) + QSGItem::mouseReleaseEvent(event); +} + +QT_END_NAMESPACE + +#include "qsgtext.moc" diff --git a/src/declarative/items/qsgtext_p.h b/src/declarative/items/qsgtext_p.h new file mode 100644 index 0000000000..090a2b0e67 --- /dev/null +++ b/src/declarative/items/qsgtext_p.h @@ -0,0 +1,214 @@ +// Commit: 27e4302b7f45f22180693d26747f419177c81e27 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXT_P_H +#define QSGTEXT_P_H + +#include "qsgimplicitsizeitem_p.h" + +#include <private/qdeclarativeglobal_p.h> + +#include <QtGui/qtextoption.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class QSGTextPrivate; +class Q_DECLARATIVE_PRIVATE_EXPORT QSGText : public QSGImplicitSizeItem +{ + Q_OBJECT + Q_ENUMS(HAlignment) + Q_ENUMS(VAlignment) + Q_ENUMS(TextStyle) + Q_ENUMS(TextFormat) + Q_ENUMS(TextElideMode) + Q_ENUMS(WrapMode) + Q_ENUMS(LineHeightMode) + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(TextStyle style READ style WRITE setStyle NOTIFY styleChanged) + Q_PROPERTY(QColor styleColor READ styleColor WRITE setStyleColor NOTIFY styleColorChanged) + Q_PROPERTY(HAlignment horizontalAlignment READ hAlign WRITE setHAlign RESET resetHAlign NOTIFY horizontalAlignmentChanged) + Q_PROPERTY(HAlignment effectiveHorizontalAlignment READ effectiveHAlign NOTIFY effectiveHorizontalAlignmentChanged) + Q_PROPERTY(VAlignment verticalAlignment READ vAlign WRITE setVAlign NOTIFY verticalAlignmentChanged) + Q_PROPERTY(WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged) + Q_PROPERTY(int lineCount READ lineCount NOTIFY lineCountChanged) + Q_PROPERTY(bool truncated READ truncated NOTIFY truncatedChanged) + Q_PROPERTY(int maximumLineCount READ maximumLineCount WRITE setMaximumLineCount NOTIFY maximumLineCountChanged RESET resetMaximumLineCount) + + Q_PROPERTY(TextFormat textFormat READ textFormat WRITE setTextFormat NOTIFY textFormatChanged) + Q_PROPERTY(TextElideMode elide READ elideMode WRITE setElideMode NOTIFY elideModeChanged) //### elideMode? + Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) + Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) + Q_PROPERTY(qreal lineHeight READ lineHeight WRITE setLineHeight NOTIFY lineHeightChanged) + Q_PROPERTY(LineHeightMode lineHeightMode READ lineHeightMode WRITE setLineHeightMode NOTIFY lineHeightModeChanged) + +public: + QSGText(QSGItem *parent=0); + ~QSGText(); + + enum HAlignment { AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter, + AlignJustify = Qt::AlignJustify }; + enum VAlignment { AlignTop = Qt::AlignTop, + AlignBottom = Qt::AlignBottom, + AlignVCenter = Qt::AlignVCenter }; + enum TextStyle { Normal, + Outline, + Raised, + Sunken }; + enum TextFormat { PlainText = Qt::PlainText, + RichText = Qt::RichText, + AutoText = Qt::AutoText, + StyledText = 4 }; + enum TextElideMode { ElideLeft = Qt::ElideLeft, + ElideRight = Qt::ElideRight, + ElideMiddle = Qt::ElideMiddle, + ElideNone = Qt::ElideNone }; + + enum WrapMode { NoWrap = QTextOption::NoWrap, + WordWrap = QTextOption::WordWrap, + WrapAnywhere = QTextOption::WrapAnywhere, + WrapAtWordBoundaryOrAnywhere = QTextOption::WrapAtWordBoundaryOrAnywhere, // COMPAT + Wrap = QTextOption::WrapAtWordBoundaryOrAnywhere + }; + + enum LineHeightMode { ProportionalHeight, FixedHeight }; + + QString text() const; + void setText(const QString &); + + QFont font() const; + void setFont(const QFont &font); + + QColor color() const; + void setColor(const QColor &c); + + TextStyle style() const; + void setStyle(TextStyle style); + + QColor styleColor() const; + void setStyleColor(const QColor &c); + + HAlignment hAlign() const; + void setHAlign(HAlignment align); + void resetHAlign(); + HAlignment effectiveHAlign() const; + + VAlignment vAlign() const; + void setVAlign(VAlignment align); + + WrapMode wrapMode() const; + void setWrapMode(WrapMode w); + + int lineCount() const; + bool truncated() const; + + int maximumLineCount() const; + void setMaximumLineCount(int lines); + void resetMaximumLineCount(); + + TextFormat textFormat() const; + void setTextFormat(TextFormat format); + + TextElideMode elideMode() const; + void setElideMode(TextElideMode); + + qreal lineHeight() const; + void setLineHeight(qreal lineHeight); + + LineHeightMode lineHeightMode() const; + void setLineHeightMode(LineHeightMode); + + virtual void componentComplete(); + + int resourcesLoading() const; // mainly for testing + + qreal paintedWidth() const; + qreal paintedHeight() const; + + QRectF boundingRect() const; + +Q_SIGNALS: + void textChanged(const QString &text); + void linkActivated(const QString &link); + void fontChanged(const QFont &font); + void colorChanged(const QColor &color); + void styleChanged(TextStyle style); + void styleColorChanged(const QColor &color); + void horizontalAlignmentChanged(HAlignment alignment); + void verticalAlignmentChanged(VAlignment alignment); + void wrapModeChanged(); + void lineCountChanged(); + void truncatedChanged(); + void maximumLineCountChanged(); + void textFormatChanged(TextFormat textFormat); + void elideModeChanged(TextElideMode mode); + void paintedSizeChanged(); + void lineHeightChanged(qreal lineHeight); + void lineHeightModeChanged(LineHeightMode mode); + void effectiveHorizontalAlignmentChanged(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + virtual QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + +private: + Q_DISABLE_COPY(QSGText) + Q_DECLARE_PRIVATE(QSGText) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGText) + +QT_END_HEADER + +#endif // QSGTEXT_P_H diff --git a/src/declarative/items/qsgtext_p_p.h b/src/declarative/items/qsgtext_p_p.h new file mode 100644 index 0000000000..8d26394c3f --- /dev/null +++ b/src/declarative/items/qsgtext_p_p.h @@ -0,0 +1,155 @@ +// Commit: 6e5a642c9484536fc173714f560f739944368cf5 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXT_P_P_H +#define QSGTEXT_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgitem.h" +#include "qsgimplicitsizeitem_p_p.h" + +#include <QtDeclarative/qdeclarative.h> +#include <QtGui/qtextlayout.h> + +#include <private/qdeclarativetextlayout_p.h> + +QT_BEGIN_NAMESPACE + +class QTextLayout; +class QSGTextDocumentWithImageResources; +class QSGPlainTexture; + +class Q_AUTOTEST_EXPORT QSGTextPrivate : public QSGImplicitSizeItemPrivate +{ + Q_DECLARE_PUBLIC(QSGText) +public: + QSGTextPrivate(); + ~QSGTextPrivate(); + void init(); + + void updateSize(); + void updateLayout(); + bool determineHorizontalAlignment(); + bool setHAlign(QSGText::HAlignment, bool forceAlign = false); + void mirrorChange(); + QTextDocument *textDocument(); + + QString text; + QFont font; + QFont sourceFont; + QColor color; + QSGText::TextStyle style; + QColor styleColor; + QString activeLink; + QSGText::HAlignment hAlign; + QSGText::VAlignment vAlign; + QSGText::TextElideMode elideMode; + QSGText::TextFormat format; + QSGText::WrapMode wrapMode; + qreal lineHeight; + QSGText::LineHeightMode lineHeightMode; + int lineCount; + int maximumLineCount; + int maximumLineCountValid; + QPointF elidePos; + + static QString elideChar; + + void invalidateImageCache(); + void checkImageCache(); + QPixmap imageCache; + QSGTexture *texture; + + bool imageCacheDirty:1; + bool updateOnComponentComplete:1; + bool richText:1; + bool singleline:1; + bool cacheAllTextAsImage:1; + bool internalWidthUpdate:1; + bool requireImplicitWidth:1; + bool truncated:1; + bool hAlignImplicit:1; + bool rightToLeftText:1; + bool layoutTextElided:1; + + QRect layedOutTextRect; + QSize paintedSize; + qreal naturalWidth; + virtual qreal getImplicitWidth() const; + + void ensureDoc(); + QPixmap textDocumentImage(bool drawStyle); + QSGTextDocumentWithImageResources *doc; + + QRect setupTextLayout(); + QPixmap textLayoutImage(bool drawStyle); + void drawTextLayout(QPainter *p, const QPointF &pos, bool drawStyle); + QTextLayout layout; + + static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource); + static QPixmap drawOutline(const QPixmap &source, const QPixmap &styleSource, int yOffset); + + static inline QSGTextPrivate *get(QSGText *t) { + return t->d_func(); + } + + enum NodeType { + NodeIsNull, + NodeIsTexture, + NodeIsText, + }; + NodeType nodeType; +}; + +QT_END_NAMESPACE + +#endif // QSGTEXT_P_P_H diff --git a/src/declarative/items/qsgtextedit.cpp b/src/declarative/items/qsgtextedit.cpp new file mode 100644 index 0000000000..1c199ecc28 --- /dev/null +++ b/src/declarative/items/qsgtextedit.cpp @@ -0,0 +1,1232 @@ +// Commit: ec40dd2bb51868bca10dbd0c9116f3224ff2b29b +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtextedit_p.h" +#include "qsgtextedit_p_p.h" +#include "qsgevents_p_p.h" +#include "qsgcanvas.h" + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qapplication.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qpainter.h> +#include <QtGui/qtextobject.h> +#include <QtCore/qmath.h> + +#include <private/qdeclarativeglobal_p.h> +#include <private/qtextcontrol_p.h> +#include <private/qtextengine_p.h> +#include <private/qwidget_p.h> + +QT_BEGIN_NAMESPACE + +QWidgetPrivate *qt_widget_private(QWidget *widget); + +QSGTextEdit::QSGTextEdit(QSGItem *parent) +: QSGImplicitSizePaintedItem(*(new QSGTextEditPrivate), parent) +{ + Q_D(QSGTextEdit); + d->init(); +} + +QString QSGTextEdit::text() const +{ + Q_D(const QSGTextEdit); + +#ifndef QT_NO_TEXTHTMLPARSER + if (d->richText) + return d->document->toHtml(); + else +#endif + return d->document->toPlainText(); +} + +void QSGTextEdit::setText(const QString &text) +{ + Q_D(QSGTextEdit); + if (QSGTextEdit::text() == text) + return; + + d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text)); + if (d->richText) { +#ifndef QT_NO_TEXTHTMLPARSER + d->control->setHtml(text); +#else + d->control->setPlainText(text); +#endif + } else { + d->control->setPlainText(text); + } + q_textChanged(); +} + +QSGTextEdit::TextFormat QSGTextEdit::textFormat() const +{ + Q_D(const QSGTextEdit); + return d->format; +} + +void QSGTextEdit::setTextFormat(TextFormat format) +{ + Q_D(QSGTextEdit); + if (format == d->format) + return; + bool wasRich = d->richText; + d->richText = format == RichText || (format == AutoText && Qt::mightBeRichText(d->text)); + + if (wasRich && !d->richText) { + d->control->setPlainText(d->text); + updateSize(); + } else if (!wasRich && d->richText) { +#ifndef QT_NO_TEXTHTMLPARSER + d->control->setHtml(d->text); +#else + d->control->setPlainText(d->text); +#endif + updateSize(); + } + d->format = format; + d->control->setAcceptRichText(d->format != PlainText); + emit textFormatChanged(d->format); +} + +QFont QSGTextEdit::font() const +{ + Q_D(const QSGTextEdit); + return d->sourceFont; +} + +void QSGTextEdit::setFont(const QFont &font) +{ + Q_D(QSGTextEdit); + if (d->sourceFont == font) + return; + + d->sourceFont = font; + QFont oldFont = d->font; + d->font = font; + if (d->font.pointSizeF() != -1) { + // 0.5pt resolution + qreal size = qRound(d->font.pointSizeF()*2.0); + d->font.setPointSizeF(size/2.0); + } + + if (oldFont != d->font) { + d->document->setDefaultFont(d->font); + if(d->cursor){ + d->cursor->setHeight(QFontMetrics(d->font).height()); + moveCursorDelegate(); + } + updateSize(); + update(); + } + emit fontChanged(d->sourceFont); +} + +QColor QSGTextEdit::color() const +{ + Q_D(const QSGTextEdit); + return d->color; +} + +void QSGTextEdit::setColor(const QColor &color) +{ + Q_D(QSGTextEdit); + if (d->color == color) + return; + + d->color = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::Text, color); + d->control->setPalette(pal); + update(); + emit colorChanged(d->color); +} + +QColor QSGTextEdit::selectionColor() const +{ + Q_D(const QSGTextEdit); + return d->selectionColor; +} + +void QSGTextEdit::setSelectionColor(const QColor &color) +{ + Q_D(QSGTextEdit); + if (d->selectionColor == color) + return; + + d->selectionColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::Highlight, color); + d->control->setPalette(pal); + update(); + emit selectionColorChanged(d->selectionColor); +} + +QColor QSGTextEdit::selectedTextColor() const +{ + Q_D(const QSGTextEdit); + return d->selectedTextColor; +} + +void QSGTextEdit::setSelectedTextColor(const QColor &color) +{ + Q_D(QSGTextEdit); + if (d->selectedTextColor == color) + return; + + d->selectedTextColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::HighlightedText, color); + d->control->setPalette(pal); + update(); + emit selectedTextColorChanged(d->selectedTextColor); +} + +QSGTextEdit::HAlignment QSGTextEdit::hAlign() const +{ + Q_D(const QSGTextEdit); + return d->hAlign; +} + +void QSGTextEdit::setHAlign(HAlignment align) +{ + Q_D(QSGTextEdit); + bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; + d->hAlignImplicit = false; + if (d->setHAlign(align, forceAlign) && isComponentComplete()) { + d->updateDefaultTextOption(); + updateSize(); + } +} + +void QSGTextEdit::resetHAlign() +{ + Q_D(QSGTextEdit); + d->hAlignImplicit = true; + if (d->determineHorizontalAlignment() && isComponentComplete()) { + d->updateDefaultTextOption(); + updateSize(); + } +} + +QSGTextEdit::HAlignment QSGTextEdit::effectiveHAlign() const +{ + Q_D(const QSGTextEdit); + QSGTextEdit::HAlignment effectiveAlignment = d->hAlign; + if (!d->hAlignImplicit && d->effectiveLayoutMirror) { + switch (d->hAlign) { + case QSGTextEdit::AlignLeft: + effectiveAlignment = QSGTextEdit::AlignRight; + break; + case QSGTextEdit::AlignRight: + effectiveAlignment = QSGTextEdit::AlignLeft; + break; + default: + break; + } + } + return effectiveAlignment; +} + +bool QSGTextEditPrivate::setHAlign(QSGTextEdit::HAlignment alignment, bool forceAlign) +{ + Q_Q(QSGTextEdit); + if (hAlign != alignment || forceAlign) { + QSGTextEdit::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); + hAlign = alignment; + emit q->horizontalAlignmentChanged(alignment); + if (oldEffectiveHAlign != q->effectiveHAlign()) + emit q->effectiveHorizontalAlignmentChanged(); + return true; + } + return false; +} + +bool QSGTextEditPrivate::determineHorizontalAlignment() +{ + Q_Q(QSGTextEdit); + if (hAlignImplicit && q->isComponentComplete()) { + bool alignToRight = text.isEmpty() ? QApplication::keyboardInputDirection() == Qt::RightToLeft : rightToLeftText; + return setHAlign(alignToRight ? QSGTextEdit::AlignRight : QSGTextEdit::AlignLeft); + } + return false; +} + +void QSGTextEditPrivate::mirrorChange() +{ + Q_Q(QSGTextEdit); + if (q->isComponentComplete()) { + if (!hAlignImplicit && (hAlign == QSGTextEdit::AlignRight || hAlign == QSGTextEdit::AlignLeft)) { + updateDefaultTextOption(); + q->updateSize(); + emit q->effectiveHorizontalAlignmentChanged(); + } + } +} + +QSGTextEdit::VAlignment QSGTextEdit::vAlign() const +{ + Q_D(const QSGTextEdit); + return d->vAlign; +} + +void QSGTextEdit::setVAlign(QSGTextEdit::VAlignment alignment) +{ + Q_D(QSGTextEdit); + if (alignment == d->vAlign) + return; + d->vAlign = alignment; + d->updateDefaultTextOption(); + updateSize(); + moveCursorDelegate(); + emit verticalAlignmentChanged(d->vAlign); +} + +QSGTextEdit::WrapMode QSGTextEdit::wrapMode() const +{ + Q_D(const QSGTextEdit); + return d->wrapMode; +} + +void QSGTextEdit::setWrapMode(WrapMode mode) +{ + Q_D(QSGTextEdit); + if (mode == d->wrapMode) + return; + d->wrapMode = mode; + d->updateDefaultTextOption(); + updateSize(); + emit wrapModeChanged(); +} + +int QSGTextEdit::lineCount() const +{ + Q_D(const QSGTextEdit); + return d->lineCount; +} + +qreal QSGTextEdit::paintedWidth() const +{ + Q_D(const QSGTextEdit); + return d->paintedSize.width(); +} + +qreal QSGTextEdit::paintedHeight() const +{ + Q_D(const QSGTextEdit); + return d->paintedSize.height(); +} + +QRectF QSGTextEdit::positionToRectangle(int pos) const +{ + Q_D(const QSGTextEdit); + QTextCursor c(d->document); + c.setPosition(pos); + return d->control->cursorRect(c); + +} + +int QSGTextEdit::positionAt(int x, int y) const +{ + Q_D(const QSGTextEdit); + int r = d->document->documentLayout()->hitTest(QPoint(x,y-d->yoff), Qt::FuzzyHit); + QTextCursor cursor = d->control->textCursor(); + if (r > cursor.position()) { + // The cursor position includes positions within the preedit text, but only positions in the + // same text block are offset so it is possible to get a position that is either part of the + // preedit or the next text block. + QTextLayout *layout = cursor.block().layout(); + const int preeditLength = layout + ? layout->preeditAreaText().length() + : 0; + if (preeditLength > 0 + && d->document->documentLayout()->blockBoundingRect(cursor.block()).contains(x,y-d->yoff)) { + r = r > cursor.position() + preeditLength + ? r - preeditLength + : cursor.position(); + } + } + return r; +} + +void QSGTextEdit::moveCursorSelection(int pos) +{ + //Note that this is the same as setCursorPosition but with the KeepAnchor flag set + Q_D(QSGTextEdit); + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos) + return; + cursor.setPosition(pos, QTextCursor::KeepAnchor); + d->control->setTextCursor(cursor); +} + +void QSGTextEdit::moveCursorSelection(int pos, SelectionMode mode) +{ + Q_D(QSGTextEdit); + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos) + return; + if (mode == SelectCharacters) { + cursor.setPosition(pos, QTextCursor::KeepAnchor); + } else if (cursor.anchor() < pos || (cursor.anchor() == pos && cursor.position() < pos)) { + if (cursor.anchor() > cursor.position()) { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + if (cursor.position() == cursor.anchor()) + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor); + else + cursor.setPosition(cursor.position(), QTextCursor::MoveAnchor); + } else { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); + } + + cursor.setPosition(pos, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + if (cursor.position() != pos) + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + } else if (cursor.anchor() > pos || (cursor.anchor() == pos && cursor.position() > pos)) { + if (cursor.anchor() < cursor.position()) { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor); + } else { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + if (cursor.position() != cursor.anchor()) { + cursor.setPosition(cursor.anchor(), QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor); + } + } + + cursor.setPosition(pos, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + if (cursor.position() != pos) { + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + } + } + d->control->setTextCursor(cursor); +} + +bool QSGTextEdit::isCursorVisible() const +{ + Q_D(const QSGTextEdit); + return d->cursorVisible; +} + +void QSGTextEdit::setCursorVisible(bool on) +{ + Q_D(QSGTextEdit); + if (d->cursorVisible == on) + return; + d->cursorVisible = on; + QFocusEvent focusEvent(on ? QEvent::FocusIn : QEvent::FocusOut); + if (!on && !d->persistentSelection) + d->control->setCursorIsFocusIndicator(true); + d->control->processEvent(&focusEvent, QPointF(0, -d->yoff)); + emit cursorVisibleChanged(d->cursorVisible); +} + +int QSGTextEdit::cursorPosition() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().position(); +} + +void QSGTextEdit::setCursorPosition(int pos) +{ + Q_D(QSGTextEdit); + if (pos < 0 || pos > d->text.length()) + return; + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos && cursor.anchor() == pos) + return; + cursor.setPosition(pos); + d->control->setTextCursor(cursor); +} + +QDeclarativeComponent* QSGTextEdit::cursorDelegate() const +{ + Q_D(const QSGTextEdit); + return d->cursorComponent; +} + +void QSGTextEdit::setCursorDelegate(QDeclarativeComponent* c) +{ + Q_D(QSGTextEdit); + if(d->cursorComponent){ + if(d->cursor){ + d->control->setCursorWidth(-1); + update(cursorRectangle()); + delete d->cursor; + d->cursor = 0; + } + } + d->cursorComponent = c; + if(c && c->isReady()){ + loadCursorDelegate(); + }else{ + if(c) + connect(c, SIGNAL(statusChanged()), + this, SLOT(loadCursorDelegate())); + } + + emit cursorDelegateChanged(); +} + +void QSGTextEdit::loadCursorDelegate() +{ + Q_D(QSGTextEdit); + if(d->cursorComponent->isLoading()) + return; + d->cursor = qobject_cast<QSGItem*>(d->cursorComponent->create(qmlContext(this))); + if(d->cursor){ + d->control->setCursorWidth(0); + update(cursorRectangle()); + QDeclarative_setParent_noEvent(d->cursor, this); + d->cursor->setParentItem(this); + d->cursor->setHeight(QFontMetrics(d->font).height()); + moveCursorDelegate(); + }else{ + qmlInfo(this) << "Error loading cursor delegate."; + } +} + +int QSGTextEdit::selectionStart() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().selectionStart(); +} + +int QSGTextEdit::selectionEnd() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().selectionEnd(); +} + +QString QSGTextEdit::selectedText() const +{ + Q_D(const QSGTextEdit); + return d->control->textCursor().selectedText(); +} + +bool QSGTextEdit::focusOnPress() const +{ + Q_D(const QSGTextEdit); + return d->focusOnPress; +} + +void QSGTextEdit::setFocusOnPress(bool on) +{ + Q_D(QSGTextEdit); + if (d->focusOnPress == on) + return; + d->focusOnPress = on; + emit activeFocusOnPressChanged(d->focusOnPress); +} + +bool QSGTextEdit::persistentSelection() const +{ + Q_D(const QSGTextEdit); + return d->persistentSelection; +} + +void QSGTextEdit::setPersistentSelection(bool on) +{ + Q_D(QSGTextEdit); + if (d->persistentSelection == on) + return; + d->persistentSelection = on; + emit persistentSelectionChanged(d->persistentSelection); +} + +qreal QSGTextEdit::textMargin() const +{ + Q_D(const QSGTextEdit); + return d->textMargin; +} + +void QSGTextEdit::setTextMargin(qreal margin) +{ + Q_D(QSGTextEdit); + if (d->textMargin == margin) + return; + d->textMargin = margin; + d->document->setDocumentMargin(d->textMargin); + emit textMarginChanged(d->textMargin); +} + +void QSGTextEdit::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.width() != oldGeometry.width()) + updateSize(); + QSGPaintedItem::geometryChanged(newGeometry, oldGeometry); +} + +void QSGTextEdit::componentComplete() +{ + Q_D(QSGTextEdit); + QSGPaintedItem::componentComplete(); + if (d->dirty) { + d->determineHorizontalAlignment(); + d->updateDefaultTextOption(); + updateSize(); + d->dirty = false; + } +} + +bool QSGTextEdit::selectByMouse() const +{ + Q_D(const QSGTextEdit); + return d->selectByMouse; +} + +void QSGTextEdit::setSelectByMouse(bool on) +{ + Q_D(QSGTextEdit); + if (d->selectByMouse != on) { + d->selectByMouse = on; + setKeepMouseGrab(on); + if (on) + setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByMouse); + else + setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse); + emit selectByMouseChanged(on); + } +} + +QSGTextEdit::SelectionMode QSGTextEdit::mouseSelectionMode() const +{ + Q_D(const QSGTextEdit); + return d->mouseSelectionMode; +} + +void QSGTextEdit::setMouseSelectionMode(SelectionMode mode) +{ + Q_D(QSGTextEdit); + if (d->mouseSelectionMode != mode) { + d->mouseSelectionMode = mode; + d->control->setWordSelectionEnabled(mode == SelectWords); + emit mouseSelectionModeChanged(mode); + } +} + +void QSGTextEdit::setReadOnly(bool r) +{ + Q_D(QSGTextEdit); + if (r == isReadOnly()) + return; + + setFlag(QSGItem::ItemAcceptsInputMethod, !r); + Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse; + if (d->selectByMouse) + flags = flags | Qt::TextSelectableByMouse; + if (!r) + flags = flags | Qt::TextSelectableByKeyboard | Qt::TextEditable; + d->control->setTextInteractionFlags(flags); + if (!r) + d->control->moveCursor(QTextCursor::End); + + emit readOnlyChanged(r); +} + +bool QSGTextEdit::isReadOnly() const +{ + Q_D(const QSGTextEdit); + return !(d->control->textInteractionFlags() & Qt::TextEditable); +} + +void QSGTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QSGTextEdit); + d->control->setTextInteractionFlags(flags); +} + +Qt::TextInteractionFlags QSGTextEdit::textInteractionFlags() const +{ + Q_D(const QSGTextEdit); + return d->control->textInteractionFlags(); +} + +QRect QSGTextEdit::cursorRectangle() const +{ + Q_D(const QSGTextEdit); + return d->control->cursorRect().toRect().translated(0,d->yoff); +} + +bool QSGTextEdit::event(QEvent *event) +{ + Q_D(QSGTextEdit); + if (event->type() == QEvent::ShortcutOverride) { + d->control->processEvent(event, QPointF(0, -d->yoff)); + return event->isAccepted(); + } + return QSGPaintedItem::event(event); +} + +/*! +\overload +Handles the given key \a event. +*/ +void QSGTextEdit::keyPressEvent(QKeyEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::keyPressEvent(event); +} + +/*! +\overload +Handles the given key \a event. +*/ +void QSGTextEdit::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::keyReleaseEvent(event); +} + +void QSGTextEdit::deselect() +{ + Q_D(QSGTextEdit); + QTextCursor c = d->control->textCursor(); + c.clearSelection(); + d->control->setTextCursor(c); +} + +void QSGTextEdit::selectAll() +{ + Q_D(QSGTextEdit); + d->control->selectAll(); +} + +void QSGTextEdit::selectWord() +{ + Q_D(QSGTextEdit); + QTextCursor c = d->control->textCursor(); + c.select(QTextCursor::WordUnderCursor); + d->control->setTextCursor(c); +} + +void QSGTextEdit::select(int start, int end) +{ + Q_D(QSGTextEdit); + if (start < 0 || end < 0 || start > d->text.length() || end > d->text.length()) + return; + QTextCursor cursor = d->control->textCursor(); + cursor.beginEditBlock(); + cursor.setPosition(start, QTextCursor::MoveAnchor); + cursor.setPosition(end, QTextCursor::KeepAnchor); + cursor.endEditBlock(); + d->control->setTextCursor(cursor); + + // QTBUG-11100 + updateSelectionMarkers(); +} + +bool QSGTextEdit::isRightToLeft(int start, int end) +{ + Q_D(QSGTextEdit); + if (start > end) { + qmlInfo(this) << "isRightToLeft(start, end) called with the end property being smaller than the start."; + return false; + } else { + return d->text.mid(start, end - start).isRightToLeft(); + } +} + +#ifndef QT_NO_CLIPBOARD +void QSGTextEdit::cut() +{ + Q_D(QSGTextEdit); + d->control->cut(); +} + +void QSGTextEdit::copy() +{ + Q_D(QSGTextEdit); + d->control->copy(); +} + +void QSGTextEdit::paste() +{ + Q_D(QSGTextEdit); + d->control->paste(); +} +#endif // QT_NO_CLIPBOARD + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + if (d->focusOnPress){ + bool hadActiveFocus = hasActiveFocus(); + forceActiveFocus(); + if (d->showInputPanelOnFocus) { + if (hasActiveFocus() && hadActiveFocus && !isReadOnly()) { + // re-open input panel on press if already focused + openSoftwareInputPanel(); + } + } else { // show input panel on click + if (hasActiveFocus() && !hadActiveFocus) { + d->clickCausedFocus = true; + } + } + } + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::mousePressEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!d->showInputPanelOnFocus) { // input panel on click + if (d->focusOnPress && !isReadOnly() && boundingRect().contains(event->pos())) { + if (canvas() && canvas() == qApp->focusWidget()) { + qt_widget_private(canvas())->handleSoftwareInputPanel(event->button(), d->clickCausedFocus); + } + } + } + d->clickCausedFocus = false; + + if (!event->isAccepted()) + QSGPaintedItem::mouseReleaseEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::mouseDoubleClickEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QSGTextEdit::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextEdit); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (!event->isAccepted()) + QSGPaintedItem::mouseMoveEvent(event); +} + +/*! +\overload +Handles the given input method \a event. +*/ +void QSGTextEdit::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QSGTextEdit); + const bool wasComposing = isInputMethodComposing(); + d->control->processEvent(event, QPointF(0, -d->yoff)); + if (wasComposing != isInputMethodComposing()) + emit inputMethodComposingChanged(); +} + +void QSGTextEdit::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QSGTextEdit); + if (change == ItemActiveFocusHasChanged) { + setCursorVisible(value.boolValue && d->canvas && d->canvas->hasFocus()); + } + QSGItem::itemChange(change, value); +} + +/*! +\overload +Returns the value of the given \a property. +*/ +QVariant QSGTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QSGTextEdit); + 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) +{ + // XXX todo + QRect bounds(0, 0, width(), height()); + Q_D(QSGTextEdit); + + painter->setRenderHint(QPainter::TextAntialiasing, true); + painter->translate(0,d->yoff); + + d->control->drawContents(painter, bounds.translated(0,-d->yoff)); + + painter->translate(0,-d->yoff); +} + +void QSGTextEdit::updateImgCache(const QRectF &rf) +{ + 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); + } else { + r = r.translated(0,d->yoff); + } + } + update(r); +} + +bool QSGTextEdit::canPaste() const +{ + Q_D(const QSGTextEdit); + return d->canPaste; +} + +bool QSGTextEdit::isInputMethodComposing() const +{ + Q_D(const QSGTextEdit); + if (QTextLayout *layout = d->control->textCursor().block().layout()) + return layout->preeditAreaText().length() > 0; + return false; +} + +void QSGTextEditPrivate::init() +{ + Q_Q(QSGTextEdit); + + q->setSmooth(smooth); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QSGItem::ItemAcceptsInputMethod); + + control = new QTextControl(q); + control->setIgnoreUnusedNavigationEvents(true); + control->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::TextSelectableByKeyboard | Qt::TextEditable); + control->setDragEnabled(false); + + // QTextControl follows the default text color + // defined by the platform, declarative text + // should be black by default + QPalette pal = control->palette(); + if (pal.color(QPalette::Text) != color) { + pal.setColor(QPalette::Text, color); + control->setPalette(pal); + } + + QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateImgCache(QRectF))); + + QObject::connect(control, SIGNAL(textChanged()), q, SLOT(q_textChanged())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SLOT(updateSelectionMarkers())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(updateSelectionMarkers())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged())); + QObject::connect(control, SIGNAL(microFocusChanged()), q, SLOT(moveCursorDelegate())); + QObject::connect(control, SIGNAL(linkActivated(QString)), q, SIGNAL(linkActivated(QString))); +#ifndef QT_NO_CLIPBOARD + QObject::connect(q, SIGNAL(readOnlyChanged(bool)), q, SLOT(q_canPasteChanged())); + QObject::connect(QApplication::clipboard(), SIGNAL(dataChanged()), q, SLOT(q_canPasteChanged())); + canPaste = control->canPaste(); +#endif + + document = control->document(); + document->setDefaultFont(font); + document->setDocumentMargin(textMargin); + document->setUndoRedoEnabled(false); // flush undo buffer. + document->setUndoRedoEnabled(true); + updateDefaultTextOption(); +} + +void QSGTextEdit::q_textChanged() +{ + Q_D(QSGTextEdit); + d->text = text(); + d->rightToLeftText = d->document->begin().layout()->engine()->isRightToLeft(); + d->determineHorizontalAlignment(); + d->updateDefaultTextOption(); + updateSize(); + updateTotalLines(); + emit textChanged(d->text); +} + +void QSGTextEdit::moveCursorDelegate() +{ + Q_D(QSGTextEdit); + updateMicroFocus(); + emit cursorRectangleChanged(); + if(!d->cursor) + return; + QRectF cursorRect = cursorRectangle(); + d->cursor->setX(cursorRect.x()); + d->cursor->setY(cursorRect.y()); +} + +void QSGTextEditPrivate::updateSelection() +{ + Q_Q(QSGTextEdit); + QTextCursor cursor = control->textCursor(); + bool startChange = (lastSelectionStart != cursor.selectionStart()); + bool endChange = (lastSelectionEnd != cursor.selectionEnd()); + cursor.beginEditBlock(); + cursor.setPosition(lastSelectionStart, QTextCursor::MoveAnchor); + cursor.setPosition(lastSelectionEnd, QTextCursor::KeepAnchor); + cursor.endEditBlock(); + control->setTextCursor(cursor); + if(startChange) + q->selectionStartChanged(); + if(endChange) + q->selectionEndChanged(); +} + +void QSGTextEdit::updateSelectionMarkers() +{ + Q_D(QSGTextEdit); + if(d->lastSelectionStart != d->control->textCursor().selectionStart()){ + d->lastSelectionStart = d->control->textCursor().selectionStart(); + emit selectionStartChanged(); + } + if(d->lastSelectionEnd != d->control->textCursor().selectionEnd()){ + d->lastSelectionEnd = d->control->textCursor().selectionEnd(); + emit selectionEndChanged(); + } +} + +QRectF QSGTextEdit::boundingRect() const +{ + Q_D(const QSGTextEdit); + QRectF r = QSGPaintedItem::boundingRect(); + int cursorWidth = 1; + if(d->cursor) + cursorWidth = d->cursor->width(); + if(!d->document->isEmpty()) + cursorWidth += 3;// ### Need a better way of accounting for space between char and cursor + + // Could include font max left/right bearings to either side of rectangle. + + r.setRight(r.right() + cursorWidth); + return r.translated(0,d->yoff); +} + +qreal QSGTextEditPrivate::getImplicitWidth() const +{ + Q_Q(const QSGTextEdit); + if (!requireImplicitWidth) { + // We don't calculate implicitWidth unless it is required. + // We need to force a size update now to ensure implicitWidth is calculated + const_cast<QSGTextEditPrivate*>(this)->requireImplicitWidth = true; + const_cast<QSGTextEdit*>(q)->updateSize(); + } + return implicitWidth; +} + +//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't +// need to do all the calculations each time +void QSGTextEdit::updateSize() +{ + Q_D(QSGTextEdit); + if (isComponentComplete()) { + qreal naturalWidth = d->implicitWidth; + // ### assumes that if the width is set, the text will fill to edges + // ### (unless wrap is false, then clipping will occur) + if (widthValid()) { + if (!d->requireImplicitWidth) { + emit implicitWidthChanged(); + // if the implicitWidth is used, then updateSize() has already been called (recursively) + if (d->requireImplicitWidth) + return; + } + if (d->requireImplicitWidth) { + d->document->setTextWidth(-1); + naturalWidth = d->document->idealWidth(); + } + if (d->document->textWidth() != width()) + d->document->setTextWidth(width()); + } else { + d->document->setTextWidth(-1); + } + QFontMetrics fm = QFontMetrics(d->font); + int dy = height(); + dy -= (int)d->document->size().height(); + + int nyoff; + if (heightValid()) { + if (d->vAlign == AlignBottom) + nyoff = dy; + else if (d->vAlign == AlignVCenter) + nyoff = dy/2; + else + nyoff = 0; + } else { + nyoff = 0; + } + if (nyoff != d->yoff) + d->yoff = nyoff; + setBaselineOffset(fm.ascent() + d->yoff + d->textMargin); + + //### need to comfirm cost of always setting these + int newWidth = qCeil(d->document->idealWidth()); + if (!widthValid() && d->document->textWidth() != newWidth) + d->document->setTextWidth(newWidth); // ### Text does not align if width is not set (QTextDoc bug) + // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed. + if (!widthValid()) + setImplicitWidth(newWidth); + else if (d->requireImplicitWidth) + setImplicitWidth(naturalWidth); + qreal newHeight = d->document->isEmpty() ? fm.height() : (int)d->document->size().height(); + setImplicitHeight(newHeight); + + d->paintedSize = QSize(newWidth, newHeight); + setContentsSize(d->paintedSize); + emit paintedSizeChanged(); + } else { + d->dirty = true; + } + update(); +} + +void QSGTextEdit::updateTotalLines() +{ + Q_D(QSGTextEdit); + + int subLines = 0; + + for (QTextBlock it = d->document->begin(); it != d->document->end(); it = it.next()) { + QTextLayout *layout = it.layout(); + if (!layout) + continue; + subLines += layout->lineCount()-1; + } + + int newTotalLines = d->document->lineCount() + subLines; + if (d->lineCount != newTotalLines) { + d->lineCount = newTotalLines; + emit lineCountChanged(); + } +} + +void QSGTextEditPrivate::updateDefaultTextOption() +{ + Q_Q(QSGTextEdit); + QTextOption opt = document->defaultTextOption(); + int oldAlignment = opt.alignment(); + + QSGTextEdit::HAlignment horizontalAlignment = q->effectiveHAlign(); + if (rightToLeftText) { + if (horizontalAlignment == QSGTextEdit::AlignLeft) + horizontalAlignment = QSGTextEdit::AlignRight; + else if (horizontalAlignment == QSGTextEdit::AlignRight) + horizontalAlignment = QSGTextEdit::AlignLeft; + } + opt.setAlignment((Qt::Alignment)(int)(horizontalAlignment | vAlign)); + + QTextOption::WrapMode oldWrapMode = opt.wrapMode(); + opt.setWrapMode(QTextOption::WrapMode(wrapMode)); + + if (oldWrapMode == opt.wrapMode() && oldAlignment == opt.alignment()) + return; + document->setDefaultTextOption(opt); +} + + +void QSGTextEdit::openSoftwareInputPanel() +{ + if (qApp) { + if (canvas() && canvas() == qApp->focusWidget()) { + QEvent event(QEvent::RequestSoftwareInputPanel); + QApplication::sendEvent(canvas(), &event); + } + } +} + +void QSGTextEdit::closeSoftwareInputPanel() +{ + if (qApp) { + if (canvas() && canvas() == qApp->focusWidget()) { + QEvent event(QEvent::CloseSoftwareInputPanel); + QApplication::sendEvent(canvas(), &event); + } + } +} + +void QSGTextEdit::focusInEvent(QFocusEvent *event) +{ + Q_D(const QSGTextEdit); + if (d->showInputPanelOnFocus) { + if (d->focusOnPress && !isReadOnly()) { + openSoftwareInputPanel(); + } + } + QSGPaintedItem::focusInEvent(event); +} + +void QSGTextEdit::q_canPasteChanged() +{ + Q_D(QSGTextEdit); + bool old = d->canPaste; + d->canPaste = d->control->canPaste(); + if(old!=d->canPaste) + emit canPasteChanged(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgtextedit_p.h b/src/declarative/items/qsgtextedit_p.h new file mode 100644 index 0000000000..547ead18e9 --- /dev/null +++ b/src/declarative/items/qsgtextedit_p.h @@ -0,0 +1,303 @@ +// Commit: 27e4302b7f45f22180693d26747f419177c81e27 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXTEDIT_P_H +#define QSGTEXTEDIT_P_H + +#include "qsgimplicitsizeitem_p.h" + +#include <QtGui/qtextoption.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGTextEditPrivate; +class Q_AUTOTEST_EXPORT QSGTextEdit : public QSGImplicitSizePaintedItem +{ + Q_OBJECT + Q_ENUMS(VAlignment) + Q_ENUMS(HAlignment) + Q_ENUMS(TextFormat) + Q_ENUMS(WrapMode) + Q_ENUMS(SelectionMode) + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor NOTIFY selectionColorChanged) + Q_PROPERTY(QColor selectedTextColor READ selectedTextColor WRITE setSelectedTextColor NOTIFY selectedTextColorChanged) + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) + Q_PROPERTY(HAlignment horizontalAlignment READ hAlign WRITE setHAlign RESET resetHAlign NOTIFY horizontalAlignmentChanged) + Q_PROPERTY(HAlignment effectiveHorizontalAlignment READ effectiveHAlign NOTIFY effectiveHorizontalAlignmentChanged) + Q_PROPERTY(VAlignment verticalAlignment READ vAlign WRITE setVAlign NOTIFY verticalAlignmentChanged) + Q_PROPERTY(WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged) + Q_PROPERTY(int lineCount READ lineCount NOTIFY lineCountChanged) + Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) + Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) + Q_PROPERTY(TextFormat textFormat READ textFormat WRITE setTextFormat NOTIFY textFormatChanged) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged) + Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible NOTIFY cursorVisibleChanged) + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) + Q_PROPERTY(QRect cursorRectangle READ cursorRectangle NOTIFY cursorRectangleChanged) + Q_PROPERTY(QDeclarativeComponent* cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged) + Q_PROPERTY(int selectionStart READ selectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd NOTIFY selectionEndChanged) + Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectionChanged) + Q_PROPERTY(bool activeFocusOnPress READ focusOnPress WRITE setFocusOnPress NOTIFY activeFocusOnPressChanged) + Q_PROPERTY(bool persistentSelection READ persistentSelection WRITE setPersistentSelection NOTIFY persistentSelectionChanged) + Q_PROPERTY(qreal textMargin READ textMargin WRITE setTextMargin NOTIFY textMarginChanged) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints) + Q_PROPERTY(bool selectByMouse READ selectByMouse WRITE setSelectByMouse NOTIFY selectByMouseChanged) + Q_PROPERTY(SelectionMode mouseSelectionMode READ mouseSelectionMode WRITE setMouseSelectionMode NOTIFY mouseSelectionModeChanged) + Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged) + Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged) + +public: + QSGTextEdit(QSGItem *parent=0); + + enum HAlignment { + AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter, + AlignJustify = Qt::AlignJustify + }; + + enum VAlignment { + AlignTop = Qt::AlignTop, + AlignBottom = Qt::AlignBottom, + AlignVCenter = Qt::AlignVCenter + }; + + enum TextFormat { + PlainText = Qt::PlainText, + RichText = Qt::RichText, + AutoText = Qt::AutoText + }; + + enum WrapMode { NoWrap = QTextOption::NoWrap, + WordWrap = QTextOption::WordWrap, + WrapAnywhere = QTextOption::WrapAnywhere, + WrapAtWordBoundaryOrAnywhere = QTextOption::WrapAtWordBoundaryOrAnywhere, // COMPAT + Wrap = QTextOption::WrapAtWordBoundaryOrAnywhere + }; + + enum SelectionMode { + SelectCharacters, + SelectWords + }; + + Q_INVOKABLE void openSoftwareInputPanel(); + Q_INVOKABLE void closeSoftwareInputPanel(); + + QString text() const; + void setText(const QString &); + + TextFormat textFormat() const; + void setTextFormat(TextFormat format); + + QFont font() const; + void setFont(const QFont &font); + + QColor color() const; + void setColor(const QColor &c); + + QColor selectionColor() const; + void setSelectionColor(const QColor &c); + + QColor selectedTextColor() const; + void setSelectedTextColor(const QColor &c); + + HAlignment hAlign() const; + void setHAlign(HAlignment align); + void resetHAlign(); + HAlignment effectiveHAlign() const; + + VAlignment vAlign() const; + void setVAlign(VAlignment align); + + WrapMode wrapMode() const; + void setWrapMode(WrapMode w); + + int lineCount() const; + + bool isCursorVisible() const; + void setCursorVisible(bool on); + + int cursorPosition() const; + void setCursorPosition(int pos); + + QDeclarativeComponent* cursorDelegate() const; + void setCursorDelegate(QDeclarativeComponent*); + + int selectionStart() const; + int selectionEnd() const; + + QString selectedText() const; + + bool focusOnPress() const; + void setFocusOnPress(bool on); + + bool persistentSelection() const; + void setPersistentSelection(bool on); + + qreal textMargin() const; + void setTextMargin(qreal margin); + + bool selectByMouse() const; + void setSelectByMouse(bool); + + SelectionMode mouseSelectionMode() const; + void setMouseSelectionMode(SelectionMode mode); + + bool canPaste() const; + + virtual void componentComplete(); + + /* FROM EDIT */ + void setReadOnly(bool); + bool isReadOnly() const; + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + QRect cursorRectangle() const; + + QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + + qreal paintedWidth() const; + qreal paintedHeight() const; + + Q_INVOKABLE QRectF positionToRectangle(int) const; + Q_INVOKABLE int positionAt(int x, int y) const; + Q_INVOKABLE void moveCursorSelection(int pos); + Q_INVOKABLE void moveCursorSelection(int pos, SelectionMode mode); + + QRectF boundingRect() const; + + bool isInputMethodComposing() const; + +Q_SIGNALS: + void textChanged(const QString &); + void paintedSizeChanged(); + void cursorPositionChanged(); + void cursorRectangleChanged(); + void selectionStartChanged(); + void selectionEndChanged(); + void selectionChanged(); + void colorChanged(const QColor &color); + void selectionColorChanged(const QColor &color); + void selectedTextColorChanged(const QColor &color); + void fontChanged(const QFont &font); + void horizontalAlignmentChanged(HAlignment alignment); + void verticalAlignmentChanged(VAlignment alignment); + void wrapModeChanged(); + void lineCountChanged(); + void textFormatChanged(TextFormat textFormat); + void readOnlyChanged(bool isReadOnly); + void cursorVisibleChanged(bool isCursorVisible); + void cursorDelegateChanged(); + void activeFocusOnPressChanged(bool activeFocusOnPressed); + void persistentSelectionChanged(bool isPersistentSelection); + void textMarginChanged(qreal textMargin); + void selectByMouseChanged(bool selectByMouse); + void mouseSelectionModeChanged(SelectionMode mode); + void linkActivated(const QString &link); + void canPasteChanged(); + void inputMethodComposingChanged(); + void effectiveHorizontalAlignmentChanged(); + +public Q_SLOTS: + void selectAll(); + void selectWord(); + void select(int start, int end); + void deselect(); + bool isRightToLeft(int start, int end); +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy(); + void paste(); +#endif + +private Q_SLOTS: + void updateImgCache(const QRectF &rect); + void q_textChanged(); + void updateSelectionMarkers(); + void moveCursorDelegate(); + void loadCursorDelegate(); + void q_canPasteChanged(); + +private: + void updateSize(); + void updateTotalLines(); + +protected: + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + + bool event(QEvent *); + void keyPressEvent(QKeyEvent *); + void keyReleaseEvent(QKeyEvent *); + void focusInEvent(QFocusEvent *event); + + // mouse filter? + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void inputMethodEvent(QInputMethodEvent *e); + virtual void itemChange(ItemChange, const ItemChangeData &); + + void paint(QPainter *); +private: + Q_DISABLE_COPY(QSGTextEdit) + Q_DECLARE_PRIVATE(QSGTextEdit) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGTextEdit) + +QT_END_HEADER + +#endif // QSGTEXTEDIT_P_H diff --git a/src/declarative/items/qsgtextedit_p_p.h b/src/declarative/items/qsgtextedit_p_p.h new file mode 100644 index 0000000000..63f4bbc341 --- /dev/null +++ b/src/declarative/items/qsgtextedit_p_p.h @@ -0,0 +1,143 @@ +// Commit: 27e4302b7f45f22180693d26747f419177c81e27 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXTEDIT_P_P_H +#define QSGTEXTEDIT_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qsgtextedit_p.h" +#include "qsgimplicitsizeitem_p_p.h" + +#include <QtDeclarative/qdeclarative.h> + +QT_BEGIN_NAMESPACE +class QTextLayout; +class QTextDocument; +class QTextControl; +class QSGTextEditPrivate : public QSGImplicitSizePaintedItemPrivate +{ + Q_DECLARE_PUBLIC(QSGTextEdit) + +public: + QSGTextEditPrivate() + : color("black"), hAlign(QSGTextEdit::AlignLeft), vAlign(QSGTextEdit::AlignTop), + imgDirty(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), + textMargin(0.0), lastSelectionStart(0), lastSelectionEnd(0), cursorComponent(0), cursor(0), + format(QSGTextEdit::AutoText), document(0), wrapMode(QSGTextEdit::NoWrap), + mouseSelectionMode(QSGTextEdit::SelectCharacters), + yoff(0) + { +#ifdef Q_OS_SYMBIAN + if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) { + showInputPanelOnFocus = false; + } +#endif + } + + void init(); + + void updateDefaultTextOption(); + void relayoutDocument(); + void updateSelection(); + bool determineHorizontalAlignment(); + bool setHAlign(QSGTextEdit::HAlignment, bool forceAlign = false); + void mirrorChange(); + qreal getImplicitWidth() const; + + QString text; + QFont font; + QFont sourceFont; + QColor color; + QColor selectionColor; + QColor selectedTextColor; + QString style; + QColor styleColor; + QPixmap imgCache; + QPixmap imgStyleCache; + QSGTextEdit::HAlignment hAlign; + QSGTextEdit::VAlignment vAlign; + + bool imgDirty : 1; + bool dirty : 1; + bool richText : 1; + bool cursorVisible : 1; + bool focusOnPress : 1; + bool showInputPanelOnFocus : 1; + bool clickCausedFocus : 1; + bool persistentSelection : 1; + bool requireImplicitWidth:1; + bool selectByMouse:1; + bool canPaste:1; + bool hAlignImplicit:1; + bool rightToLeftText:1; + + qreal textMargin; + int lastSelectionStart; + int lastSelectionEnd; + QDeclarativeComponent* cursorComponent; + QSGItem* cursor; + QSGTextEdit::TextFormat format; + QTextDocument *document; + QTextControl *control; + QSGTextEdit::WrapMode wrapMode; + QSGTextEdit::SelectionMode mouseSelectionMode; + int lineCount; + int yoff; + QSize paintedSize; +}; + +QT_END_NAMESPACE + +#endif // QSGTEXTEDIT_P_P_H diff --git a/src/declarative/items/qsgtextinput.cpp b/src/declarative/items/qsgtextinput.cpp new file mode 100644 index 0000000000..4eab28bbcf --- /dev/null +++ b/src/declarative/items/qsgtextinput.cpp @@ -0,0 +1,1294 @@ +// Commit: 47712d1f330e4b22ce6dd30e7557288ef7f7fca0 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtextinput_p.h" +#include "qsgtextinput_p_p.h" +#include "qsgcanvas.h" + +#include <private/qdeclarativeglobal_p.h> +#include <private/qwidget_p.h> + +#include <QtDeclarative/qdeclarativeinfo.h> +#include <QtGui/qgraphicssceneevent.h> +#include <QtGui/qinputcontext.h> +#include <QTextBoundaryFinder> +#include <qstyle.h> + +QT_BEGIN_NAMESPACE + +QWidgetPrivate *qt_widget_private(QWidget *widget); + +QSGTextInput::QSGTextInput(QSGItem* parent) +: QSGImplicitSizePaintedItem(*(new QSGTextInputPrivate), parent) +{ + Q_D(QSGTextInput); + d->init(); +} + +QSGTextInput::~QSGTextInput() +{ +} + +QString QSGTextInput::text() const +{ + Q_D(const QSGTextInput); + return d->control->text(); +} + +void QSGTextInput::setText(const QString &s) +{ + Q_D(QSGTextInput); + if(s == text()) + return; + d->control->setText(s); +} + +QFont QSGTextInput::font() const +{ + Q_D(const QSGTextInput); + return d->sourceFont; +} + +void QSGTextInput::setFont(const QFont &font) +{ + Q_D(QSGTextInput); + if (d->sourceFont == font) + return; + + d->sourceFont = font; + QFont oldFont = d->font; + d->font = font; + if (d->font.pointSizeF() != -1) { + // 0.5pt resolution + qreal size = qRound(d->font.pointSizeF()*2.0); + d->font.setPointSizeF(size/2.0); + } + if (oldFont != d->font) { + d->control->setFont(d->font); + if(d->cursorItem){ + d->cursorItem->setHeight(QFontMetrics(d->font).height()); + moveCursor(); + } + updateSize(); + } + emit fontChanged(d->sourceFont); +} + +QColor QSGTextInput::color() const +{ + Q_D(const QSGTextInput); + return d->color; +} + +void QSGTextInput::setColor(const QColor &c) +{ + Q_D(QSGTextInput); + if (c != d->color) { + d->color = c; + update(); + emit colorChanged(c); + } +} + +QColor QSGTextInput::selectionColor() const +{ + Q_D(const QSGTextInput); + return d->selectionColor; +} + +void QSGTextInput::setSelectionColor(const QColor &color) +{ + Q_D(QSGTextInput); + if (d->selectionColor == color) + return; + + d->selectionColor = color; + QPalette p = d->control->palette(); + p.setColor(QPalette::Highlight, d->selectionColor); + d->control->setPalette(p); + if (d->control->hasSelectedText()) + update(); + emit selectionColorChanged(color); +} + +QColor QSGTextInput::selectedTextColor() const +{ + Q_D(const QSGTextInput); + return d->selectedTextColor; +} + +void QSGTextInput::setSelectedTextColor(const QColor &color) +{ + Q_D(QSGTextInput); + if (d->selectedTextColor == color) + return; + + d->selectedTextColor = color; + QPalette p = d->control->palette(); + p.setColor(QPalette::HighlightedText, d->selectedTextColor); + d->control->setPalette(p); + if (d->control->hasSelectedText()) + update(); + emit selectedTextColorChanged(color); +} + +QSGTextInput::HAlignment QSGTextInput::hAlign() const +{ + Q_D(const QSGTextInput); + return d->hAlign; +} + +void QSGTextInput::setHAlign(HAlignment align) +{ + Q_D(QSGTextInput); + bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror; + d->hAlignImplicit = false; + if (d->setHAlign(align, forceAlign) && isComponentComplete()) { + updateRect(); + d->updateHorizontalScroll(); + } +} + +void QSGTextInput::resetHAlign() +{ + Q_D(QSGTextInput); + d->hAlignImplicit = true; + if (d->determineHorizontalAlignment() && isComponentComplete()) { + updateRect(); + d->updateHorizontalScroll(); + } +} + +QSGTextInput::HAlignment QSGTextInput::effectiveHAlign() const +{ + Q_D(const QSGTextInput); + QSGTextInput::HAlignment effectiveAlignment = d->hAlign; + if (!d->hAlignImplicit && d->effectiveLayoutMirror) { + switch (d->hAlign) { + case QSGTextInput::AlignLeft: + effectiveAlignment = QSGTextInput::AlignRight; + break; + case QSGTextInput::AlignRight: + effectiveAlignment = QSGTextInput::AlignLeft; + break; + default: + break; + } + } + return effectiveAlignment; +} + +bool QSGTextInputPrivate::setHAlign(QSGTextInput::HAlignment alignment, bool forceAlign) +{ + Q_Q(QSGTextInput); + if ((hAlign != alignment || forceAlign) && alignment <= QSGTextInput::AlignHCenter) { // justify not supported + QSGTextInput::HAlignment oldEffectiveHAlign = q->effectiveHAlign(); + hAlign = alignment; + emit q->horizontalAlignmentChanged(alignment); + if (oldEffectiveHAlign != q->effectiveHAlign()) + emit q->effectiveHorizontalAlignmentChanged(); + return true; + } + return false; +} + +bool QSGTextInputPrivate::determineHorizontalAlignment() +{ + if (hAlignImplicit) { + // if no explicit alignment has been set, follow the natural layout direction of the text + QString text = control->text(); + bool isRightToLeft = text.isEmpty() ? QApplication::keyboardInputDirection() == Qt::RightToLeft : text.isRightToLeft(); + return setHAlign(isRightToLeft ? QSGTextInput::AlignRight : QSGTextInput::AlignLeft); + } + return false; +} + +void QSGTextInputPrivate::mirrorChange() +{ + Q_Q(QSGTextInput); + if (q->isComponentComplete()) { + if (!hAlignImplicit && (hAlign == QSGTextInput::AlignRight || hAlign == QSGTextInput::AlignLeft)) { + q->updateRect(); + updateHorizontalScroll(); + emit q->effectiveHorizontalAlignmentChanged(); + } + } +} + +bool QSGTextInput::isReadOnly() const +{ + Q_D(const QSGTextInput); + return d->control->isReadOnly(); +} + +void QSGTextInput::setReadOnly(bool ro) +{ + Q_D(QSGTextInput); + if (d->control->isReadOnly() == ro) + return; + + setFlag(QSGItem::ItemAcceptsInputMethod, !ro); + d->control->setReadOnly(ro); + + emit readOnlyChanged(ro); +} + +int QSGTextInput::maxLength() const +{ + Q_D(const QSGTextInput); + return d->control->maxLength(); +} + +void QSGTextInput::setMaxLength(int ml) +{ + Q_D(QSGTextInput); + if (d->control->maxLength() == ml) + return; + + d->control->setMaxLength(ml); + + emit maximumLengthChanged(ml); +} + +bool QSGTextInput::isCursorVisible() const +{ + Q_D(const QSGTextInput); + return d->cursorVisible; +} + +void QSGTextInput::setCursorVisible(bool on) +{ + Q_D(QSGTextInput); + if (d->cursorVisible == on) + return; + d->cursorVisible = on; + d->control->setCursorBlinkPeriod(on?QApplication::cursorFlashTime():0); + QRect r = d->control->cursorRect(); + if (d->control->inputMask().isEmpty()) + updateRect(r); + else + updateRect(); + emit cursorVisibleChanged(d->cursorVisible); +} + +int QSGTextInput::cursorPosition() const +{ + Q_D(const QSGTextInput); + return d->control->cursor(); +} +void QSGTextInput::setCursorPosition(int cp) +{ + Q_D(QSGTextInput); + if (cp < 0 || cp > d->control->text().length()) + return; + d->control->moveCursor(cp); +} + +QRect QSGTextInput::cursorRectangle() const +{ + Q_D(const QSGTextInput); + QRect r = d->control->cursorRect(); + // Scroll and make consistent with TextEdit + // QLineControl inexplicably adds 1 to the height and horizontal padding + // for unicode direction markers. + r.adjust(5 - d->hscroll, 0, -4 - d->hscroll, -1); + return r; +} + +int QSGTextInput::selectionStart() const +{ + Q_D(const QSGTextInput); + return d->lastSelectionStart; +} + +int QSGTextInput::selectionEnd() const +{ + Q_D(const QSGTextInput); + return d->lastSelectionEnd; +} + +void QSGTextInput::select(int start, int end) +{ + Q_D(QSGTextInput); + if (start < 0 || end < 0 || start > d->control->text().length() || end > d->control->text().length()) + return; + d->control->setSelection(start, end-start); +} + +QString QSGTextInput::selectedText() const +{ + Q_D(const QSGTextInput); + return d->control->selectedText(); +} + +bool QSGTextInput::focusOnPress() const +{ + Q_D(const QSGTextInput); + return d->focusOnPress; +} + +void QSGTextInput::setFocusOnPress(bool b) +{ + Q_D(QSGTextInput); + if (d->focusOnPress == b) + return; + + d->focusOnPress = b; + + emit activeFocusOnPressChanged(d->focusOnPress); +} + +bool QSGTextInput::autoScroll() const +{ + Q_D(const QSGTextInput); + return d->autoScroll; +} + +void QSGTextInput::setAutoScroll(bool b) +{ + Q_D(QSGTextInput); + if (d->autoScroll == b) + return; + + d->autoScroll = b; + //We need to repaint so that the scrolling is taking into account. + updateSize(true); + d->updateHorizontalScroll(); + emit autoScrollChanged(d->autoScroll); +} + +#ifndef QT_NO_VALIDATOR +QValidator* QSGTextInput::validator() const +{ + Q_D(const QSGTextInput); + //###const cast isn't good, but needed for property system? + return const_cast<QValidator*>(d->control->validator()); +} + +void QSGTextInput::setValidator(QValidator* v) +{ + Q_D(QSGTextInput); + if (d->control->validator() == v) + return; + + d->control->setValidator(v); + if(!d->control->hasAcceptableInput()){ + d->oldValidity = false; + emit acceptableInputChanged(); + } + + emit validatorChanged(); +} +#endif // QT_NO_VALIDATOR + +QString QSGTextInput::inputMask() const +{ + Q_D(const QSGTextInput); + return d->control->inputMask(); +} + +void QSGTextInput::setInputMask(const QString &im) +{ + Q_D(QSGTextInput); + if (d->control->inputMask() == im) + return; + + d->control->setInputMask(im); + emit inputMaskChanged(d->control->inputMask()); +} + +bool QSGTextInput::hasAcceptableInput() const +{ + Q_D(const QSGTextInput); + return d->control->hasAcceptableInput(); +} + +void QSGTextInputPrivate::updateInputMethodHints() +{ + Q_Q(QSGTextInput); + Qt::InputMethodHints hints = inputMethodHints; + uint echo = control->echoMode(); + if (echo == QSGTextInput::Password || echo == QSGTextInput::NoEcho) + hints |= Qt::ImhHiddenText; + else if (echo == QSGTextInput::PasswordEchoOnEdit) + hints &= ~Qt::ImhHiddenText; + if (echo != QSGTextInput::Normal) + hints |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText); + q->setInputMethodHints(hints); +} + +QSGTextInput::EchoMode QSGTextInput::echoMode() const +{ + Q_D(const QSGTextInput); + return (QSGTextInput::EchoMode)d->control->echoMode(); +} + +void QSGTextInput::setEchoMode(QSGTextInput::EchoMode echo) +{ + Q_D(QSGTextInput); + if (echoMode() == echo) + return; + d->control->setEchoMode((uint)echo); + d->updateInputMethodHints(); + q_textChanged(); + emit echoModeChanged(echoMode()); +} + +Qt::InputMethodHints QSGTextInput::imHints() const +{ + Q_D(const QSGTextInput); + return d->inputMethodHints; +} + +void QSGTextInput::setIMHints(Qt::InputMethodHints hints) +{ + Q_D(QSGTextInput); + if (d->inputMethodHints == hints) + return; + d->inputMethodHints = hints; + d->updateInputMethodHints(); +} + +QDeclarativeComponent* QSGTextInput::cursorDelegate() const +{ + Q_D(const QSGTextInput); + return d->cursorComponent; +} + +void QSGTextInput::setCursorDelegate(QDeclarativeComponent* c) +{ + Q_D(QSGTextInput); + if (d->cursorComponent == c) + return; + + d->cursorComponent = c; + if(!c){ + //note that the components are owned by something else + disconnect(d->control, SIGNAL(cursorPositionChanged(int,int)), + this, SLOT(moveCursor())); + disconnect(d->control, SIGNAL(updateMicroFocus()), + this, SLOT(moveCursor())); + delete d->cursorItem; + }else{ + d->startCreatingCursor(); + } + + emit cursorDelegateChanged(); +} + +void QSGTextInputPrivate::startCreatingCursor() +{ + Q_Q(QSGTextInput); + q->connect(control, SIGNAL(cursorPositionChanged(int,int)), + q, SLOT(moveCursor()), Qt::UniqueConnection); + q->connect(control, SIGNAL(updateMicroFocus()), + q, SLOT(moveCursor()), Qt::UniqueConnection); + if(cursorComponent->isReady()){ + q->createCursor(); + }else if(cursorComponent->isLoading()){ + q->connect(cursorComponent, SIGNAL(statusChanged(int)), + q, SLOT(createCursor())); + }else {//isError + qmlInfo(q, cursorComponent->errors()) << QSGTextInput::tr("Could not load cursor delegate"); + } +} + +void QSGTextInput::createCursor() +{ + Q_D(QSGTextInput); + if(d->cursorComponent->isError()){ + qmlInfo(this, d->cursorComponent->errors()) << tr("Could not load cursor delegate"); + return; + } + + if(!d->cursorComponent->isReady()) + return; + + if(d->cursorItem) + delete d->cursorItem; + d->cursorItem = qobject_cast<QSGItem*>(d->cursorComponent->create()); + if(!d->cursorItem){ + qmlInfo(this, d->cursorComponent->errors()) << tr("Could not instantiate cursor delegate"); + return; + } + + QDeclarative_setParent_noEvent(d->cursorItem, this); + d->cursorItem->setParentItem(this); + d->cursorItem->setX(d->control->cursorToX()); + d->cursorItem->setHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. +} + +void QSGTextInput::moveCursor() +{ + Q_D(QSGTextInput); + if(!d->cursorItem) + return; + d->updateHorizontalScroll(); + d->cursorItem->setX(d->control->cursorToX() - d->hscroll); +} + +QRectF QSGTextInput::positionToRectangle(int pos) const +{ + Q_D(const QSGTextInput); + if (pos > d->control->cursorPosition()) + pos += d->control->preeditAreaText().length(); + return QRectF(d->control->cursorToX(pos)-d->hscroll, + 0.0, + d->control->cursorWidth(), + cursorRectangle().height()); +} + +int QSGTextInput::positionAt(int x) const +{ + return positionAt(x, CursorBetweenCharacters); +} + +int QSGTextInput::positionAt(int x, CursorPosition position) const +{ + Q_D(const QSGTextInput); + int pos = d->control->xToPos(x + d->hscroll, QTextLine::CursorPosition(position)); + const int cursor = d->control->cursor(); + if (pos > cursor) { + const int preeditLength = d->control->preeditAreaText().length(); + pos = pos > cursor + preeditLength + ? pos - preeditLength + : cursor; + } + return pos; +} + +void QSGTextInput::keyPressEvent(QKeyEvent* ev) +{ + Q_D(QSGTextInput); + // Don't allow MacOSX up/down support, and we don't allow a completer. + bool ignore = (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) && ev->modifiers() == Qt::NoModifier; + if (!ignore && (d->lastSelectionStart == d->lastSelectionEnd) && (ev->key() == Qt::Key_Right || ev->key() == Qt::Key_Left)) { + // Ignore when moving off the end unless there is a selection, + // because then moving will do something (deselect). + int cursorPosition = d->control->cursor(); + if (cursorPosition == 0) + ignore = ev->key() == (d->control->layoutDirection() == Qt::LeftToRight ? Qt::Key_Left : Qt::Key_Right); + if (cursorPosition == d->control->text().length()) + ignore = ev->key() == (d->control->layoutDirection() == Qt::LeftToRight ? Qt::Key_Right : Qt::Key_Left); + } + if (ignore) { + ev->ignore(); + } else { + d->control->processKeyEvent(ev); + } + if (!ev->isAccepted()) + QSGPaintedItem::keyPressEvent(ev); +} + +void QSGTextInput::inputMethodEvent(QInputMethodEvent *ev) +{ + Q_D(QSGTextInput); + const bool wasComposing = d->control->preeditAreaText().length() > 0; + if (d->control->isReadOnly()) { + ev->ignore(); + } else { + d->control->processInputMethodEvent(ev); + updateSize(); + d->updateHorizontalScroll(); + } + if (!ev->isAccepted()) + QSGPaintedItem::inputMethodEvent(ev); + + if (wasComposing != (d->control->preeditAreaText().length() > 0)) + emit inputMethodComposingChanged(); +} + +void QSGTextInput::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextInput); + if (d->sendMouseEventToInputContext(event, QEvent::MouseButtonDblClick)) + return; + if (d->selectByMouse) { + int cursor = d->xToPos(event->pos().x()); + d->control->selectWordAtPos(cursor); + event->setAccepted(true); + } else { + QSGPaintedItem::mouseDoubleClickEvent(event); + } +} + +void QSGTextInput::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextInput); + if (d->sendMouseEventToInputContext(event, QEvent::MouseButtonPress)) + return; + if(d->focusOnPress){ + bool hadActiveFocus = hasActiveFocus(); + forceActiveFocus(); + if (d->showInputPanelOnFocus) { + if (hasActiveFocus() && hadActiveFocus && !isReadOnly()) { + // re-open input panel on press if already focused + openSoftwareInputPanel(); + } + } else { // show input panel on click + if (hasActiveFocus() && !hadActiveFocus) { + d->clickCausedFocus = true; + } + } + } + if (d->selectByMouse) { + setKeepMouseGrab(false); + d->selectPressed = true; + d->pressPos = event->pos(); + } + bool mark = (event->modifiers() & Qt::ShiftModifier) && d->selectByMouse; + int cursor = d->xToPos(event->pos().x()); + d->control->moveCursor(cursor, mark); + event->setAccepted(true); +} + +void QSGTextInput::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextInput); + if (d->sendMouseEventToInputContext(event, QEvent::MouseMove)) + return; + if (d->selectPressed) { + if (qAbs(int(event->pos().x() - d->pressPos.x())) > QApplication::startDragDistance()) + setKeepMouseGrab(true); + moveCursorSelection(d->xToPos(event->pos().x()), d->mouseSelectionMode); + event->setAccepted(true); + } else { + QSGPaintedItem::mouseMoveEvent(event); + } +} + +void QSGTextInput::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QSGTextInput); + if (d->sendMouseEventToInputContext(event, QEvent::MouseButtonRelease)) + return; + if (d->selectPressed) { + d->selectPressed = false; + setKeepMouseGrab(false); + } + if (!d->showInputPanelOnFocus) { // input panel on click + if (d->focusOnPress && !isReadOnly() && boundingRect().contains(event->pos())) { + if (canvas() && canvas() == qApp->focusWidget()) { + qt_widget_private(canvas())->handleSoftwareInputPanel(event->button(), d->clickCausedFocus); + } + } + } + d->clickCausedFocus = false; + d->control->processEvent(event); + if (!event->isAccepted()) + QSGPaintedItem::mouseReleaseEvent(event); +} + +bool QSGTextInputPrivate::sendMouseEventToInputContext( + QGraphicsSceneMouseEvent *event, QEvent::Type eventType) +{ +#if !defined QT_NO_IM + if (event->widget() && control->composeMode()) { + int tmp_cursor = xToPos(event->pos().x()); + int mousePos = tmp_cursor - control->cursor(); + if (mousePos < 0 || mousePos > control->preeditAreaText().length()) { + mousePos = -1; + // don't send move events outside the preedit area + if (eventType == QEvent::MouseMove) + return true; + } + + QInputContext *qic = event->widget()->inputContext(); + if (qic) { + QMouseEvent mouseEvent( + eventType, + event->widget()->mapFromGlobal(event->screenPos()), + event->screenPos(), + event->button(), + event->buttons(), + event->modifiers()); + // may be causing reset() in some input methods + qic->mouseHandler(mousePos, &mouseEvent); + event->setAccepted(mouseEvent.isAccepted()); + } + if (!control->preeditAreaText().isEmpty()) + return true; + } +#else + Q_UNUSED(event); + Q_UNUSED(eventType) +#endif + + return false; +} + +void QSGTextInput::mouseUngrabEvent() +{ + Q_D(QSGTextInput); + d->selectPressed = false; + setKeepMouseGrab(false); +} + +bool QSGTextInput::event(QEvent* ev) +{ + Q_D(QSGTextInput); + //Anything we don't deal with ourselves, pass to the control + bool handled = false; + switch(ev->type()){ + case QEvent::KeyPress: + case QEvent::KeyRelease://###Should the control be doing anything with release? + case QEvent::InputMethod: + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseDoubleClick: + break; + default: + handled = d->control->processEvent(ev); + } + if(!handled) + handled = QSGPaintedItem::event(ev); + return handled; +} + +void QSGTextInput::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + Q_D(QSGTextInput); + if (newGeometry.width() != oldGeometry.width()) { + updateSize(); + d->updateHorizontalScroll(); + } + QSGPaintedItem::geometryChanged(newGeometry, oldGeometry); +} + +int QSGTextInputPrivate::calculateTextWidth() +{ + return qRound(control->naturalTextWidth()); +} + +void QSGTextInputPrivate::updateHorizontalScroll() +{ + Q_Q(QSGTextInput); + const int preeditLength = control->preeditAreaText().length(); + int cix = qRound(control->cursorToX(control->cursor() + preeditLength)); + QRect br(q->boundingRect().toRect()); + int widthUsed = calculateTextWidth(); + + QSGTextInput::HAlignment effectiveHAlign = q->effectiveHAlign(); + if (autoScroll) { + if (widthUsed <= br.width()) { + // text fits in br; use hscroll for alignment + switch (effectiveHAlign & ~(Qt::AlignAbsolute|Qt::AlignVertical_Mask)) { + case Qt::AlignRight: + hscroll = widthUsed - br.width() - 1; + break; + case Qt::AlignHCenter: + hscroll = (widthUsed - br.width()) / 2; + break; + default: + // Left + hscroll = 0; + break; + } + } else if (cix - hscroll >= br.width()) { + // text doesn't fit, cursor is to the right of br (scroll right) + hscroll = cix - br.width() + 1; + } else if (cix - hscroll < 0 && hscroll < widthUsed) { + // text doesn't fit, cursor is to the left of br (scroll left) + hscroll = cix; + } else if (widthUsed - hscroll < br.width()) { + // text doesn't fit, text document is to the left of br; align + // right + hscroll = widthUsed - br.width() + 1; + } + if (preeditLength > 0) { + // check to ensure long pre-edit text doesn't push the cursor + // off to the left + cix = qRound(control->cursorToX( + control->cursor() + qMax(0, control->preeditCursor() - 1))); + if (cix < hscroll) + hscroll = cix; + } + } else { + switch (effectiveHAlign) { + case QSGTextInput::AlignRight: + hscroll = q->width() - widthUsed; + break; + case QSGTextInput::AlignHCenter: + hscroll = (q->width() - widthUsed) / 2; + break; + default: + // Left + hscroll = 0; + break; + } + } +} + +void QSGTextInput::paint(QPainter *p) +{ + // XXX todo + QRect r(0, 0, width(), height()); + + 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; + 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); + } + d->control->draw(p, offset, r, flags); + p->restore(); +} + +QVariant QSGTextInput::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QSGTextInput); + switch(property) { + case Qt::ImMicroFocus: + return cursorRectangle(); + case Qt::ImFont: + return font(); + case Qt::ImCursorPosition: + return QVariant(d->control->cursor()); + case Qt::ImSurroundingText: + if (d->control->echoMode() == PasswordEchoOnEdit && !d->control->passwordEchoEditing()) + return QVariant(displayText()); + else + return QVariant(text()); + case Qt::ImCurrentSelection: + return QVariant(selectedText()); + case Qt::ImMaximumTextLength: + return QVariant(maxLength()); + case Qt::ImAnchorPosition: + if (d->control->selectionStart() == d->control->selectionEnd()) + return QVariant(d->control->cursor()); + else if (d->control->selectionStart() == d->control->cursor()) + return QVariant(d->control->selectionEnd()); + else + return QVariant(d->control->selectionStart()); + default: + return QVariant(); + } +} + +void QSGTextInput::deselect() +{ + Q_D(QSGTextInput); + d->control->deselect(); +} + +void QSGTextInput::selectAll() +{ + Q_D(QSGTextInput); + d->control->setSelection(0, d->control->text().length()); +} + +bool QSGTextInput::isRightToLeft(int start, int end) +{ + Q_D(QSGTextInput); + if (start > end) { + qmlInfo(this) << "isRightToLeft(start, end) called with the end property being smaller than the start."; + return false; + } else { + return d->control->text().mid(start, end - start).isRightToLeft(); + } +} + +#ifndef QT_NO_CLIPBOARD +void QSGTextInput::cut() +{ + Q_D(QSGTextInput); + d->control->copy(); + d->control->del(); +} + +void QSGTextInput::copy() +{ + Q_D(QSGTextInput); + d->control->copy(); +} + +void QSGTextInput::paste() +{ + Q_D(QSGTextInput); + if (!d->control->isReadOnly()) + d->control->paste(); +} +#endif // QT_NO_CLIPBOARD + +void QSGTextInput::selectWord() +{ + Q_D(QSGTextInput); + d->control->selectWordAtPos(d->control->cursor()); +} + +QString QSGTextInput::passwordCharacter() const +{ + Q_D(const QSGTextInput); + return QString(d->control->passwordCharacter()); +} + +void QSGTextInput::setPasswordCharacter(const QString &str) +{ + Q_D(QSGTextInput); + if(str.length() < 1) + return; + d->control->setPasswordCharacter(str.constData()[0]); + EchoMode echoMode_ = echoMode(); + if (echoMode_ == Password || echoMode_ == PasswordEchoOnEdit) { + updateSize(); + } + emit passwordCharacterChanged(); +} + +QString QSGTextInput::displayText() const +{ + Q_D(const QSGTextInput); + return d->control->displayText(); +} + +bool QSGTextInput::selectByMouse() const +{ + Q_D(const QSGTextInput); + return d->selectByMouse; +} + +void QSGTextInput::setSelectByMouse(bool on) +{ + Q_D(QSGTextInput); + if (d->selectByMouse != on) { + d->selectByMouse = on; + emit selectByMouseChanged(on); + } +} + +QSGTextInput::SelectionMode QSGTextInput::mouseSelectionMode() const +{ + Q_D(const QSGTextInput); + return d->mouseSelectionMode; +} + +void QSGTextInput::setMouseSelectionMode(SelectionMode mode) +{ + Q_D(QSGTextInput); + if (d->mouseSelectionMode != mode) { + d->mouseSelectionMode = mode; + emit mouseSelectionModeChanged(mode); + } +} + +bool QSGTextInput::canPaste() const +{ + Q_D(const QSGTextInput); + return d->canPaste; +} + +void QSGTextInput::moveCursorSelection(int position) +{ + Q_D(QSGTextInput); + d->control->moveCursor(position, true); + d->updateHorizontalScroll(); +} + +void QSGTextInput::moveCursorSelection(int pos, SelectionMode mode) +{ + Q_D(QSGTextInput); + + if (mode == SelectCharacters) { + d->control->moveCursor(pos, true); + } else if (pos != d->control->cursor()){ + const int cursor = d->control->cursor(); + int anchor; + if (!d->control->hasSelectedText()) + anchor = d->control->cursor(); + else if (d->control->selectionStart() == d->control->cursor()) + anchor = d->control->selectionEnd(); + else + anchor = d->control->selectionStart(); + + if (anchor < pos || (anchor == pos && cursor < pos)) { + const QString text = d->control->text(); + QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text); + finder.setPosition(anchor); + + const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons(); + if (anchor < text.length() && (!(reasons & QTextBoundaryFinder::StartWord) + || ((reasons & QTextBoundaryFinder::EndWord) && anchor > cursor))) { + finder.toPreviousBoundary(); + } + anchor = finder.position() != -1 ? finder.position() : 0; + + finder.setPosition(pos); + if (pos > 0 && !finder.boundaryReasons()) + finder.toNextBoundary(); + const int cursor = finder.position() != -1 ? finder.position() : text.length(); + + d->control->setSelection(anchor, cursor - anchor); + } else if (anchor > pos || (anchor == pos && cursor > pos)) { + const QString text = d->control->text(); + QTextBoundaryFinder finder(QTextBoundaryFinder::Word, text); + finder.setPosition(anchor); + + const QTextBoundaryFinder::BoundaryReasons reasons = finder.boundaryReasons(); + if (anchor > 0 && (!(reasons & QTextBoundaryFinder::EndWord) + || ((reasons & QTextBoundaryFinder::StartWord) && anchor < cursor))) { + finder.toNextBoundary(); + } + + anchor = finder.position() != -1 ? finder.position() : text.length(); + + finder.setPosition(pos); + if (pos < text.length() && !finder.boundaryReasons()) + finder.toPreviousBoundary(); + const int cursor = finder.position() != -1 ? finder.position() : 0; + + d->control->setSelection(anchor, cursor - anchor); + } + } +} + +void QSGTextInput::openSoftwareInputPanel() +{ + QEvent event(QEvent::RequestSoftwareInputPanel); + if (qApp) { + if (canvas() && canvas() == qApp->focusWidget()) { + QEvent event(QEvent::RequestSoftwareInputPanel); + QApplication::sendEvent(canvas(), &event); + } + } +} + +void QSGTextInput::closeSoftwareInputPanel() +{ + if (qApp) { + if (canvas() && canvas() == qApp->focusWidget()) { + QEvent event(QEvent::CloseSoftwareInputPanel); + QApplication::sendEvent(canvas(), &event); + } + } +} + +void QSGTextInput::focusInEvent(QFocusEvent *event) +{ + Q_D(const QSGTextInput); + if (d->showInputPanelOnFocus) { + if (d->focusOnPress && !isReadOnly()) { + openSoftwareInputPanel(); + } + } + QSGPaintedItem::focusInEvent(event); +} + +void QSGTextInput::itemChange(ItemChange change, const ItemChangeData &value) +{ + Q_D(QSGTextInput); + if (change == ItemActiveFocusHasChanged) { + bool hasFocus = value.boolValue; + d->focused = hasFocus; + setCursorVisible(hasFocus && d->canvas && d->canvas->hasFocus()); + if(echoMode() == QSGTextInput::PasswordEchoOnEdit && !hasFocus) + d->control->updatePasswordEchoEditing(false);//QLineControl sets it on key events, but doesn't deal with focus events + if (!hasFocus) + d->control->deselect(); + } + QSGItem::itemChange(change, value); +} + +bool QSGTextInput::isInputMethodComposing() const +{ + Q_D(const QSGTextInput); + return d->control->preeditAreaText().length() > 0; +} + +void QSGTextInputPrivate::init() +{ + Q_Q(QSGTextInput); + control->setCursorWidth(1); + control->setPasswordCharacter(QLatin1Char('*')); + q->setSmooth(smooth); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QSGItem::ItemAcceptsInputMethod); + q->connect(control, SIGNAL(cursorPositionChanged(int,int)), + q, SLOT(cursorPosChanged())); + q->connect(control, SIGNAL(selectionChanged()), + q, SLOT(selectionChanged())); + q->connect(control, SIGNAL(textChanged(QString)), + q, SLOT(q_textChanged())); + q->connect(control, SIGNAL(accepted()), + q, SIGNAL(accepted())); + q->connect(control, SIGNAL(updateNeeded(QRect)), + q, SLOT(updateRect(QRect))); +#ifndef QT_NO_CLIPBOARD + q->connect(q, SIGNAL(readOnlyChanged(bool)), + q, SLOT(q_canPasteChanged())); + q->connect(QApplication::clipboard(), SIGNAL(dataChanged()), + q, SLOT(q_canPasteChanged())); + canPaste = !control->isReadOnly() && QApplication::clipboard()->text().length() != 0; +#endif // QT_NO_CLIPBOARD + q->connect(control, SIGNAL(updateMicroFocus()), + q, SLOT(updateMicroFocus())); + q->connect(control, SIGNAL(displayTextChanged(QString)), + q, SLOT(updateRect())); + q->updateSize(); + oldValidity = control->hasAcceptableInput(); + lastSelectionStart = 0; + lastSelectionEnd = 0; + QPalette p = control->palette(); + selectedTextColor = p.color(QPalette::HighlightedText); + selectionColor = p.color(QPalette::Highlight); + determineHorizontalAlignment(); +} + +void QSGTextInput::cursorPosChanged() +{ + Q_D(QSGTextInput); + d->updateHorizontalScroll(); + updateRect();//TODO: Only update rect between pos's + updateMicroFocus(); + emit cursorPositionChanged(); + // XXX todo - not in 4.8? +#if 0 + d->control->resetCursorBlinkTimer(); +#endif + + if(!d->control->hasSelectedText()){ + if(d->lastSelectionStart != d->control->cursor()){ + d->lastSelectionStart = d->control->cursor(); + emit selectionStartChanged(); + } + if(d->lastSelectionEnd != d->control->cursor()){ + d->lastSelectionEnd = d->control->cursor(); + emit selectionEndChanged(); + } + } +} + +void QSGTextInput::selectionChanged() +{ + Q_D(QSGTextInput); + updateRect();//TODO: Only update rect in selection + emit selectedTextChanged(); + + if(d->lastSelectionStart != d->control->selectionStart()){ + d->lastSelectionStart = d->control->selectionStart(); + if(d->lastSelectionStart == -1) + d->lastSelectionStart = d->control->cursor(); + emit selectionStartChanged(); + } + if(d->lastSelectionEnd != d->control->selectionEnd()){ + d->lastSelectionEnd = d->control->selectionEnd(); + if(d->lastSelectionEnd == -1) + d->lastSelectionEnd = d->control->cursor(); + emit selectionEndChanged(); + } +} + +void QSGTextInput::q_textChanged() +{ + Q_D(QSGTextInput); + updateSize(); + d->determineHorizontalAlignment(); + d->updateHorizontalScroll(); + updateMicroFocus(); + emit textChanged(); + emit displayTextChanged(); + if(hasAcceptableInput() != d->oldValidity){ + d->oldValidity = hasAcceptableInput(); + emit acceptableInputChanged(); + } +} + +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())); +} + +QRectF QSGTextInput::boundingRect() const +{ + Q_D(const QSGTextInput); + QRectF r = QSGPaintedItem::boundingRect(); + + int cursorWidth = d->cursorItem ? d->cursorItem->width() : d->control->cursorWidth(); + + // Could include font max left/right bearings to either side of rectangle. + + r.setRight(r.right() + cursorWidth); + return r; +} + +void QSGTextInput::updateSize(bool needsRedraw) +{ + Q_D(QSGTextInput); + int w = width(); + int h = height(); + setImplicitHeight(d->control->height()-1); // -1 to counter QLineControl's +1 which is not consistent with Text. + setImplicitWidth(d->calculateTextWidth()); + setContentsSize(QSize(width(), height())); + if(w==width() && h==height() && needsRedraw) + update(); +} + +void QSGTextInput::q_canPasteChanged() +{ + Q_D(QSGTextInput); + bool old = d->canPaste; +#ifndef QT_NO_CLIPBOARD + d->canPaste = !d->control->isReadOnly() && QApplication::clipboard()->text().length() != 0; +#endif + if(d->canPaste != old) + emit canPasteChanged(); +} + +QT_END_NAMESPACE + diff --git a/src/declarative/items/qsgtextinput_p.h b/src/declarative/items/qsgtextinput_p.h new file mode 100644 index 0000000000..ccea485e99 --- /dev/null +++ b/src/declarative/items/qsgtextinput_p.h @@ -0,0 +1,302 @@ +// Commit: 2f173e4945dd8414636c1061acfaf9c2d8b718d8 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXTINPUT_P_H +#define QSGTEXTINPUT_P_H + +#include "qsgimplicitsizeitem_p.h" +#include <QtGui/qvalidator.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGTextInputPrivate; +class QValidator; +class Q_AUTOTEST_EXPORT QSGTextInput : public QSGImplicitSizePaintedItem +{ + Q_OBJECT + Q_ENUMS(HAlignment) + Q_ENUMS(EchoMode) + Q_ENUMS(SelectionMode) + + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor NOTIFY selectionColorChanged) + Q_PROPERTY(QColor selectedTextColor READ selectedTextColor WRITE setSelectedTextColor NOTIFY selectedTextColorChanged) + Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) + Q_PROPERTY(HAlignment horizontalAlignment READ hAlign WRITE setHAlign RESET resetHAlign NOTIFY horizontalAlignmentChanged) + Q_PROPERTY(HAlignment effectiveHorizontalAlignment READ effectiveHAlign NOTIFY effectiveHorizontalAlignmentChanged) + + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged) + Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible NOTIFY cursorVisibleChanged) + Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) + Q_PROPERTY(QRect cursorRectangle READ cursorRectangle NOTIFY cursorPositionChanged) + Q_PROPERTY(QDeclarativeComponent *cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged) + Q_PROPERTY(int selectionStart READ selectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd NOTIFY selectionEndChanged) + Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectedTextChanged) + + Q_PROPERTY(int maximumLength READ maxLength WRITE setMaxLength NOTIFY maximumLengthChanged) +#ifndef QT_NO_VALIDATOR + Q_PROPERTY(QValidator* validator READ validator WRITE setValidator NOTIFY validatorChanged) +#endif + Q_PROPERTY(QString inputMask READ inputMask WRITE setInputMask NOTIFY inputMaskChanged) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ imHints WRITE setIMHints) + + Q_PROPERTY(bool acceptableInput READ hasAcceptableInput NOTIFY acceptableInputChanged) + Q_PROPERTY(EchoMode echoMode READ echoMode WRITE setEchoMode NOTIFY echoModeChanged) + Q_PROPERTY(bool activeFocusOnPress READ focusOnPress WRITE setFocusOnPress NOTIFY activeFocusOnPressChanged) + Q_PROPERTY(QString passwordCharacter READ passwordCharacter WRITE setPasswordCharacter NOTIFY passwordCharacterChanged) + Q_PROPERTY(QString displayText READ displayText NOTIFY displayTextChanged) + Q_PROPERTY(bool autoScroll READ autoScroll WRITE setAutoScroll NOTIFY autoScrollChanged) + Q_PROPERTY(bool selectByMouse READ selectByMouse WRITE setSelectByMouse NOTIFY selectByMouseChanged) + Q_PROPERTY(SelectionMode mouseSelectionMode READ mouseSelectionMode WRITE setMouseSelectionMode NOTIFY mouseSelectionModeChanged) + Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged) + Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged) + +public: + QSGTextInput(QSGItem * parent=0); + ~QSGTextInput(); + + enum EchoMode {//To match QLineEdit::EchoMode + Normal, + NoEcho, + Password, + PasswordEchoOnEdit + }; + + enum HAlignment { + AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter + }; + + enum SelectionMode { + SelectCharacters, + SelectWords + }; + + enum CursorPosition { + CursorBetweenCharacters, + CursorOnCharacter + }; + + //Auxilliary functions needed to control the TextInput from QML + Q_INVOKABLE int positionAt(int x) const; + Q_INVOKABLE int positionAt(int x, CursorPosition position) const; + Q_INVOKABLE QRectF positionToRectangle(int pos) const; + Q_INVOKABLE void moveCursorSelection(int pos); + Q_INVOKABLE void moveCursorSelection(int pos, SelectionMode mode); + + Q_INVOKABLE void openSoftwareInputPanel(); + Q_INVOKABLE void closeSoftwareInputPanel(); + + QString text() const; + void setText(const QString &); + + QFont font() const; + void setFont(const QFont &font); + + QColor color() const; + void setColor(const QColor &c); + + QColor selectionColor() const; + void setSelectionColor(const QColor &c); + + QColor selectedTextColor() const; + void setSelectedTextColor(const QColor &c); + + HAlignment hAlign() const; + void setHAlign(HAlignment align); + void resetHAlign(); + HAlignment effectiveHAlign() const; + + bool isReadOnly() const; + void setReadOnly(bool); + + bool isCursorVisible() const; + void setCursorVisible(bool on); + + int cursorPosition() const; + void setCursorPosition(int cp); + + QRect cursorRectangle() const; + + int selectionStart() const; + int selectionEnd() const; + + QString selectedText() const; + + int maxLength() const; + void setMaxLength(int ml); + +#ifndef QT_NO_VALIDATOR + QValidator * validator() const; + void setValidator(QValidator* v); +#endif + QString inputMask() const; + void setInputMask(const QString &im); + + EchoMode echoMode() const; + void setEchoMode(EchoMode echo); + + QString passwordCharacter() const; + void setPasswordCharacter(const QString &str); + + QString displayText() const; + + QDeclarativeComponent* cursorDelegate() const; + void setCursorDelegate(QDeclarativeComponent*); + + bool focusOnPress() const; + void setFocusOnPress(bool); + + bool autoScroll() const; + void setAutoScroll(bool); + + bool selectByMouse() const; + void setSelectByMouse(bool); + + SelectionMode mouseSelectionMode() const; + void setMouseSelectionMode(SelectionMode mode); + + bool hasAcceptableInput() const; + + void paint(QPainter *p); + QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + + QRectF boundingRect() const; + bool canPaste() const; + + bool isInputMethodComposing() const; + + Qt::InputMethodHints imHints() const; + void setIMHints(Qt::InputMethodHints hints); + +Q_SIGNALS: + void textChanged(); + void cursorPositionChanged(); + void selectionStartChanged(); + void selectionEndChanged(); + void selectedTextChanged(); + void accepted(); + void acceptableInputChanged(); + void colorChanged(const QColor &color); + void selectionColorChanged(const QColor &color); + void selectedTextColorChanged(const QColor &color); + void fontChanged(const QFont &font); + void horizontalAlignmentChanged(HAlignment alignment); + void readOnlyChanged(bool isReadOnly); + void cursorVisibleChanged(bool isCursorVisible); + void cursorDelegateChanged(); + void maximumLengthChanged(int maximumLength); + void validatorChanged(); + void inputMaskChanged(const QString &inputMask); + void echoModeChanged(EchoMode echoMode); + void passwordCharacterChanged(); + void displayTextChanged(); + void activeFocusOnPressChanged(bool activeFocusOnPress); + void autoScrollChanged(bool autoScroll); + void selectByMouseChanged(bool selectByMouse); + void mouseSelectionModeChanged(SelectionMode mode); + void canPasteChanged(); + void inputMethodComposingChanged(); + void effectiveHorizontalAlignmentChanged(); + +protected: + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + bool sceneEvent(QEvent *event); + void keyPressEvent(QKeyEvent* ev); + void inputMethodEvent(QInputMethodEvent *); + void mouseUngrabEvent(); + bool event(QEvent *e); + void focusInEvent(QFocusEvent *event); + virtual void itemChange(ItemChange, const ItemChangeData &); + +public Q_SLOTS: + void selectAll(); + void selectWord(); + void select(int start, int end); + void deselect(); + bool isRightToLeft(int start, int end); +#ifndef QT_NO_CLIPBOARD + void cut(); + void copy(); + void paste(); +#endif + +private Q_SLOTS: + void updateSize(bool needsRedraw = true); + void q_textChanged(); + void selectionChanged(); + void createCursor(); + void moveCursor(); + void cursorPosChanged(); + void updateRect(const QRect &r = QRect()); + void q_canPasteChanged(); + +private: + Q_DECLARE_PRIVATE(QSGTextInput) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGTextInput) +#ifndef QT_NO_VALIDATOR +QML_DECLARE_TYPE(QValidator) +QML_DECLARE_TYPE(QIntValidator) +QML_DECLARE_TYPE(QDoubleValidator) +QML_DECLARE_TYPE(QRegExpValidator) +#endif + +QT_END_HEADER + +#endif // QSGTEXTINPUT_P_H diff --git a/src/declarative/items/qsgtextinput_p_p.h b/src/declarative/items/qsgtextinput_p_p.h new file mode 100644 index 0000000000..22c95a7b1d --- /dev/null +++ b/src/declarative/items/qsgtextinput_p_p.h @@ -0,0 +1,154 @@ +// Commit: 47712d1f330e4b22ce6dd30e7557288ef7f7fca0 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXTINPUT_P_P_H +#define QSGTEXTINPUT_P_P_H + +#include "qsgtextinput_p.h" +#include "qsgtext_p.h" +#include "qsgimplicitsizeitem_p_p.h" + +#include <private/qlinecontrol_p.h> + +#include <QtDeclarative/qdeclarative.h> +#include <QtCore/qpointer.h> + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +QT_BEGIN_NAMESPACE + +class Q_AUTOTEST_EXPORT QSGTextInputPrivate : public QSGImplicitSizePaintedItemPrivate +{ + 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) + { +#ifdef Q_OS_SYMBIAN + if (QSysInfo::symbianVersion() == QSysInfo::SV_SF_1 || QSysInfo::symbianVersion() == QSysInfo::SV_SF_3) { + showInputPanelOnFocus = false; + } +#endif + + } + + ~QSGTextInputPrivate() + { + delete control; + } + + int xToPos(int x, QTextLine::CursorPosition betweenOrOn = QTextLine::CursorBetweenCharacters) const + { + Q_Q(const QSGTextInput); + QRect cr = q->boundingRect().toRect(); + x-= cr.x() - hscroll; + return control->xToPos(x, betweenOrOn); + } + + void init(); + void startCreatingCursor(); + void updateHorizontalScroll(); + bool determineHorizontalAlignment(); + bool setHAlign(QSGTextInput::HAlignment, bool forceAlign = false); + void mirrorChange(); + int calculateTextWidth(); + bool sendMouseEventToInputContext(QGraphicsSceneMouseEvent *event, QEvent::Type eventType); + void updateInputMethodHints(); + + QLineControl* control; + + QFont font; + QFont sourceFont; + QColor color; + QColor selectionColor; + QColor selectedTextColor; + QSGText::TextStyle style; + QColor styleColor; + QSGTextInput::HAlignment hAlign; + QSGTextInput::SelectionMode mouseSelectionMode; + Qt::InputMethodHints inputMethodHints; + QPointer<QDeclarativeComponent> cursorComponent; + QPointer<QSGItem> cursorItem; + QPointF pressPos; + + int lastSelectionStart; + int lastSelectionEnd; + int oldHeight; + int oldWidth; + int hscroll; + int oldScroll; + + bool oldValidity:1; + bool focused:1; + bool focusOnPress:1; + bool showInputPanelOnFocus:1; + bool clickCausedFocus:1; + bool cursorVisible:1; + bool autoScroll:1; + bool selectByMouse:1; + bool canPaste:1; + bool hAlignImplicit:1; + bool selectPressed:1; + + static inline QSGTextInputPrivate *get(QSGTextInput *t) { + return t->d_func(); + } +}; + +QT_END_NAMESPACE + +#endif // QSGTEXTINPUT_P_P_H diff --git a/src/declarative/items/qsgtextnode.cpp b/src/declarative/items/qsgtextnode.cpp new file mode 100644 index 0000000000..a887c2360a --- /dev/null +++ b/src/declarative/items/qsgtextnode.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtextnode_p.h" +#include "qsgsimplerectnode.h" +#include <private/qsgadaptationlayer_p.h> +#include <private/qsgdistancefieldglyphcache_p.h> +#include <private/qsgdistancefieldglyphnode_p.h> + +#include <private/qsgcontext_p.h> + +#include <qmath.h> +#include <qtextdocument.h> +#include <qtextlayout.h> +#include <qabstracttextdocumentlayout.h> +#include <qxmlstream.h> +#include <qrawfont.h> +#include <private/qdeclarativestyledtext_p.h> +#include <private/qfont_p.h> +#include <private/qfontengine_p.h> +#include <private/qrawfont_p.h> + +QT_BEGIN_NAMESPACE + +/*! + Creates an empty QSGTextNode +*/ +QSGTextNode::QSGTextNode(QSGContext *context) +: m_context(context) +{ +#if defined(QML_RUNTIME_TESTING) + description = QLatin1String("text"); +#endif +} + +QSGTextNode::~QSGTextNode() +{ +} + +#if 0 +void QSGTextNode::setColor(const QColor &color) +{ + if (m_usePixmapCache) { + setUpdateFlag(UpdateNodes); + } else { + for (int i=0; i<childCount(); ++i) { + QSGNode *childNode = childAtIndex(i); + if (childNode->subType() == GlyphNodeSubType) { + QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode); + if (glyphNode->color() == m_color) + glyphNode->setColor(color); + } else if (childNode->subType() == SolidRectNodeSubType) { + QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode); + if (solidRectNode->color() == m_color) + solidRectNode->setColor(color); + } + } + } + m_color = color; +} + +void QSGTextNode::setStyleColor(const QColor &styleColor) +{ + if (m_textStyle != QSGTextNode::NormalTextStyle) { + if (m_usePixmapCache) { + setUpdateFlag(UpdateNodes); + } else { + for (int i=0; i<childCount(); ++i) { + QSGNode *childNode = childAtIndex(i); + if (childNode->subType() == GlyphNodeSubType) { + QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode); + if (glyphNode->color() == m_styleColor) + glyphNode->setColor(styleColor); + } else if (childNode->subType() == SolidRectNodeSubType) { + QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode); + if (solidRectNode->color() == m_styleColor) + solidRectNode->setColor(styleColor); + } + } + } + } + m_styleColor = styleColor; +} +#endif + +void QSGTextNode::addTextDecorations(const QPointF &position, const QRawFont &font, const QColor &color, + qreal width, bool hasOverline, bool hasStrikeOut, bool hasUnderline) +{ + Q_ASSERT(font.isValid()); + QRawFontPrivate *dptrFont = QRawFontPrivate::get(font); + QFontEngine *fontEngine = dptrFont->fontEngine; + + qreal lineThickness = fontEngine->lineThickness().toReal(); + + QRectF line(position.x(), position.y() - lineThickness / 2.0, width, lineThickness); + + if (hasUnderline) { + int underlinePosition = fontEngine->underlinePosition().ceil().toInt(); + QRectF underline(line); + underline.translate(0.0, underlinePosition); + appendChildNode(new QSGSimpleRectNode(underline, color)); + } + + qreal ascent = font.ascent(); + if (hasOverline) { + QRectF overline(line); + overline.translate(0.0, -ascent); + appendChildNode(new QSGSimpleRectNode(overline, color)); + } + + if (hasStrikeOut) { + QRectF strikeOut(line); + strikeOut.translate(0.0, ascent / -3.0); + appendChildNode(new QSGSimpleRectNode(strikeOut, color)); + } +} + +QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphs &glyphs, const QColor &color, + QSGText::TextStyle style, const QColor &styleColor) +{ + QSGGlyphNode *node = m_context->createGlyphNode(); + if (QSGDistanceFieldGlyphCache::distanceFieldEnabled()) { + QSGDistanceFieldGlyphNode *dfNode = static_cast<QSGDistanceFieldGlyphNode *>(node); + dfNode->setStyle(style); + dfNode->setStyleColor(styleColor); + } + node->setGlyphs(position, glyphs); + node->setColor(color); + + appendChildNode(node); + + return node; +} + +void QSGTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color, + QSGText::TextStyle style, const QColor &styleColor) +{ + Q_UNUSED(position) + QTextFrame *textFrame = textDocument->rootFrame(); + QPointF p = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft(); + + QTextFrame::iterator it = textFrame->begin(); + while (!it.atEnd()) { + addTextBlock(p, textDocument, it.currentBlock(), color, style, styleColor); + ++it; + } +} + +void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color, + QSGText::TextStyle style, const QColor &styleColor) +{ + QList<QGlyphs> glyphsList(textLayout->glyphs()); + for (int i=0; i<glyphsList.size(); ++i) { + QGlyphs glyphs = glyphsList.at(i); + QRawFont font = glyphs.font(); + addGlyphs(position + QPointF(0, font.ascent()), glyphs, color, style, styleColor); + } + + QFont font = textLayout->font(); + QRawFont rawFont = QRawFont::fromFont(font); + if (font.strikeOut() || font.underline() || font.overline()) { + addTextDecorations(position, rawFont, color, textLayout->boundingRect().width(), + font.overline(), font.strikeOut(), font.underline()); + } +} + + +/*! + Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated + to text, fonts or text layout. Otherwise the function returns false. If the return value is + false, \a text is considered to be easily representable in the scenegraph. If it returns true, + then the text should be prerendered into a pixmap before it's displayed on screen. +*/ +bool QSGTextNode::isComplexRichText(QTextDocument *doc) +{ + if (doc == 0) + return false; + + static QSet<QString> supportedTags; + if (supportedTags.isEmpty()) { + supportedTags.insert(QLatin1String("i")); + supportedTags.insert(QLatin1String("b")); + supportedTags.insert(QLatin1String("u")); + supportedTags.insert(QLatin1String("div")); + supportedTags.insert(QLatin1String("big")); + supportedTags.insert(QLatin1String("blockquote")); + supportedTags.insert(QLatin1String("body")); + supportedTags.insert(QLatin1String("br")); + supportedTags.insert(QLatin1String("center")); + supportedTags.insert(QLatin1String("cite")); + supportedTags.insert(QLatin1String("code")); + supportedTags.insert(QLatin1String("tt")); + supportedTags.insert(QLatin1String("dd")); + supportedTags.insert(QLatin1String("dfn")); + supportedTags.insert(QLatin1String("em")); + supportedTags.insert(QLatin1String("font")); + supportedTags.insert(QLatin1String("h1")); + supportedTags.insert(QLatin1String("h2")); + supportedTags.insert(QLatin1String("h3")); + supportedTags.insert(QLatin1String("h4")); + supportedTags.insert(QLatin1String("h5")); + supportedTags.insert(QLatin1String("h6")); + supportedTags.insert(QLatin1String("head")); + supportedTags.insert(QLatin1String("html")); + supportedTags.insert(QLatin1String("meta")); + supportedTags.insert(QLatin1String("nobr")); + supportedTags.insert(QLatin1String("p")); + supportedTags.insert(QLatin1String("pre")); + supportedTags.insert(QLatin1String("qt")); + supportedTags.insert(QLatin1String("s")); + supportedTags.insert(QLatin1String("samp")); + supportedTags.insert(QLatin1String("small")); + supportedTags.insert(QLatin1String("span")); + supportedTags.insert(QLatin1String("strong")); + supportedTags.insert(QLatin1String("sub")); + supportedTags.insert(QLatin1String("sup")); + supportedTags.insert(QLatin1String("title")); + supportedTags.insert(QLatin1String("var")); + supportedTags.insert(QLatin1String("style")); + } + + static QSet<QCss::Property> supportedCssProperties; + if (supportedCssProperties.isEmpty()) { + supportedCssProperties.insert(QCss::Color); + supportedCssProperties.insert(QCss::Float); + supportedCssProperties.insert(QCss::Font); + supportedCssProperties.insert(QCss::FontFamily); + supportedCssProperties.insert(QCss::FontSize); + supportedCssProperties.insert(QCss::FontStyle); + supportedCssProperties.insert(QCss::FontWeight); + supportedCssProperties.insert(QCss::Margin); + supportedCssProperties.insert(QCss::MarginBottom); + supportedCssProperties.insert(QCss::MarginLeft); + supportedCssProperties.insert(QCss::MarginRight); + supportedCssProperties.insert(QCss::MarginTop); + supportedCssProperties.insert(QCss::TextDecoration); + supportedCssProperties.insert(QCss::TextIndent); + supportedCssProperties.insert(QCss::TextUnderlineStyle); + supportedCssProperties.insert(QCss::VerticalAlignment); + supportedCssProperties.insert(QCss::Whitespace); + supportedCssProperties.insert(QCss::Padding); + supportedCssProperties.insert(QCss::PaddingLeft); + supportedCssProperties.insert(QCss::PaddingRight); + supportedCssProperties.insert(QCss::PaddingTop); + supportedCssProperties.insert(QCss::PaddingBottom); + supportedCssProperties.insert(QCss::PageBreakBefore); + supportedCssProperties.insert(QCss::PageBreakAfter); + supportedCssProperties.insert(QCss::Width); + supportedCssProperties.insert(QCss::Height); + supportedCssProperties.insert(QCss::MinimumWidth); + supportedCssProperties.insert(QCss::MinimumHeight); + supportedCssProperties.insert(QCss::MaximumWidth); + supportedCssProperties.insert(QCss::MaximumHeight); + supportedCssProperties.insert(QCss::Left); + supportedCssProperties.insert(QCss::Right); + supportedCssProperties.insert(QCss::Top); + supportedCssProperties.insert(QCss::Bottom); + supportedCssProperties.insert(QCss::Position); + supportedCssProperties.insert(QCss::TextAlignment); + supportedCssProperties.insert(QCss::FontVariant); + } + + QXmlStreamReader reader(doc->toHtml("utf-8")); + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isStartElement()) { + if (!supportedTags.contains(reader.name().toString().toLower())) + return true; + + QXmlStreamAttributes attributes = reader.attributes(); + if (attributes.hasAttribute(QLatin1String("bgcolor"))) + return true; + if (attributes.hasAttribute(QLatin1String("style"))) { + QCss::StyleSheet styleSheet; + QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet); + + QVector<QCss::Declaration> decls; + for (int i=0; i<styleSheet.pageRules.size(); ++i) + decls += styleSheet.pageRules.at(i).declarations; + + QVector<QCss::StyleRule> styleRules = + styleSheet.styleRules + + styleSheet.idIndex.values().toVector() + + styleSheet.nameIndex.values().toVector(); + for (int i=0; i<styleSheet.mediaRules.size(); ++i) + styleRules += styleSheet.mediaRules.at(i).styleRules; + + for (int i=0; i<styleRules.size(); ++i) + decls += styleRules.at(i).declarations; + + for (int i=0; i<decls.size(); ++i) { + if (!supportedCssProperties.contains(decls.at(i).d->propertyId)) + return true; + } + + } + } + } + + 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<QGlyphs> glyphsList = fragment.glyphs(); + for (int i=0; i<glyphsList.size(); ++i) { + QGlyphs glyphs = glyphsList.at(i); + QRawFont font = glyphs.font(); + QSGGlyphNode *glyphNode = addGlyphs(position + blockPosition + QPointF(0, font.ascent()), + glyphs, color, style, styleColor); + + QPointF baseLine = glyphNode->baseLine(); + qreal width = glyphNode->boundingRect().width(); + addTextDecorations(baseLine, font, color, width, + glyphs.overline(), glyphs.strikeOut(), glyphs.underline()); + } + } + + ++it; + } +} + +void QSGTextNode::deleteContent() +{ + while (childCount() > 0) + delete childAtIndex(0); +} + +#if 0 +void QSGTextNode::updateNodes() +{ + return; + deleteContent(); + if (m_text.isEmpty()) + return; + + if (m_usePixmapCache) { + // ### gunnar: port properly +// QPixmap pixmap = generatedPixmap(); +// if (pixmap.isNull()) +// return; + +// QSGImageNode *pixmapNode = m_context->createImageNode(); +// pixmapNode->setRect(pixmap.rect()); +// pixmapNode->setSourceRect(pixmap.rect()); +// pixmapNode->setOpacity(m_opacity); +// pixmapNode->setClampToEdge(true); +// pixmapNode->setLinearFiltering(m_linearFiltering); + +// appendChildNode(pixmapNode); + } else { + if (m_text.isEmpty()) + return; + + // Implement styling by drawing text several times at slight shifts. shiftForStyle + // contains the sequence of shifted positions at which to draw the text. All except + // the last will be drawn with styleColor. + QList<QPointF> shiftForStyle; + switch (m_textStyle) { + case OutlineTextStyle: + // ### Should be made faster by implementing outline material + shiftForStyle << QPointF(-1, 0); + shiftForStyle << QPointF(0, -1); + shiftForStyle << QPointF(1, 0); + shiftForStyle << QPointF(0, 1); + break; + case SunkenTextStyle: + shiftForStyle << QPointF(0, -1); + break; + case RaisedTextStyle: + shiftForStyle << QPointF(0, 1); + break; + default: + break; + } + + shiftForStyle << QPointF(0, 0); // Regular position + while (!shiftForStyle.isEmpty()) { + QPointF shift = shiftForStyle.takeFirst(); + + // Use styleColor for all but last shift + if (m_richText) { + QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor; + + QTextFrame *textFrame = m_textDocument->rootFrame(); + QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft(); + + QTextFrame::iterator it = textFrame->begin(); + while (!it.atEnd()) { + addTextBlock(shift + p, it.currentBlock(), overrideColor); + ++it; + } + } else { + addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty() + ? m_color + : m_styleColor); + } + } + } +} +#endif + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgtextnode_p.h b/src/declarative/items/qsgtextnode_p.h new file mode 100644 index 0000000000..e7bd95faee --- /dev/null +++ b/src/declarative/items/qsgtextnode_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTEXTNODE_P_H +#define QSGTEXTNODE_P_H + +#include <qsgnode.h> +#include <qsgtext_p.h> + +QT_BEGIN_NAMESPACE + +class QTextLayout; +class QSGGlyphNode; +class QTextBlock; +class QColor; +class QTextDocument; +class QSGContext; +class QRawFont; + +class QSGTextNode : public QSGTransformNode +{ +public: + QSGTextNode(QSGContext *); + ~QSGTextNode(); + + static bool isComplexRichText(QTextDocument *); + + void deleteContent(); + void addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color = QColor(), + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + void addTextDocument(const QPointF &position, QTextDocument *textDocument, const QColor &color = QColor(), + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + +private: + 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 QGlyphs &glyphs, const QColor &color, + QSGText::TextStyle style = QSGText::Normal, const QColor &styleColor = QColor()); + void addTextDecorations(const QPointF &position, const QRawFont &font, const QColor &color, + qreal width, bool hasOverline, bool hasStrikeOut, bool hasUnderline); + QSGContext *m_context; +}; + +QT_END_NAMESPACE + +#endif // QSGTEXTNODE_P_H diff --git a/src/declarative/items/qsgtranslate.cpp b/src/declarative/items/qsgtranslate.cpp new file mode 100644 index 0000000000..5f7112bd42 --- /dev/null +++ b/src/declarative/items/qsgtranslate.cpp @@ -0,0 +1,297 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtranslate_p.h" +#include "qsgitem_p.h" + +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +class QSGTranslatePrivate : public QSGTransformPrivate +{ +public: + QSGTranslatePrivate() + : x(0), y(0) {} + + qreal x; + qreal y; +}; + +QSGTranslate::QSGTranslate(QObject *parent) +: QSGTransform(*new QSGTranslatePrivate, parent) +{ +} + +QSGTranslate::~QSGTranslate() +{ +} + +qreal QSGTranslate::x() const +{ + Q_D(const QSGTranslate); + return d->x; +} + +void QSGTranslate::setX(qreal x) +{ + Q_D(QSGTranslate); + if (d->x == x) + return; + d->x = x; + update(); + emit xChanged(); +} + +qreal QSGTranslate::y() const +{ + Q_D(const QSGTranslate); + return d->y; +} +void QSGTranslate::setY(qreal y) +{ + Q_D(QSGTranslate); + if (d->y == y) + return; + d->y = y; + update(); + emit yChanged(); +} + +void QSGTranslate::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QSGTranslate); + matrix->translate(d->x, d->y, 0); +} + +class QSGScalePrivate : public QSGTransformPrivate +{ +public: + QSGScalePrivate() + : xScale(1), yScale(1), zScale(1) {} + QVector3D origin; + qreal xScale; + qreal yScale; + qreal zScale; +}; + +QSGScale::QSGScale(QObject *parent) + : QSGTransform(*new QSGScalePrivate, parent) +{ +} + +QSGScale::~QSGScale() +{ +} + +QVector3D QSGScale::origin() const +{ + Q_D(const QSGScale); + return d->origin; +} +void QSGScale::setOrigin(const QVector3D &point) +{ + Q_D(QSGScale); + if (d->origin == point) + return; + d->origin = point; + update(); + emit originChanged(); +} + +qreal QSGScale::xScale() const +{ + Q_D(const QSGScale); + return d->xScale; +} +void QSGScale::setXScale(qreal scale) +{ + Q_D(QSGScale); + if (d->xScale == scale) + return; + d->xScale = scale; + update(); + emit xScaleChanged(); + emit scaleChanged(); +} + +qreal QSGScale::yScale() const +{ + Q_D(const QSGScale); + return d->yScale; +} +void QSGScale::setYScale(qreal scale) +{ + Q_D(QSGScale); + if (d->yScale == scale) + return; + d->yScale = scale; + update(); + emit yScaleChanged(); + emit scaleChanged(); +} + +qreal QSGScale::zScale() const +{ + Q_D(const QSGScale); + return d->zScale; +} +void QSGScale::setZScale(qreal scale) +{ + Q_D(QSGScale); + if (d->zScale == scale) + return; + d->zScale = scale; + update(); + emit zScaleChanged(); + emit scaleChanged(); +} + +void QSGScale::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QSGScale); + matrix->translate(d->origin); + matrix->scale(d->xScale, d->yScale, d->zScale); + matrix->translate(-d->origin); +} + +class QSGRotationPrivate : public QSGTransformPrivate +{ +public: + QSGRotationPrivate() + : angle(0), axis(0, 0, 1) {} + QVector3D origin; + qreal angle; + QVector3D axis; +}; + +QSGRotation::QSGRotation(QObject *parent) + : QSGTransform(*new QSGRotationPrivate, parent) +{ +} + +QSGRotation::~QSGRotation() +{ +} + +QVector3D QSGRotation::origin() const +{ + Q_D(const QSGRotation); + return d->origin; +} + +void QSGRotation::setOrigin(const QVector3D &point) +{ + Q_D(QSGRotation); + if (d->origin == point) + return; + d->origin = point; + update(); + emit originChanged(); +} + +qreal QSGRotation::angle() const +{ + Q_D(const QSGRotation); + return d->angle; +} +void QSGRotation::setAngle(qreal angle) +{ + Q_D(QSGRotation); + if (d->angle == angle) + return; + d->angle = angle; + update(); + emit angleChanged(); +} + +QVector3D QSGRotation::axis() const +{ + Q_D(const QSGRotation); + return d->axis; +} +void QSGRotation::setAxis(const QVector3D &axis) +{ + Q_D(QSGRotation); + if (d->axis == axis) + return; + d->axis = axis; + update(); + emit axisChanged(); +} + +void QSGRotation::setAxis(Qt::Axis axis) +{ + switch (axis) + { + case Qt::XAxis: + setAxis(QVector3D(1, 0, 0)); + break; + case Qt::YAxis: + setAxis(QVector3D(0, 1, 0)); + break; + case Qt::ZAxis: + setAxis(QVector3D(0, 0, 1)); + break; + } +} + +struct QGraphicsRotation { + static inline void projectedRotate(QMatrix4x4 *matrix, qreal angle, qreal x, qreal y, qreal z) + { + matrix->projectedRotate(angle, x, y, z); + } +}; + +void QSGRotation::applyTo(QMatrix4x4 *matrix) const +{ + Q_D(const QSGRotation); + + if (d->angle == 0. || d->axis.isNull()) + return; + + matrix->translate(d->origin); + QGraphicsRotation::projectedRotate(matrix, d->angle, d->axis.x(), d->axis.y(), d->axis.z()); + matrix->translate(-d->origin); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgtranslate_p.h b/src/declarative/items/qsgtranslate_p.h new file mode 100644 index 0000000000..de05778b1e --- /dev/null +++ b/src/declarative/items/qsgtranslate_p.h @@ -0,0 +1,162 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTRANSLATE_P_H +#define QSGTRANSLATE_P_H + +#include "qsgitem.h" + +#include <QtGui/qmatrix4x4.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGTranslatePrivate; +class Q_AUTOTEST_EXPORT QSGTranslate : public QSGTransform +{ + Q_OBJECT + + Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged) + Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged) + +public: + QSGTranslate(QObject *parent = 0); + ~QSGTranslate(); + + qreal x() const; + void setX(qreal); + + qreal y() const; + void setY(qreal); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void xChanged(); + void yChanged(); + +private: + Q_DECLARE_PRIVATE(QSGTranslate) + Q_DISABLE_COPY(QSGTranslate) +}; + +class QSGScalePrivate; +class Q_AUTOTEST_EXPORT QSGScale : public QSGTransform +{ + Q_OBJECT + + Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged) + Q_PROPERTY(qreal xScale READ xScale WRITE setXScale NOTIFY xScaleChanged) + Q_PROPERTY(qreal yScale READ yScale WRITE setYScale NOTIFY yScaleChanged) + Q_PROPERTY(qreal zScale READ zScale WRITE setZScale NOTIFY zScaleChanged) +public: + QSGScale(QObject *parent = 0); + ~QSGScale(); + + QVector3D origin() const; + void setOrigin(const QVector3D &point); + + qreal xScale() const; + void setXScale(qreal); + + qreal yScale() const; + void setYScale(qreal); + + qreal zScale() const; + void setZScale(qreal); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void originChanged(); + void xScaleChanged(); + void yScaleChanged(); + void zScaleChanged(); + void scaleChanged(); + +private: + Q_DECLARE_PRIVATE(QSGScale) +}; + +class QSGRotationPrivate; +class Q_AUTOTEST_EXPORT QSGRotation : public QSGTransform +{ + Q_OBJECT + + Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged) + Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged) + Q_PROPERTY(QVector3D axis READ axis WRITE setAxis NOTIFY axisChanged) +public: + QSGRotation(QObject *parent = 0); + ~QSGRotation(); + + QVector3D origin() const; + void setOrigin(const QVector3D &point); + + qreal angle() const; + void setAngle(qreal); + + QVector3D axis() const; + void setAxis(const QVector3D &axis); + void setAxis(Qt::Axis axis); + + void applyTo(QMatrix4x4 *matrix) const; + +Q_SIGNALS: + void originChanged(); + void angleChanged(); + void axisChanged(); + +private: + Q_DECLARE_PRIVATE(QSGRotation) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGTranslate) + +QT_END_HEADER + +#endif diff --git a/src/declarative/items/qsgview.cpp b/src/declarative/items/qsgview.cpp new file mode 100644 index 0000000000..1169c59a1b --- /dev/null +++ b/src/declarative/items/qsgview.cpp @@ -0,0 +1,466 @@ +// Commit: 55c4d94dfea78951f3371d3697a3cb28539b3012 +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgview.h" + +#include "qsgcanvas_p.h" +#include "qsgitem_p.h" +#include "qsgitemchangelistener_p.h" + +#include <private/qdeclarativedebugtrace_p.h> + +#include <QtDeclarative/qdeclarativeengine.h> +#include <private/qdeclarativeengine_p.h> +#include <QtCore/qbasictimer.h> + +// XXX todo - This whole class should probably be merged with QDeclarativeView for +// maximum seamlessness +QT_BEGIN_NAMESPACE + +DEFINE_BOOL_CONFIG_OPTION(frameRateDebug, QML_SHOW_FRAMERATE) + +class QSGViewPrivate : public QSGCanvasPrivate, + public QSGItemChangeListener +{ + Q_DECLARE_PUBLIC(QSGView) +public: + QSGViewPrivate(); + ~QSGViewPrivate(); + + void execute(); + void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry); + void initResize(); + void updateSize(); + void setRootObject(QObject *); + + void init(); + + QSize rootObjectSize() const; + + QPointer<QSGItem> root; + + QUrl source; + + QDeclarativeEngine engine; + QDeclarativeComponent *component; + QBasicTimer resizetimer; + + QSGView::ResizeMode resizeMode; + QSize initialSize; + QElapsedTimer frameTimer; +}; + +void QSGViewPrivate::init() +{ + q_func()->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred); + QDeclarativeEnginePrivate::get(&engine)->sgContext = QSGCanvasPrivate::context; +} + +QSGViewPrivate::QSGViewPrivate() +: root(0), component(0), resizeMode(QSGView::SizeViewToRootObject), initialSize(0,0) +{ +} + +QSGViewPrivate::~QSGViewPrivate() +{ + delete root; +} + +void QSGViewPrivate::execute() +{ + Q_Q(QSGView); + if (root) { + delete root; + root = 0; + } + if (component) { + delete component; + component = 0; + } + if (!source.isEmpty()) { + component = new QDeclarativeComponent(&engine, source, q); + if (!component->isLoading()) { + q->continueExecute(); + } else { + QObject::connect(component, SIGNAL(statusChanged(QDeclarativeComponent::Status)), + q, SLOT(continueExecute())); + } + } +} + +void QSGViewPrivate::itemGeometryChanged(QSGItem *resizeItem, const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_Q(QSGView); + if (resizeItem == root && resizeMode == QSGView::SizeViewToRootObject) { + // wait for both width and height to be changed + resizetimer.start(0,q); + } + QSGItemChangeListener::itemGeometryChanged(resizeItem, newGeometry, oldGeometry); +} + +QSGView::QSGView(QWidget *parent, Qt::WindowFlags f) +: QSGCanvas(*(new QSGViewPrivate), parent, f) +{ + d_func()->init(); +} + +QSGView::QSGView(const QGLFormat &format, QWidget *parent, Qt::WindowFlags f) +: QSGCanvas(*(new QSGViewPrivate), format, parent, f) +{ + d_func()->init(); +} + +QSGView::QSGView(const QUrl &source, QWidget *parent, Qt::WindowFlags f) +: QSGCanvas(*(new QSGViewPrivate), parent, f) +{ + d_func()->init(); + setSource(source); +} + +QSGView::QSGView(const QUrl &source, const QGLFormat &format, QWidget *parent, Qt::WindowFlags f) +: QSGCanvas(*(new QSGViewPrivate), format, parent, f) +{ + d_func()->init(); + setSource(source); +} + +QSGView::~QSGView() +{ +} + +void QSGView::setSource(const QUrl& url) +{ + Q_D(QSGView); + d->source = url; + d->execute(); +} + +QUrl QSGView::source() const +{ + Q_D(const QSGView); + return d->source; +} + +QDeclarativeEngine* QSGView::engine() const +{ + Q_D(const QSGView); + return const_cast<QDeclarativeEngine *>(&d->engine); +} + +QDeclarativeContext* QSGView::rootContext() const +{ + Q_D(const QSGView); + return d->engine.rootContext(); +} + +QSGView::Status QSGView::status() const +{ + Q_D(const QSGView); + if (!d->component) + return QSGView::Null; + + return QSGView::Status(d->component->status()); +} + +QList<QDeclarativeError> QSGView::errors() const +{ + Q_D(const QSGView); + if (d->component) + return d->component->errors(); + return QList<QDeclarativeError>(); +} + +void QSGView::setResizeMode(ResizeMode mode) +{ + Q_D(QSGView); + if (d->resizeMode == mode) + return; + + if (d->root) { + if (d->resizeMode == SizeViewToRootObject) { + QSGItemPrivate *p = QSGItemPrivate::get(d->root); + p->removeItemChangeListener(d, QSGItemPrivate::Geometry); + } + } + + d->resizeMode = mode; + if (d->root) { + d->initResize(); + } +} + +void QSGViewPrivate::initResize() +{ + if (root) { + if (resizeMode == QSGView::SizeViewToRootObject) { + QSGItemPrivate *p = QSGItemPrivate::get(root); + p->addItemChangeListener(this, QSGItemPrivate::Geometry); + } + } + updateSize(); +} + +void QSGViewPrivate::updateSize() +{ + Q_Q(QSGView); + if (!root) + return; + + if (resizeMode == QSGView::SizeViewToRootObject) { + QSize newSize = QSize(root->width(), root->height()); + if (newSize.isValid() && newSize != q->size()) { + q->resize(newSize); + } + } else if (resizeMode == QSGView::SizeRootObjectToView) { + if (!qFuzzyCompare(q->width(), root->width())) + root->setWidth(q->width()); + if (!qFuzzyCompare(q->height(), root->height())) + root->setHeight(q->height()); + } + + q->updateGeometry(); +} + +QSize QSGViewPrivate::rootObjectSize() const +{ + QSize rootObjectSize(0,0); + int widthCandidate = -1; + int heightCandidate = -1; + if (root) { + widthCandidate = root->width(); + heightCandidate = root->height(); + } + if (widthCandidate > 0) { + rootObjectSize.setWidth(widthCandidate); + } + if (heightCandidate > 0) { + rootObjectSize.setHeight(heightCandidate); + } + return rootObjectSize; +} + +QSGView::ResizeMode QSGView::resizeMode() const +{ + Q_D(const QSGView); + return d->resizeMode; +} + +/*! + \internal + */ +void QSGView::continueExecute() +{ + Q_D(QSGView); + disconnect(d->component, SIGNAL(statusChanged(QDeclarativeComponent::Status)), this, SLOT(continueExecute())); + + if (d->component->isError()) { + QList<QDeclarativeError> errorList = d->component->errors(); + foreach (const QDeclarativeError &error, errorList) { + qWarning() << error; + } + emit statusChanged(status()); + return; + } + + QObject *obj = d->component->create(); + + if(d->component->isError()) { + QList<QDeclarativeError> errorList = d->component->errors(); + foreach (const QDeclarativeError &error, errorList) { + qWarning() << error; + } + emit statusChanged(status()); + return; + } + + d->setRootObject(obj); + emit statusChanged(status()); +} + + +/*! + \internal +*/ +void QSGViewPrivate::setRootObject(QObject *obj) +{ + Q_Q(QSGView); + if (root == obj) + return; + if (QSGItem *sgItem = qobject_cast<QSGItem *>(obj)) { + root = sgItem; + sgItem->setParentItem(q->QSGCanvas::rootItem()); + } else { + qWarning() << "QSGView only supports loading of root objects that derive from QSGItem." << endl + << endl + << "If your example is using QML 2, (such as qmlscene) and the .qml file you" << endl + << "loaded has 'import QtQuick 1.0' or 'import Qt 4.7', this error will occur." << endl + << endl + << "To load files with 'import QtQuick 1.0' with QML 2, specify:" << endl + << " QMLSCENE_IMPORT_NAME=quick1" << endl + << "on as an environment variable prior to launching the application." << endl + << endl + << "To load files with 'import Qt 4.7' with QML 2, specify:" << endl + << " QMLSCENE_IMPORT_NAME=qt" << endl + << "on as an environment variable prior to launching the application." << endl; + delete obj; + root = 0; + } + + if (root) { + initialSize = rootObjectSize(); + if (initialSize != q->size()) { + if (!(q->parentWidget() && q->parentWidget()->layout())) { + q->resize(initialSize); + } + } + initResize(); + } +} + +/*! + \internal + If the \l {QTimerEvent} {timer event} \a e is this + view's resize timer, sceneResized() is emitted. + */ +void QSGView::timerEvent(QTimerEvent* e) +{ + Q_D(QSGView); + if (!e || e->timerId() == d->resizetimer.timerId()) { + d->updateSize(); + d->resizetimer.stop(); + } +} + +/*! + \internal + Preferred size follows the root object geometry. +*/ +QSize QSGView::sizeHint() const +{ + Q_D(const QSGView); + QSize rootObjectSize = d->rootObjectSize(); + if (rootObjectSize.isEmpty()) { + return size(); + } else { + return rootObjectSize; + } +} + +QSize QSGView::initialSize() const +{ + Q_D(const QSGView); + return d->initialSize; +} + +QSGItem *QSGView::rootObject() const +{ + Q_D(const QSGView); + return d->root; +} + +/*! + \internal + This function handles the \l {QResizeEvent} {resize event} + \a e. + */ +void QSGView::resizeEvent(QResizeEvent *e) +{ + Q_D(QSGView); + if (d->resizeMode == SizeRootObjectToView) + d->updateSize(); + + QSGCanvas::resizeEvent(e); +} + +/*! + \internal +*/ +void QSGView::paintEvent(QPaintEvent *event) +{ + Q_D(QSGView); + int time = 0; + if (frameRateDebug()) + time = d->frameTimer.restart(); + + QSGCanvas::paintEvent(event); + + if (frameRateDebug()) + qDebug() << "paintEvent:" << d->frameTimer.elapsed() << "time since last frame:" << time; +} + +void QSGView::keyPressEvent(QKeyEvent *e) +{ + QDeclarativeDebugTrace::addEvent(QDeclarativeDebugTrace::Key); + + QSGCanvas::keyPressEvent(e); +} + +void QSGView::keyReleaseEvent(QKeyEvent *e) +{ + QDeclarativeDebugTrace::addEvent(QDeclarativeDebugTrace::Key); + + QSGCanvas::keyReleaseEvent(e); +} + +void QSGView::mouseMoveEvent(QMouseEvent *e) +{ + QDeclarativeDebugTrace::addEvent(QDeclarativeDebugTrace::Mouse); + + QSGCanvas::mouseMoveEvent(e); +} + +void QSGView::mousePressEvent(QMouseEvent *e) +{ + QDeclarativeDebugTrace::addEvent(QDeclarativeDebugTrace::Mouse); + + QSGCanvas::mousePressEvent(e); +} + +void QSGView::mouseReleaseEvent(QMouseEvent *e) +{ + QDeclarativeDebugTrace::addEvent(QDeclarativeDebugTrace::Mouse); + + QSGCanvas::mouseReleaseEvent(e); +} + + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgview.h b/src/declarative/items/qsgview.h new file mode 100644 index 0000000000..8e174b7170 --- /dev/null +++ b/src/declarative/items/qsgview.h @@ -0,0 +1,120 @@ +// Commit: 0b83a2161261be525f01359397ab1c8c34827749 +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGVIEW_H +#define QSGVIEW_H + +#include <QtCore/qurl.h> +#include <qsgcanvas.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QDeclarativeEngine; +class QDeclarativeContext; +class QDeclarativeError; +class QSGItem; + +class QSGViewPrivate; +class Q_DECLARATIVE_EXPORT QSGView : public QSGCanvas +{ + Q_OBJECT + Q_PROPERTY(ResizeMode resizeMode READ resizeMode WRITE setResizeMode) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QUrl source READ source WRITE setSource DESIGNABLE true) + Q_ENUMS(ResizeMode Status) +public: + explicit QSGView(QWidget *parent = 0, Qt::WindowFlags f = 0); + explicit QSGView(const QGLFormat &format, QWidget *parent = 0, Qt::WindowFlags f = 0); + QSGView(const QUrl &source, QWidget *parent = 0, Qt::WindowFlags f = 0); + QSGView(const QUrl &source, const QGLFormat &format, QWidget *parent = 0, Qt::WindowFlags f = 0); + virtual ~QSGView(); + + QUrl source() const; + void setSource(const QUrl&); + + QDeclarativeEngine* engine() const; + QDeclarativeContext* rootContext() const; + + QSGItem *rootObject() const; + + enum ResizeMode { SizeViewToRootObject, SizeRootObjectToView }; + ResizeMode resizeMode() const; + void setResizeMode(ResizeMode); + + enum Status { Null, Ready, Loading, Error }; + Status status() const; + + QList<QDeclarativeError> errors() const; + + QSize sizeHint() const; + QSize initialSize() const; + +Q_SIGNALS: + void statusChanged(QSGView::Status); + +private Q_SLOTS: + void continueExecute(); + +protected: + virtual void resizeEvent(QResizeEvent *); + virtual void paintEvent(QPaintEvent *event); + virtual void timerEvent(QTimerEvent*); + + virtual void keyPressEvent(QKeyEvent *); + virtual void keyReleaseEvent(QKeyEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); +private: + Q_DISABLE_COPY(QSGView) + Q_DECLARE_PRIVATE(QSGView) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSGVIEW_H diff --git a/src/declarative/items/qsgvisualitemmodel.cpp b/src/declarative/items/qsgvisualitemmodel.cpp new file mode 100644 index 0000000000..c7628b230e --- /dev/null +++ b/src/declarative/items/qsgvisualitemmodel.cpp @@ -0,0 +1,1247 @@ +// Commit: 45153a37e4d9e39e8c326a0f33ea17be49bb29e2 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgvisualitemmodel_p.h" +#include "qsgitem.h" + +#include <QtDeclarative/qdeclarativecontext.h> +#include <QtDeclarative/qdeclarativeengine.h> +#include <QtDeclarative/qdeclarativeexpression.h> +#include <QtDeclarative/qdeclarativeinfo.h> + +#include <private/qdeclarativecontext_p.h> +#include <private/qdeclarativepackage_p.h> +#include <private/qdeclarativeopenmetaobject_p.h> +#include <private/qdeclarativelistaccessor_p.h> +#include <private/qdeclarativedata_p.h> +#include <private/qdeclarativepropertycache_p.h> +#include <private/qdeclarativeguard_p.h> +#include <private/qdeclarativeglobal_p.h> +#include <private/qlistmodelinterface_p.h> +#include <private/qmetaobjectbuilder_p.h> +#include <private/qobject_p.h> + +#include <QtCore/qhash.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +QHash<QObject*, QSGVisualItemModelAttached*> QSGVisualItemModelAttached::attachedProperties; + + +class QSGVisualItemModelPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QSGVisualItemModel) +public: + QSGVisualItemModelPrivate() : QObjectPrivate() {} + + static void children_append(QDeclarativeListProperty<QSGItem> *prop, QSGItem *item) { + QDeclarative_setParent_noEvent(item, prop->object); + static_cast<QSGVisualItemModelPrivate *>(prop->data)->children.append(Item(item)); + static_cast<QSGVisualItemModelPrivate *>(prop->data)->itemAppended(); + static_cast<QSGVisualItemModelPrivate *>(prop->data)->emitChildrenChanged(); + } + + static int children_count(QDeclarativeListProperty<QSGItem> *prop) { + return static_cast<QSGVisualItemModelPrivate *>(prop->data)->children.count(); + } + + static QSGItem *children_at(QDeclarativeListProperty<QSGItem> *prop, int index) { + return static_cast<QSGVisualItemModelPrivate *>(prop->data)->children.at(index).item; + } + + void itemAppended() { + Q_Q(QSGVisualItemModel); + QSGVisualItemModelAttached *attached = QSGVisualItemModelAttached::properties(children.last().item); + attached->setIndex(children.count()-1); + emit q->itemsInserted(children.count()-1, 1); + emit q->countChanged(); + } + + void emitChildrenChanged() { + Q_Q(QSGVisualItemModel); + emit q->childrenChanged(); + } + + int indexOf(QSGItem *item) const { + for (int i = 0; i < children.count(); ++i) + if (children.at(i).item == item) + return i; + return -1; + } + + class Item { + public: + Item(QSGItem *i) : item(i), ref(0) {} + + void addRef() { ++ref; } + bool deref() { return --ref == 0; } + + QSGItem *item; + int ref; + }; + + QList<Item> children; +}; + +QSGVisualItemModel::QSGVisualItemModel(QObject *parent) + : QSGVisualModel(*(new QSGVisualItemModelPrivate), parent) +{ +} + +QDeclarativeListProperty<QSGItem> QSGVisualItemModel::children() +{ + Q_D(QSGVisualItemModel); + return QDeclarativeListProperty<QSGItem>(this, d, d->children_append, + d->children_count, d->children_at); +} + +int QSGVisualItemModel::count() const +{ + Q_D(const QSGVisualItemModel); + return d->children.count(); +} + +bool QSGVisualItemModel::isValid() const +{ + return true; +} + +QSGItem *QSGVisualItemModel::item(int index, bool) +{ + Q_D(QSGVisualItemModel); + QSGVisualItemModelPrivate::Item &item = d->children[index]; + item.addRef(); + return item.item; +} + +QSGVisualModel::ReleaseFlags QSGVisualItemModel::release(QSGItem *item) +{ + Q_D(QSGVisualItemModel); + int idx = d->indexOf(item); + if (idx >= 0) { + if (d->children[idx].deref()) { + // XXX todo - the original did item->scene()->removeItem(). Why? + item->setParentItem(0); + QDeclarative_setParent_noEvent(item, this); + } + } + return 0; +} + +bool QSGVisualItemModel::completePending() const +{ + return false; +} + +void QSGVisualItemModel::completeItem() +{ + // Nothing to do +} + +QString QSGVisualItemModel::stringValue(int index, const QString &name) +{ + Q_D(QSGVisualItemModel); + if (index < 0 || index >= d->children.count()) + return QString(); + return QDeclarativeEngine::contextForObject(d->children.at(index).item)->contextProperty(name).toString(); +} + +int QSGVisualItemModel::indexOf(QSGItem *item, QObject *) const +{ + Q_D(const QSGVisualItemModel); + return d->indexOf(item); +} + +QSGVisualItemModelAttached *QSGVisualItemModel::qmlAttachedProperties(QObject *obj) +{ + return QSGVisualItemModelAttached::properties(obj); +} + +//============================================================================ + +class VDMDelegateDataType : public QDeclarativeOpenMetaObjectType +{ +public: + VDMDelegateDataType(const QMetaObject *base, QDeclarativeEngine *engine) : QDeclarativeOpenMetaObjectType(base, engine) {} + + void propertyCreated(int, QMetaPropertyBuilder &prop) { + prop.setWritable(false); + } +}; + +class QSGVisualDataModelParts; +class QSGVisualDataModelData; +class QSGVisualDataModelPrivate : public QObjectPrivate +{ +public: + QSGVisualDataModelPrivate(QDeclarativeContext *); + + static QSGVisualDataModelPrivate *get(QSGVisualDataModel *m) { + return static_cast<QSGVisualDataModelPrivate *>(QObjectPrivate::get(m)); + } + + QDeclarativeGuard<QListModelInterface> m_listModelInterface; + QDeclarativeGuard<QAbstractItemModel> m_abstractItemModel; + QDeclarativeGuard<QSGVisualDataModel> m_visualItemModel; + QString m_part; + + QDeclarativeComponent *m_delegate; + 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); + } + } + } + } + } + } + + 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_modelDataPropId = m_delegateDataType->createProperty("modelData") - m_delegateDataType->propertyOffset(); + m_metaDataCreated = true; + } + } + } + + struct ObjectRef { + ObjectRef(QObject *object=0) : obj(object), ref(1) {} + QObject *obj; + int ref; + }; + class Cache : public QHash<int, ObjectRef> { + public: + QObject *getItem(int index) { + QObject *item = 0; + QHash<int,ObjectRef>::iterator it = find(index); + if (it != end()) { + (*it).ref++; + item = (*it).obj; + } + return item; + } + QObject *item(int index) { + QObject *item = 0; + QHash<int, ObjectRef>::const_iterator it = find(index); + if (it != end()) + item = (*it).obj; + return item; + } + void insertItem(int index, QObject *obj) { + insert(index, ObjectRef(obj)); + } + bool releaseItem(QObject *obj) { + QHash<int, ObjectRef>::iterator it = begin(); + for (; it != end(); ++it) { + ObjectRef &objRef = *it; + if (objRef.obj == obj) { + if (--objRef.ref == 0) { + erase(it); + return true; + } + break; + } + } + return false; + } + }; + + int modelCount() const { + if (m_visualItemModel) + return m_visualItemModel->count(); + if (m_listModelInterface) + return m_listModelInterface->count(); + if (m_abstractItemModel) + return m_abstractItemModel->rowCount(m_root); + if (m_listAccessor) + return m_listAccessor->count(); + return 0; + } + + Cache m_cache; + QHash<QObject *, QDeclarativePackage*> m_packaged; + + QSGVisualDataModelParts *m_parts; + friend class QSGVisualItemParts; + + VDMDelegateDataType *m_delegateDataType; + friend class QSGVisualDataModelData; + bool m_metaDataCreated : 1; + bool m_metaDataCacheable : 1; + bool m_delegateValidated : 1; + bool m_completePending : 1; + + QSGVisualDataModelData *data(QObject *item); + + QVariant m_modelVariant; + QDeclarativeListAccessor *m_listAccessor; + + 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; +}; + +class QSGVisualDataModelData : public QObject +{ +Q_OBJECT +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; + int m_index; + QDeclarativeGuard<QSGVisualDataModel> m_model; + QSGVisualDataModelDataMetaObject *m_meta; +}; + +int QSGVisualDataModelData::propForRole(int id) const +{ + 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; + + return -1; +} + +void QSGVisualDataModelData::setValue(int id, const QVariant &val) +{ + m_meta->setValue(id, val); +} + +int QSGVisualDataModelDataMetaObject::createProperty(const char *name, const char *type) +{ + QSGVisualDataModelData *data = + static_cast<QSGVisualDataModelData *>(object()); + + if (!data->m_model) + return -1; + + QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(data->m_model); + if (data->m_index < 0 || data->m_index >= model->modelCount()) + return -1; + + 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); + } + } + return -1; +} + +QVariant QSGVisualDataModelDataMetaObject::initialValue(int propId) +{ + QSGVisualDataModelData *data = + static_cast<QSGVisualDataModelData *>(object()); + + Q_ASSERT(data->m_model); + QSGVisualDataModelPrivate *model = QSGVisualDataModelPrivate::get(data->m_model); + + 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); + } else { + // return any property of a single object instance. + QObject *object = model->m_listAccessor->at(data->m_index).value<QObject*>(); + return object->property(propName); + } + } 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; + } + } 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()); + } + } + } + Q_ASSERT(!"Can never be reached"); + return QVariant(); +} + +QSGVisualDataModelData::QSGVisualDataModelData(int index, + QSGVisualDataModel *model) +: m_index(index), m_model(model), +m_meta(new QSGVisualDataModelDataMetaObject(this, QSGVisualDataModelPrivate::get(model)->m_delegateDataType)) +{ + ensureProperties(); +} + +QSGVisualDataModelData::~QSGVisualDataModelData() +{ +} + +void QSGVisualDataModelData::ensureProperties() +{ + 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 +{ + return m_index; +} + +// This is internal only - it should not be set from qml +void QSGVisualDataModelData::setIndex(int index) +{ + m_index = index; + emit indexChanged(); +} + +//--------------------------------------------------------------------------- + +class QSGVisualDataModelPartsMetaObject : public QDeclarativeOpenMetaObject +{ +public: + QSGVisualDataModelPartsMetaObject(QObject *parent) + : QDeclarativeOpenMetaObject(parent) {} + + virtual void propertyCreated(int, QMetaPropertyBuilder &); + virtual QVariant initialValue(int); +}; + +class QSGVisualDataModelParts : public QObject +{ +Q_OBJECT +public: + QSGVisualDataModelParts(QSGVisualDataModel *parent); + +private: + friend class QSGVisualDataModelPartsMetaObject; + QSGVisualDataModel *model; +}; + +void QSGVisualDataModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop) +{ + prop.setWritable(false); +} + +QVariant QSGVisualDataModelPartsMetaObject::initialValue(int id) +{ + QSGVisualDataModel *m = new QSGVisualDataModel; + m->setParent(object()); + m->setPart(QString::fromUtf8(name(id))); + m->setModel(QVariant::fromValue(static_cast<QSGVisualDataModelParts *>(object())->model)); + + QVariant var = QVariant::fromValue((QObject *)m); + return var; +} + +QSGVisualDataModelParts::QSGVisualDataModelParts(QSGVisualDataModel *parent) +: QObject(parent), model(parent) +{ + new QSGVisualDataModelPartsMetaObject(this); +} + +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) +{ +} + +QSGVisualDataModelData *QSGVisualDataModelPrivate::data(QObject *item) +{ + QSGVisualDataModelData *dataItem = + item->findChild<QSGVisualDataModelData *>(); + Q_ASSERT(dataItem); + return dataItem; +} + +//--------------------------------------------------------------------------- + +QSGVisualDataModel::QSGVisualDataModel() +: QSGVisualModel(*(new QSGVisualDataModelPrivate(0))) +{ +} + +QSGVisualDataModel::QSGVisualDataModel(QDeclarativeContext *ctxt, QObject *parent) +: QSGVisualModel(*(new QSGVisualDataModelPrivate(ctxt)), parent) +{ +} + +QSGVisualDataModel::~QSGVisualDataModel() +{ + Q_D(QSGVisualDataModel); + if (d->m_listAccessor) + delete d->m_listAccessor; + if (d->m_delegateDataType) + d->m_delegateDataType->release(); +} + +QVariant QSGVisualDataModel::model() const +{ + Q_D(const QSGVisualDataModel); + return d->m_modelVariant; +} + +void QSGVisualDataModel::setModel(const QVariant &model) +{ + Q_D(QSGVisualDataModel); + delete d->m_listAccessor; + d->m_listAccessor = 0; + d->m_modelVariant = model; + if (d->m_listModelInterface) { + // Assume caller has released all items. + QObject::disconnect(d->m_listModelInterface, SIGNAL(itemsChanged(int,int,QList<int>)), + this, SLOT(_q_itemsChanged(int,int,QList<int>))); + QObject::disconnect(d->m_listModelInterface, SIGNAL(itemsInserted(int,int)), + this, SLOT(_q_itemsInserted(int,int))); + QObject::disconnect(d->m_listModelInterface, SIGNAL(itemsRemoved(int,int)), + this, SLOT(_q_itemsRemoved(int,int))); + QObject::disconnect(d->m_listModelInterface, SIGNAL(itemsMoved(int,int,int)), + this, SLOT(_q_itemsMoved(int,int,int))); + d->m_listModelInterface = 0; + } else if (d->m_abstractItemModel) { + QObject::disconnect(d->m_abstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex,int,int))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(_q_rowsMoved(QModelIndex,int,int,QModelIndex,int))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(modelReset()), this, SLOT(_q_modelReset())); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(layoutChanged()), this, SLOT(_q_layoutChanged())); + d->m_abstractItemModel = 0; + } else if (d->m_visualItemModel) { + QObject::disconnect(d->m_visualItemModel, SIGNAL(itemsInserted(int,int)), + this, SIGNAL(itemsInserted(int,int))); + QObject::disconnect(d->m_visualItemModel, SIGNAL(itemsRemoved(int,int)), + this, SIGNAL(itemsRemoved(int,int))); + QObject::disconnect(d->m_visualItemModel, SIGNAL(itemsMoved(int,int,int)), + this, SIGNAL(itemsMoved(int,int,int))); + QObject::disconnect(d->m_visualItemModel, SIGNAL(createdPackage(int,QDeclarativePackage*)), + this, SLOT(_q_createdPackage(int,QDeclarativePackage*))); + QObject::disconnect(d->m_visualItemModel, SIGNAL(destroyingPackage(QDeclarativePackage*)), + this, SLOT(_q_destroyingPackage(QDeclarativePackage*))); + d->m_visualItemModel = 0; + } + + d->m_roles.clear(); + 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)); + + QObject *object = qvariant_cast<QObject *>(model); + if (object && (d->m_listModelInterface = qobject_cast<QListModelInterface *>(object))) { + QObject::connect(d->m_listModelInterface, SIGNAL(itemsChanged(int,int,QList<int>)), + this, SLOT(_q_itemsChanged(int,int,QList<int>))); + QObject::connect(d->m_listModelInterface, SIGNAL(itemsInserted(int,int)), + this, SLOT(_q_itemsInserted(int,int))); + QObject::connect(d->m_listModelInterface, SIGNAL(itemsRemoved(int,int)), + 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; + } else if (object && (d->m_abstractItemModel = qobject_cast<QAbstractItemModel *>(object))) { + QObject::connect(d->m_abstractItemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(_q_rowsInserted(QModelIndex,int,int))); + QObject::connect(d->m_abstractItemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); + QObject::connect(d->m_abstractItemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(_q_dataChanged(QModelIndex,QModelIndex))); + QObject::connect(d->m_abstractItemModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + 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; + } + if ((d->m_visualItemModel = qvariant_cast<QSGVisualDataModel *>(model))) { + QObject::connect(d->m_visualItemModel, SIGNAL(itemsInserted(int,int)), + this, SIGNAL(itemsInserted(int,int))); + QObject::connect(d->m_visualItemModel, SIGNAL(itemsRemoved(int,int)), + this, SIGNAL(itemsRemoved(int,int))); + QObject::connect(d->m_visualItemModel, SIGNAL(itemsMoved(int,int,int)), + this, SIGNAL(itemsMoved(int,int,int))); + QObject::connect(d->m_visualItemModel, SIGNAL(createdPackage(int,QDeclarativePackage*)), + this, SLOT(_q_createdPackage(int,QDeclarativePackage*))); + QObject::connect(d->m_visualItemModel, SIGNAL(destroyingPackage(QDeclarativePackage*)), + this, SLOT(_q_destroyingPackage(QDeclarativePackage*))); + return; + } + 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(); + } +} + +QDeclarativeComponent *QSGVisualDataModel::delegate() const +{ + Q_D(const QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->delegate(); + return d->m_delegate; +} + +void QSGVisualDataModel::setDelegate(QDeclarativeComponent *delegate) +{ + Q_D(QSGVisualDataModel); + bool wasValid = d->m_delegate != 0; + d->m_delegate = delegate; + d->m_delegateValidated = false; + if (!wasValid && d->modelCount() && d->m_delegate) { + emit itemsInserted(0, d->modelCount()); + emit countChanged(); + } + if (wasValid && !d->m_delegate && d->modelCount()) { + emit itemsRemoved(0, d->modelCount()); + emit countChanged(); + } +} + +QVariant QSGVisualDataModel::rootIndex() const +{ + Q_D(const QSGVisualDataModel); + return QVariant::fromValue(d->m_root); +} + +void QSGVisualDataModel::setRootIndex(const QVariant &root) +{ + Q_D(QSGVisualDataModel); + QModelIndex modelIndex = qvariant_cast<QModelIndex>(root); + if (d->m_root != modelIndex) { + int oldCount = d->modelCount(); + d->m_root = modelIndex; + if (d->m_abstractItemModel && d->m_abstractItemModel->canFetchMore(modelIndex)) + d->m_abstractItemModel->fetchMore(modelIndex); + int newCount = d->modelCount(); + if (d->m_delegate && oldCount) + emit itemsRemoved(0, oldCount); + if (d->m_delegate && newCount) + emit itemsInserted(0, newCount); + if (newCount != oldCount) + emit countChanged(); + emit rootIndexChanged(); + } +} + +QVariant QSGVisualDataModel::modelIndex(int idx) const +{ + Q_D(const QSGVisualDataModel); + if (d->m_abstractItemModel) + return QVariant::fromValue(d->m_abstractItemModel->index(idx, 0, d->m_root)); + return QVariant::fromValue(QModelIndex()); +} + +QVariant QSGVisualDataModel::parentModelIndex() const +{ + Q_D(const QSGVisualDataModel); + if (d->m_abstractItemModel) + return QVariant::fromValue(d->m_abstractItemModel->parent(d->m_root)); + return QVariant::fromValue(QModelIndex()); +} + +QString QSGVisualDataModel::part() const +{ + Q_D(const QSGVisualDataModel); + return d->m_part; +} + +void QSGVisualDataModel::setPart(const QString &part) +{ + Q_D(QSGVisualDataModel); + d->m_part = part; +} + +int QSGVisualDataModel::count() const +{ + Q_D(const QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->count(); + if (!d->m_delegate) + return 0; + return d->modelCount(); +} + +QSGItem *QSGVisualDataModel::item(int index, bool complete) +{ + Q_D(QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->item(index, d->m_part.toUtf8(), complete); + return item(index, QByteArray(), complete); +} + +/* + Returns ReleaseStatus flags. +*/ +QSGVisualDataModel::ReleaseFlags QSGVisualDataModel::release(QSGItem *item) +{ + Q_D(QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->release(item); + + ReleaseFlags stat = 0; + QObject *obj = item; + bool inPackage = false; + + QHash<QObject*,QDeclarativePackage*>::iterator it = d->m_packaged.find(item); + if (it != d->m_packaged.end()) { + QDeclarativePackage *package = *it; + d->m_packaged.erase(it); + if (d->m_packaged.contains(item)) + stat |= Referenced; + inPackage = true; + obj = package; // fall through and delete + } + + if (d->m_cache.releaseItem(obj)) { + // Remove any bindings to avoid warnings due to parent change. + QObjectPrivate *p = QObjectPrivate::get(obj); + Q_ASSERT(p->declarativeData); + QDeclarativeData *d = static_cast<QDeclarativeData*>(p->declarativeData); + if (d->ownContext && d->context) + d->context->clearContext(); + + if (inPackage) { + emit destroyingPackage(qobject_cast<QDeclarativePackage*>(obj)); + } else { + // XXX todo - the original did item->scene()->removeItem(). Why? + item->setParentItem(0); + } + stat |= Destroyed; + obj->deleteLater(); + } else if (!inPackage) { + stat |= Referenced; + } + + return stat; +} + +QObject *QSGVisualDataModel::parts() +{ + Q_D(QSGVisualDataModel); + if (!d->m_parts) + d->m_parts = new QSGVisualDataModelParts(this); + return d->m_parts; +} + +QSGItem *QSGVisualDataModel::item(int index, const QByteArray &viewId, bool complete) +{ + Q_D(QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->item(index, viewId, complete); + + if (d->modelCount() <= 0 || !d->m_delegate) + return 0; + QObject *nobj = d->m_cache.getItem(index); + bool needComplete = false; + if (!nobj) { + 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); + } + ctxt->setContextProperty(QLatin1String("model"), data); + ctxt->setContextObject(data); + d->m_completePending = false; + nobj = d->m_delegate->beginCreate(ctxt); + if (complete) { + d->m_delegate->completeCreate(); + } else { + d->m_completePending = true; + needComplete = true; + } + if (nobj) { + QDeclarative_setParent_noEvent(ctxt, nobj); + QDeclarative_setParent_noEvent(data, nobj); + d->m_cache.insertItem(index, nobj); + if (QDeclarativePackage *package = qobject_cast<QDeclarativePackage *>(nobj)) + emit createdPackage(index, package); + } else { + delete data; + delete ctxt; + qmlInfo(this, d->m_delegate->errors()) << "Error creating delegate"; + } + } + QSGItem *item = qobject_cast<QSGItem *>(nobj); + if (!item) { + QDeclarativePackage *package = qobject_cast<QDeclarativePackage *>(nobj); + if (package) { + QObject *o = package->part(QString::fromUtf8(viewId)); + item = qobject_cast<QSGItem *>(o); + if (item) + d->m_packaged.insertMulti(item, package); + } + } + if (!item) { + if (needComplete) + d->m_delegate->completeCreate(); + d->m_cache.releaseItem(nobj); + if (!d->m_delegateValidated) { + qmlInfo(d->m_delegate) << QSGVisualDataModel::tr("Delegate component must be Item type."); + d->m_delegateValidated = true; + } + } + if (d->modelCount()-1 == index && d->m_abstractItemModel && d->m_abstractItemModel->canFetchMore(d->m_root)) + d->m_abstractItemModel->fetchMore(d->m_root); + + return item; +} + +bool QSGVisualDataModel::completePending() const +{ + Q_D(const QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->completePending(); + return d->m_completePending; +} + +void QSGVisualDataModel::completeItem() +{ + Q_D(QSGVisualDataModel); + if (d->m_visualItemModel) { + d->m_visualItemModel->completeItem(); + return; + } + + d->m_delegate->completeCreate(); + d->m_completePending = false; +} + +QString QSGVisualDataModel::stringValue(int index, const QString &name) +{ + Q_D(QSGVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->stringValue(index, name); + + if ((!d->m_listModelInterface || !d->m_abstractItemModel) && d->m_listAccessor) { + if (QObject *object = d->m_listAccessor->at(index).value<QObject*>()) + return object->property(name.toUtf8()).toString(); + } + + if ((!d->m_listModelInterface && !d->m_abstractItemModel) || !d->m_delegate) + return QString(); + + QString val; + QObject *data = 0; + bool tempData = false; + + if (QObject *nobj = d->m_cache.item(index)) + data = d->data(nobj); + if (!data) { + data = new QSGVisualDataModelData(index, this); + tempData = true; + } + + QDeclarativeData *ddata = QDeclarativeData::get(data); + if (ddata && ddata->propertyCache) { + QDeclarativePropertyCache::Data *prop = ddata->propertyCache->property(name); + if (prop) { + if (prop->propType == QVariant::String) { + void *args[] = { &val, 0 }; + QMetaObject::metacall(data, QMetaObject::ReadProperty, prop->coreIndex, args); + } else if (prop->propType == qMetaTypeId<QVariant>()) { + QVariant v; + void *args[] = { &v, 0 }; + QMetaObject::metacall(data, QMetaObject::ReadProperty, prop->coreIndex, args); + val = v.toString(); + } + } else { + val = data->property(name.toUtf8()).toString(); + } + } else { + val = data->property(name.toUtf8()).toString(); + } + + if (tempData) + delete data; + + return val; +} + +int QSGVisualDataModel::indexOf(QSGItem *item, QObject *) const +{ + QVariant val = QDeclarativeEngine::contextForObject(item)->contextProperty(QLatin1String("index")); + return val.toInt(); + return -1; +} + +void QSGVisualDataModel::setWatchedRoles(QList<QByteArray> roles) +{ + Q_D(QSGVisualDataModel); + d->watchedRoles = roles; + d->watchedRoleIds.clear(); +} + +void QSGVisualDataModel::_q_itemsChanged(int index, int count, + const QList<int> &roles) +{ + Q_D(QSGVisualDataModel); + bool changed = false; + if (!d->watchedRoles.isEmpty() && d->watchedRoleIds.isEmpty()) { + foreach (QByteArray r, d->watchedRoles) { + if (d->m_roleNames.contains(r)) + d->watchedRoleIds << d->m_roleNames.value(r); + } + } + + for (QHash<int,QSGVisualDataModelPrivate::ObjectRef>::ConstIterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ++iter) { + const int idx = iter.key(); + + 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_roles.count() == 1) { + // Handle the modelData role we add if there is just one role. + int propId = data->modelDataPropertyId(); + if (data->hasValue(propId)) { + int role = d->m_roles.at(0); + 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)); + } + } + } + } + } + if (changed) + emit itemsChanged(index, count); +} + +void QSGVisualDataModel::_q_itemsInserted(int index, int count) +{ + Q_D(QSGVisualDataModel); + if (!count) + return; + // XXX - highly inefficient + QHash<int,QSGVisualDataModelPrivate::ObjectRef> items; + for (QHash<int,QSGVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + + if (iter.key() >= index) { + QSGVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() + count; + iter = d->m_cache.erase(iter); + + items.insert(index, objRef); + + QSGVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + d->m_cache.unite(items); + + emit itemsInserted(index, count); + emit countChanged(); +} + +void QSGVisualDataModel::_q_itemsRemoved(int index, int count) +{ + Q_D(QSGVisualDataModel); + if (!count) + return; + // XXX - highly inefficient + QHash<int, QSGVisualDataModelPrivate::ObjectRef> items; + for (QHash<int, QSGVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + if (iter.key() >= index && iter.key() < index + count) { + QSGVisualDataModelPrivate::ObjectRef objRef = *iter; + iter = d->m_cache.erase(iter); + items.insertMulti(-1, objRef); //XXX perhaps better to maintain separately + QSGVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(-1); + } else if (iter.key() >= index + count) { + QSGVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() - count; + iter = d->m_cache.erase(iter); + items.insert(index, objRef); + QSGVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + + d->m_cache.unite(items); + emit itemsRemoved(index, count); + emit countChanged(); +} + +void QSGVisualDataModel::_q_itemsMoved(int from, int to, int count) +{ + Q_D(QSGVisualDataModel); + // XXX - highly inefficient + QHash<int,QSGVisualDataModelPrivate::ObjectRef> items; + for (QHash<int,QSGVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + + if (iter.key() >= from && iter.key() < from + count) { + QSGVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() - from + to; + iter = d->m_cache.erase(iter); + + items.insert(index, objRef); + + QSGVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + for (QHash<int,QSGVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + + int diff = from > to ? count : -count; + if (iter.key() >= qMin(from,to) && iter.key() < qMax(from+count,to+count)) { + QSGVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() + diff; + iter = d->m_cache.erase(iter); + + items.insert(index, objRef); + + QSGVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + d->m_cache.unite(items); + + emit itemsMoved(from, to, count); +} + +void QSGVisualDataModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end) +{ + Q_D(QSGVisualDataModel); + if (parent == d->m_root) + _q_itemsInserted(begin, end - begin + 1); +} + +void QSGVisualDataModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end) +{ + Q_D(QSGVisualDataModel); + if (parent == d->m_root) + _q_itemsRemoved(begin, end - begin + 1); +} + +void QSGVisualDataModel::_q_rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) +{ + Q_D(QSGVisualDataModel); + const int count = sourceEnd - sourceStart + 1; + if (destinationParent == d->m_root && sourceParent == d->m_root) { + _q_itemsMoved(sourceStart, sourceStart > destinationRow ? destinationRow : destinationRow-1, count); + } else if (sourceParent == d->m_root) { + _q_itemsRemoved(sourceStart, count); + } else if (destinationParent == d->m_root) { + _q_itemsInserted(destinationRow, count); + } +} + +void QSGVisualDataModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end) +{ + Q_D(QSGVisualDataModel); + if (begin.parent() == d->m_root) + _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, d->m_roles); +} + +void QSGVisualDataModel::_q_layoutChanged() +{ + Q_D(QSGVisualDataModel); + _q_itemsChanged(0, count(), d->m_roles); +} + +void QSGVisualDataModel::_q_modelReset() +{ + emit modelReset(); +} + +void QSGVisualDataModel::_q_createdPackage(int index, QDeclarativePackage *package) +{ + Q_D(QSGVisualDataModel); + emit createdItem(index, qobject_cast<QSGItem*>(package->part(d->m_part))); +} + +void QSGVisualDataModel::_q_destroyingPackage(QDeclarativePackage *package) +{ + Q_D(QSGVisualDataModel); + emit destroyingItem(qobject_cast<QSGItem*>(package->part(d->m_part))); +} + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QListModelInterface) + +#include <qsgvisualitemmodel.moc> diff --git a/src/declarative/items/qsgvisualitemmodel_p.h b/src/declarative/items/qsgvisualitemmodel_p.h new file mode 100644 index 0000000000..1f735e7cbc --- /dev/null +++ b/src/declarative/items/qsgvisualitemmodel_p.h @@ -0,0 +1,257 @@ +// Commit: ac5c099cc3c5b8c7eec7a49fdeb8a21037230350 +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** 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. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGVISUALITEMMODEL_P_H +#define QSGVISUALITEMMODEL_P_H + +#include <QtDeclarative/qdeclarative.h> +#include <QtCore/qobject.h> +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +Q_DECLARE_METATYPE(QModelIndex) + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGItem; +class QDeclarativeComponent; +class QDeclarativePackage; +class QSGVisualDataModelPrivate; + +class Q_AUTOTEST_EXPORT QSGVisualModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + +public: + virtual ~QSGVisualModel() {} + + enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 }; + Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag) + + virtual int count() const = 0; + virtual bool isValid() const = 0; + virtual QSGItem *item(int index, bool complete=true) = 0; + virtual ReleaseFlags release(QSGItem *item) = 0; + virtual bool completePending() const = 0; + virtual void completeItem() = 0; + virtual QString stringValue(int, const QString &) = 0; + virtual void setWatchedRoles(QList<QByteArray> roles) = 0; + + virtual int indexOf(QSGItem *item, QObject *objectContext) const = 0; + +Q_SIGNALS: + void countChanged(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int from, int to, int count); + void itemsChanged(int index, int count); + void modelReset(); + void createdItem(int index, QSGItem *item); + void destroyingItem(QSGItem *item); + +protected: + QSGVisualModel(QObjectPrivate &dd, QObject *parent = 0) + : QObject(dd, parent) {} + +private: + Q_DISABLE_COPY(QSGVisualModel) +}; + +class QSGVisualItemModelAttached; +class QSGVisualItemModelPrivate; +class Q_AUTOTEST_EXPORT QSGVisualItemModel : public QSGVisualModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGVisualItemModel) + + Q_PROPERTY(QDeclarativeListProperty<QSGItem> children READ children NOTIFY childrenChanged DESIGNABLE false) + Q_CLASSINFO("DefaultProperty", "children") + +public: + QSGVisualItemModel(QObject *parent=0); + virtual ~QSGVisualItemModel() {} + + virtual int count() const; + virtual bool isValid() const; + virtual QSGItem *item(int index, bool complete=true); + virtual ReleaseFlags release(QSGItem *item); + virtual bool completePending() const; + virtual void completeItem(); + virtual QString stringValue(int index, const QString &role); + virtual void setWatchedRoles(QList<QByteArray>) {} + + virtual int indexOf(QSGItem *item, QObject *objectContext) const; + + QDeclarativeListProperty<QSGItem> children(); + + static QSGVisualItemModelAttached *qmlAttachedProperties(QObject *obj); + +Q_SIGNALS: + void childrenChanged(); + +private: + Q_DISABLE_COPY(QSGVisualItemModel) +}; + + +class Q_AUTOTEST_EXPORT QSGVisualDataModel : public QSGVisualModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGVisualDataModel) + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(QString part READ part WRITE setPart) + Q_PROPERTY(QObject *parts READ parts CONSTANT) + Q_PROPERTY(QVariant rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged) + Q_CLASSINFO("DefaultProperty", "delegate") +public: + QSGVisualDataModel(); + QSGVisualDataModel(QDeclarativeContext *, QObject *parent=0); + virtual ~QSGVisualDataModel(); + + QVariant model() const; + void setModel(const QVariant &); + + QDeclarativeComponent *delegate() const; + void setDelegate(QDeclarativeComponent *); + + QVariant rootIndex() const; + void setRootIndex(const QVariant &root); + + Q_INVOKABLE QVariant modelIndex(int idx) const; + Q_INVOKABLE QVariant parentModelIndex() const; + + QString part() const; + void setPart(const QString &); + + int count() const; + bool isValid() const { return delegate() != 0; } + QSGItem *item(int index, bool complete=true); + QSGItem *item(int index, const QByteArray &, bool complete=true); + ReleaseFlags release(QSGItem *item); + bool completePending() const; + void completeItem(); + virtual QString stringValue(int index, const QString &role); + virtual void setWatchedRoles(QList<QByteArray> roles); + + int indexOf(QSGItem *item, QObject *objectContext) const; + + QObject *parts(); + +Q_SIGNALS: + void createdPackage(int index, QDeclarativePackage *package); + void destroyingPackage(QDeclarativePackage *package); + void rootIndexChanged(); + +private Q_SLOTS: + void _q_itemsChanged(int, int, const QList<int> &); + void _q_itemsInserted(int index, int count); + void _q_itemsRemoved(int index, int count); + void _q_itemsMoved(int from, int to, int count); + void _q_rowsInserted(const QModelIndex &,int,int); + void _q_rowsRemoved(const QModelIndex &,int,int); + void _q_rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int); + void _q_dataChanged(const QModelIndex&,const QModelIndex&); + void _q_layoutChanged(); + void _q_modelReset(); + void _q_createdPackage(int index, QDeclarativePackage *package); + void _q_destroyingPackage(QDeclarativePackage *package); + +private: + Q_DISABLE_COPY(QSGVisualDataModel) +}; + +class QSGVisualItemModelAttached : public QObject +{ + Q_OBJECT + +public: + QSGVisualItemModelAttached(QObject *parent) + : QObject(parent), m_index(0) {} + ~QSGVisualItemModelAttached() { + attachedProperties.remove(parent()); + } + + Q_PROPERTY(int index READ index NOTIFY indexChanged) + int index() const { return m_index; } + void setIndex(int idx) { + if (m_index != idx) { + m_index = idx; + emit indexChanged(); + } + } + + static QSGVisualItemModelAttached *properties(QObject *obj) { + QSGVisualItemModelAttached *rv = attachedProperties.value(obj); + if (!rv) { + rv = new QSGVisualItemModelAttached(obj); + attachedProperties.insert(obj, rv); + } + return rv; + } + +Q_SIGNALS: + void indexChanged(); + +public: + int m_index; + + static QHash<QObject*, QSGVisualItemModelAttached*> attachedProperties; +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QSGVisualModel) +QML_DECLARE_TYPE(QSGVisualItemModel) +QML_DECLARE_TYPEINFO(QSGVisualItemModel, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QSGVisualDataModel) + +QT_END_HEADER + +#endif // QSGVISUALITEMMODEL_P_H diff --git a/src/declarative/items/syncexcludes b/src/declarative/items/syncexcludes new file mode 100644 index 0000000000..ab7a374a5b --- /dev/null +++ b/src/declarative/items/syncexcludes @@ -0,0 +1,11 @@ +qdeclarativegraphicswidget.cpp +qdeclarativegraphicswidget_p.h +qdeclarativetextlayout_p.h +qdeclarativetextlayout.cpp +qdeclarativelayoutitem.cpp +qdeclarativelayoutitem_p.h +qdeclarativefocuspanel.cpp +qdeclarativefocuspanel_p.h +qdeclarativepath_p.h +qdeclarativepath_p_p.h +qdeclarativepath.cpp |