diff options
Diffstat (limited to 'src/declarative/graphicsitems')
82 files changed, 31628 insertions, 0 deletions
diff --git a/src/declarative/graphicsitems/graphicsitems.pri b/src/declarative/graphicsitems/graphicsitems.pri new file mode 100644 index 0000000..eb6e0ad --- /dev/null +++ b/src/declarative/graphicsitems/graphicsitems.pri @@ -0,0 +1,90 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qmlgraphicsanchors_p.h \ + $$PWD/qmlgraphicsanchors_p_p.h \ + $$PWD/qmlgraphicsevents_p_p.h \ + $$PWD/qmlgraphicsflickable_p.h \ + $$PWD/qmlgraphicsflickable_p_p.h \ + $$PWD/qmlgraphicsflipable_p.h \ + $$PWD/qmlgraphicsgridview_p.h \ + $$PWD/qmlgraphicsimage_p.h \ + $$PWD/qmlgraphicsimagebase_p.h \ + $$PWD/qmlgraphicsborderimage_p.h \ + $$PWD/qmlgraphicspainteditem_p.h \ + $$PWD/qmlgraphicspainteditem_p_p.h \ + $$PWD/qmlgraphicsimage_p_p.h \ + $$PWD/qmlgraphicsborderimage_p_p.h \ + $$PWD/qmlgraphicsimagebase_p_p.h \ + $$PWD/qmlgraphicsanimatedimage_p.h \ + $$PWD/qmlgraphicsanimatedimage_p_p.h \ + $$PWD/qmlgraphicsitem.h \ + $$PWD/qmlgraphicsitem_p.h \ + $$PWD/qmlgraphicsfocuspanel_p.h \ + $$PWD/qmlgraphicsfocusscope_p.h \ + $$PWD/qmlgraphicspositioners_p.h \ + $$PWD/qmlgraphicspositioners_p_p.h \ + $$PWD/qmlgraphicsloader_p.h \ + $$PWD/qmlgraphicsloader_p_p.h \ + $$PWD/qmlgraphicsmouseregion_p.h \ + $$PWD/qmlgraphicsmouseregion_p_p.h \ + $$PWD/qmlgraphicspath_p.h \ + $$PWD/qmlgraphicspath_p_p.h \ + $$PWD/qmlgraphicspathview_p.h \ + $$PWD/qmlgraphicspathview_p_p.h \ + $$PWD/qmlgraphicsrectangle_p.h \ + $$PWD/qmlgraphicsrectangle_p_p.h \ + $$PWD/qmlgraphicsrepeater_p.h \ + $$PWD/qmlgraphicsrepeater_p_p.h \ + $$PWD/qmlgraphicsscalegrid_p_p.h \ + $$PWD/qmlgraphicstextinput_p.h \ + $$PWD/qmlgraphicstextinput_p_p.h \ + $$PWD/qmlgraphicstextedit_p.h \ + $$PWD/qmlgraphicstextedit_p_p.h \ + $$PWD/qmlgraphicstext_p.h \ + $$PWD/qmlgraphicstext_p_p.h \ + $$PWD/qmlgraphicsvisualitemmodel_p.h \ + $$PWD/qmlgraphicslistview_p.h \ + $$PWD/qmlgraphicsgraphicsobjectcontainer_p.h \ + $$PWD/qmlgraphicsparticles_p.h \ + $$PWD/qmlgraphicslayoutitem_p.h \ + $$PWD/qmlgraphicsitemchangelistener_p.h \ + $$PWD/qmlgraphicseffects.cpp + +SOURCES += \ + $$PWD/qmlgraphicsanchors.cpp \ + $$PWD/qmlgraphicsevents.cpp \ + $$PWD/qmlgraphicsflickable.cpp \ + $$PWD/qmlgraphicsflipable.cpp \ + $$PWD/qmlgraphicsgridview.cpp \ + $$PWD/qmlgraphicsimage.cpp \ + $$PWD/qmlgraphicsborderimage.cpp \ + $$PWD/qmlgraphicsimagebase.cpp \ + $$PWD/qmlgraphicsanimatedimage.cpp \ + $$PWD/qmlgraphicspainteditem.cpp \ + $$PWD/qmlgraphicsitem.cpp \ + $$PWD/qmlgraphicsfocuspanel.cpp \ + $$PWD/qmlgraphicsfocusscope.cpp \ + $$PWD/qmlgraphicspositioners.cpp \ + $$PWD/qmlgraphicsloader.cpp \ + $$PWD/qmlgraphicsmouseregion.cpp \ + $$PWD/qmlgraphicspath.cpp \ + $$PWD/qmlgraphicspathview.cpp \ + $$PWD/qmlgraphicsrectangle.cpp \ + $$PWD/qmlgraphicsrepeater.cpp \ + $$PWD/qmlgraphicsscalegrid.cpp \ + $$PWD/qmlgraphicstextinput.cpp \ + $$PWD/qmlgraphicstext.cpp \ + $$PWD/qmlgraphicstextedit.cpp \ + $$PWD/qmlgraphicsvisualitemmodel.cpp \ + $$PWD/qmlgraphicslistview.cpp \ + $$PWD/qmlgraphicsgraphicsobjectcontainer.cpp \ + $$PWD/qmlgraphicsparticles.cpp \ + $$PWD/qmlgraphicslayoutitem.cpp \ + +contains(QT_CONFIG, webkit) { + QT+=webkit + SOURCES += $$PWD/qmlgraphicswebview.cpp + HEADERS += $$PWD/qmlgraphicswebview_p.h + HEADERS += $$PWD/qmlgraphicswebview_p_p.h +} diff --git a/src/declarative/graphicsitems/qmlgraphicsanchors.cpp b/src/declarative/graphicsitems/qmlgraphicsanchors.cpp new file mode 100644 index 0000000..93055fc --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsanchors.cpp @@ -0,0 +1,1060 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsanchors_p_p.h" + +#include "qmlgraphicsitem.h" +#include "qmlgraphicsitem_p.h" + +#include <qmlinfo.h> + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsAnchors) + +//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) + +//### const item? +//local position +static qreal position(QmlGraphicsItem *item, QmlGraphicsAnchorLine::AnchorLine anchorLine) +{ + qreal ret = 0.0; + switch(anchorLine) { + case QmlGraphicsAnchorLine::Left: + ret = item->x(); + break; + case QmlGraphicsAnchorLine::Right: + ret = item->x() + item->width(); + break; + case QmlGraphicsAnchorLine::Top: + ret = item->y(); + break; + case QmlGraphicsAnchorLine::Bottom: + ret = item->y() + item->height(); + break; + case QmlGraphicsAnchorLine::HCenter: + ret = item->x() + item->width()/2; + break; + case QmlGraphicsAnchorLine::VCenter: + ret = item->y() + item->height()/2; + break; + case QmlGraphicsAnchorLine::Baseline: + ret = item->y() + item->baselineOffset(); + break; + default: + break; + } + + return ret; +} + +//position when origin is 0,0 +static qreal adjustedPosition(QmlGraphicsItem *item, QmlGraphicsAnchorLine::AnchorLine anchorLine) +{ + int ret = 0; + switch(anchorLine) { + case QmlGraphicsAnchorLine::Left: + ret = 0; + break; + case QmlGraphicsAnchorLine::Right: + ret = item->width(); + break; + case QmlGraphicsAnchorLine::Top: + ret = 0; + break; + case QmlGraphicsAnchorLine::Bottom: + ret = item->height(); + break; + case QmlGraphicsAnchorLine::HCenter: + ret = item->width()/2; + break; + case QmlGraphicsAnchorLine::VCenter: + ret = item->height()/2; + break; + case QmlGraphicsAnchorLine::Baseline: + ret = item->baselineOffset(); + break; + default: + break; + } + + return ret; +} + +/*! + \internal + \class QmlGraphicsAnchors + \ingroup group_layouts + \brief The QmlGraphicsAnchors class provides a way to lay out items relative to other items. + + \warning Currently, only anchoring to siblings or parent is supported. +*/ + +QmlGraphicsAnchors::QmlGraphicsAnchors(QObject *parent) + : QObject(*new QmlGraphicsAnchorsPrivate(0), parent) +{ + qFatal("QmlGraphicsAnchors::QmlGraphicsAnchors(QObject*) called"); +} + +QmlGraphicsAnchors::QmlGraphicsAnchors(QmlGraphicsItem *item, QObject *parent) + : QObject(*new QmlGraphicsAnchorsPrivate(item), parent) +{ +} + +QmlGraphicsAnchors::~QmlGraphicsAnchors() +{ + Q_D(QmlGraphicsAnchors); + 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 QmlGraphicsAnchorsPrivate::fillChanged() +{ + if (!fill || !isItemComplete()) + return; + + if (updatingFill < 2) { + ++updatingFill; + + if (fill == item->parentItem()) { //child-parent + setItemPos(QPointF(leftMargin, topMargin)); + } else if (fill->parentItem() == item->parentItem()) { //siblings + setItemPos(QPointF(fill->x()+leftMargin, fill->y()+topMargin)); + } + setItemWidth(fill->width()-leftMargin-rightMargin); + setItemHeight(fill->height()-topMargin-bottomMargin); + + --updatingFill; + } else { + // ### Make this certain :) + qmlInfo(item) << QmlGraphicsAnchors::tr("Possible anchor loop detected on fill."); + } + +} + +void QmlGraphicsAnchorsPrivate::centerInChanged() +{ + if (!centerIn || fill || !isItemComplete()) + return; + + if (updatingCenterIn < 2) { + ++updatingCenterIn; + + if (centerIn == item->parentItem()) { + QPointF p((item->parentItem()->width() - item->width()) / 2. + hCenterOffset, + (item->parentItem()->height() - item->height()) / 2. + vCenterOffset); + setItemPos(p); + + } else if (centerIn->parentItem() == item->parentItem()) { + + QPointF p(centerIn->x() + (centerIn->width() - item->width()) / 2. + hCenterOffset, + centerIn->y() + (centerIn->height() - item->height()) / 2. + vCenterOffset); + setItemPos(p); + } + + --updatingCenterIn; + } else { + // ### Make this certain :) + qmlInfo(item) << QmlGraphicsAnchors::tr("Possible anchor loop detected on centerIn."); + } +} + +void QmlGraphicsAnchorsPrivate::clearItem(QmlGraphicsItem *item) +{ + if (!item) + return; + if (fill == item) + fill = 0; + if (centerIn == item) + centerIn = 0; + if (left.item == item) { + left.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasLeftAnchor; + } + if (right.item == item) { + right.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasRightAnchor; + } + if (top.item == item) { + top.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasTopAnchor; + } + if (bottom.item == item) { + bottom.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasBottomAnchor; + } + if (vCenter.item == item) { + vCenter.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasVCenterAnchor; + } + if (hCenter.item == item) { + hCenter.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasHCenterAnchor; + } + if (baseline.item == item) { + baseline.item = 0; + usedAnchors &= ~QmlGraphicsAnchors::HasBaselineAnchor; + } +} + +void QmlGraphicsAnchorsPrivate::addDepend(QmlGraphicsItem *item) +{ + if (!item) + return; + QmlGraphicsItemPrivate *p = + static_cast<QmlGraphicsItemPrivate *>(QGraphicsItemPrivate::get(item)); + p->addItemChangeListener(this, QmlGraphicsItemPrivate::Geometry); +} + +void QmlGraphicsAnchorsPrivate::remDepend(QmlGraphicsItem *item) +{ + if (!item) + return; + QmlGraphicsItemPrivate *p = + static_cast<QmlGraphicsItemPrivate *>(QGraphicsItemPrivate::get(item)); + p->removeItemChangeListener(this, QmlGraphicsItemPrivate::Geometry); +} + +bool QmlGraphicsAnchorsPrivate::isItemComplete() const +{ + return componentComplete; +} + +void QmlGraphicsAnchors::classBegin() +{ + Q_D(QmlGraphicsAnchors); + d->componentComplete = false; +} + +void QmlGraphicsAnchors::componentComplete() +{ + Q_D(QmlGraphicsAnchors); + d->componentComplete = true; +} + +void QmlGraphicsAnchorsPrivate::setItemHeight(qreal v) +{ + updatingMe = true; + item->setHeight(v); + updatingMe = false; +} + +void QmlGraphicsAnchorsPrivate::setItemWidth(qreal v) +{ + updatingMe = true; + item->setWidth(v); + updatingMe = false; +} + +void QmlGraphicsAnchorsPrivate::setItemX(qreal v) +{ + updatingMe = true; + item->setX(v); + updatingMe = false; +} + +void QmlGraphicsAnchorsPrivate::setItemY(qreal v) +{ + updatingMe = true; + item->setY(v); + updatingMe = false; +} + +void QmlGraphicsAnchorsPrivate::setItemPos(const QPointF &v) +{ + updatingMe = true; + item->setPos(v); + updatingMe = false; +} + +void QmlGraphicsAnchorsPrivate::updateMe() +{ + if (updatingMe) { + updatingMe = false; + return; + } + + fillChanged(); + centerInChanged(); + updateHorizontalAnchors(); + updateVerticalAnchors(); +} + +void QmlGraphicsAnchorsPrivate::updateOnComplete() +{ + fillChanged(); + centerInChanged(); + updateHorizontalAnchors(); + updateVerticalAnchors(); +} + +void QmlGraphicsAnchorsPrivate::itemGeometryChanged(QmlGraphicsItem *, 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(); +} + +QmlGraphicsItem *QmlGraphicsAnchors::fill() const +{ + Q_D(const QmlGraphicsAnchors); + return d->fill; +} + +void QmlGraphicsAnchors::setFill(QmlGraphicsItem *f) +{ + Q_D(QmlGraphicsAnchors); + 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("Can't 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 QmlGraphicsAnchors::resetFill() +{ + setFill(0); +} + +QmlGraphicsItem *QmlGraphicsAnchors::centerIn() const +{ + Q_D(const QmlGraphicsAnchors); + return d->centerIn; +} + +void QmlGraphicsAnchors::setCenterIn(QmlGraphicsItem* c) +{ + Q_D(QmlGraphicsAnchors); + 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("Can't 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 QmlGraphicsAnchors::resetCenterIn() +{ + setCenterIn(0); +} + +bool QmlGraphicsAnchorsPrivate::calcStretch(const QmlGraphicsAnchorLine &edge1, + const QmlGraphicsAnchorLine &edge2, + int offset1, + int offset2, + QmlGraphicsAnchorLine::AnchorLine line, + int &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 = ((int)position(edge2.item, edge2.anchorLine) + offset2) + - ((int)position(edge1.item, edge1.anchorLine) + offset1); + } else if (edge2IsParent && edge1IsSibling) { + stretch = ((int)position(edge2.item, edge2.anchorLine) + offset2) + - ((int)position(item->parentItem(), line) + + (int)position(edge1.item, edge1.anchorLine) + offset1); + } else if (edge2IsSibling && edge1IsParent) { + stretch = ((int)position(item->parentItem(), line) + (int)position(edge2.item, edge2.anchorLine) + offset2) + - ((int)position(edge1.item, edge1.anchorLine) + offset1); + } else + invalid = true; + + return invalid; +} + +void QmlGraphicsAnchorsPrivate::updateVerticalAnchors() +{ + if (fill || centerIn || !isItemComplete()) + return; + + if (updatingVerticalAnchor < 2) { + ++updatingVerticalAnchor; + if (usedAnchors & QmlGraphicsAnchors::HasTopAnchor) { + //Handle stretching + bool invalid = true; + int height = 0; + if (usedAnchors & QmlGraphicsAnchors::HasBottomAnchor) { + invalid = calcStretch(top, bottom, topMargin, -bottomMargin, QmlGraphicsAnchorLine::Top, height); + } else if (usedAnchors & QmlGraphicsAnchors::HasVCenterAnchor) { + invalid = calcStretch(top, vCenter, topMargin, vCenterOffset, QmlGraphicsAnchorLine::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 & QmlGraphicsAnchors::HasBottomAnchor) { + //Handle stretching (top + bottom case is handled above) + if (usedAnchors & QmlGraphicsAnchors::HasVCenterAnchor) { + int height = 0; + bool invalid = calcStretch(vCenter, bottom, vCenterOffset, -bottomMargin, + QmlGraphicsAnchorLine::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 & QmlGraphicsAnchors::HasVCenterAnchor) { + //(stetching handled above) + + //Handle vCenter + if (vCenter.item == item->parentItem()) { + setItemY(adjustedPosition(vCenter.item, vCenter.anchorLine) + - item->height()/2 + vCenterOffset); + } else if (vCenter.item->parentItem() == item->parentItem()) { + setItemY(position(vCenter.item, vCenter.anchorLine) - item->height()/2 + vCenterOffset); + } + } else if (usedAnchors & QmlGraphicsAnchors::HasBaselineAnchor) { + //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) << QmlGraphicsAnchors::tr("Possible anchor loop detected on vertical anchor."); + } +} + +void QmlGraphicsAnchorsPrivate::updateHorizontalAnchors() +{ + if (fill || centerIn || !isItemComplete()) + return; + + if (updatingHorizontalAnchor < 2) { + ++updatingHorizontalAnchor; + + if (usedAnchors & QmlGraphicsAnchors::HasLeftAnchor) { + //Handle stretching + bool invalid = true; + int width = 0; + if (usedAnchors & QmlGraphicsAnchors::HasRightAnchor) { + invalid = calcStretch(left, right, leftMargin, -rightMargin, QmlGraphicsAnchorLine::Left, width); + } else if (usedAnchors & QmlGraphicsAnchors::HasHCenterAnchor) { + invalid = calcStretch(left, hCenter, leftMargin, hCenterOffset, QmlGraphicsAnchorLine::Left, width); + width *= 2; + } + if (!invalid) + setItemWidth(width); + + //Handle left + if (left.item == item->parentItem()) { + setItemX(adjustedPosition(left.item, left.anchorLine) + leftMargin); + } else if (left.item->parentItem() == item->parentItem()) { + setItemX(position(left.item, left.anchorLine) + leftMargin); + } + } else if (usedAnchors & QmlGraphicsAnchors::HasRightAnchor) { + //Handle stretching (left + right case is handled in updateLeftAnchor) + if (usedAnchors & QmlGraphicsAnchors::HasHCenterAnchor) { + int width = 0; + bool invalid = calcStretch(hCenter, right, hCenterOffset, -rightMargin, + QmlGraphicsAnchorLine::Left, width); + if (!invalid) + setItemWidth(width*2); + } + + //Handle right + if (right.item == item->parentItem()) { + setItemX(adjustedPosition(right.item, right.anchorLine) - item->width() - rightMargin); + } else if (right.item->parentItem() == item->parentItem()) { + setItemX(position(right.item, right.anchorLine) - item->width() - rightMargin); + } + } else if (usedAnchors & QmlGraphicsAnchors::HasHCenterAnchor) { + //Handle hCenter + if (hCenter.item == item->parentItem()) { + setItemX(adjustedPosition(hCenter.item, hCenter.anchorLine) - item->width()/2 + hCenterOffset); + } else if (hCenter.item->parentItem() == item->parentItem()) { + setItemX(position(hCenter.item, hCenter.anchorLine) - item->width()/2 + hCenterOffset); + } + } + + --updatingHorizontalAnchor; + } else { + // ### Make this certain :) + qmlInfo(item) << QmlGraphicsAnchors::tr("Possible anchor loop detected on horizontal anchor."); + } +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::top() const +{ + Q_D(const QmlGraphicsAnchors); + return d->top; +} + +void QmlGraphicsAnchors::setTop(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkVAnchorValid(edge) || d->top == edge) + return; + + d->usedAnchors |= HasTopAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~HasTopAnchor; + return; + } + + d->remDepend(d->top.item); + d->top = edge; + d->addDepend(d->top.item); + emit topChanged(); + d->updateVerticalAnchors(); +} + +void QmlGraphicsAnchors::resetTop() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasTopAnchor; + d->remDepend(d->top.item); + d->top = QmlGraphicsAnchorLine(); + emit topChanged(); + d->updateVerticalAnchors(); +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::bottom() const +{ + Q_D(const QmlGraphicsAnchors); + return d->bottom; +} + +void QmlGraphicsAnchors::setBottom(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkVAnchorValid(edge) || d->bottom == edge) + return; + + d->usedAnchors |= HasBottomAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~HasBottomAnchor; + return; + } + + d->remDepend(d->bottom.item); + d->bottom = edge; + d->addDepend(d->bottom.item); + emit bottomChanged(); + d->updateVerticalAnchors(); +} + +void QmlGraphicsAnchors::resetBottom() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasBottomAnchor; + d->remDepend(d->bottom.item); + d->bottom = QmlGraphicsAnchorLine(); + emit bottomChanged(); + d->updateVerticalAnchors(); +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::verticalCenter() const +{ + Q_D(const QmlGraphicsAnchors); + return d->vCenter; +} + +void QmlGraphicsAnchors::setVerticalCenter(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkVAnchorValid(edge) || d->vCenter == edge) + return; + + d->usedAnchors |= HasVCenterAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~HasVCenterAnchor; + return; + } + + d->remDepend(d->vCenter.item); + d->vCenter = edge; + d->addDepend(d->vCenter.item); + emit verticalCenterChanged(); + d->updateVerticalAnchors(); +} + +void QmlGraphicsAnchors::resetVerticalCenter() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasVCenterAnchor; + d->remDepend(d->vCenter.item); + d->vCenter = QmlGraphicsAnchorLine(); + emit verticalCenterChanged(); + d->updateVerticalAnchors(); +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::baseline() const +{ + Q_D(const QmlGraphicsAnchors); + return d->baseline; +} + +void QmlGraphicsAnchors::setBaseline(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkVAnchorValid(edge) || d->baseline == edge) + return; + + d->usedAnchors |= HasBaselineAnchor; + + if (!d->checkVValid()) { + d->usedAnchors &= ~HasBaselineAnchor; + return; + } + + d->remDepend(d->baseline.item); + d->baseline = edge; + d->addDepend(d->baseline.item); + emit baselineChanged(); + d->updateVerticalAnchors(); +} + +void QmlGraphicsAnchors::resetBaseline() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasBaselineAnchor; + d->remDepend(d->baseline.item); + d->baseline = QmlGraphicsAnchorLine(); + emit baselineChanged(); + d->updateVerticalAnchors(); +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::left() const +{ + Q_D(const QmlGraphicsAnchors); + return d->left; +} + +void QmlGraphicsAnchors::setLeft(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkHAnchorValid(edge) || d->left == edge) + return; + + d->usedAnchors |= HasLeftAnchor; + + if (!d->checkHValid()) { + d->usedAnchors &= ~HasLeftAnchor; + return; + } + + d->remDepend(d->left.item); + d->left = edge; + d->addDepend(d->left.item); + emit leftChanged(); + d->updateHorizontalAnchors(); +} + +void QmlGraphicsAnchors::resetLeft() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasLeftAnchor; + d->remDepend(d->left.item); + d->left = QmlGraphicsAnchorLine(); + emit leftChanged(); + d->updateHorizontalAnchors(); +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::right() const +{ + Q_D(const QmlGraphicsAnchors); + return d->right; +} + +void QmlGraphicsAnchors::setRight(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkHAnchorValid(edge) || d->right == edge) + return; + + d->usedAnchors |= HasRightAnchor; + + if (!d->checkHValid()) { + d->usedAnchors &= ~HasRightAnchor; + return; + } + + d->remDepend(d->right.item); + d->right = edge; + d->addDepend(d->right.item); + emit rightChanged(); + d->updateHorizontalAnchors(); +} + +void QmlGraphicsAnchors::resetRight() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasRightAnchor; + d->remDepend(d->right.item); + d->right = QmlGraphicsAnchorLine(); + emit rightChanged(); + d->updateHorizontalAnchors(); +} + +QmlGraphicsAnchorLine QmlGraphicsAnchors::horizontalCenter() const +{ + Q_D(const QmlGraphicsAnchors); + return d->hCenter; +} + +void QmlGraphicsAnchors::setHorizontalCenter(const QmlGraphicsAnchorLine &edge) +{ + Q_D(QmlGraphicsAnchors); + if (!d->checkHAnchorValid(edge) || d->hCenter == edge) + return; + + d->usedAnchors |= HasHCenterAnchor; + + if (!d->checkHValid()) { + d->usedAnchors &= ~HasHCenterAnchor; + return; + } + + d->remDepend(d->hCenter.item); + d->hCenter = edge; + d->addDepend(d->hCenter.item); + emit horizontalCenterChanged(); + d->updateHorizontalAnchors(); +} + +void QmlGraphicsAnchors::resetHorizontalCenter() +{ + Q_D(QmlGraphicsAnchors); + d->usedAnchors &= ~HasHCenterAnchor; + d->remDepend(d->hCenter.item); + d->hCenter = QmlGraphicsAnchorLine(); + emit horizontalCenterChanged(); + d->updateHorizontalAnchors(); +} + +qreal QmlGraphicsAnchors::leftMargin() const +{ + Q_D(const QmlGraphicsAnchors); + return d->leftMargin; +} + +void QmlGraphicsAnchors::setLeftMargin(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->leftMargin == offset) + return; + d->leftMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateHorizontalAnchors(); + emit leftMarginChanged(); +} + +qreal QmlGraphicsAnchors::rightMargin() const +{ + Q_D(const QmlGraphicsAnchors); + return d->rightMargin; +} + +void QmlGraphicsAnchors::setRightMargin(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->rightMargin == offset) + return; + d->rightMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateHorizontalAnchors(); + emit rightMarginChanged(); +} + +qreal QmlGraphicsAnchors::margins() const +{ + Q_D(const QmlGraphicsAnchors); + return d->margins; +} + +void QmlGraphicsAnchors::setMargins(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + 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 QmlGraphicsAnchors::horizontalCenterOffset() const +{ + Q_D(const QmlGraphicsAnchors); + return d->hCenterOffset; +} + +void QmlGraphicsAnchors::setHorizontalCenterOffset(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->hCenterOffset == offset) + return; + d->hCenterOffset = offset; + if(d->centerIn) + d->centerInChanged(); + else + d->updateHorizontalAnchors(); + emit horizontalCenterOffsetChanged(); +} + +qreal QmlGraphicsAnchors::topMargin() const +{ + Q_D(const QmlGraphicsAnchors); + return d->topMargin; +} + +void QmlGraphicsAnchors::setTopMargin(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->topMargin == offset) + return; + d->topMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateVerticalAnchors(); + emit topMarginChanged(); +} + +qreal QmlGraphicsAnchors::bottomMargin() const +{ + Q_D(const QmlGraphicsAnchors); + return d->bottomMargin; +} + +void QmlGraphicsAnchors::setBottomMargin(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->bottomMargin == offset) + return; + d->bottomMargin = offset; + if(d->fill) + d->fillChanged(); + else + d->updateVerticalAnchors(); + emit bottomMarginChanged(); +} + +qreal QmlGraphicsAnchors::verticalCenterOffset() const +{ + Q_D(const QmlGraphicsAnchors); + return d->vCenterOffset; +} + +void QmlGraphicsAnchors::setVerticalCenterOffset(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->vCenterOffset == offset) + return; + d->vCenterOffset = offset; + if(d->centerIn) + d->centerInChanged(); + else + d->updateVerticalAnchors(); + emit verticalCenterOffsetChanged(); +} + +qreal QmlGraphicsAnchors::baselineOffset() const +{ + Q_D(const QmlGraphicsAnchors); + return d->baselineOffset; +} + +void QmlGraphicsAnchors::setBaselineOffset(qreal offset) +{ + Q_D(QmlGraphicsAnchors); + if (d->baselineOffset == offset) + return; + d->baselineOffset = offset; + d->updateVerticalAnchors(); + emit baselineOffsetChanged(); +} + +QmlGraphicsAnchors::UsedAnchors QmlGraphicsAnchors::usedAnchors() const +{ + Q_D(const QmlGraphicsAnchors); + return d->usedAnchors; +} + +bool QmlGraphicsAnchorsPrivate::checkHValid() const +{ + if (usedAnchors & QmlGraphicsAnchors::HasLeftAnchor && + usedAnchors & QmlGraphicsAnchors::HasRightAnchor && + usedAnchors & QmlGraphicsAnchors::HasHCenterAnchor) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't specify left, right, and hcenter anchors."); + return false; + } + + return true; +} + +bool QmlGraphicsAnchorsPrivate::checkHAnchorValid(QmlGraphicsAnchorLine anchor) const +{ + if (!anchor.item) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor to a null item."); + return false; + } else if (anchor.anchorLine & QmlGraphicsAnchorLine::Vertical_Mask) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor a horizontal edge to a vertical edge."); + return false; + } else if (anchor.item != item->parentItem() && anchor.item->parentItem() != item->parentItem()){ + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor to an item that isn't a parent or sibling."); + return false; + } else if (anchor.item == item) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor item to self."); + return false; + } + + return true; +} + +bool QmlGraphicsAnchorsPrivate::checkVValid() const +{ + if (usedAnchors & QmlGraphicsAnchors::HasTopAnchor && + usedAnchors & QmlGraphicsAnchors::HasBottomAnchor && + usedAnchors & QmlGraphicsAnchors::HasVCenterAnchor) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't specify top, bottom, and vcenter anchors."); + return false; + } else if (usedAnchors & QmlGraphicsAnchors::HasBaselineAnchor && + (usedAnchors & QmlGraphicsAnchors::HasTopAnchor || + usedAnchors & QmlGraphicsAnchors::HasBottomAnchor || + usedAnchors & QmlGraphicsAnchors::HasVCenterAnchor)) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Baseline anchor can't be used in conjunction with top, bottom, or vcenter anchors."); + return false; + } + + return true; +} + +bool QmlGraphicsAnchorsPrivate::checkVAnchorValid(QmlGraphicsAnchorLine anchor) const +{ + if (!anchor.item) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor to a null item."); + return false; + } else if (anchor.anchorLine & QmlGraphicsAnchorLine::Horizontal_Mask) { + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor a vertical edge to a horizontal edge."); + return false; + } else if (anchor.item != item->parentItem() && anchor.item->parentItem() != item->parentItem()){ + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor to an item that isn't a parent or sibling."); + return false; + } else if (anchor.item == item){ + qmlInfo(item) << QmlGraphicsAnchors::tr("Can't anchor item to self."); + return false; + } + + return true; +} + +#include <moc_qmlgraphicsanchors_p.cpp> + +QT_END_NAMESPACE + diff --git a/src/declarative/graphicsitems/qmlgraphicsanchors_p.h b/src/declarative/graphicsitems/qmlgraphicsanchors_p.h new file mode 100644 index 0000000..5a8f8c1 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsanchors_p.h @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSANCHORS_H +#define QMLGRAPHICSANCHORS_H + +#include "qmlgraphicsitem.h" + +#include <qml.h> + +#include <QtCore/QObject> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsAnchorsPrivate; +class QmlGraphicsAnchorLine; +class Q_DECLARATIVE_EXPORT QmlGraphicsAnchors : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QmlGraphicsAnchorLine left READ left WRITE setLeft RESET resetLeft NOTIFY leftChanged) + Q_PROPERTY(QmlGraphicsAnchorLine right READ right WRITE setRight RESET resetRight NOTIFY rightChanged) + Q_PROPERTY(QmlGraphicsAnchorLine horizontalCenter READ horizontalCenter WRITE setHorizontalCenter RESET resetHorizontalCenter NOTIFY horizontalCenterChanged) + Q_PROPERTY(QmlGraphicsAnchorLine top READ top WRITE setTop RESET resetTop NOTIFY topChanged) + Q_PROPERTY(QmlGraphicsAnchorLine bottom READ bottom WRITE setBottom RESET resetBottom NOTIFY bottomChanged) + Q_PROPERTY(QmlGraphicsAnchorLine verticalCenter READ verticalCenter WRITE setVerticalCenter RESET resetVerticalCenter NOTIFY verticalCenterChanged) + Q_PROPERTY(QmlGraphicsAnchorLine 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(QmlGraphicsItem *fill READ fill WRITE setFill RESET resetFill NOTIFY fillChanged) + Q_PROPERTY(QmlGraphicsItem *centerIn READ centerIn WRITE setCenterIn RESET resetCenterIn NOTIFY centerInChanged) + +public: + QmlGraphicsAnchors(QObject *parent=0); + QmlGraphicsAnchors(QmlGraphicsItem *item, QObject *parent=0); + virtual ~QmlGraphicsAnchors(); + + enum UsedAnchor { + HasLeftAnchor = 0x01, + HasRightAnchor = 0x02, + HasTopAnchor = 0x04, + HasBottomAnchor = 0x08, + HasHCenterAnchor = 0x10, + HasVCenterAnchor = 0x20, + HasBaselineAnchor = 0x40, + Horizontal_Mask = HasLeftAnchor | HasRightAnchor | HasHCenterAnchor, + Vertical_Mask = HasTopAnchor | HasBottomAnchor | HasVCenterAnchor | HasBaselineAnchor + }; + Q_DECLARE_FLAGS(UsedAnchors, UsedAnchor) + + QmlGraphicsAnchorLine left() const; + void setLeft(const QmlGraphicsAnchorLine &edge); + void resetLeft(); + + QmlGraphicsAnchorLine right() const; + void setRight(const QmlGraphicsAnchorLine &edge); + void resetRight(); + + QmlGraphicsAnchorLine horizontalCenter() const; + void setHorizontalCenter(const QmlGraphicsAnchorLine &edge); + void resetHorizontalCenter(); + + QmlGraphicsAnchorLine top() const; + void setTop(const QmlGraphicsAnchorLine &edge); + void resetTop(); + + QmlGraphicsAnchorLine bottom() const; + void setBottom(const QmlGraphicsAnchorLine &edge); + void resetBottom(); + + QmlGraphicsAnchorLine verticalCenter() const; + void setVerticalCenter(const QmlGraphicsAnchorLine &edge); + void resetVerticalCenter(); + + QmlGraphicsAnchorLine baseline() const; + void setBaseline(const QmlGraphicsAnchorLine &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); + + QmlGraphicsItem *fill() const; + void setFill(QmlGraphicsItem *); + void resetFill(); + + QmlGraphicsItem *centerIn() const; + void setCenterIn(QmlGraphicsItem *); + void resetCenterIn(); + + UsedAnchors usedAnchors() const; + + 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(); + +private: + friend class QmlGraphicsItem; + Q_DISABLE_COPY(QmlGraphicsAnchors) + Q_DECLARE_PRIVATE(QmlGraphicsAnchors) +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QmlGraphicsAnchors::UsedAnchors) + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsAnchors) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsanchors_p_p.h b/src/declarative/graphicsitems/qmlgraphicsanchors_p_p.h new file mode 100644 index 0000000..5b02158 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsanchors_p_p.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSANCHORS_P_H +#define QMLGRAPHICSANCHORS_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 "qmlgraphicsanchors_p.h" +#include "qmlgraphicsitemchangelistener_p.h" +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QmlGraphicsAnchorLine +{ +public: + QmlGraphicsAnchorLine() : 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 + }; + + QmlGraphicsItem *item; + AnchorLine anchorLine; + + bool operator==(const QmlGraphicsAnchorLine& other) const + { + return item == other.item && anchorLine == other.anchorLine; + } +}; +Q_DECLARE_METATYPE(QmlGraphicsAnchorLine) + + + +class QmlGraphicsAnchorsPrivate : public QObjectPrivate, public QmlGraphicsItemChangeListener +{ + Q_DECLARE_PUBLIC(QmlGraphicsAnchors) +public: + QmlGraphicsAnchorsPrivate(QmlGraphicsItem *i) + : 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), + componentComplete(true) + { + } + + void init() + { + } + + void clearItem(QmlGraphicsItem *); + + void addDepend(QmlGraphicsItem *); + void remDepend(QmlGraphicsItem *); + bool isItemComplete() const; + + bool updatingMe; + int updatingHorizontalAnchor; + int updatingVerticalAnchor; + int updatingFill; + int updatingCenterIn; + + void setItemHeight(qreal); + void setItemWidth(qreal); + void setItemX(qreal); + void setItemY(qreal); + void setItemPos(const QPointF &); + + void updateOnComplete(); + void updateMe(); + + // QmlGraphicsItemGeometryListener interface + void itemGeometryChanged(QmlGraphicsItem *, const QRectF &, const QRectF &); + QmlGraphicsAnchorsPrivate *anchorPrivate() { return this; } + + bool checkHValid() const; + bool checkVValid() const; + bool checkHAnchorValid(QmlGraphicsAnchorLine anchor) const; + bool checkVAnchorValid(QmlGraphicsAnchorLine anchor) const; + bool calcStretch(const QmlGraphicsAnchorLine &edge1, const QmlGraphicsAnchorLine &edge2, int offset1, int offset2, QmlGraphicsAnchorLine::AnchorLine line, int &stretch); + + void updateHorizontalAnchors(); + void updateVerticalAnchors(); + void fillChanged(); + void centerInChanged(); + + QmlGraphicsItem *item; + QmlGraphicsAnchors::UsedAnchors usedAnchors; + + QmlGraphicsItem *fill; + QmlGraphicsItem *centerIn; + + QmlGraphicsAnchorLine left; + QmlGraphicsAnchorLine right; + QmlGraphicsAnchorLine top; + QmlGraphicsAnchorLine bottom; + QmlGraphicsAnchorLine vCenter; + QmlGraphicsAnchorLine hCenter; + QmlGraphicsAnchorLine baseline; + + qreal leftMargin; + qreal rightMargin; + qreal topMargin; + qreal bottomMargin; + qreal margins; + qreal vCenterOffset; + qreal hCenterOffset; + qreal baselineOffset; + + bool componentComplete; +}; + +QT_END_NAMESPACE +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsanimatedimage.cpp b/src/declarative/graphicsitems/qmlgraphicsanimatedimage.cpp new file mode 100644 index 0000000..e01e569 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsanimatedimage.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsanimatedimage_p.h" +#include "qmlgraphicsanimatedimage_p_p.h" + +#include <qmlengine.h> + +#include <QMovie> +#include <QNetworkRequest> +#include <QNetworkReply> + +QT_BEGIN_NAMESPACE + +/*! + \class QmlGraphicsAnimatedImage + \internal +*/ + +/*! + \qmlclass AnimatedImage QmlGraphicsAnimatedImage + \inherits Image + + This item provides for playing animations stored as images containing a series of frames, + such as GIF files. The full list of supported formats can be determined with + QMovie::supportedFormats(). + + \table + \row + \o \image animatedimageitem.gif + \o + \qml +Item { + width: anim.width; height: anim.height+8 + AnimatedImage { id: anim; source: "pics/games-anim.gif" } + Rectangle { color: "red"; width: 4; height: 8; y: anim.height + x: (anim.width-width)*anim.currentFrame/(anim.frameCount-1) + } +} + \endqml + \endtable +*/ +QML_DEFINE_TYPE(Qt,4,6,AnimatedImage,QmlGraphicsAnimatedImage) + +QmlGraphicsAnimatedImage::QmlGraphicsAnimatedImage(QmlGraphicsItem *parent) + : QmlGraphicsImage(*(new QmlGraphicsAnimatedImagePrivate), parent) +{ +} + +QmlGraphicsAnimatedImage::~QmlGraphicsAnimatedImage() +{ + Q_D(QmlGraphicsAnimatedImage); + delete d->_movie; +} + +/*! + \qmlproperty bool AnimatedImage::paused + This property holds whether the animated image is paused or not + + Defaults to false, and can be set to true when you want to pause. +*/ +bool QmlGraphicsAnimatedImage::isPaused() const +{ + Q_D(const QmlGraphicsAnimatedImage); + if(!d->_movie) + return false; + return d->_movie->state()==QMovie::Paused; +} + +void QmlGraphicsAnimatedImage::setPaused(bool pause) +{ + Q_D(QmlGraphicsAnimatedImage); + if(pause == d->paused) + return; + d->paused = pause; + if(!d->_movie) + return; + d->_movie->setPaused(pause); +} +/*! + \qmlproperty bool AnimatedImage::playing + This property holds whether the animated image is playing or not + + Defaults to true, so as to start playing immediately. +*/ +bool QmlGraphicsAnimatedImage::isPlaying() const +{ + Q_D(const QmlGraphicsAnimatedImage); + if (!d->_movie) + return false; + return d->_movie->state()!=QMovie::NotRunning; +} + +void QmlGraphicsAnimatedImage::setPlaying(bool play) +{ + Q_D(QmlGraphicsAnimatedImage); + if(play == d->playing) + return; + d->playing = play; + if (!d->_movie) + return; + if (play) + d->_movie->start(); + else + d->_movie->stop(); +} + +/*! + \qmlproperty int AnimatedImage::currentFrame + \qmlproperty int AnimatedImage::frameCount + + currentFrame is the frame that is currently visible. Watching when this changes can + allow other things to animate at the same time as the image. frameCount is the number + of frames in the animation. For some animation formats, frameCount is unknown and set to zero. +*/ +int QmlGraphicsAnimatedImage::currentFrame() const +{ + Q_D(const QmlGraphicsAnimatedImage); + if (!d->_movie) + return d->preset_currentframe; + return d->_movie->currentFrameNumber(); +} + +void QmlGraphicsAnimatedImage::setCurrentFrame(int frame) +{ + Q_D(QmlGraphicsAnimatedImage); + if (!d->_movie) { + d->preset_currentframe = frame; + return; + } + d->_movie->jumpToFrame(frame); +} + +int QmlGraphicsAnimatedImage::frameCount() const +{ + Q_D(const QmlGraphicsAnimatedImage); + if (!d->_movie) + return 0; + return d->_movie->frameCount(); +} + +static QString toLocalFileOrQrc(const QUrl& url) +{ + QString r = url.toLocalFile(); + if (r.isEmpty() && url.scheme() == QLatin1String("qrc")) + r = QLatin1Char(':') + url.path(); + return r; +} + +void QmlGraphicsAnimatedImage::setSource(const QUrl &url) +{ + Q_D(QmlGraphicsAnimatedImage); + if (url == d->url) + return; + + delete d->_movie; + d->_movie = 0; + + if (d->reply) { + d->reply->deleteLater(); + d->reply = 0; + } + + d->url = url; + + if (url.isEmpty()) { + delete d->_movie; + d->status = Null; + } else { +#ifndef QT_NO_LOCALFILE_OPTIMIZED_QML + QString lf = toLocalFileOrQrc(url); + if (!lf.isEmpty()) { + //### should be unified with movieRequestFinished + d->_movie = new QMovie(lf); + if (!d->_movie->isValid()){ + qWarning() << "Error Reading Animated Image File" << d->url; + delete d->_movie; + d->_movie = 0; + 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; + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit progressChanged(d->progress); + return; + } +#endif + d->status = Loading; + 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())); + } + + emit statusChanged(d->status); +} + +void QmlGraphicsAnimatedImage::movieRequestFinished() +{ + Q_D(QmlGraphicsAnimatedImage); + d->_movie = new QMovie(d->reply); + if (!d->_movie->isValid()){ + qWarning() << "Error Reading Animated Image File " << d->url; + delete d->_movie; + d->_movie = 0; + 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()); +} + +void QmlGraphicsAnimatedImage::movieUpdate() +{ + Q_D(QmlGraphicsAnimatedImage); + d->setPixmap(d->_movie->currentPixmap()); + emit frameChanged(); +} + +void QmlGraphicsAnimatedImage::playingStatusChanged() +{ + Q_D(QmlGraphicsAnimatedImage); + 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 QmlGraphicsAnimatedImage::componentComplete() +{ + Q_D(QmlGraphicsAnimatedImage); + if (!d->reply) { + setCurrentFrame(d->preset_currentframe); + d->preset_currentframe = 0; + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsanimatedimage_p.h b/src/declarative/graphicsitems/qmlgraphicsanimatedimage_p.h new file mode 100644 index 0000000..a837702 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsanimatedimage_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSANIMATEDIMAGE_H +#define QMLGRAPHICSANIMATEDIMAGE_H + +#include "qmlgraphicsimage_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QMovie; +class QmlGraphicsAnimatedImagePrivate; + +class Q_DECLARATIVE_EXPORT QmlGraphicsAnimatedImage : public QmlGraphicsImage +{ + 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) +public: + QmlGraphicsAnimatedImage(QmlGraphicsItem *parent=0); + ~QmlGraphicsAnimatedImage(); + + 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 QmlGraphicsImage's src property*/ + virtual void setSource(const QUrl&); + +Q_SIGNALS: + void playingChanged(); + void pausedChanged(); + void frameChanged(); + +private Q_SLOTS: + void movieUpdate(); + void movieRequestFinished(); + void playingStatusChanged(); + +protected: + void componentComplete(); + +private: + Q_DISABLE_COPY(QmlGraphicsAnimatedImage) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsAnimatedImage) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsAnimatedImage) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsanimatedimage_p_p.h b/src/declarative/graphicsitems/qmlgraphicsanimatedimage_p_p.h new file mode 100644 index 0000000..0d1c749 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsanimatedimage_p_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSANIMATEDIMAGE_P_H +#define QMLGRAPHICSANIMATEDIMAGE_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 "qmlgraphicsimage_p_p.h" + +QT_BEGIN_NAMESPACE + +class QMovie; +class QNetworkReply; + +class QmlGraphicsAnimatedImagePrivate : public QmlGraphicsImagePrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsAnimatedImage) + +public: + QmlGraphicsAnimatedImagePrivate() + : playing(true), paused(false), preset_currentframe(0), _movie(0), reply(0) + { + } + + bool playing; + bool paused; + int preset_currentframe; + QMovie *_movie; + QNetworkReply *reply; +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSANIMATEDIMAGE_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsborderimage.cpp b/src/declarative/graphicsitems/qmlgraphicsborderimage.cpp new file mode 100644 index 0000000..877e141 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsborderimage.cpp @@ -0,0 +1,409 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsborderimage_p.h" +#include "qmlgraphicsborderimage_p_p.h" + +#include <qmlengine.h> + +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QFile> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_TYPE(Qt,4,6,BorderImage,QmlGraphicsBorderImage) + +/*! + \qmlclass BorderImage QmlGraphicsBorderImage + \brief The BorderImage element provides an image that can be used as a border. + \inherits Item + + \snippet snippets/declarative/border-image.qml 0 + + \image BorderImage.png + */ + +/*! + \internal + \class QmlGraphicsBorderImage BorderImage + \brief The QmlGraphicsBorderImage class provides an image item that you can add to a QmlView. +*/ + +QmlGraphicsBorderImage::QmlGraphicsBorderImage(QmlGraphicsItem *parent) + : QmlGraphicsImageBase(*(new QmlGraphicsBorderImagePrivate), parent) +{ +} + +QmlGraphicsBorderImage::~QmlGraphicsBorderImage() +{ + Q_D(QmlGraphicsBorderImage); + if (d->sciReply) + d->sciReply->deleteLater(); + if (d->sciPendingPixmapCache) + QmlPixmapCache::cancel(d->sciurl, this); +} +/*! + \qmlproperty enum BorderImage::status + + This property holds the status of image loading. It can be one of: + \list + \o Null - no image has been set + \o Ready - the image has been loaded + \o Loading - the image is currently being loaded + \o Error - an error occurred while loading the image + \endlist + + \sa progress +*/ + +/*! + \qmlproperty real BorderImage::progress + + This property holds the progress of image loading, from 0.0 (nothing loaded) + to 1.0 (finished). + + \sa status +*/ + +/*! + \qmlproperty bool BorderImage::smooth + + Set this property if you want the image to be smoothly filtered when scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the image is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the image is stationary on + the screen. A common pattern when animating an image is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +/*! + \qmlproperty url BorderImage::source + + BorderImage can handle any image format supported by Qt, loaded from any URL scheme supported by Qt. + + It can also handle .sci files, which are a Qml-specific format. A .sci file uses a simple text-based format that specifies + the borders, the image file and the tile rules. + + The following .sci file sets the borders to 10 on each side for the image \c picture.png: + \qml + border.left: 10 + border.top: 10 + border.bottom: 10 + border.right: 10 + source: picture.png + \endqml + + The URL may be absolute, or relative to the URL of the component. +*/ + +static QString toLocalFileOrQrc(const QUrl& url) +{ + QString r = url.toLocalFile(); + if (r.isEmpty() && url.scheme() == QLatin1String("qrc")) + r = QLatin1Char(':') + url.path(); + return r; +} + + +void QmlGraphicsBorderImage::setSource(const QUrl &url) +{ + Q_D(QmlGraphicsBorderImage); + //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; + } + + if (d->pendingPixmapCache) { + QmlPixmapCache::cancel(d->url, this); + d->pendingPixmapCache = false; + } + if (d->sciPendingPixmapCache) { + QmlPixmapCache::cancel(d->sciurl, this); + d->sciPendingPixmapCache = false; + } + + d->url = url; + d->sciurl = QUrl(); + if (d->progress != 0.0) { + d->progress = 0.0; + emit progressChanged(d->progress); + } + + if (url.isEmpty()) { + d->pix = QPixmap(); + d->status = Null; + setImplicitWidth(0); + setImplicitHeight(0); + emit statusChanged(d->status); + emit sourceChanged(d->url); + update(); + } else { + d->status = Loading; + if (d->url.path().endsWith(QLatin1String(".sci"))) { +#ifndef QT_NO_LOCALFILE_OPTIMIZED_QML + QString lf = toLocalFileOrQrc(d->url); + if (!lf.isEmpty()) { + QFile file(lf); + file.open(QIODevice::ReadOnly); + setGridScaledImage(QmlGraphicsGridScaledImage(&file)); + } else +#endif + { + QNetworkRequest req(d->url); + d->sciReply = qmlEngine(this)->networkAccessManager()->get(req); + QObject::connect(d->sciReply, SIGNAL(finished()), + this, SLOT(sciRequestFinished())); + } + } else { + QmlPixmapReply::Status status = QmlPixmapCache::get(d->url, &d->pix); + if (status != QmlPixmapReply::Ready && status != QmlPixmapReply::Error) { + QmlPixmapReply *reply = QmlPixmapCache::request(qmlEngine(this), d->url); + d->pendingPixmapCache = true; + connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), + this, SLOT(requestProgress(qint64,qint64))); + } else { + //### should be unified with requestFinished + setImplicitWidth(d->pix.width()); + setImplicitHeight(d->pix.height()); + + if (d->pix.isNull()) + d->status = Error; + if (d->status == Loading) + d->status = Ready; + d->progress = 1.0; + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit progressChanged(d->progress); + update(); + } + } + } + + emit statusChanged(d->status); +} + +/*! + \qmlproperty int BorderImage::border.left + \qmlproperty int BorderImage::border.right + \qmlproperty int BorderImage::border.top + \qmlproperty int BorderImage::border.bottom + + \target ImagexmlpropertiesscaleGrid + + The 4 border lines (2 horizontal and 2 vertical) break an image into 9 sections, as shown below: + + \image declarative-scalegrid.png + + When the image is scaled: + \list + \i the corners (sections 1, 3, 7, and 9) are not scaled at all + \i the middle (section 5) is scaled according to BorderImage::horizontalTileMode and BorderImage::verticalTileMode + \i sections 2 and 8 are scaled according to BorderImage::horizontalTileMode + \i sections 4 and 6 are scaled according to BorderImage::verticalTileMode + \endlist + + Each border line (left, right, top, and bottom) specifies an offset from the respective side. For example, \c{border.bottom: 10} sets the bottom line 10 pixels up from the bottom of the image. + + The border lines can also be specified using a + \l {BorderImage::source}{.sci file}. +*/ + +QmlGraphicsScaleGrid *QmlGraphicsBorderImage::border() +{ + Q_D(QmlGraphicsBorderImage); + return d->getScaleGrid(); +} + +/*! + \qmlproperty TileMode BorderImage::horizontalTileMode + \qmlproperty TileMode BorderImage::verticalTileMode + + This property describes how to repeat or stretch the middle parts of the border image. + + \list + \o Stretch - Scale the image to fit to the available area. + \o Repeat - Tile the image until there is no more space. May crop the last image. + \o Round - Like Repeat, but scales the images down to ensure that the last image is not cropped. + \endlist +*/ +QmlGraphicsBorderImage::TileMode QmlGraphicsBorderImage::horizontalTileMode() const +{ + Q_D(const QmlGraphicsBorderImage); + return d->horizontalTileMode; +} + +void QmlGraphicsBorderImage::setHorizontalTileMode(TileMode t) +{ + Q_D(QmlGraphicsBorderImage); + if (t != d->horizontalTileMode) { + d->horizontalTileMode = t; + emit horizontalTileModeChanged(); + update(); + } +} + +QmlGraphicsBorderImage::TileMode QmlGraphicsBorderImage::verticalTileMode() const +{ + Q_D(const QmlGraphicsBorderImage); + return d->verticalTileMode; +} + +void QmlGraphicsBorderImage::setVerticalTileMode(TileMode t) +{ + Q_D(QmlGraphicsBorderImage); + if (t != d->verticalTileMode) { + d->verticalTileMode = t; + emit verticalTileModeChanged(); + update(); + } +} + +void QmlGraphicsBorderImage::setGridScaledImage(const QmlGraphicsGridScaledImage& sci) +{ + Q_D(QmlGraphicsBorderImage); + if (!sci.isValid()) { + d->status = Error; + emit statusChanged(d->status); + } else { + QmlGraphicsScaleGrid *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())); + QmlPixmapReply::Status status = QmlPixmapCache::get(d->sciurl, &d->pix); + if (status != QmlPixmapReply::Ready && status != QmlPixmapReply::Error) { + QmlPixmapReply *reply = QmlPixmapCache::request(qmlEngine(this), d->sciurl); + d->sciPendingPixmapCache = true; + connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); + connect(reply, SIGNAL(downloadProgress(qint64,qint64)), + this, SLOT(requestProgress(qint64,qint64))); + } else { + //### should be unified with requestFinished + setImplicitWidth(d->pix.width()); + setImplicitHeight(d->pix.height()); + + if (d->pix.isNull()) + d->status = Error; + if (d->status == Loading) + d->status = Ready; + d->progress = 1.0; + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit progressChanged(1.0); + update(); + } + } +} + +void QmlGraphicsBorderImage::requestFinished() +{ + Q_D(QmlGraphicsBorderImage); + + if (d->url.path().endsWith(QLatin1String(".sci"))) { + d->sciPendingPixmapCache = false; + QmlPixmapCache::get(d->sciurl, &d->pix); + } else { + d->pendingPixmapCache = false; + if (QmlPixmapCache::get(d->url, &d->pix) != QmlPixmapReply::Ready) + d->status = Error; + } + setImplicitWidth(d->pix.width()); + setImplicitHeight(d->pix.height()); + + if (d->status == Loading) + d->status = Ready; + d->progress = 1.0; + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit progressChanged(1.0); + update(); +} + +void QmlGraphicsBorderImage::sciRequestFinished() +{ + Q_D(QmlGraphicsBorderImage); + if (d->sciReply->error() != QNetworkReply::NoError) { + d->status = Error; + d->sciReply->deleteLater(); + d->sciReply = 0; + emit statusChanged(d->status); + } else { + QmlGraphicsGridScaledImage sci(d->sciReply); + d->sciReply->deleteLater(); + d->sciReply = 0; + setGridScaledImage(sci); + } +} + +void QmlGraphicsBorderImage::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + Q_D(QmlGraphicsBorderImage); + if (d->pix.isNull()) + return; + + bool oldAA = p->testRenderHint(QPainter::Antialiasing); + bool oldSmooth = p->testRenderHint(QPainter::SmoothPixmapTransform); + if (d->smooth) + p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth); + + const QmlGraphicsScaleGrid *border = d->getScaleGrid(); + QMargins margins(border->left(), border->top(), border->right(), border->bottom()); + QTileRules rules((Qt::TileRule)d->horizontalTileMode, (Qt::TileRule)d->verticalTileMode); + qDrawBorderPixmap(p, QRect(0, 0, (int)d->width, (int)d->height), margins, d->pix, d->pix.rect(), margins, rules); + if (d->smooth) { + p->setRenderHint(QPainter::Antialiasing, oldAA); + p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth); + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsborderimage_p.h b/src/declarative/graphicsitems/qmlgraphicsborderimage_p.h new file mode 100644 index 0000000..cf3c518 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsborderimage_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSBORDERIMAGE_H +#define QMLGRAPHICSBORDERIMAGE_H + +#include "qmlgraphicsimagebase_p.h" + +#include <QtNetwork/qnetworkreply.h> + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsScaleGrid; +class QmlGraphicsGridScaledImage; +class QmlGraphicsBorderImagePrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsBorderImage : public QmlGraphicsImageBase +{ + Q_OBJECT + Q_ENUMS(TileMode) + + Q_PROPERTY(QmlGraphicsScaleGrid *border READ border CONSTANT) + Q_PROPERTY(TileMode horizontalTileMode READ horizontalTileMode WRITE setHorizontalTileMode NOTIFY horizontalTileModeChanged) + Q_PROPERTY(TileMode verticalTileMode READ verticalTileMode WRITE setVerticalTileMode NOTIFY verticalTileModeChanged) + +public: + QmlGraphicsBorderImage(QmlGraphicsItem *parent=0); + ~QmlGraphicsBorderImage(); + + QmlGraphicsScaleGrid *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 paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + void setSource(const QUrl &url); + +Q_SIGNALS: + void horizontalTileModeChanged(); + void verticalTileModeChanged(); + +private: + void setGridScaledImage(const QmlGraphicsGridScaledImage& sci); + +private Q_SLOTS: + void requestFinished(); + void sciRequestFinished(); + +private: + Q_DISABLE_COPY(QmlGraphicsBorderImage) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsBorderImage) +}; + +QT_END_NAMESPACE +QML_DECLARE_TYPE(QmlGraphicsBorderImage) +QT_END_HEADER + +#endif // QMLGRAPHICSBORDERIMAGE_H diff --git a/src/declarative/graphicsitems/qmlgraphicsborderimage_p_p.h b/src/declarative/graphicsitems/qmlgraphicsborderimage_p_p.h new file mode 100644 index 0000000..51ebb02 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsborderimage_p_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSBORDERIMAGE_P_H +#define QMLGRAPHICSBORDERIMAGE_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 "qmlgraphicsimagebase_p_p.h" +#include "qmlgraphicsscalegrid_p_p.h" + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QmlGraphicsBorderImagePrivate : public QmlGraphicsImageBasePrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsBorderImage) + +public: + QmlGraphicsBorderImagePrivate() + : border(0), sciReply(0), + sciPendingPixmapCache(false), + horizontalTileMode(QmlGraphicsBorderImage::Stretch), + verticalTileMode(QmlGraphicsBorderImage::Stretch) + { + } + + ~QmlGraphicsBorderImagePrivate() + { + } + + QmlGraphicsScaleGrid *getScaleGrid() + { + Q_Q(QmlGraphicsBorderImage); + if (!border) + border = new QmlGraphicsScaleGrid(q); + return border; + } + + QmlGraphicsScaleGrid *border; + QUrl sciurl; + QNetworkReply *sciReply; + bool sciPendingPixmapCache; + QmlGraphicsBorderImage::TileMode horizontalTileMode; + QmlGraphicsBorderImage::TileMode verticalTileMode; +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSBORDERIMAGE_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicseffects.cpp b/src/declarative/graphicsitems/qmlgraphicseffects.cpp new file mode 100644 index 0000000..e1f5687 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicseffects.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** 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 <qml.h> + +#include <QtGui/qgraphicseffect.h> + +QML_DECLARE_TYPE(QGraphicsEffect) +QML_DEFINE_NOCREATE_TYPE(QGraphicsEffect) + +QML_DECLARE_TYPE(QGraphicsBlurEffect) +QML_DEFINE_TYPE(Qt,4,6,Blur,QGraphicsBlurEffect) + +/*! + \qmlclass Blur QGraphicsBlurEffect + \brief The Blur object provides a blur effect. + + A blur effect blurs the source item. This effect is useful for reducing details; + for example, when the a source loses focus and attention should be drawn to other + elements. Use blurRadius to control the level of detail and blurHint to control + the quality of the blur. + + By default, the blur radius is 5 pixels. + + \img graphicseffect-blur.png +*/ + +/*! + \qmlproperty real Blur::blurRadius + + This controls how blurry an item will appear. + + A smaller radius produces a sharper appearance, and a larger radius produces + a more blurred appearance. + + The default radius is 5 pixels. +*/ +/*! + \qmlproperty enumeration Blur::blurHint + + Use Qt.PerformanceHint to specify a faster blur or Qt.QualityHint hint + to specify a higher quality blur. + + If the blur radius is animated, it is recommended you use Qt.PerformanceHint. + + The default hint is Qt.PerformanceHint. +*/ + +QML_DECLARE_TYPE(QGraphicsColorizeEffect) +QML_DEFINE_TYPE(Qt,4,6,Colorize,QGraphicsColorizeEffect) + +/*! + \qmlclass Colorize QGraphicsColorizeEffect + \brief The Colorize object provides a colorize effect. + + A colorize effect renders the source item with a tint of its color. + + By default, the color is light blue. + + \img graphicseffect-colorize.png +*/ + +/*! + \qmlproperty color Colorize::color + The color of the effect. + + By default, the color is light blue. +*/ + +/*! + \qmlproperty real Colorize::strength + + To what extent the source item is "colored". A strength of 0.0 is equal to no effect, + while 1.0 means full colorization. By default, the strength is 1.0. +*/ + +QML_DECLARE_TYPE(QGraphicsDropShadowEffect) +QML_DEFINE_TYPE(Qt,4,6,DropShadow,QGraphicsDropShadowEffect) + +/*! + \qmlclass DropShadow QGraphicsDropShadowEffect + \brief The DropShadow object provides a drop shadow effect. + + A drop shadow effect renders the source item with a drop shadow. The color of + the drop shadow can be modified using the color property. The drop + shadow offset can be modified using the xOffset and yOffset properties and the blur + radius of the drop shadow can be changed with the blurRadius property. + + By default, the drop shadow is a semi-transparent dark gray shadow, + blurred with a radius of 1 at an offset of 8 pixels towards the lower right. + + \img graphicseffect-drop-shadow.png +*/ + +/*! + \qmlproperty real DropShadow::xOffset + \qmlproperty real DropShadow::yOffset + The shadow offset in pixels. + + By default, xOffset and yOffset are 8 pixels. +*/ + +/*! + \qmlproperty real DropShadow::blurRadius + The blur radius in pixels of the drop shadow. + + Using a smaller radius results in a sharper shadow, whereas using a bigger + radius results in a more blurred shadow. + + By default, the blur radius is 1 pixel. +*/ + +/*! + \qmlproperty color DropShadow::color + The color of the drop shadow. + + By default, the drop color is a semi-transparent dark gray. +*/ + +QML_DECLARE_TYPE(QGraphicsOpacityEffect) +QML_DEFINE_TYPE(Qt,4,6,Opacity,QGraphicsOpacityEffect) + +/*! + \qmlclass Opacity QGraphicsOpacityEffect + \brief The Opacity object provides an opacity effect. + + An opacity effect renders the source with an opacity. This effect is useful + for making the source semi-transparent, similar to a fade-in/fade-out + sequence. The opacity can be modified using the opacity property. + + By default, the opacity is 0.7. + + \img graphicseffect-opacity.png +*/ + +/*! + \qmlproperty real Opacity::opacity + This property specifies how opaque an item should appear. + + The value should be in the range of 0.0 to 1.0, where 0.0 is + fully transparent and 1.0 is fully opaque. + + By default, the opacity is 0.7. +*/ + diff --git a/src/declarative/graphicsitems/qmlgraphicsevents.cpp b/src/declarative/graphicsitems/qmlgraphicsevents.cpp new file mode 100644 index 0000000..9958dea --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsevents.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsevents_p_p.h" + +QT_BEGIN_NAMESPACE +/*! + \qmlclass KeyEvent QmlGraphicsKeyEvent + \brief The KeyEvent object provides information about a key event. + + For example, the following changes the Item's state property when the Enter + key is pressed: + \qml +Item { + focus: true + Keys.onPressed: { if (event.key == Qt.Key_Enter) state = 'ShowDetails'; } +} + \endqml +*/ + +/*! + \internal + \class QmlGraphicsKeyEvent +*/ + +/*! + \qmlproperty int KeyEvent::key + + This property holds the code of the key that was pressed or released. + + See \l {Qt::Key}{Qt.Key} for the list of keyboard codes. These codes are + independent of the underlying window system. Note that this + function does not distinguish between capital and non-capital + letters, use the text() function (returning the Unicode text the + key generated) for this purpose. + + A value of either 0 or \l {Qt::Key_unknown}{Qt.Key_Unknown} means that the event is not + the result of a known key; for example, it may be the result of + a compose sequence, a keyboard macro, or due to key event + compression. +*/ + +/*! + \qmlproperty string KeyEvent::text + + This property holds the Unicode text that the key generated. + The text returned can be an empty string in cases where modifier keys, + such as Shift, Control, Alt, and Meta, are being pressed or released. + In such cases \c key will contain a valid value +*/ + +/*! + \qmlproperty bool KeyEvent::isAutoRepeat + + This property holds whether this event comes from an auto-repeating key. +*/ + +/*! + \qmlproperty int KeyEvent::count + + This property holds the number of keys involved in this event. If \l KeyEvent::text + is not empty, this is simply the length of the string. +*/ + +/*! + \qmlproperty bool KeyEvent::accepted + + Setting \a accepted to true prevents the key event from being + propagated to the item's parent. + + Generally, if the item acts on the key event then it should be accepted + so that ancestor items do not also respond to the same event. +*/ + + +/*! + \qmlclass MouseEvent QmlGraphicsMouseEvent + \brief The MouseEvent object provides information about a mouse event. + + The position of the mouse can be found via the x and y properties. + The button that caused the event is available via the button property. +*/ + +/*! + \internal + \class QmlGraphicsMouseEvent +*/ + +/*! + \qmlproperty int MouseEvent::x + \qmlproperty int MouseEvent::y + + These properties hold the position of the mouse event. +*/ + +/*! + \qmlproperty enum MouseEvent::button + + This property holds the button that caused the event. It can be one of: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MidButton + \endlist +*/ + +/*! + \qmlproperty bool MouseEvent::wasHeld + + This property is true if the mouse button has been held pressed longer the + threshold (800ms). +*/ + +/*! + \qmlproperty int MouseEvent::buttons + + This property holds the mouse buttons pressed when the event was generated. + For mouse move events, this is all buttons that are pressed down. For mouse + press and double click events this includes the button that caused the event. + For mouse release events this excludes the button that caused the event. + + It contains a bitwise combination of: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MidButton + \endlist +*/ + +/*! + \qmlproperty int MouseEvent::modifiers + + This property holds the keyboard modifier flags that existed immediately + before the event occurred. + + It contains a bitwise combination of: + \list + \o Qt.NoModifier - No modifier key is pressed. + \o Qt.ShiftModifier - A Shift key on the keyboard is pressed. + \o Qt.ControlModifier - A Ctrl key on the keyboard is pressed. + \o Qt.AltModifier - An Alt key on the keyboard is pressed. + \o Qt.MetaModifier - A Meta key on the keyboard is pressed. + \o Qt.KeypadModifier - A keypad button is pressed. + \endlist + + For example, to react to a Shift key + Left mouse button click: + \qml +MouseRegion { + onClicked: { if (mouse.button == Qt.LeftButton && mouse.modifiers & Qt.ShiftModifier) doSomething(); } +} + \endqml +*/ + +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsKeyEvent) +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsMouseEvent) + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsevents_p_p.h b/src/declarative/graphicsitems/qmlgraphicsevents_p_p.h new file mode 100644 index 0000000..b07fd88 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsevents_p_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSEVENTS_P_H +#define QMLGRAPHICSEVENTS_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 <qml.h> + +#include <QtCore/qobject.h> +#include <QtGui/qevent.h> + +QT_BEGIN_NAMESPACE + +class QmlGraphicsKeyEvent : 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: + QmlGraphicsKeyEvent(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); } + QmlGraphicsKeyEvent(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 QmlGraphicsMouseEvent : 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: + QmlGraphicsMouseEvent(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; } + + 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(QmlGraphicsKeyEvent) +QML_DECLARE_TYPE(QmlGraphicsMouseEvent) + +#endif // QMLGRAPHICSEVENTS_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsflickable.cpp b/src/declarative/graphicsitems/qmlgraphicsflickable.cpp new file mode 100644 index 0000000..2ff3b30 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsflickable.cpp @@ -0,0 +1,1374 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsflickable_p.h" +#include "qmlgraphicsflickable_p_p.h" + +#include <QGraphicsSceneMouseEvent> +#include <QPointer> +#include <QTimer> + +QT_BEGIN_NAMESPACE + + +// FlickThreshold determines how far the "mouse" must have moved +// before we perform a flick. +static const int FlickThreshold = 20; + +// Really slow flicks can be annoying. +static const int minimumFlickVelocity = 200; + +class QmlGraphicsFlickableVisibleArea : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal xPosition READ xPosition NOTIFY pageChanged) + Q_PROPERTY(qreal yPosition READ yPosition NOTIFY pageChanged) + Q_PROPERTY(qreal widthRatio READ widthRatio NOTIFY pageChanged) + Q_PROPERTY(qreal heightRatio READ heightRatio NOTIFY pageChanged) + +public: + QmlGraphicsFlickableVisibleArea(QmlGraphicsFlickable *parent=0); + + qreal xPosition() const; + qreal widthRatio() const; + qreal yPosition() const; + qreal heightRatio() const; + + void updateVisible(); + +signals: + void pageChanged(); + +private: + QmlGraphicsFlickable *flickable; + qreal m_xPosition; + qreal m_widthRatio; + qreal m_yPosition; + qreal m_heightRatio; +}; + +QmlGraphicsFlickableVisibleArea::QmlGraphicsFlickableVisibleArea(QmlGraphicsFlickable *parent) + : QObject(parent), flickable(parent), m_xPosition(0.), m_widthRatio(0.) + , m_yPosition(0.), m_heightRatio(0.) +{ +} + +qreal QmlGraphicsFlickableVisibleArea::widthRatio() const +{ + return m_widthRatio; +} + +qreal QmlGraphicsFlickableVisibleArea::xPosition() const +{ + return m_xPosition; +} + +qreal QmlGraphicsFlickableVisibleArea::heightRatio() const +{ + return m_heightRatio; +} + +qreal QmlGraphicsFlickableVisibleArea::yPosition() const +{ + return m_yPosition; +} + +void QmlGraphicsFlickableVisibleArea::updateVisible() +{ + QmlGraphicsFlickablePrivate *p = static_cast<QmlGraphicsFlickablePrivate *>(QGraphicsItemPrivate::get(flickable)); + bool pageChange = false; + + // Vertical + const qreal viewheight = flickable->height(); + const qreal maxyextent = -flickable->maxYExtent() + flickable->minYExtent(); + qreal pagePos = (-p->_moveY.value() + flickable->minYExtent()) / (maxyextent + viewheight); + qreal pageSize = viewheight / (maxyextent + viewheight); + + if (pageSize != m_heightRatio) { + m_heightRatio = pageSize; + pageChange = true; + } + if (pagePos != m_yPosition) { + m_yPosition = pagePos; + pageChange = true; + } + + // Horizontal + const qreal viewwidth = flickable->width(); + const qreal maxxextent = -flickable->maxXExtent() + flickable->minXExtent(); + pagePos = (-p->_moveX.value() + flickable->minXExtent()) / (maxxextent + viewwidth); + pageSize = viewwidth / (maxxextent + viewwidth); + + if (pageSize != m_widthRatio) { + m_widthRatio = pageSize; + pageChange = true; + } + if (pagePos != m_xPosition) { + m_xPosition = pagePos; + pageChange = true; + } + if (pageChange) + emit pageChanged(); +} + + +QmlGraphicsFlickablePrivate::QmlGraphicsFlickablePrivate() + : viewport(new QmlGraphicsItem) + , _moveX(this, &QmlGraphicsFlickablePrivate::setRoundedViewportX) + , _moveY(this, &QmlGraphicsFlickablePrivate::setRoundedViewportY) + , vWidth(-1), vHeight(-1), overShoot(true), flicked(false), moving(false), stealMouse(false) + , pressed(false), atXEnd(false), atXBeginning(true), atYEnd(false), atYBeginning(true) + , interactive(true), deceleration(500), maxVelocity(2000), reportedVelocitySmoothing(100) + , delayedPressEvent(0), delayedPressTarget(0), pressDelay(0), fixupDuration(200) + , horizontalVelocity(this), verticalVelocity(this), vTime(0), visibleArea(0) + , flickDirection(QmlGraphicsFlickable::AutoFlickDirection) +{ + fixupXEvent = QmlTimeLineEvent::timeLineEvent<QmlGraphicsFlickablePrivate, &QmlGraphicsFlickablePrivate::fixupX>(&_moveX, this); + fixupYEvent = QmlTimeLineEvent::timeLineEvent<QmlGraphicsFlickablePrivate, &QmlGraphicsFlickablePrivate::fixupY>(&_moveY, this); +} + +void QmlGraphicsFlickablePrivate::init() +{ + Q_Q(QmlGraphicsFlickable); + viewport->setParent(q); + QObject::connect(&timeline, SIGNAL(updated()), q, SLOT(ticked())); + QObject::connect(&timeline, SIGNAL(completed()), q, SLOT(movementEnding())); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFiltersChildEvents(true); + QObject::connect(viewport, SIGNAL(xChanged()), q, SIGNAL(positionXChanged())); + QObject::connect(viewport, SIGNAL(yChanged()), q, SIGNAL(positionYChanged())); + QObject::connect(q, SIGNAL(heightChanged()), q, SLOT(heightChange())); + QObject::connect(q, SIGNAL(widthChanged()), q, SLOT(widthChange())); +} + +void QmlGraphicsFlickablePrivate::flickX(qreal velocity) +{ + Q_Q(QmlGraphicsFlickable); + qreal maxDistance = -1; + // -ve velocity means list is moving up + if (velocity > 0) { + const qreal minX = q->minXExtent(); + if (_moveX.value() < minX) + maxDistance = qAbs(minX -_moveX.value() + (overShoot?30:0)); + flickTargetX = minX; + } else { + const qreal maxX = q->maxXExtent(); + if (_moveX.value() > maxX) + maxDistance = qAbs(maxX - _moveX.value()) + (overShoot?30:0); + flickTargetX = maxX; + } + if (maxDistance > 0) { + qreal v = velocity; + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { + if (v < 0) + v = -maxVelocity; + else + v = maxVelocity; + } + timeline.reset(_moveX); + timeline.accel(_moveX, v, deceleration, maxDistance); + timeline.execute(fixupXEvent); + if (!flicked) { + flicked = true; + emit q->flickingChanged(); + emit q->flickStarted(); + } + } else { + timeline.reset(_moveX); + fixupX(); + } +} + +void QmlGraphicsFlickablePrivate::flickY(qreal velocity) +{ + Q_Q(QmlGraphicsFlickable); + qreal maxDistance = -1; + // -ve velocity means list is moving up + if (velocity > 0) { + const qreal minY = q->minYExtent(); + if (_moveY.value() < minY) + maxDistance = qAbs(minY -_moveY.value() + (overShoot?30:0)); + flickTargetY = minY; + } else { + const qreal maxY = q->maxYExtent(); + if (_moveY.value() > maxY) + maxDistance = qAbs(maxY - _moveY.value()) + (overShoot?30:0); + flickTargetY = maxY; + } + if (maxDistance > 0) { + qreal v = velocity; + if (maxVelocity != -1 && maxVelocity < qAbs(v)) { + if (v < 0) + v = -maxVelocity; + else + v = maxVelocity; + } + timeline.reset(_moveY); + timeline.accel(_moveY, v, deceleration, maxDistance); + timeline.execute(fixupYEvent); + if (!flicked) { + flicked = true; + emit q->flickingChanged(); + emit q->flickStarted(); + } + } else { + timeline.reset(_moveY); + fixupY(); + } +} + +void QmlGraphicsFlickablePrivate::fixupX() +{ + Q_Q(QmlGraphicsFlickable); + if (!q->xflick() || _moveX.timeLine()) + return; + + if (_moveX.value() > q->minXExtent() || (q->maxXExtent() > q->minXExtent())) { + timeline.reset(_moveX); + if (_moveX.value() != q->minXExtent()) { + if (fixupDuration) + timeline.move(_moveX, q->minXExtent(), QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + else + _moveY.setValue(q->minYExtent()); + } + //emit flickingChanged(); + } else if (_moveX.value() < q->maxXExtent()) { + timeline.reset(_moveX); + if (fixupDuration) + timeline.move(_moveX, q->maxXExtent(), QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + else + _moveY.setValue(q->maxYExtent()); + //emit flickingChanged(); + } else { + flicked = false; + } + + vTime = timeline.time(); +} + +void QmlGraphicsFlickablePrivate::fixupY() +{ + Q_Q(QmlGraphicsFlickable); + if (!q->yflick() || _moveY.timeLine()) + return; + + if (_moveY.value() > q->minYExtent() || (q->maxYExtent() > q->minYExtent())) { + timeline.reset(_moveY); + if (_moveY.value() != q->minYExtent()) { + if (fixupDuration) + timeline.move(_moveY, q->minYExtent(), QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + else + _moveY.setValue(q->minYExtent()); + } + //emit flickingChanged(); + } else if (_moveY.value() < q->maxYExtent()) { + timeline.reset(_moveY); + if (fixupDuration) + timeline.move(_moveY, q->maxYExtent(), QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + else + _moveY.setValue(q->maxYExtent()); + //emit flickingChanged(); + } else { + flicked = false; + } + + vTime = timeline.time(); +} + +void QmlGraphicsFlickablePrivate::updateBeginningEnd() +{ + Q_Q(QmlGraphicsFlickable); + bool atBoundaryChange = false; + + // Vertical + const int maxyextent = int(-q->maxYExtent()); + const qreal ypos = -_moveY.value(); + bool atBeginning = (ypos <= -q->minYExtent()); + bool atEnd = (maxyextent <= ypos); + + if (atBeginning != atYBeginning) { + atYBeginning = atBeginning; + atBoundaryChange = true; + } + if (atEnd != atYEnd) { + atYEnd = atEnd; + atBoundaryChange = true; + } + + // Horizontal + const int maxxextent = int(-q->maxXExtent()); + const qreal xpos = -_moveX.value(); + atBeginning = (xpos <= -q->minXExtent()); + atEnd = (maxxextent <= xpos); + + if (atBeginning != atXBeginning) { + atXBeginning = atBeginning; + atBoundaryChange = true; + } + if (atEnd != atXEnd) { + atXEnd = atEnd; + atBoundaryChange = true; + } + + if (atBoundaryChange) + emit q->isAtBoundaryChanged(); + + if (visibleArea) + visibleArea->updateVisible(); +} + +QML_DEFINE_TYPE(Qt,4,6,Flickable,QmlGraphicsFlickable) + +/*! + \qmlclass Flickable QmlGraphicsFlickable + \brief The Flickable item provides a surface that can be "flicked". + \inherits Item + + Flickable places its children on a surface that can be dragged and flicked. + + \code + Flickable { + width: 200; height: 200; viewportWidth: image.width; viewportHeight: image.height + Image { id: image; source: "bigimage.png" } + } + \endcode + + \image flickable.gif + + \note Flickable does not automatically clip its contents. If + it is not full-screen it is likely that \c clip should be set + to true. + + \note Due to an implementation detail items placed inside a flickable cannot anchor to it by + id, use 'parent' instead. +*/ + +/*! + \qmlsignal Flickable::onMovementStarted() + + This handler is called when the view begins moving due to user + interaction. +*/ + +/*! + \qmlsignal Flickable::onMovementEnded() + + This handler is called when the view stops moving due to user + interaction. If a flick was generated, this handler will + be triggered once the flick stops. If a flick was not + generated, the handler will be triggered when the + user stops dragging - i.e. a mouse or touch release. +*/ + +/*! + \qmlsignal Flickable::onFlickStarted() + + This handler is called when the view is flicked. A flick + starts from the point that the mouse or touch is released, + while still in motion. +*/ + +/*! + \qmlsignal Flickable::onFlickEnded() + + This handler is called when the view stops moving due to a flick. +*/ + +/*! + \qmlproperty real Flickable::visibleArea.xPosition + \qmlproperty real Flickable::visibleArea.widthRatio + \qmlproperty real Flickable::visibleArea.yPosition + \qmlproperty real Flickable::visibleArea.heightRatio + + These properties describe the position and size of the currently viewed area. + The size is defined as the percentage of the full view currently visible, + scaled to 0.0 - 1.0. The page position is in the range 0.0 (beginning) to + size ratio (end), i.e. yPosition is in the range 0.0 - heightRatio. + + These properties are typically used to draw a scrollbar, for example: + \code + Rectangle { + opacity: 0.5; anchors.right: MyListView.right-2; width: 6 + y: MyListView.visibleArea.yPosition * MyListView.height + height: MyListView.visibleArea.heightRatio * MyListView.height + } + \endcode +*/ + +QmlGraphicsFlickable::QmlGraphicsFlickable(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsFlickablePrivate), parent) +{ + Q_D(QmlGraphicsFlickable); + d->init(); +} + +QmlGraphicsFlickable::QmlGraphicsFlickable(QmlGraphicsFlickablePrivate &dd, QmlGraphicsItem *parent) + : QmlGraphicsItem(dd, parent) +{ + Q_D(QmlGraphicsFlickable); + d->init(); +} + +QmlGraphicsFlickable::~QmlGraphicsFlickable() +{ +} + +/*! + \qmlproperty int Flickable::viewportX + \qmlproperty int Flickable::viewportY + + These properties hold the surface coordinate currently at the top-left + corner of the Flickable. For example, if you flick an image up 100 pixels, + \c yPosition will be 100. +*/ +qreal QmlGraphicsFlickable::viewportX() const +{ + Q_D(const QmlGraphicsFlickable); + return -d->_moveX.value(); +} + +void QmlGraphicsFlickable::setViewportX(qreal pos) +{ + Q_D(QmlGraphicsFlickable); + pos = qRound(pos); + d->timeline.reset(d->_moveX); + d->vTime = d->timeline.time(); + if (-pos != d->_moveX.value()) { + d->_moveX.setValue(-pos); + viewportMoved(); + } +} + +qreal QmlGraphicsFlickable::viewportY() const +{ + Q_D(const QmlGraphicsFlickable); + return -d->_moveY.value(); +} + +void QmlGraphicsFlickable::setViewportY(qreal pos) +{ + Q_D(QmlGraphicsFlickable); + pos = qRound(pos); + d->timeline.reset(d->_moveY); + d->vTime = d->timeline.time(); + if (-pos != d->_moveY.value()) { + d->_moveY.setValue(-pos); + viewportMoved(); + } +} + +/*! + \qmlproperty bool Flickable::interactive + + A user cannot drag or flick a Flickable that is not interactive. + + This property is useful for temporarily disabling flicking. This allows + special interaction with Flickable's children: for example, you might want to + freeze a flickable map while viewing detailed information on a location popup that is a child of the Flickable. +*/ +bool QmlGraphicsFlickable::isInteractive() const +{ + Q_D(const QmlGraphicsFlickable); + return d->interactive; +} + +void QmlGraphicsFlickable::setInteractive(bool interactive) +{ + Q_D(QmlGraphicsFlickable); + if (interactive != d->interactive) { + d->interactive = interactive; + if (!interactive && d->flicked) { + d->timeline.clear(); + d->vTime = d->timeline.time(); + d->flicked = false; + emit flickingChanged(); + emit flickEnded(); + } + emit interactiveChanged(); + } +} + +/*! + \qmlproperty real Flickable::horizontalVelocity + \qmlproperty real Flickable::verticalVelocity + \qmlproperty real Flickable::reportedVelocitySmoothing + + The instantaneous velocity of movement along the x and y axes, in pixels/sec. + + The reported velocity is smoothed to avoid erratic output. + reportedVelocitySmoothing determines how much smoothing is applied. +*/ +qreal QmlGraphicsFlickable::horizontalVelocity() const +{ + Q_D(const QmlGraphicsFlickable); + return d->horizontalVelocity.value(); +} + +qreal QmlGraphicsFlickable::verticalVelocity() const +{ + Q_D(const QmlGraphicsFlickable); + return d->verticalVelocity.value(); +} + +/*! + \qmlproperty bool Flickable::atXBeginning + \qmlproperty bool Flickable::atXEnd + \qmlproperty bool Flickable::atYBeginning + \qmlproperty bool Flickable::atYEnd + + These properties are true if the flickable view is positioned at the beginning, + or end respecively. +*/ +bool QmlGraphicsFlickable::isAtXEnd() const +{ + Q_D(const QmlGraphicsFlickable); + return d->atXEnd; +} + +bool QmlGraphicsFlickable::isAtXBeginning() const +{ + Q_D(const QmlGraphicsFlickable); + return d->atXBeginning; +} + +bool QmlGraphicsFlickable::isAtYEnd() const +{ + Q_D(const QmlGraphicsFlickable); + return d->atYEnd; +} + +bool QmlGraphicsFlickable::isAtYBeginning() const +{ + Q_D(const QmlGraphicsFlickable); + return d->atYBeginning; +} + +void QmlGraphicsFlickable::ticked() +{ + viewportMoved(); +} + +QmlGraphicsItem *QmlGraphicsFlickable::viewport() +{ + Q_D(QmlGraphicsFlickable); + return d->viewport; +} + +QmlGraphicsFlickableVisibleArea *QmlGraphicsFlickable::visibleArea() +{ + Q_D(QmlGraphicsFlickable); + if (!d->visibleArea) + d->visibleArea = new QmlGraphicsFlickableVisibleArea(this); + return d->visibleArea; +} + +/*! + \qmlproperty enumeration Flickable::flickDirection + + This property determines which directions the view can be flicked. + + \list + \o AutoFlickDirection (default) - allows flicking vertically if the + \e viewportHeight is not equal to the \e height of the Flickable. + Allows flicking horizontally if the \e viewportWidth is not equal + to the \e width of the Flickable. + \o HorizontalFlick - allows flicking horizontally. + \o VerticalFlick - allows flicking vertically. + \o HorizontalAndVerticalFlick - allows flicking in both directions. + \endlist +*/ +QmlGraphicsFlickable::FlickDirection QmlGraphicsFlickable::flickDirection() const +{ + Q_D(const QmlGraphicsFlickable); + return d->flickDirection; +} + +void QmlGraphicsFlickable::setFlickDirection(FlickDirection direction) +{ + Q_D(QmlGraphicsFlickable); + if (direction != d->flickDirection) { + d->flickDirection = direction; + emit flickDirectionChanged(); + } +} + +void QmlGraphicsFlickablePrivate::handleMousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (interactive && timeline.isActive() && (qAbs(velocityX) > 10 || qAbs(velocityY) > 10)) + stealMouse = true; // If we've been flicked then steal the click. + else + stealMouse = false; + pressed = true; + timeline.clear(); + velocityX = 0; + velocityY = 0; + lastPos = QPoint(); + QmlGraphicsItemPrivate::start(lastPosTime); + pressPos = event->pos(); + pressX = _moveX.value(); + pressY = _moveY.value(); + flicked = false; + QmlGraphicsItemPrivate::start(pressTime); + QmlGraphicsItemPrivate::start(velocityTime); +} + +void QmlGraphicsFlickablePrivate::handleMouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QmlGraphicsFlickable); + if (!interactive || lastPosTime.isNull()) + return; + bool rejectY = false; + bool rejectX = false; + bool moved = false; + + if (q->yflick()) { + int dy = int(event->pos().y() - pressPos.y()); + if (qAbs(dy) > QApplication::startDragDistance() || QmlGraphicsItemPrivate::elapsed(pressTime) > 200) { + qreal newY = dy + pressY; + const qreal minY = q->minYExtent(); + const qreal maxY = q->maxYExtent(); + if (newY > minY) + newY = minY + (newY - minY) / 2; + if (newY < maxY && maxY - minY <= 0) + newY = maxY + (newY - maxY) / 2; + if (!q->overShoot() && (newY > minY || newY < maxY)) { + if (newY > minY) + newY = minY; + else if (newY < maxY) + newY = maxY; + else + rejectY = true; + } + if (!rejectY) { + _moveY.setValue(newY); + moved = true; + } + if (qAbs(dy) > QApplication::startDragDistance()) + stealMouse = true; + } + } + + if (q->xflick()) { + int dx = int(event->pos().x() - pressPos.x()); + if (qAbs(dx) > QApplication::startDragDistance() || QmlGraphicsItemPrivate::elapsed(pressTime) > 200) { + qreal newX = dx + pressX; + const qreal minX = q->minXExtent(); + const qreal maxX = q->maxXExtent(); + if (newX > minX) + newX = minX + (newX - minX) / 2; + if (newX < maxX && maxX - minX <= 0) + newX = maxX + (newX - maxX) / 2; + if (!q->overShoot() && (newX > minX || newX < maxX)) { + if (newX > minX) + newX = minX; + else if (newX < maxX) + newX = maxX; + else + rejectX = true; + } + if (!rejectX) { + _moveX.setValue(newX); + moved = true; + } + + if (qAbs(dx) > QApplication::startDragDistance()) + stealMouse = true; + } + } + + if (!lastPos.isNull()) { + qreal elapsed = qreal(QmlGraphicsItemPrivate::restart(lastPosTime)) / 1000.; + if (elapsed <= 0) + elapsed = 1; + if (q->yflick()) { + qreal diff = event->pos().y() - lastPos.y(); + // average to reduce the effect of spurious moves + velocityY += diff / elapsed; + velocityY /= 2; + } + + if (q->xflick()) { + qreal diff = event->pos().x() - lastPos.x(); + // average to reduce the effect of spurious moves + velocityX += diff / elapsed; + velocityX /= 2; + } + } + + if (rejectY) velocityY = 0; + if (rejectX) velocityX = 0; + + if (moved) { + q->movementStarting(); + q->viewportMoved(); + } + + lastPos = event->pos(); +} + +void QmlGraphicsFlickablePrivate::handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QmlGraphicsFlickable); + pressed = false; + if (lastPosTime.isNull()) + return; + + if (QmlGraphicsItemPrivate::elapsed(lastPosTime) > 100) { + // if we drag then pause before release we should not cause a flick. + velocityX = 0.0; + velocityY = 0.0; + } + + vTime = timeline.time(); + if (qAbs(velocityY) > 10 && qAbs(event->pos().y() - pressPos.y()) > FlickThreshold) { + qreal velocity = velocityY; + if (qAbs(velocity) < minimumFlickVelocity) // Minimum velocity to avoid annoyingly slow flicks. + velocity = velocity < 0 ? -minimumFlickVelocity : minimumFlickVelocity; + flickY(velocity); + } else { + fixupY(); + } + + if (qAbs(velocityX) > 10 && qAbs(event->pos().x() - pressPos.x()) > FlickThreshold) { + qreal velocity = velocityX; + if (qAbs(velocity) < minimumFlickVelocity) // Minimum velocity to avoid annoyingly slow flicks. + velocity = velocity < 0 ? -minimumFlickVelocity : minimumFlickVelocity; + flickX(velocity); + } else { + fixupX(); + } + + stealMouse = false; + lastPosTime = QTime(); + + if (!timeline.isActive()) + q->movementEnding(); +} + +void QmlGraphicsFlickable::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsFlickable); + if (d->interactive) { + d->handleMousePressEvent(event); + event->accept(); + } else { + QmlGraphicsItem::mousePressEvent(event); + } +} + +void QmlGraphicsFlickable::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsFlickable); + if (d->interactive) { + d->handleMouseMoveEvent(event); + event->accept(); + } else { + QmlGraphicsItem::mouseMoveEvent(event); + } +} + +void QmlGraphicsFlickable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsFlickable); + if (d->interactive) { + d->clearDelayedPress(); + d->handleMouseReleaseEvent(event); + event->accept(); + ungrabMouse(); + } else { + QmlGraphicsItem::mouseReleaseEvent(event); + } +} + +void QmlGraphicsFlickable::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + Q_D(QmlGraphicsFlickable); + if (!d->interactive) { + QmlGraphicsItem::wheelEvent(event); + } else if (yflick()) { + if (event->delta() > 0) + d->velocityY = qMax(event->delta() - d->verticalVelocity.value(), qreal(250.0)); + else + d->velocityY = qMin(event->delta() - d->verticalVelocity.value(), qreal(-250.0)); + d->flicked = false; + d->flickY(d->velocityY); + event->accept(); + } else if (xflick()) { + if (event->delta() > 0) + d->velocityX = qMax(event->delta() - d->horizontalVelocity.value(), qreal(250.0)); + else + d->velocityX = qMin(event->delta() - d->horizontalVelocity.value(), qreal(-250.0)); + d->flicked = false; + d->flickX(d->velocityX); + event->accept(); + } else { + QmlGraphicsItem::wheelEvent(event); + } +} + +void QmlGraphicsFlickablePrivate::captureDelayedPress(QGraphicsSceneMouseEvent *event) +{ + Q_Q(QmlGraphicsFlickable); + if (!q->scene() || pressDelay <= 0) + return; + delayedPressTarget = q->scene()->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 QmlGraphicsFlickablePrivate::clearDelayedPress() +{ + if (delayedPressEvent) { + delayedPressTimer.stop(); + delete delayedPressEvent; + delayedPressEvent = 0; + } +} + +void QmlGraphicsFlickablePrivate::setRoundedViewportX(qreal x) +{ + viewport->setX(qRound(x)); +} + +void QmlGraphicsFlickablePrivate::setRoundedViewportY(qreal y) +{ + viewport->setY(qRound(y)); +} + +void QmlGraphicsFlickable::timerEvent(QTimerEvent *event) +{ + Q_D(QmlGraphicsFlickable); + if (event->timerId() == d->delayedPressTimer.timerId()) { + d->delayedPressTimer.stop(); + if (d->delayedPressEvent) { + QmlGraphicsItem *grabber = scene() ? qobject_cast<QmlGraphicsItem*>(scene()->mouseGrabberItem()) : 0; + if (!grabber || grabber != this) + scene()->sendEvent(d->delayedPressTarget, d->delayedPressEvent); + delete d->delayedPressEvent; + d->delayedPressEvent = 0; + } + } +} + +qreal QmlGraphicsFlickable::minYExtent() const +{ + return 0.0; +} + +qreal QmlGraphicsFlickable::minXExtent() const +{ + return 0.0; +} + +/* returns -ve */ +qreal QmlGraphicsFlickable::maxXExtent() const +{ + return width() - vWidth(); +} +/* returns -ve */ +qreal QmlGraphicsFlickable::maxYExtent() const +{ + return height() - vHeight(); +} + +void QmlGraphicsFlickable::viewportMoved() +{ + Q_D(QmlGraphicsFlickable); + + int elapsed = QmlGraphicsItemPrivate::restart(d->velocityTime); + if (!elapsed) + return; + + qreal prevY = d->lastFlickablePosition.x(); + qreal prevX = d->lastFlickablePosition.y(); + d->velocityTimeline.clear(); + if (d->pressed) { + qreal horizontalVelocity = (prevX - d->_moveX.value()) * 1000 / elapsed; + qreal verticalVelocity = (prevY - d->_moveY.value()) * 1000 / elapsed; + d->velocityTimeline.move(d->horizontalVelocity, horizontalVelocity, d->reportedVelocitySmoothing); + d->velocityTimeline.move(d->horizontalVelocity, 0, d->reportedVelocitySmoothing); + d->velocityTimeline.move(d->verticalVelocity, verticalVelocity, d->reportedVelocitySmoothing); + d->velocityTimeline.move(d->verticalVelocity, 0, d->reportedVelocitySmoothing); + } else { + if (d->timeline.time() > d->vTime) { + qreal horizontalVelocity = (prevX - d->_moveX.value()) * 1000 / (d->timeline.time() - d->vTime); + qreal verticalVelocity = (prevY - d->_moveY.value()) * 1000 / (d->timeline.time() - d->vTime); + d->horizontalVelocity.setValue(horizontalVelocity); + d->verticalVelocity.setValue(verticalVelocity); + } + } + + d->lastFlickablePosition = QPointF(d->_moveY.value(), d->_moveX.value()); + + d->vTime = d->timeline.time(); + d->updateBeginningEnd(); +} + +void QmlGraphicsFlickable::cancelFlick() +{ + Q_D(QmlGraphicsFlickable); + d->timeline.reset(d->_moveX); + d->timeline.reset(d->_moveY); + movementEnding(); +} + +void QmlGraphicsFlickablePrivate::data_removeAt(int) +{ + // ### +} + +int QmlGraphicsFlickablePrivate::data_count() const +{ + // ### + return 0; +} + +void QmlGraphicsFlickablePrivate::data_append(QObject *o) +{ + Q_Q(QmlGraphicsFlickable); + QmlGraphicsItem *i = qobject_cast<QmlGraphicsItem *>(o); + if (i) + viewport->fxChildren()->append(i); + else + o->setParent(q); +} + +void QmlGraphicsFlickablePrivate::data_insert(int, QObject *) +{ + // ### +} + +QObject *QmlGraphicsFlickablePrivate::data_at(int) const +{ + // ### + return 0; +} + +void QmlGraphicsFlickablePrivate::data_clear() +{ + // ### +} + + +QmlList<QObject *> *QmlGraphicsFlickable::flickableData() +{ + Q_D(QmlGraphicsFlickable); + return &d->data; +} + +QmlList<QmlGraphicsItem *> *QmlGraphicsFlickable::flickableChildren() +{ + Q_D(QmlGraphicsFlickable); + return d->viewport->fxChildren(); +} + +/*! + \qmlproperty bool Flickable::overShoot + This property holds the number of pixels the surface may overshoot the + Flickable's boundaries when flicked. + + If overShoot is non-zero the contents can be flicked beyond the boundary + of the Flickable before being moved back to the boundary. This provides + the feeling that the edges of the view are soft, rather than a hard + physical boundary. +*/ +bool QmlGraphicsFlickable::overShoot() const +{ + Q_D(const QmlGraphicsFlickable); + return d->overShoot; +} + +void QmlGraphicsFlickable::setOverShoot(bool o) +{ + Q_D(QmlGraphicsFlickable); + d->overShoot = o; +} + +/*! + \qmlproperty int Flickable::viewportWidth + \qmlproperty int Flickable::viewportHeight + + The dimensions of the viewport (the surface controlled by Flickable). Typically this + should be set to the combined size of the items placed in the Flickable. + + \code + Flickable { + width: 320; height: 480; viewportWidth: image.width; viewportHeight: image.height + Image { id: image; source: "bigimage.png" } + } + \endcode +*/ +qreal QmlGraphicsFlickable::viewportWidth() const +{ + Q_D(const QmlGraphicsFlickable); + return d->vWidth; +} + +void QmlGraphicsFlickable::setViewportWidth(qreal w) +{ + Q_D(QmlGraphicsFlickable); + if (d->vWidth == w) + return; + d->vWidth = w; + if (w < 0) + d->viewport->setWidth(width()); + else + d->viewport->setWidth(w); + // Make sure that we're entirely in view. + if (!d->pressed) + d->fixupX(); + emit viewportWidthChanged(); + d->updateBeginningEnd(); +} + +void QmlGraphicsFlickable::widthChange() +{ + Q_D(QmlGraphicsFlickable); + if (d->vWidth < 0) { + d->viewport->setWidth(width()); + emit viewportWidthChanged(); + } + d->updateBeginningEnd(); +} + +void QmlGraphicsFlickable::heightChange() +{ + Q_D(QmlGraphicsFlickable); + if (d->vHeight < 0) { + d->viewport->setHeight(height()); + emit viewportHeightChanged(); + } + d->updateBeginningEnd(); +} + +qreal QmlGraphicsFlickable::viewportHeight() const +{ + Q_D(const QmlGraphicsFlickable); + return d->vHeight; +} + +void QmlGraphicsFlickable::setViewportHeight(qreal h) +{ + Q_D(QmlGraphicsFlickable); + if (d->vHeight == h) + return; + d->vHeight = h; + if (h < 0) + d->viewport->setHeight(height()); + else + d->viewport->setHeight(h); + // Make sure that we're entirely in view. + if (!d->pressed) + d->fixupY(); + emit viewportHeightChanged(); + d->updateBeginningEnd(); +} + +qreal QmlGraphicsFlickable::vWidth() const +{ + Q_D(const QmlGraphicsFlickable); + if (d->vWidth < 0) + return width(); + else + return d->vWidth; +} + +qreal QmlGraphicsFlickable::vHeight() const +{ + Q_D(const QmlGraphicsFlickable); + if (d->vHeight < 0) + return height(); + else + return d->vHeight; +} + +bool QmlGraphicsFlickable::xflick() const +{ + Q_D(const QmlGraphicsFlickable); + if (d->flickDirection == QmlGraphicsFlickable::AutoFlickDirection) + return vWidth() != width(); + return d->flickDirection & QmlGraphicsFlickable::HorizontalFlick; +} + +bool QmlGraphicsFlickable::yflick() const +{ + Q_D(const QmlGraphicsFlickable); + if (d->flickDirection == QmlGraphicsFlickable::AutoFlickDirection) + return vHeight() != height(); + return d->flickDirection & QmlGraphicsFlickable::VerticalFlick; +} + +bool QmlGraphicsFlickable::sendMouseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsFlickable); + QGraphicsSceneMouseEvent mouseEvent(event->type()); + QRectF myRect = mapToScene(QRectF(0, 0, width(), height())).boundingRect(); + + QGraphicsScene *s = scene(); + QmlGraphicsItem *grabber = s ? qobject_cast<QmlGraphicsItem*>(s->mouseGrabberItem()) : 0; + if ((d->stealMouse || 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: + if (d->delayedPressEvent) + return false; + + d->handleMousePressEvent(&mouseEvent); + d->captureDelayedPress(event); + break; + case QEvent::GraphicsSceneMouseRelease: + if (d->delayedPressEvent) { + scene()->sendEvent(d->delayedPressTarget, d->delayedPressEvent); + d->clearDelayedPress(); + } + d->handleMouseReleaseEvent(&mouseEvent); + break; + default: + break; + } + grabber = qobject_cast<QmlGraphicsItem*>(s->mouseGrabberItem()); + if (grabber && d->stealMouse && !grabber->keepMouseGrab() && grabber != this) { + d->clearDelayedPress(); + grabMouse(); + } + + return d->stealMouse || d->delayedPressEvent; + } else if (!d->lastPosTime.isNull()) { + d->lastPosTime = QTime(); + } + if (mouseEvent.type() == QEvent::GraphicsSceneMouseRelease) + d->clearDelayedPress(); + return false; +} + +bool QmlGraphicsFlickable::sceneEventFilter(QGraphicsItem *i, QEvent *e) +{ + Q_D(QmlGraphicsFlickable); + if (!isVisible() || !d->interactive) + return QmlGraphicsItem::sceneEventFilter(i, e); + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + return sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); + default: + break; + } + + return QmlGraphicsItem::sceneEventFilter(i, e); +} + +/*! + \qmlproperty real Flickable::maximumFlickVelocity + This property holds the maximum velocity that the user can flick the view in pixels/second. + + The default is 2000 pixels/s +*/ +qreal QmlGraphicsFlickable::maximumFlickVelocity() const +{ + Q_D(const QmlGraphicsFlickable); + return d->maxVelocity; +} + +void QmlGraphicsFlickable::setMaximumFlickVelocity(qreal v) +{ + Q_D(QmlGraphicsFlickable); + if (v == d->maxVelocity) + return; + d->maxVelocity = v; +} + +/*! + \qmlproperty real Flickable::flickDeceleration + This property holds the rate at which a flick will decelerate. + + The default is 500. +*/ +qreal QmlGraphicsFlickable::flickDeceleration() const +{ + Q_D(const QmlGraphicsFlickable); + return d->deceleration; +} + +void QmlGraphicsFlickable::setFlickDeceleration(qreal deceleration) +{ + Q_D(QmlGraphicsFlickable); + d->deceleration = deceleration; +} + +/*! + \qmlproperty bool Flickable::flicking + + This property holds whether the view is currently moving due to + the user flicking the view. +*/ +bool QmlGraphicsFlickable::isFlicking() const +{ + Q_D(const QmlGraphicsFlickable); + return d->flicked; +} + +/*! + \qmlproperty int Flickable::pressDelay + + This property holds the time to delay (ms) delivering a press to + children of the Flickable. This can be useful where reacting + to a press before a flicking action has undesireable effects. + + If the flickable is dragged/flicked before the delay times out + the press event will not be delivered. If the button is released + within the timeout, both the press and release will be delivered. +*/ +int QmlGraphicsFlickable::pressDelay() const +{ + Q_D(const QmlGraphicsFlickable); + return d->pressDelay; +} + +void QmlGraphicsFlickable::setPressDelay(int delay) +{ + Q_D(QmlGraphicsFlickable); + if (d->pressDelay == delay) + return; + d->pressDelay = delay; +} + +qreal QmlGraphicsFlickable::reportedVelocitySmoothing() const +{ + Q_D(const QmlGraphicsFlickable); + return d->reportedVelocitySmoothing; +} + +void QmlGraphicsFlickable::setReportedVelocitySmoothing(qreal reportedVelocitySmoothing) +{ + Q_D(QmlGraphicsFlickable); + Q_ASSERT(reportedVelocitySmoothing >= 0); + if (reportedVelocitySmoothing == d->reportedVelocitySmoothing) + return; + d->reportedVelocitySmoothing = reportedVelocitySmoothing; + emit reportedVelocitySmoothingChanged(reportedVelocitySmoothing); +} + +/*! + \qmlproperty bool Flickable::moving + + This property holds whether the view is currently moving due to + the user either dragging or flicking the view. +*/ +bool QmlGraphicsFlickable::isMoving() const +{ + Q_D(const QmlGraphicsFlickable); + return d->moving; +} + +void QmlGraphicsFlickable::movementStarting() +{ + Q_D(QmlGraphicsFlickable); + if (!d->moving) { + d->moving = true; + emit movingChanged(); + emit movementStarted(); + } +} + +void QmlGraphicsFlickable::movementEnding() +{ + Q_D(QmlGraphicsFlickable); + if (d->moving) { + d->moving = false; + emit movingChanged(); + emit movementEnded(); + } + if (d->flicked) { + d->flicked = false; + emit flickingChanged(); + emit flickEnded(); + } + d->horizontalVelocity.setValue(0); + d->verticalVelocity.setValue(0); +} + +void QmlGraphicsFlickablePrivate::updateVelocity() +{ + Q_Q(QmlGraphicsFlickable); + emit q->horizontalVelocityChanged(); + emit q->verticalVelocityChanged(); +} + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsFlickableVisibleArea) +QML_DEFINE_TYPE(Qt,4,6,VisibleArea,QmlGraphicsFlickableVisibleArea) + +#include <qmlgraphicsflickable.moc> diff --git a/src/declarative/graphicsitems/qmlgraphicsflickable_p.h b/src/declarative/graphicsitems/qmlgraphicsflickable_p.h new file mode 100644 index 0000000..ea07da4 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsflickable_p.h @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSFLICKABLE_H +#define QMLGRAPHICSFLICKABLE_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsFlickablePrivate; +class QmlGraphicsFlickableVisibleArea; +class Q_DECLARATIVE_EXPORT QmlGraphicsFlickable : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(qreal viewportWidth READ viewportWidth WRITE setViewportWidth NOTIFY viewportWidthChanged) + Q_PROPERTY(qreal viewportHeight READ viewportHeight WRITE setViewportHeight NOTIFY viewportHeightChanged) + Q_PROPERTY(qreal viewportX READ viewportX WRITE setViewportX NOTIFY positionXChanged) + Q_PROPERTY(qreal viewportY READ viewportY WRITE setViewportY NOTIFY positionYChanged) + + Q_PROPERTY(qreal horizontalVelocity READ horizontalVelocity NOTIFY horizontalVelocityChanged) + Q_PROPERTY(qreal verticalVelocity READ verticalVelocity NOTIFY verticalVelocityChanged) + Q_PROPERTY(qreal reportedVelocitySmoothing READ reportedVelocitySmoothing WRITE setReportedVelocitySmoothing NOTIFY reportedVelocitySmoothingChanged) + + Q_PROPERTY(bool overShoot READ overShoot WRITE setOverShoot) + Q_PROPERTY(qreal maximumFlickVelocity READ maximumFlickVelocity WRITE setMaximumFlickVelocity) + Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration) + Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged) + Q_PROPERTY(bool flicking READ isFlicking NOTIFY flickingChanged) + Q_PROPERTY(FlickDirection flickDirection READ flickDirection WRITE setFlickDirection NOTIFY flickDirectionChanged) + + Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged) + Q_PROPERTY(int pressDelay READ pressDelay WRITE setPressDelay) + + 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(QmlGraphicsFlickableVisibleArea *visibleArea READ visibleArea CONSTANT) + + Q_PROPERTY(QmlList<QObject *>* flickableData READ flickableData) + Q_PROPERTY(QmlList<QmlGraphicsItem *>* flickableChildren READ flickableChildren) + Q_CLASSINFO("DefaultProperty", "flickableData") + + Q_ENUMS(FlickDirection) + +public: + QmlGraphicsFlickable(QmlGraphicsItem *parent=0); + ~QmlGraphicsFlickable(); + + QmlList<QObject *> *flickableData(); + QmlList<QmlGraphicsItem *> *flickableChildren(); + + bool overShoot() const; + void setOverShoot(bool); + + qreal viewportWidth() const; + void setViewportWidth(qreal); + + qreal viewportHeight() const; + void setViewportHeight(qreal); + + qreal viewportX() const; + void setViewportX(qreal pos); + + qreal viewportY() const; + void setViewportY(qreal pos); + + bool isMoving() const; + bool isFlicking() const; + + int pressDelay() const; + void setPressDelay(int delay); + + qreal reportedVelocitySmoothing() const; + void setReportedVelocitySmoothing(qreal); + + 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; + + QmlGraphicsItem *viewport(); + + enum FlickDirection { AutoFlickDirection=0x00, HorizontalFlick=0x01, VerticalFlick=0x02, HorizontalAndVerticalFlick=0x03 }; + FlickDirection flickDirection() const; + void setFlickDirection(FlickDirection); + +Q_SIGNALS: + void viewportWidthChanged(); + void viewportHeightChanged(); + void positionXChanged(); + void positionYChanged(); + void movingChanged(); + void flickingChanged(); + void movementStarted(); + void movementEnded(); + void flickStarted(); + void flickEnded(); + void reportedVelocitySmoothingChanged(int); + void horizontalVelocityChanged(); + void verticalVelocityChanged(); + void isAtBoundaryChanged(); + void pageChanged(); + void flickDirectionChanged(); + void interactiveChanged(); + +protected: + virtual bool sceneEventFilter(QGraphicsItem *, QEvent *); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void wheelEvent(QGraphicsSceneWheelEvent *event); + void timerEvent(QTimerEvent *event); + + QmlGraphicsFlickableVisibleArea *visibleArea(); + +protected Q_SLOTS: + virtual void ticked(); + void movementStarting(); + void movementEnding(); + void heightChange(); + void widthChange(); + +protected: + 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(); + bool sendMouseEvent(QGraphicsSceneMouseEvent *event); + + bool xflick() const; + bool yflick() const; + void cancelFlick(); + +protected: + QmlGraphicsFlickable(QmlGraphicsFlickablePrivate &dd, QmlGraphicsItem *parent); + +private: + Q_DISABLE_COPY(QmlGraphicsFlickable) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsFlickable) + friend class QmlGraphicsFlickableVisibleArea; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsFlickable) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h b/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h new file mode 100644 index 0000000..ae164cc --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsflickable_p_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSFLICKABLE_P_H +#define QMLGRAPHICSFLICKABLE_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 "qmlgraphicsflickable_p.h" + +#include "qmlgraphicsitem_p.h" + +#include <qml.h> +#include <qmltimeline_p_p.h> +#include <qmlanimation_p_p.h> + +#include <qdatetime.h> + +QT_BEGIN_NAMESPACE + +class QmlGraphicsFlickableVisibleArea; +class QmlGraphicsFlickablePrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsFlickable) + +public: + QmlGraphicsFlickablePrivate(); + void init(); + virtual void flickX(qreal velocity); + virtual void flickY(qreal velocity); + virtual void fixupX(); + virtual void fixupY(); + void updateBeginningEnd(); + + void captureDelayedPress(QGraphicsSceneMouseEvent *event); + void clearDelayedPress(); + + void setRoundedViewportX(qreal x); + void setRoundedViewportY(qreal y); + +public: + QmlGraphicsItem *viewport; + QmlTimeLineValueProxy<QmlGraphicsFlickablePrivate> _moveX; + QmlTimeLineValueProxy<QmlGraphicsFlickablePrivate> _moveY; + QmlTimeLine timeline; + qreal vWidth; + qreal vHeight; + bool overShoot : 1; + bool flicked : 1; + bool moving : 1; + bool stealMouse : 1; + bool pressed : 1; + bool atXEnd : 1; + bool atXBeginning : 1; + bool atYEnd : 1; + bool atYBeginning : 1; + bool interactive : 1; + QTime lastPosTime; + QPointF lastPos; + QPointF pressPos; + qreal pressX; + qreal pressY; + qreal velocityX; + qreal velocityY; + QTime pressTime; + QmlTimeLineEvent fixupXEvent; + QmlTimeLineEvent fixupYEvent; + qreal deceleration; + qreal maxVelocity; + QTime velocityTime; + QPointF lastFlickablePosition; + qreal reportedVelocitySmoothing; + qreal flickTargetX; + qreal flickTargetY; + QGraphicsSceneMouseEvent *delayedPressEvent; + QGraphicsItem *delayedPressTarget; + QBasicTimer delayedPressTimer; + int pressDelay; + int fixupDuration; + + void updateVelocity(); + struct Velocity : public QmlTimeLineValue + { + Velocity(QmlGraphicsFlickablePrivate *p) + : parent(p) {} + virtual void setValue(qreal v) { + QmlTimeLineValue::setValue(v); + parent->updateVelocity(); + } + QmlGraphicsFlickablePrivate *parent; + }; + Velocity horizontalVelocity; + Velocity verticalVelocity; + int vTime; + QmlTimeLine velocityTimeline; + QmlGraphicsFlickableVisibleArea *visibleArea; + QmlGraphicsFlickable::FlickDirection flickDirection; + + void handleMousePressEvent(QGraphicsSceneMouseEvent *); + void handleMouseMoveEvent(QGraphicsSceneMouseEvent *); + void handleMouseReleaseEvent(QGraphicsSceneMouseEvent *); + + // flickableData property + void data_removeAt(int); + int data_count() const; + void data_append(QObject *); + void data_insert(int, QObject *); + QObject *data_at(int) const; + void data_clear(); + + friend class QmlGraphicsFlickableVisibleArea; + QML_DECLARE_LIST_PROXY(QmlGraphicsFlickablePrivate, QObject *, data) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsflipable.cpp b/src/declarative/graphicsitems/qmlgraphicsflipable.cpp new file mode 100644 index 0000000..ff8995b --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsflipable.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsflipable_p.h" + +#include "qmlgraphicsitem_p.h" + +#include <qmlinfo.h> + +#include <QtGui/qgraphicstransform.h> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_TYPE(Qt,4,6,Flipable,QmlGraphicsFlipable) + +class QmlGraphicsFlipablePrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsFlipable) +public: + QmlGraphicsFlipablePrivate() : current(QmlGraphicsFlipable::Front), front(0), back(0) {} + + void updateSceneTransformFromParent(); + + QmlGraphicsFlipable::Side current; + QmlGraphicsItem *front; + QmlGraphicsItem *back; +}; + +/*! + \qmlclass Flipable QmlGraphicsFlipable + \brief The Flipable item provides a surface that can be flipped. + \inherits Item + + Flipable allows you to specify a front and a back and then flip between those sides. + + Here's an example that flips between the front and back sides when clicked: + + \qml + + Flipable { + id: flipable + width: 250; height: 250 + property int angle: 0 + + transform: Rotation { + id: rotation + origin.x: flipable.width/2; origin.y: flipable.height/2 + axis.x: 0; axis.y: 1; axis.z: 0 // rotate around y-axis + angle: flipable.angle + } + + front: Image { source: "front.png" } + back: Image { source: "back.png" } + + states: State { + name: "back" + PropertyChanges { target: flipable; angle: 180 } + } + + transitions: Transition { + NumberAnimation { matchProperties: "angle"; duration: 2000 } + } + + MouseRegion { + // change between default and 'back' states + onClicked: flipable.state = (flipable.state == 'back' ? '' : 'back') + anchors.fill: parent + } + } + \endqml + + \image flipable.gif +*/ + +/*! + \internal + \class QmlGraphicsFlipable + \brief The QmlGraphicsFlipable class provides a flipable surface. + + \ingroup group_widgets + + QmlGraphicsFlipable allows you to specify a front and a back, as well as an + axis for the flip. +*/ + +QmlGraphicsFlipable::QmlGraphicsFlipable(QmlGraphicsItem *parent) +: QmlGraphicsItem(*(new QmlGraphicsFlipablePrivate), parent) +{ +} + +QmlGraphicsFlipable::~QmlGraphicsFlipable() +{ +} + +/*! + \qmlproperty Item Flipable::front + \qmlproperty Item Flipable::back + + The front and back sides of the flipable. +*/ + +QmlGraphicsItem *QmlGraphicsFlipable::front() +{ + Q_D(const QmlGraphicsFlipable); + return d->front; +} + +void QmlGraphicsFlipable::setFront(QmlGraphicsItem *front) +{ + Q_D(QmlGraphicsFlipable); + if (d->front) { + qmlInfo(this) << tr("front is a write-once property"); + return; + } + d->front = front; + fxChildren()->append(d->front); + if (Back == d->current) + d->front->setOpacity(0.); +} + +QmlGraphicsItem *QmlGraphicsFlipable::back() +{ + Q_D(const QmlGraphicsFlipable); + return d->back; +} + +void QmlGraphicsFlipable::setBack(QmlGraphicsItem *back) +{ + Q_D(QmlGraphicsFlipable); + if (d->back) { + qmlInfo(this) << tr("back is a write-once property"); + return; + } + d->back = back; + fxChildren()->append(d->back); + if (Front == d->current) + d->back->setOpacity(0.); +} + +/*! + \qmlproperty enumeration Flipable::side + + The side of the Flippable currently visible. Possible values are \c + Front and \c Back. +*/ +QmlGraphicsFlipable::Side QmlGraphicsFlipable::side() const +{ + Q_D(const QmlGraphicsFlipable); + if (d->dirtySceneTransform) + const_cast<QmlGraphicsFlipablePrivate *>(d)->updateSceneTransformFromParent(); + + return d->current; +} + +// determination on the currently visible side of the flipable +// has to be done on the complete scene transform to give +// correct results. +void QmlGraphicsFlipablePrivate::updateSceneTransformFromParent() +{ + Q_Q(QmlGraphicsFlipable); + + QmlGraphicsItemPrivate::updateSceneTransformFromParent(); + QPointF p1(0, 0); + QPointF p2(1, 0); + QPointF p3(1, 1); + + p1 = sceneTransform.map(p1); + p2 = sceneTransform.map(p2); + p3 = sceneTransform.map(p3); + + qreal cross = (p1.x() - p2.x()) * (p3.y() - p2.y()) - + (p1.y() - p2.y()) * (p3.x() - p2.x()); + + QmlGraphicsFlipable::Side newSide; + if (cross > 0) { + newSide = QmlGraphicsFlipable::Back; + } else { + newSide = QmlGraphicsFlipable::Front; + } + + if (newSide != current) { + current = newSide; + if (current == QmlGraphicsFlipable::Back) { + QTransform mat; + mat.translate(back->width()/2,back->height()/2); + if (back->width() && p1.x() >= p2.x()) + mat.rotate(180, Qt::YAxis); + if (back->height() && p2.y() >= p3.y()) + mat.rotate(180, Qt::XAxis); + mat.translate(-back->width()/2,-back->height()/2); + back->setTransform(mat); + } + if (front) + front->setOpacity((current==QmlGraphicsFlipable::Front)?1.:0.); + if (back) + back->setOpacity((current==QmlGraphicsFlipable::Back)?1.:0.); + emit q->sideChanged(); + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsflipable_p.h b/src/declarative/graphicsitems/qmlgraphicsflipable_p.h new file mode 100644 index 0000000..c189786 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsflipable_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSFLIPABLE_H +#define QMLGRAPHICSFLIPABLE_H + +#include "qmlgraphicsitem.h" + +#include <QtCore/QObject> +#include <QtGui/QTransform> +#include <QtGui/qvector3d.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsFlipablePrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsFlipable : public QmlGraphicsItem +{ + Q_OBJECT + + Q_ENUMS(Side) + Q_PROPERTY(QmlGraphicsItem *front READ front WRITE setFront) + Q_PROPERTY(QmlGraphicsItem *back READ back WRITE setBack) + Q_PROPERTY(Side side READ side NOTIFY sideChanged) + //### flipAxis + //### flipRotation +public: + QmlGraphicsFlipable(QmlGraphicsItem *parent=0); + ~QmlGraphicsFlipable(); + + QmlGraphicsItem *front(); + void setFront(QmlGraphicsItem *); + + QmlGraphicsItem *back(); + void setBack(QmlGraphicsItem *); + + enum Side { Front, Back }; + Side side() const; + +Q_SIGNALS: + void sideChanged(); + +private: + Q_DISABLE_COPY(QmlGraphicsFlipable) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsFlipable) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsFlipable) + +QT_END_HEADER + +#endif // QMLGRAPHICSFLIPABLE_H diff --git a/src/declarative/graphicsitems/qmlgraphicsfocuspanel.cpp b/src/declarative/graphicsitems/qmlgraphicsfocuspanel.cpp new file mode 100644 index 0000000..333b689 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsfocuspanel.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsfocuspanel_p.h" + +#include <QtGui/qgraphicsscene.h> +#include <QEvent> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_TYPE(Qt,4,6,FocusPanel,QmlGraphicsFocusPanel) + +/*! + \qmlclass FocusPanel QmlGraphicsFocusPanel + \brief The FocusPanel item explicitly creates a focus panel. + \inherits Item + + Focus panels assist in keyboard focus handling when building QML + applications. All the details are covered in the + \l {qmlfocus}{keyboard focus documentation}. +*/ + +/*! + \internal + \class QmlGraphicsFocusPanel +*/ + +QmlGraphicsFocusPanel::QmlGraphicsFocusPanel(QmlGraphicsItem *parent) : + QmlGraphicsItem(parent) +{ + setFlag(ItemIsPanel); +} + +QmlGraphicsFocusPanel::~QmlGraphicsFocusPanel() +{ +} + +/*! + \qmlproperty bool FocusPanel::active + + Sets whether the item is the active focus panel. +*/ + +bool QmlGraphicsFocusPanel::sceneEvent(QEvent *event) +{ + if (event->type() == QEvent::WindowActivate || + event->type() == QEvent::WindowDeactivate) + emit activeChanged(); + return QmlGraphicsItem::sceneEvent(event); +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsfocuspanel_p.h b/src/declarative/graphicsitems/qmlgraphicsfocuspanel_p.h new file mode 100644 index 0000000..4c5cc14 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsfocuspanel_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSFOCUSPANEL_H +#define QMLGRAPHICSFOCUSPANEL_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QmlGraphicsFocusPanel : public QmlGraphicsItem +{ + Q_OBJECT + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) +public: + QmlGraphicsFocusPanel(QmlGraphicsItem *parent=0); + virtual ~QmlGraphicsFocusPanel(); + +Q_SIGNALS: + void activeChanged(); + +protected: + bool sceneEvent(QEvent *event); + +private: + Q_DISABLE_COPY(QmlGraphicsFocusPanel) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsFocusPanel) + +QT_END_HEADER + +#endif // QMLGRAPHICSFOCUSPANEL_H diff --git a/src/declarative/graphicsitems/qmlgraphicsfocusscope.cpp b/src/declarative/graphicsitems/qmlgraphicsfocusscope.cpp new file mode 100644 index 0000000..828756c --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsfocusscope.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsfocusscope_p.h" + +QT_BEGIN_NAMESPACE +QML_DEFINE_TYPE(Qt,4,6,FocusScope,QmlGraphicsFocusScope) + +/*! + \qmlclass FocusScope QmlGraphicsFocusScope + \brief The FocusScope object explicitly creates a focus scope. + \inherits Item + + Focus scopes assist in keyboard focus handling when building reusable QML + components. All the details are covered in the + \l {qmlfocus}{keyboard focus documentation}. +*/ + +/*! + \internal + \class QmlGraphicsFocusScope +*/ + +QmlGraphicsFocusScope::QmlGraphicsFocusScope(QmlGraphicsItem *parent) : + QmlGraphicsItem(parent) +{ + setFlag(QGraphicsItem::ItemIsFocusScope); +} + +QmlGraphicsFocusScope::~QmlGraphicsFocusScope() +{ +} +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsfocusscope_p.h b/src/declarative/graphicsitems/qmlgraphicsfocusscope_p.h new file mode 100644 index 0000000..0ea9da5 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsfocusscope_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSFOCUSSCOPE_H +#define QMLGRAPHICSFOCUSSCOPE_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +//### set component root as focusscope +class Q_DECLARATIVE_EXPORT QmlGraphicsFocusScope : public QmlGraphicsItem +{ + Q_OBJECT +public: + QmlGraphicsFocusScope(QmlGraphicsItem *parent=0); + virtual ~QmlGraphicsFocusScope(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsFocusScope) + +QT_END_HEADER + +#endif // QMLGRAPHICSFOCUSSCOPE_H diff --git a/src/declarative/graphicsitems/qmlgraphicsgraphicsobjectcontainer.cpp b/src/declarative/graphicsitems/qmlgraphicsgraphicsobjectcontainer.cpp new file mode 100644 index 0000000..a5a7935 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsgraphicsobjectcontainer.cpp @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsgraphicsobjectcontainer_p.h" + +#include "qmlgraphicsitem_p.h" + +#include <QGraphicsObject> +#include <QGraphicsWidget> +#include <QGraphicsSceneResizeEvent> + +QT_BEGIN_NAMESPACE + +class QmlGraphicsGraphicsObjectContainerPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsGraphicsObjectContainer) + +public: + QmlGraphicsGraphicsObjectContainerPrivate() : QmlGraphicsItemPrivate(), graphicsObject(0), syncedResize(false) + { } + + void _q_updateSize(); + + void setFiltering(bool on) + { + Q_Q(QmlGraphicsGraphicsObjectContainer); + if (graphicsObject && graphicsObject->isWidget()) { + if (!on) { + graphicsObject->removeEventFilter(q); + QObject::disconnect(q, SIGNAL(widthChanged()), q, SLOT(_q_updateSize())); + QObject::disconnect(q, SIGNAL(heightChanged()), q, SLOT(_q_updateSize())); + } else { + graphicsObject->installEventFilter(q); + QObject::connect(q, SIGNAL(widthChanged()), q, SLOT(_q_updateSize())); + QObject::connect(q, SIGNAL(heightChanged()), q, SLOT(_q_updateSize())); + } + } + } + + + QGraphicsObject *graphicsObject; + bool syncedResize; +}; + + +/*! + \qmlclass GraphicsObjectContainer QmlGraphicsGraphicsObjectContainer + \brief The GraphicsObjectContainer element allows you to add QGraphicsObjects into Fluid UI elements. + + While any QObject based class can be exposed to QML, QmlGraphicsItem + provides a lot of important functionality, including anchors and proper + management of child items. GraphicsObjectContainer helps provide these + functions to other QGraphicsObjects, so that they can be used unaltered in + a QML scene. QGraphicsObjects, which are not QmlGraphicsItems, and which are + placed in a QML scene outside of a GraphicsObjectContainer, will not appear + on screen at all. + + A GraphicsObjectContainer can have one element inside it, and it must be a + QGraphicsObject or subclass which has been exposed to the QML engine. + The graphics object inside the GraphicsObjectContainer can then be used + like any other item in QML with the exception of not being reparentable + and not having the standard properties of QML items (such as anchors). + + As the contained object is positioned relative to the container, anchors + affecting the container item will affect the onscreen position of the + contained item. If synchronizedResizing is set to true, then anchors + affecting the container item's size will also affect the contained item's + size. + + Example: + \code + import Qt 4.6 + import MyApp 2.1 as Widgets + Rectangle{ + id: rect + property alias widgetPropertyThree: widget.myThirdProperty; + GraphicsObjectContainer{ + synchronizedResizing: true + anchors.margins: 10 + anchors.fill: parent + Widgets.MyWidget{ + myProperty: "A Value" + myOtherProperty: rect.color + } + } + } + \endcode +*/ + +/*! + \internal + \class QmlGraphicsGraphicsObjectContainer + \brief The QmlGraphicsGraphicsObjectContainer class allows you to add QGraphicsObjects into Fluid UI applications. +*/ + +QML_DEFINE_NOCREATE_TYPE(QGraphicsObject) +QML_DEFINE_TYPE(Qt,4,6,GraphicsObjectContainer,QmlGraphicsGraphicsObjectContainer) + +QmlGraphicsGraphicsObjectContainer::QmlGraphicsGraphicsObjectContainer(QmlGraphicsItem *parent) +: QmlGraphicsItem(*new QmlGraphicsGraphicsObjectContainerPrivate, parent) +{ +} + +QmlGraphicsGraphicsObjectContainer::~QmlGraphicsGraphicsObjectContainer() +{ +} + +QGraphicsObject *QmlGraphicsGraphicsObjectContainer::graphicsObject() const +{ + Q_D(const QmlGraphicsGraphicsObjectContainer); + return d->graphicsObject; +} + +/*! + \qmlproperty QGraphicsObject GraphicsObjectContainer::graphicsObject + The QGraphicsObject associated with this element. +*/ +void QmlGraphicsGraphicsObjectContainer::setGraphicsObject(QGraphicsObject *object) +{ + Q_D(QmlGraphicsGraphicsObjectContainer); + if (object == d->graphicsObject) + return; + + //### remove previously set item? + + d->setFiltering(false); + + d->graphicsObject = object; + + if (d->graphicsObject) { + d->graphicsObject->setParentItem(this); + + if (d->syncedResize && d->graphicsObject->isWidget()) { + QGraphicsWidget *gw = static_cast<QGraphicsWidget*>(d->graphicsObject); + QSizeF gwSize = gw->size(); //### should we use sizeHint? + QSizeF newSize = gwSize; + if (heightValid()) + newSize.setHeight(height()); + if (widthValid()) + newSize.setWidth(width()); + if (gwSize != newSize) + gw->resize(newSize); + + gwSize = gw->size(); + setImplicitWidth(gwSize.width()); + setImplicitHeight(gwSize.height()); + + d->setFiltering(true); + } + } +} + +QVariant QmlGraphicsGraphicsObjectContainer::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_D(QmlGraphicsGraphicsObjectContainer); + if (change == ItemSceneHasChanged) { + QGraphicsObject *o = d->graphicsObject; + d->graphicsObject = 0; + setGraphicsObject(o); + } + return QmlGraphicsItem::itemChange(change, value); +} + +bool QmlGraphicsGraphicsObjectContainer::eventFilter(QObject *watched, QEvent *e) +{ + Q_D(QmlGraphicsGraphicsObjectContainer); + if (watched == d->graphicsObject && e->type() == QEvent::GraphicsSceneResize) { + if (d->graphicsObject && d->graphicsObject->isWidget() && d->syncedResize) { + QSizeF newSize = static_cast<QGraphicsWidget*>(d->graphicsObject)->size(); + setImplicitWidth(newSize.width()); + setImplicitHeight(newSize.height()); + } + } + return QmlGraphicsItem::eventFilter(watched, e); +} + +/*! + \qmlproperty bool GraphicsObjectContainer::synchronizedResizing + + This property determines whether or not the container and graphics object will synchronize their + sizes. + + \note This property only applies when wrapping a QGraphicsWidget. + + If synchronizedResizing is enabled, the container and widget will + synchronize their sizes as follows. + \list + \o If a size has been set on the container, the widget will be resized to the container. + Any changes in the container's size will be reflected in the widget. + + \o \e Otherwise, the container will initially be sized to the preferred size of the widget. + Any changes to the container's size will be reflected in the widget, and any changes to the + widget's size will be reflected in the container. + \endlist +*/ +bool QmlGraphicsGraphicsObjectContainer::synchronizedResizing() const +{ + Q_D(const QmlGraphicsGraphicsObjectContainer); + return d->syncedResize; +} + +void QmlGraphicsGraphicsObjectContainer::setSynchronizedResizing(bool on) +{ + Q_D(QmlGraphicsGraphicsObjectContainer); + if (on == d->syncedResize) + return; + + d->syncedResize = on; + d->setFiltering(on); +} + +void QmlGraphicsGraphicsObjectContainerPrivate::_q_updateSize() +{ + if (!graphicsObject || !graphicsObject->isWidget() || !syncedResize) + return; + + QGraphicsWidget *gw = static_cast<QGraphicsWidget*>(graphicsObject); + const QSizeF newSize(width, height); + gw->resize(newSize); + + //### will respecting the widgets min/max ever get us in trouble? (all other items always + // size to exactly what you tell them) + /*QSizeF constrainedSize = newSize.expandedTo(gw->minimumSize()).boundedTo(gw->maximumSize()); + gw->resize(constrainedSize); + if (constrainedSize != newSize) { + setImplicitWidth(constrainedSize.width()); + setImplicitHeight(constrainedSize.height()); + }*/ +} + +#include <moc_qmlgraphicsgraphicsobjectcontainer_p.cpp> + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsgraphicsobjectcontainer_p.h b/src/declarative/graphicsitems/qmlgraphicsgraphicsobjectcontainer_p.h new file mode 100644 index 0000000..1091145 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsgraphicsobjectcontainer_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSGRAPHICSOBJECTCONTAINER_H +#define QMLGRAPHICSGRAPHICSOBJECTCONTAINER_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QGraphicsObject; +class QmlGraphicsGraphicsObjectContainerPrivate; + +class Q_DECLARATIVE_EXPORT QmlGraphicsGraphicsObjectContainer : public QmlGraphicsItem +{ + Q_OBJECT + + Q_CLASSINFO("DefaultProperty", "graphicsObject") + Q_PROPERTY(QGraphicsObject *graphicsObject READ graphicsObject WRITE setGraphicsObject) + Q_PROPERTY(bool synchronizedResizing READ synchronizedResizing WRITE setSynchronizedResizing) + +public: + QmlGraphicsGraphicsObjectContainer(QmlGraphicsItem *parent = 0); + ~QmlGraphicsGraphicsObjectContainer(); + + QGraphicsObject *graphicsObject() const; + void setGraphicsObject(QGraphicsObject *); + + bool synchronizedResizing() const; + void setSynchronizedResizing(bool on); + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + bool eventFilter(QObject *watched, QEvent *e); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_updateSize()) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsGraphicsObjectContainer) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QGraphicsObject) +QML_DECLARE_TYPE(QmlGraphicsGraphicsObjectContainer) + +QT_END_HEADER + +#endif // QMLGRAPHICSGRAPHICSOBJECTCONTAINER_H diff --git a/src/declarative/graphicsitems/qmlgraphicsgridview.cpp b/src/declarative/graphicsitems/qmlgraphicsgridview.cpp new file mode 100644 index 0000000..83911c0 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsgridview.cpp @@ -0,0 +1,1761 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsgridview_p.h" + +#include "qmlgraphicsvisualitemmodel_p.h" +#include "qmlgraphicsflickable_p_p.h" + +#include <qmleasefollow_p.h> + +#include <qlistmodelinterface_p.h> +#include <QKeyEvent> + +QT_BEGIN_NAMESPACE + +class QmlGraphicsGridViewAttached : public QObject +{ + Q_OBJECT +public: + QmlGraphicsGridViewAttached(QObject *parent) + : QObject(parent), m_isCurrent(false), m_delayRemove(false) {} + ~QmlGraphicsGridViewAttached() { + attachedProperties.remove(parent()); + } + + Q_PROPERTY(QmlGraphicsGridView *view READ view CONSTANT) + QmlGraphicsGridView *view() { return m_view; } + + 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(); + } + } + + static QmlGraphicsGridViewAttached *properties(QObject *obj) { + QmlGraphicsGridViewAttached *rv = attachedProperties.value(obj); + if (!rv) { + rv = new QmlGraphicsGridViewAttached(obj); + attachedProperties.insert(obj, rv); + } + return rv; + } + + void emitAdd() { emit add(); } + void emitRemove() { emit remove(); } + +Q_SIGNALS: + void currentItemChanged(); + void delayRemoveChanged(); + void add(); + void remove(); + +public: + QmlGraphicsGridView *m_view; + bool m_isCurrent; + bool m_delayRemove; + + static QHash<QObject*, QmlGraphicsGridViewAttached*> attachedProperties; +}; + +QHash<QObject*, QmlGraphicsGridViewAttached*> QmlGraphicsGridViewAttached::attachedProperties; + + +//---------------------------------------------------------------------------- + +class FxGridItem +{ +public: + FxGridItem(QmlGraphicsItem *i, QmlGraphicsGridView *v) : item(i), view(v) { + attached = QmlGraphicsGridViewAttached::properties(item); + attached->m_view = view; + } + ~FxGridItem() {} + + qreal rowPos() const { return (view->flow() == QmlGraphicsGridView::LeftToRight ? item->y() : item->x()); } + qreal colPos() const { return (view->flow() == QmlGraphicsGridView::LeftToRight ? item->x() : item->y()); } + qreal endRowPos() const { + return view->flow() == QmlGraphicsGridView::LeftToRight + ? item->y() + view->cellHeight() - 1 + : item->x() + view->cellWidth() - 1; + } + void setPosition(qreal col, qreal row) { + if (view->flow() == QmlGraphicsGridView::LeftToRight) { + item->setPos(QPointF(col, row)); + } else { + item->setPos(QPointF(row, col)); + } + } + + QmlGraphicsItem *item; + QmlGraphicsGridView *view; + QmlGraphicsGridViewAttached *attached; + int index; +}; + +//---------------------------------------------------------------------------- + +class QmlGraphicsGridViewPrivate : public QmlGraphicsFlickablePrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsGridView) + +public: + QmlGraphicsGridViewPrivate() + : currentItem(0), flow(QmlGraphicsGridView::LeftToRight) + , visiblePos(0), visibleIndex(0) , currentIndex(-1) + , cellWidth(100), cellHeight(100), columns(1), requestedIndex(-1) + , highlightComponent(0), highlight(0), trackedItem(0) + , moveReason(Other), buffer(0), highlightXAnimator(0), highlightYAnimator(0) + , bufferMode(NoBuffer) + , ownModel(false), wrap(false), autoHighlight(true) + , fixCurrentVisibility(false), lazyRelease(false), layoutScheduled(false) + , deferredRelease(false) {} + + void init(); + void clear(); + FxGridItem *createItem(int modelIndex); + void releaseItem(FxGridItem *item); + void refill(qreal from, qreal to, bool doBuffer=false); + + void updateGrid(); + void scheduleLayout(); + void layout(bool removed=false); + void updateUnrequestedIndexes(); + void updateUnrequestedPositions(); + void updateTrackedItem(); + void createHighlight(); + void updateHighlight(); + void updateCurrent(int modelIndex); + + FxGridItem *visibleItem(int modelIndex) const { + if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) { + for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) { + FxGridItem *item = visibleItems.at(i); + if (item->index == modelIndex) + return item; + } + } + return 0; + } + + qreal position() const { + Q_Q(const QmlGraphicsGridView); + return flow == QmlGraphicsGridView::LeftToRight ? q->viewportY() : q->viewportX(); + } + void setPosition(qreal pos) { + Q_Q(QmlGraphicsGridView); + if (flow == QmlGraphicsGridView::LeftToRight) + q->setViewportY(pos); + else + q->setViewportX(pos); + } + int size() const { + Q_Q(const QmlGraphicsGridView); + return flow == QmlGraphicsGridView::LeftToRight ? q->height() : q->width(); + } + qreal startPosition() const { + qreal pos = 0; + if (!visibleItems.isEmpty()) + pos = visibleItems.first()->rowPos() - visibleIndex / columns * rowSize(); + return pos; + } + + qreal endPosition() const { + qreal pos = 0; + if (model && model->count()) + pos = rowPosAt(model->count() - 1) + rowSize(); + return pos; + } + + bool isValid() const { + return model && model->count() && model->isValid(); + } + + int rowSize() const { + return flow == QmlGraphicsGridView::LeftToRight ? cellHeight : cellWidth; + } + int colSize() const { + return flow == QmlGraphicsGridView::LeftToRight ? cellWidth : cellHeight; + } + + qreal colPosAt(int modelIndex) const { + if (FxGridItem *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 (FxGridItem *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 { + return (modelIndex / columns) * rowSize(); + } + return 0; + } + + FxGridItem *firstVisibleItem() const { + const qreal pos = position(); + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItem *item = visibleItems.at(i); + if (item->index != -1 && item->endRowPos() > pos) + return item; + } + return visibleItems.count() ? visibleItems.first() : 0; + } + + // 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) { + FxGridItem *listItem = visibleItems.at(i); + if (listItem->index == modelIndex) + return i + visibleIndex; + if (listItem->index > modelIndex) + return -1; + } + return -1; // Not in visibleList + } + + // for debugging only + void checkVisible() const { + int skip = 0; + for (int i = 0; i < visibleItems.count(); ++i) { + FxGridItem *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); + } + } + } + + QGuard<QmlGraphicsVisualModel> model; + QVariant modelVariant; + QList<FxGridItem*> visibleItems; + QHash<QmlGraphicsItem*,int> unrequestedItems; + FxGridItem *currentItem; + QmlGraphicsGridView::Flow flow; + int visiblePos; + int visibleIndex; + int currentIndex; + int cellWidth; + int cellHeight; + int columns; + int requestedIndex; + QmlComponent *highlightComponent; + FxGridItem *highlight; + FxGridItem *trackedItem; + enum MovementReason { Other, SetIndex, Mouse }; + MovementReason moveReason; + int buffer; + QmlEaseFollow *highlightXAnimator; + QmlEaseFollow *highlightYAnimator; + enum BufferMode { NoBuffer = 0x00, BufferBefore = 0x01, BufferAfter = 0x02 }; + BufferMode bufferMode; + + bool ownModel : 1; + bool wrap : 1; + bool autoHighlight : 1; + bool fixCurrentVisibility : 1; + bool lazyRelease : 1; + bool layoutScheduled : 1; + bool deferredRelease : 1; +}; + +void QmlGraphicsGridViewPrivate::init() +{ + Q_Q(QmlGraphicsGridView); + q->setFlag(QGraphicsItem::ItemIsFocusScope); + QObject::connect(q, SIGNAL(widthChanged()), q, SLOT(sizeChange())); + QObject::connect(q, SIGNAL(heightChanged()), q, SLOT(sizeChange())); + q->setFlickDirection(QmlGraphicsFlickable::VerticalFlick); +} + +void QmlGraphicsGridViewPrivate::clear() +{ + for (int i = 0; i < visibleItems.count(); ++i) + releaseItem(visibleItems.at(i)); + visibleItems.clear(); + visiblePos = 0; + visibleIndex = 0; + releaseItem(currentItem); + currentItem = 0; + createHighlight(); + trackedItem = 0; +} + +FxGridItem *QmlGraphicsGridViewPrivate::createItem(int modelIndex) +{ + Q_Q(QmlGraphicsGridView); + // create object + requestedIndex = modelIndex; + FxGridItem *listItem = 0; + if (QmlGraphicsItem *item = model->item(modelIndex, false)) { + listItem = new FxGridItem(item, q); + listItem->index = modelIndex; + // complete + model->completeItem(); + listItem->item->setZValue(1); + listItem->item->setParent(q->viewport()); + unrequestedItems.remove(listItem->item); + } + requestedIndex = 0; + return listItem; +} + + +void QmlGraphicsGridViewPrivate::releaseItem(FxGridItem *item) +{ + Q_Q(QmlGraphicsGridView); + 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 QmlGraphicsGridViewPrivate::refill(qreal from, qreal to, bool doBuffer) +{ + Q_Q(QmlGraphicsGridView); + if (!isValid() || !q->isComponentComplete()) + return; + + 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; + } + int colNum = colPos / colSize(); + + FxGridItem *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->endRowPos() < 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 (flow == QmlGraphicsGridView::LeftToRight) + q->setViewportHeight(endPosition() - startPosition()); + else + q->setViewportWidth(endPosition() - startPosition()); + } else if (!doBuffer && buffer && bufferMode != NoBuffer) { + refill(from, to, true); + } + lazyRelease = false; +} + +void QmlGraphicsGridViewPrivate::updateGrid() +{ + Q_Q(QmlGraphicsGridView); + columns = (int)qMax((flow == QmlGraphicsGridView::LeftToRight ? q->width() : q->height()) / colSize(), qreal(1.)); + if (isValid()) { + if (flow == QmlGraphicsGridView::LeftToRight) + q->setViewportHeight(endPosition() - startPosition()); + else + q->setViewportWidth(endPosition() - startPosition()); + } +} + +void QmlGraphicsGridViewPrivate::scheduleLayout() +{ + Q_Q(QmlGraphicsGridView); + if (!layoutScheduled) { + layoutScheduled = true; + QMetaObject::invokeMethod(q, "layout", Qt::QueuedConnection); + } +} + +void QmlGraphicsGridViewPrivate::layout(bool removed) +{ + Q_Q(QmlGraphicsGridView); + layoutScheduled = false; + if (visibleItems.count()) { + qreal rowPos = visibleItems.first()->rowPos(); + qreal colPos = visibleItems.first()->colPos(); + int col = visibleIndex % columns; + if (colPos != col * colSize()) { + if (removed) + rowPos -= rowSize(); + colPos = col * colSize(); + visibleItems.first()->setPosition(colPos, rowPos); + } + for (int i = 1; i < visibleItems.count(); ++i) { + FxGridItem *item = visibleItems.at(i); + colPos += colSize(); + if (colPos > colSize() * (columns-1)) { + colPos = 0; + rowPos += rowSize(); + } + item->setPosition(colPos, rowPos); + } + } + q->refill(); + updateHighlight(); + moveReason = Other; + if (flow == QmlGraphicsGridView::LeftToRight) { + q->setViewportHeight(endPosition() - startPosition()); + fixupY(); + } else { + q->setViewportWidth(endPosition() - startPosition()); + fixupX(); + } + updateUnrequestedPositions(); +} + +void QmlGraphicsGridViewPrivate::updateUnrequestedIndexes() +{ + Q_Q(QmlGraphicsGridView); + QHash<QmlGraphicsItem*,int>::iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) + *it = model->indexOf(it.key(), q); +} + +void QmlGraphicsGridViewPrivate::updateUnrequestedPositions() +{ + QHash<QmlGraphicsItem*,int>::const_iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) { + if (flow == QmlGraphicsGridView::LeftToRight) { + it.key()->setPos(QPointF(colPosAt(*it), rowPosAt(*it))); + } else { + it.key()->setPos(QPointF(rowPosAt(*it), colPosAt(*it))); + } + } +} + +void QmlGraphicsGridViewPrivate::updateTrackedItem() +{ + Q_Q(QmlGraphicsGridView); + FxGridItem *item = currentItem; + if (highlight) + item = highlight; + + FxGridItem *oldTracked = trackedItem; + + 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 && trackedItem != oldTracked) + q->trackedPositionChanged(); +} + +void QmlGraphicsGridViewPrivate::createHighlight() +{ + Q_Q(QmlGraphicsGridView); + 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) { + QmlGraphicsItem *item = 0; + if (highlightComponent) { + QmlContext *highlightContext = new QmlContext(qmlContext(q)); + QObject *nobj = highlightComponent->create(highlightContext); + if (nobj) { + highlightContext->setParent(nobj); + item = qobject_cast<QmlGraphicsItem *>(nobj); + if (!item) + delete nobj; + } else { + delete highlightContext; + } + } else { + item = new QmlGraphicsItem; + item->setParent(q->viewport()); + } + if (item) { + item->setParent(q->viewport()); + highlight = new FxGridItem(item, q); + highlightXAnimator = new QmlEaseFollow(q); + highlightXAnimator->setTarget(QmlMetaProperty(highlight->item, QLatin1String("x"))); + highlightXAnimator->setDuration(150); + highlightXAnimator->setEnabled(autoHighlight); + highlightYAnimator = new QmlEaseFollow(q); + highlightYAnimator->setTarget(QmlMetaProperty(highlight->item, QLatin1String("y"))); + highlightYAnimator->setDuration(150); + highlightYAnimator->setEnabled(autoHighlight); + changed = true; + } + } + if (changed) + emit q->highlightChanged(); +} + +void QmlGraphicsGridViewPrivate::updateHighlight() +{ + if ((!currentItem && highlight) || (currentItem && !highlight)) + createHighlight(); + if (currentItem && autoHighlight && highlight && !moving) { + // auto-update highlight + highlightXAnimator->setSourceValue(currentItem->item->x()); + highlightYAnimator->setSourceValue(currentItem->item->y()); + highlight->item->setWidth(currentItem->item->width()); + highlight->item->setHeight(currentItem->item->height()); + } + updateTrackedItem(); +} + +void QmlGraphicsGridViewPrivate::updateCurrent(int modelIndex) +{ + Q_Q(QmlGraphicsGridView); + if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) { + if (currentItem) { + currentItem->attached->setIsCurrentItem(false); + releaseItem(currentItem); + currentItem = 0; + currentIndex = -1; + updateHighlight(); + emit q->currentIndexChanged(); + } + return; + } + + if (currentItem && currentIndex == modelIndex) { + updateHighlight(); + return; + } + + FxGridItem *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); +} + +//---------------------------------------------------------------------------- + +/*! + \qmlclass GridView QmlGraphicsGridView + \inherits Flickable + \brief The GridView item provides a grid view of items provided by a model. + + The model is typically provided by a QAbstractListModel "C++ model object", + but can also be created directly in QML. + + The items are laid out top to bottom (vertically) or left to right (horizontally) + and may be flicked to scroll. + + The below example creates a very simple grid, using a QML model. + + \image gridview.png + + \snippet doc/src/snippets/declarative/gridview/gridview.qml 3 + + The model is defined as a ListModel using QML: + \quotefile doc/src/snippets/declarative/gridview/dummydata/ContactModel.qml + + In this case ListModel is a handy way for us to test our UI. In practice + the model would be implemented in C++, or perhaps via a SQL data source. +*/ +QmlGraphicsGridView::QmlGraphicsGridView(QmlGraphicsItem *parent) + : QmlGraphicsFlickable(*(new QmlGraphicsGridViewPrivate), parent) +{ + Q_D(QmlGraphicsGridView); + d->init(); +} + +QmlGraphicsGridView::~QmlGraphicsGridView() +{ + Q_D(QmlGraphicsGridView); + d->clear(); + if (d->ownModel) + delete d->model; +} + +/*! + \qmlattachedproperty bool GridView::isCurrentItem + This attched property is true if this delegate is the current item; otherwise false. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty GridView GridView::view + This attached property holds the view that manages this delegate instance. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty bool GridView::delayRemove + This attached property holds whether the delegate may be destroyed. + + It is attached to each instance of the delegate. + + It is sometimes necessary to delay the destruction of an item + until an animation completes. + + The example below ensures that the animation completes before + the item is removed from the grid. + + \code + Component { + id: myDelegate + Item { + id: wrapper + GridView.onRemove: SequentialAnimation { + PropertyAction { target: wrapper.GridView; property: "delayRemove"; value: true } + NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing: "easeInOutQuad" } + PropertyAction { target: wrapper.GridView; property: "delayRemove"; value: false } + } + } + } + \endcode +*/ + +/*! + \qmlattachedsignal GridView::onAdd() + This attached handler is called immediately after an item is added to the view. +*/ + +/*! + \qmlattachedsignal GridView::onRemove() + This attached handler is called immediately before an item is removed from the view. +*/ + + +/*! + \qmlproperty model GridView::model + This property holds the model providing data for the grid. + + The model provides a set of data that is used to create the items + for the view. For large or dynamic datasets the model is usually + provided by a C++ model object. The C++ model object must be a \l + {QAbstractItemModel} subclass, a VisualModel, or a simple list. + + \sa {qmlmodels}{Data Models} +*/ +QVariant QmlGraphicsGridView::model() const +{ + Q_D(const QmlGraphicsGridView); + return d->modelVariant; +} + +void QmlGraphicsGridView::setModel(const QVariant &model) +{ + Q_D(QmlGraphicsGridView); + 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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + disconnect(d->model, SIGNAL(destroyingItem(QmlGraphicsItem*)), this, SLOT(destroyingItem(QmlGraphicsItem*))); + } + d->clear(); + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QmlGraphicsVisualModel *vim = 0; + if (object && (vim = qobject_cast<QmlGraphicsVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) + dataModel->setModel(model); + } + if (d->model) { + if (isComponentComplete()) { + refill(); + if (d->currentIndex >= d->model->count() || d->currentIndex < 0) { + setCurrentIndex(0); + } else { + d->moveReason = QmlGraphicsGridViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + } + } + 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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + connect(d->model, SIGNAL(destroyingItem(QmlGraphicsItem*)), this, SLOT(destroyingItem(QmlGraphicsItem*))); + emit countChanged(); + } +} + +/*! + \qmlproperty component GridView::delegate + + The delegate provides a template defining each item instantiated by the view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + + Note that the GridView will layout the items based on the size of the root item + in the delegate. + + Here is an example delegate: + \snippet doc/src/snippets/declarative/gridview/gridview.qml 0 +*/ +QmlComponent *QmlGraphicsGridView::delegate() const +{ + Q_D(const QmlGraphicsGridView); + if (d->model) { + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QmlGraphicsGridView::setDelegate(QmlComponent *delegate) +{ + Q_D(QmlGraphicsGridView); + if (delegate == this->delegate()) + return; + + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) { + dataModel->setDelegate(delegate); + if (isComponentComplete()) { + refill(); + d->moveReason = QmlGraphicsGridViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + } + } +} + +/*! + \qmlproperty int GridView::currentIndex + \qmlproperty Item GridView::currentItem + + \c currentIndex holds the index of the current item. + \c currentItem is the current item. Note that the position of the current item + may only be approximate until it becomes visible in the view. +*/ +int QmlGraphicsGridView::currentIndex() const +{ + Q_D(const QmlGraphicsGridView); + return d->currentIndex; +} + +void QmlGraphicsGridView::setCurrentIndex(int index) +{ + Q_D(QmlGraphicsGridView); + if (isComponentComplete() && d->isValid() && index != d->currentIndex && index < d->model->count() && index >= 0) { + d->moveReason = QmlGraphicsGridViewPrivate::SetIndex; + cancelFlick(); + d->updateCurrent(index); + } else { + d->currentIndex = index; + } +} + +QmlGraphicsItem *QmlGraphicsGridView::currentItem() +{ + Q_D(QmlGraphicsGridView); + if (!d->currentItem) + return 0; + return d->currentItem->item; +} + +/*! + \qmlproperty Item GridView::highlightItem + + \c highlightItem holds the highlight item, which was created + from the \l highlight component. + + The highlightItem is managed by the view unless + \l highlightFollowsCurrentItem is set to false. + + \sa highlight, highlightFollowsCurrentItem +*/ +QmlGraphicsItem *QmlGraphicsGridView::highlightItem() +{ + Q_D(QmlGraphicsGridView); + if (!d->highlight) + return 0; + return d->highlight->item; +} + +/*! + \qmlproperty int GridView::count + This property holds the number of items in the view. +*/ +int QmlGraphicsGridView::count() const +{ + Q_D(const QmlGraphicsGridView); + if (d->model) + return d->model->count(); + return 0; +} + +/*! + \qmlproperty component GridView::highlight + This property holds the component to use as the highlight. + + An instance of the highlight component will be created for each view. + The geometry of the resultant component instance will be managed by the view + so as to stay with the current item, unless the highlightFollowsCurrentItem property is false. + + The below example demonstrates how to make a simple highlight: + \snippet doc/src/snippets/declarative/gridview/gridview.qml 1 + + \sa highlightItem, highlightFollowsCurrentItem +*/ +QmlComponent *QmlGraphicsGridView::highlight() const +{ + Q_D(const QmlGraphicsGridView); + return d->highlightComponent; +} + +void QmlGraphicsGridView::setHighlight(QmlComponent *highlight) +{ + Q_D(QmlGraphicsGridView); + if (highlight != d->highlightComponent) { + d->highlightComponent = highlight; + d->updateCurrent(d->currentIndex); + } +} + +/*! + \qmlproperty bool GridView::highlightFollowsCurrentItem + This property sets whether the highlight is managed by the view. + + If highlightFollowsCurrentItem is true, the highlight will be moved smoothly + to follow the current item. If highlightFollowsCurrentItem is false, the + highlight will not be moved by the view, and must be implemented + by the highlight component, for example: + + \code + Component { + id: myHighlight + Rectangle { + id: wrapper; color: "lightsteelblue"; radius: 4; width: 320; height: 60 > + y: SpringFollow { source: Wrapper.GridView.view.currentItem.y; spring: 3; damping: 0.2 } + x: SpringFollow { source: Wrapper.GridView.view.currentItem.x; spring: 3; damping: 0.2 } + } + } + \endcode +*/ +bool QmlGraphicsGridView::highlightFollowsCurrentItem() const +{ + Q_D(const QmlGraphicsGridView); + return d->autoHighlight; +} + +void QmlGraphicsGridView::setHighlightFollowsCurrentItem(bool autoHighlight) +{ + Q_D(QmlGraphicsGridView); + if (d->autoHighlight != autoHighlight) { + d->autoHighlight = autoHighlight; + if (d->highlightXAnimator) { + d->highlightXAnimator->setEnabled(d->autoHighlight); + d->highlightYAnimator->setEnabled(d->autoHighlight); + } + d->updateHighlight(); + } +} + +/*! + \qmlproperty enumeration GridView::flow + This property holds the flow of the grid. + + Possible values are \c LeftToRight (default) and \c TopToBottom. + + If \a flow is \c LeftToRight, the view will scroll vertically. + If \a flow is \c TopToBottom, the view will scroll horizontally. +*/ +QmlGraphicsGridView::Flow QmlGraphicsGridView::flow() const +{ + Q_D(const QmlGraphicsGridView); + return d->flow; +} + +void QmlGraphicsGridView::setFlow(Flow flow) +{ + Q_D(QmlGraphicsGridView); + if (d->flow != flow) { + d->flow = flow; + if (d->flow == LeftToRight) { + setViewportWidth(-1); + setFlickDirection(QmlGraphicsFlickable::VerticalFlick); + } else { + setViewportHeight(-1); + setFlickDirection(QmlGraphicsFlickable::HorizontalFlick); + } + d->clear(); + d->updateGrid(); + refill(); + d->updateCurrent(d->currentIndex); + } +} + +/*! + \qmlproperty bool GridView::keyNavigationWraps + This property holds whether the grid wraps key navigation + + If this property is true then key presses to move off of one end of the grid will cause the + selection to jump to the other side. +*/ +bool QmlGraphicsGridView::isWrapEnabled() const +{ + Q_D(const QmlGraphicsGridView); + return d->wrap; +} + +void QmlGraphicsGridView::setWrapEnabled(bool wrap) +{ + Q_D(QmlGraphicsGridView); + d->wrap = wrap; +} + +/*! + \qmlproperty int GridView::cacheBuffer + This property holds the number of off-screen pixels to cache. + + This property determines the number of pixels above the top of the view + and below the bottom of the view to cache. Setting this value can make + scrolling the view smoother at the expense of additional memory usage. +*/ +int QmlGraphicsGridView::cacheBuffer() const +{ + Q_D(const QmlGraphicsGridView); + return d->buffer; +} + +void QmlGraphicsGridView::setCacheBuffer(int buffer) +{ + Q_D(QmlGraphicsGridView); + if (d->buffer != buffer) { + d->buffer = buffer; + if (isComponentComplete()) + refill(); + } +} + +/*! + \qmlproperty int GridView::cellWidth + \qmlproperty int GridView::cellHeight + + These properties holds the width and height of each cell in the grid + + The default cell size is 100x100. +*/ +int QmlGraphicsGridView::cellWidth() const +{ + Q_D(const QmlGraphicsGridView); + return d->cellWidth; +} + +void QmlGraphicsGridView::setCellWidth(int cellWidth) +{ + Q_D(QmlGraphicsGridView); + if (cellWidth != d->cellWidth && cellWidth > 0) { + d->cellWidth = qMax(1, cellWidth); + d->updateGrid(); + emit cellWidthChanged(); + d->layout(); + } +} + +int QmlGraphicsGridView::cellHeight() const +{ + Q_D(const QmlGraphicsGridView); + return d->cellHeight; +} + +void QmlGraphicsGridView::setCellHeight(int cellHeight) +{ + Q_D(QmlGraphicsGridView); + if (cellHeight != d->cellHeight && cellHeight > 0) { + d->cellHeight = qMax(1, cellHeight); + d->updateGrid(); + emit cellHeightChanged(); + d->layout(); + } +} + +void QmlGraphicsGridView::sizeChange() +{ + Q_D(QmlGraphicsGridView); + if (isComponentComplete()) { + d->updateGrid(); + d->layout(); + } +} + +void QmlGraphicsGridView::viewportMoved() +{ + Q_D(QmlGraphicsGridView); + QmlGraphicsFlickable::viewportMoved(); + d->lazyRelease = true; + if (d->flicked) { + if (yflick()) { + if (d->velocityY > 0) + d->bufferMode = QmlGraphicsGridViewPrivate::BufferBefore; + else if (d->velocityY < 0) + d->bufferMode = QmlGraphicsGridViewPrivate::BufferAfter; + } + + if (xflick()) { + if (d->velocityX > 0) + d->bufferMode = QmlGraphicsGridViewPrivate::BufferBefore; + else if (d->velocityX < 0) + d->bufferMode = QmlGraphicsGridViewPrivate::BufferAfter; + } + } + refill(); +} + +qreal QmlGraphicsGridView::minYExtent() const +{ + Q_D(const QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::TopToBottom) + return QmlGraphicsFlickable::minYExtent(); + return -d->startPosition(); +} + +qreal QmlGraphicsGridView::maxYExtent() const +{ + Q_D(const QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::TopToBottom) + return QmlGraphicsFlickable::maxYExtent(); + qreal extent = -(d->endPosition() - height()); + const qreal minY = minYExtent(); + if (extent > minY) + extent = minY; + return extent; +} + +qreal QmlGraphicsGridView::minXExtent() const +{ + Q_D(const QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::LeftToRight) + return QmlGraphicsFlickable::minXExtent(); + return -d->startPosition(); +} + +qreal QmlGraphicsGridView::maxXExtent() const +{ + Q_D(const QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::LeftToRight) + return QmlGraphicsFlickable::maxXExtent(); + qreal extent = -(d->endPosition() - width()); + const qreal minX = minXExtent(); + if (extent > minX) + extent = minX; + return extent; +} + +void QmlGraphicsGridView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QmlGraphicsGridView); + QmlGraphicsFlickable::keyPressEvent(event); + if (event->isAccepted()) + return; + if (d->model && d->model->count() && d->interactive) { + d->moveReason = QmlGraphicsGridViewPrivate::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 = QmlGraphicsGridViewPrivate::Other; + event->ignore(); +} + +/*! + \qmlmethod GridView::moveCurrentIndexUp() + + Move the currentIndex up one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. +*/ +void QmlGraphicsGridView::moveCurrentIndexUp() +{ + Q_D(QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::LeftToRight) { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex(index >= 0 ? index : d->model->count()-1); + } + } else { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex(index >= 0 ? index : d->model->count()-1); + } + } +} + +/*! + \qmlmethod GridView::moveCurrentIndexDown() + + Move the currentIndex down one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. +*/ +void QmlGraphicsGridView::moveCurrentIndexDown() +{ + Q_D(QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::LeftToRight) { + if (currentIndex() < d->model->count() - d->columns || d->wrap) { + int index = currentIndex()+d->columns; + setCurrentIndex(index < d->model->count() ? index : 0); + } + } else { + if (currentIndex() < d->model->count() - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex(index < d->model->count() ? index : 0); + } + } +} + +/*! + \qmlmethod GridView::moveCurrentIndexLeft() + + Move the currentIndex left one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. +*/ +void QmlGraphicsGridView::moveCurrentIndexLeft() +{ + Q_D(QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::LeftToRight) { + if (currentIndex() > 0 || d->wrap) { + int index = currentIndex() - 1; + setCurrentIndex(index >= 0 ? index : d->model->count()-1); + } + } else { + if (currentIndex() >= d->columns || d->wrap) { + int index = currentIndex() - d->columns; + setCurrentIndex(index >= 0 ? index : d->model->count()-1); + } + } +} + +/*! + \qmlmethod GridView::moveCurrentIndexRight() + + Move the currentIndex right one item in the view. + The current index will wrap if keyNavigationWraps is true and it + is currently at the end. +*/ +void QmlGraphicsGridView::moveCurrentIndexRight() +{ + Q_D(QmlGraphicsGridView); + if (d->flow == QmlGraphicsGridView::LeftToRight) { + if (currentIndex() < d->model->count() - 1 || d->wrap) { + int index = currentIndex() + 1; + setCurrentIndex(index < d->model->count() ? index : 0); + } + } else { + if (currentIndex() < d->model->count() - d->columns || d->wrap) { + int index = currentIndex()+d->columns; + setCurrentIndex(index < d->model->count() ? index : 0); + } + } +} + +void QmlGraphicsGridView::positionViewAtIndex(int index) +{ + Q_D(QmlGraphicsGridView); + if (!d->isValid() || index < 0 || index >= d->model->count()) + return; + + qreal maxExtent = d->flow == QmlGraphicsGridView::LeftToRight ? -maxYExtent() : -maxXExtent(); + FxGridItem *item = d->visibleItem(index); + if (item) { + // Already created - just move to top of view + int pos = qMin(item->rowPos(), maxExtent); + d->setPosition(pos); + } else { + int pos = d->rowPosAt(index); + // save the currently visible items in case any of them end up visible again + QList<FxGridItem*> oldVisible = d->visibleItems; + d->visibleItems.clear(); + d->visibleIndex = index - index % d->columns; + d->setPosition(pos); + // setPosition() will cause refill. Adjust if we have moved beyond range + if (d->position() > maxExtent) + d->setPosition(maxExtent); + // now release the reference to all the old visible items. + for (int i = 0; i < oldVisible.count(); ++i) + d->releaseItem(oldVisible.at(i)); + } +} + + +void QmlGraphicsGridView::componentComplete() +{ + Q_D(QmlGraphicsGridView); + QmlGraphicsFlickable::componentComplete(); + d->updateGrid(); + refill(); + if (d->currentIndex < 0) + d->updateCurrent(0); + else + d->updateCurrent(d->currentIndex); +} + +void QmlGraphicsGridView::trackedPositionChanged() +{ + Q_D(QmlGraphicsGridView); + if (!d->trackedItem || !d->currentItem) + return; + if (!isFlicking() && !d->moving && d->moveReason == QmlGraphicsGridViewPrivate::SetIndex) { + const qreal viewPos = d->position(); + if (d->trackedItem->rowPos() < viewPos && d->currentItem->rowPos() < viewPos) { + d->setPosition(d->currentItem->rowPos() < d->trackedItem->rowPos() ? d->trackedItem->rowPos() : d->currentItem->rowPos()); + } else if (d->trackedItem->endRowPos() > viewPos + d->size() + && d->currentItem->endRowPos() > viewPos + d->size()) { + qreal pos; + if (d->trackedItem->endRowPos() < d->currentItem->endRowPos()) { + pos = d->trackedItem->endRowPos() - d->size(); + if (d->rowSize() > d->size()) + pos = d->trackedItem->rowPos(); + } else { + pos = d->currentItem->endRowPos() - d->size(); + if (d->rowSize() > d->size()) + pos = d->currentItem->rowPos(); + } + d->setPosition(pos); + } + } +} + +void QmlGraphicsGridView::itemsInserted(int modelIndex, int count) +{ + Q_D(QmlGraphicsGridView); + if (!d->visibleItems.count() || d->model->count() <= 1) { + refill(); + d->updateCurrent(qMax(0, qMin(d->currentIndex, d->model->count()-1))); + emit countChanged(); + return; + } + + int index = d->mapFromModel(modelIndex); + if (index == -1) { + 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) { + FxGridItem *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; + } + d->layout(); + emit countChanged(); + return; + } + } + + // At least some of the added items will be visible + int insertCount = count; + if (index < d->visibleIndex) { + insertCount -= d->visibleIndex - index; + index = d->visibleIndex; + modelIndex = d->visibleIndex; + } + + index -= d->visibleIndex; + int to = d->buffer+d->position()+d->size()-1; + int colPos, rowPos; + 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(); + } + } + + QList<FxGridItem*> added; + int i = 0; + while (i < insertCount && rowPos <= to + d->rowSize()*(d->columns - (colPos/d->colSize()))/qreal(d->columns)) { + FxGridItem *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()); + } + } + + if (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)); + } + } + // Update the indexes of the following visible items. + for (; index < d->visibleItems.count(); ++index) { + FxGridItem *listItem = d->visibleItems.at(index); + 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->layout(); + emit countChanged(); +} + +void QmlGraphicsGridView::itemsRemoved(int modelIndex, int count) +{ + Q_D(QmlGraphicsGridView); + 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<FxGridItem*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxGridItem *item = *it; + if (item->index == -1 || item->index < modelIndex) { + // already removed, or before removed items + if (item->index < modelIndex) + removedVisible = true; + ++it; + } else if (item->index >= modelIndex + count) { + // after removed items + item->index -= count; + ++it; + } else { + // removed item + 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; + } else if (currentRemoved) { + // current item has been removed. + d->releaseItem(d->currentItem); + d->currentItem = 0; + d->currentIndex = -1; + d->updateCurrent(qMin(modelIndex, d->model->count()-1)); + } + + // update visibleIndex + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + if (removedVisible) { + if (d->visibleItems.isEmpty()) { + d->visibleIndex = 0; + d->setPosition(0); + refill(); + } else { + // Correct the positioning of the items + d->scheduleLayout(); + } + } + + emit countChanged(); +} + +void QmlGraphicsGridView::layout() +{ + Q_D(QmlGraphicsGridView); + if (d->layoutScheduled) + d->layout(); +} + +void QmlGraphicsGridView::destroyRemoved() +{ + Q_D(QmlGraphicsGridView); + for (QList<FxGridItem*>::Iterator it = d->visibleItems.begin(); + it != d->visibleItems.end();) { + FxGridItem *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 QmlGraphicsGridView::itemsMoved(int from, int to, int count) +{ + Q_D(QmlGraphicsGridView); + QHash<int,FxGridItem*> moved; + + bool removedBeforeVisible = false; + FxGridItem *firstItem = d->firstVisibleItem(); + + if (from < to && from < d->visibleIndex && to > d->visibleIndex) + removedBeforeVisible = true; + + QList<FxGridItem*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxGridItem *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); + if (item->rowPos() < firstItem->rowPos()) + removedBeforeVisible = true; + } 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; + } else if (item->index != -1) { + removedBeforeVisible = true; + } + ++it; + } + } + + int remaining = count; + int endIndex = d->visibleIndex; + it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxGridItem *item = *it; + if (remaining && item->index >= to && item->index < to + count) { + // place items in the target position, reusing any existing items + FxGridItem *movedItem = moved.take(item->index); + if (!movedItem) + movedItem = d->createItem(item->index); + 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 (FxGridItem *item = moved.take(endIndex+1)) { + d->visibleItems.append(item); + ++endIndex; + } + + // Whatever moved items remain are no longer visible items. + while (moved.count()) + d->releaseItem(moved.take(moved.begin().key())); + + d->layout(removedBeforeVisible); +} + +void QmlGraphicsGridView::createdItem(int index, QmlGraphicsItem *item) +{ + Q_D(QmlGraphicsGridView); + item->setParentItem(this); + if (d->requestedIndex != index) { + item->setParentItem(this); + d->unrequestedItems.insert(item, index); + if (d->flow == QmlGraphicsGridView::LeftToRight) { + item->setPos(QPointF(d->colPosAt(index), d->rowPosAt(index))); + } else { + item->setPos(QPointF(d->rowPosAt(index), d->colPosAt(index))); + } + } +} + +void QmlGraphicsGridView::destroyingItem(QmlGraphicsItem *item) +{ + Q_D(QmlGraphicsGridView); + d->unrequestedItems.remove(item); +} + + +void QmlGraphicsGridView::refill() +{ + Q_D(QmlGraphicsGridView); + d->refill(d->position(), d->position()+d->size()-1); +} + + +QmlGraphicsGridViewAttached *QmlGraphicsGridView::qmlAttachedProperties(QObject *obj) +{ + return QmlGraphicsGridViewAttached::properties(obj); +} + +QML_DEFINE_TYPE(Qt, 4,6, GridView, QmlGraphicsGridView) + +QT_END_NAMESPACE + +#include <qmlgraphicsgridview.moc> diff --git a/src/declarative/graphicsitems/qmlgraphicsgridview_p.h b/src/declarative/graphicsitems/qmlgraphicsgridview_p.h new file mode 100644 index 0000000..d2ef70e --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsgridview_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSGRIDVIEW_H +#define QMLGRAPHICSGRIDVIEW_H + +#include "qmlgraphicsflickable_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class QmlGraphicsVisualModel; +class QmlGraphicsGridViewAttached; +class QmlGraphicsGridViewPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsGridView : public QmlGraphicsFlickable +{ + Q_OBJECT + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsGridView) + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QmlGraphicsItem *currentItem READ currentItem NOTIFY currentIndexChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + + Q_PROPERTY(QmlComponent *highlight READ highlight WRITE setHighlight) + Q_PROPERTY(QmlGraphicsItem *highlightItem READ highlightItem NOTIFY highlightChanged) + Q_PROPERTY(bool highlightFollowsCurrentItem READ highlightFollowsCurrentItem WRITE setHighlightFollowsCurrentItem) + + Q_PROPERTY(Flow flow READ flow WRITE setFlow) + Q_PROPERTY(bool keyNavigationWraps READ isWrapEnabled WRITE setWrapEnabled) + Q_PROPERTY(int cacheBuffer READ cacheBuffer WRITE setCacheBuffer) + Q_PROPERTY(int cellWidth READ cellWidth WRITE setCellWidth NOTIFY cellWidthChanged) + Q_PROPERTY(int cellHeight READ cellHeight WRITE setCellHeight NOTIFY cellHeightChanged) + Q_CLASSINFO("DefaultProperty", "data") + +public: + QmlGraphicsGridView(QmlGraphicsItem *parent=0); + ~QmlGraphicsGridView(); + + QVariant model() const; + void setModel(const QVariant &); + + QmlComponent *delegate() const; + void setDelegate(QmlComponent *); + + int currentIndex() const; + void setCurrentIndex(int idx); + + QmlGraphicsItem *currentItem(); + QmlGraphicsItem *highlightItem(); + int count() const; + + QmlComponent *highlight() const; + void setHighlight(QmlComponent *highlight); + + bool highlightFollowsCurrentItem() const; + void setHighlightFollowsCurrentItem(bool); + + Q_ENUMS(Flow) + 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); + + static QmlGraphicsGridViewAttached *qmlAttachedProperties(QObject *); + +public Q_SLOTS: + void moveCurrentIndexUp(); + void moveCurrentIndexDown(); + void moveCurrentIndexLeft(); + void moveCurrentIndexRight(); + void positionViewAtIndex(int index); + +Q_SIGNALS: + void countChanged(); + void currentIndexChanged(); + void cellWidthChanged(); + void cellHeightChanged(); + void highlightChanged(); + +protected: + 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 destroyRemoved(); + void createdItem(int index, QmlGraphicsItem *item); + void destroyingItem(QmlGraphicsItem *item); + void sizeChange(); + void layout(); + +private: + void refill(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsGridView) +QML_DECLARE_TYPEINFO(QmlGraphicsGridView, QML_HAS_ATTACHED_PROPERTIES) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsimage.cpp b/src/declarative/graphicsitems/qmlgraphicsimage.cpp new file mode 100644 index 0000000..7e63c8b --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsimage.cpp @@ -0,0 +1,369 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsimage_p.h" +#include "qmlgraphicsimage_p_p.h" + +#include <QKeyEvent> +#include <QPainter> + +QT_BEGIN_NAMESPACE + + +QML_DEFINE_TYPE(Qt,4,6,Image,QmlGraphicsImage) + +/*! + \qmlclass Image QmlGraphicsImage + \brief The Image element allows you to add bitmaps to a scene. + \inherits Item + + The Image element supports untransformed, stretched and tiled. + + For an explanation of stretching and tiling, see the fillMode property description. + + Examples: + \table + \row + \o \image declarative-qtlogo1.png + \o Untransformed + \qml + Image { source: "pics/qtlogo.png" } + \endqml + \row + \o \image declarative-qtlogo2.png + \o fillMode: Stretch (default) + \qml + Image { + width: 160 + height: 160 + source: "pics/qtlogo.png" + } + \endqml + \row + \o \image declarative-qtlogo3.png + \o fillMode: Tile + \qml + Image { + fillMode: Image.Tile + width: 160; height: 160 + source: "pics/qtlogo.png" + } + \endqml + \row + \o \image declarative-qtlogo6.png + \o fillMode: TileVertically + \qml + Image { + fillMode: Image.TileVertically + width: 160; height: 160 + source: "pics/qtlogo.png" + } + \endqml + \row + \o \image declarative-qtlogo5.png + \o fillMode: TileHorizontally + \qml + Image { + fillMode: Image.TileHorizontally + width: 160; height: 160 + source: "pics/qtlogo.png" + } + \endqml + \endtable + */ + +/*! + \internal + \class QmlGraphicsImage Image + \brief The QmlGraphicsImage class provides an image item that you can add to a QmlView. + + \ingroup group_coreitems + + Example: + \qml + Image { source: "pics/star.png" } + \endqml + + A QmlGraphicsImage object can be instantiated in Qml using the tag \l Image. +*/ + +QmlGraphicsImage::QmlGraphicsImage(QmlGraphicsItem *parent) + : QmlGraphicsImageBase(*(new QmlGraphicsImagePrivate), parent) +{ + connect(this, SIGNAL(sourceChanged(QUrl)), this, SLOT(updatePaintedGeometry())); +} + +QmlGraphicsImage::QmlGraphicsImage(QmlGraphicsImagePrivate &dd, QmlGraphicsItem *parent) + : QmlGraphicsImageBase(dd, parent) +{ +} + +QmlGraphicsImage::~QmlGraphicsImage() +{ +} + +void QmlGraphicsImage::setSource(const QUrl &url) +{ + QmlGraphicsImageBase::setSource(url); + updatePaintedGeometry(); +} + +/*! + \qmlproperty QPixmap Image::pixmap + + This property holds the QPixmap image to display. + + This is useful for displaying images provided by a C++ implementation, + for example, a model may provide a data role of type QPixmap. +*/ + +QPixmap QmlGraphicsImage::pixmap() const +{ + Q_D(const QmlGraphicsImage); + return d->pix; +} + +void QmlGraphicsImage::setPixmap(const QPixmap &pix) +{ + Q_D(QmlGraphicsImage); + if (!d->url.isEmpty()) + return; + d->setPixmap(pix); +} + +void QmlGraphicsImagePrivate::setPixmap(const QPixmap &pixmap) +{ + Q_Q(QmlGraphicsImage); + pix = pixmap; + + q->setImplicitWidth(pix.width()); + q->setImplicitHeight(pix.height()); + status = pix.isNull() ? QmlGraphicsImageBase::Null : QmlGraphicsImageBase::Ready; + + q->update(); + emit q->pixmapChanged(); +} + +/*! + \qmlproperty enumeration Image::fillMode + + Set this property to define what happens when the image set for the item is smaller + than the size of the item. + + \list + \o Stretch - the image is scaled to fit + \o PreserveAspectFit - the image is scaled uniformly to fit without cropping + \o PreserveAspectCrop - the image is scaled uniformly to fill, cropping if necessary + \o Tile - the image is duplicated horizontally and vertically + \o TileVertically - the image is stretched horizontally and tiled vertically + \o TileHorizontally - the image is stretched vertically and tiled horizontally + \endlist + + \image declarative-image_fillMode.gif +*/ +QmlGraphicsImage::FillMode QmlGraphicsImage::fillMode() const +{ + Q_D(const QmlGraphicsImage); + return d->fillMode; +} + +void QmlGraphicsImage::setFillMode(FillMode mode) +{ + Q_D(QmlGraphicsImage); + if (d->fillMode == mode) + return; + d->fillMode = mode; + update(); + updatePaintedGeometry(); + emit fillModeChanged(); +} + +qreal QmlGraphicsImage::paintedWidth() const +{ + Q_D(const QmlGraphicsImage); + return d->paintedWidth; +} + +qreal QmlGraphicsImage::paintedHeight() const +{ + Q_D(const QmlGraphicsImage); + return d->paintedHeight; +} + +/*! + \qmlproperty enum Image::status + + This property holds the status of image loading. It can be one of: + \list + \o Null - no image has been set + \o Ready - the image has been loaded + \o Loading - the image is currently being loaded + \o Error - an error occurred while loading the image + \endlist + + \sa progress +*/ + +/*! + \qmlproperty real Image::progress + + This property holds the progress of image loading, from 0.0 (nothing loaded) + to 1.0 (finished). + + \sa status +*/ + +/*! + \qmlproperty bool Image::smooth + + Set this property if you want the image to be smoothly filtered when scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the image is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the image is stationary on + the screen. A common pattern when animating an image is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +void QmlGraphicsImage::updatePaintedGeometry() +{ + Q_D(QmlGraphicsImage); + + if (d->fillMode == PreserveAspectFit) { + qreal widthScale = width() / qreal(d->pix.width()); + qreal heightScale = height() / qreal(d->pix.height()); + if (!d->pix.width() || !d->pix.height()) + return; + if (widthScale <= heightScale) { + d->paintedWidth = width(); + d->paintedHeight = widthScale * qreal(d->pix.height()); + } else if(heightScale < widthScale) { + d->paintedWidth = heightScale * qreal(d->pix.width()); + d->paintedHeight = height(); + } + } else { + d->paintedWidth = width(); + d->paintedHeight = height(); + } + emit paintedGeometryChanged(); +} + +void QmlGraphicsImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QmlGraphicsImageBase::geometryChanged(newGeometry, oldGeometry); + updatePaintedGeometry(); +} + +/*! + \qmlproperty url Image::source + + Image can handle any image format supported by Qt, loaded from any URL scheme supported by Qt. + + The URL may be absolute, or relative to the URL of the component. +*/ + +void QmlGraphicsImage::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + Q_D(QmlGraphicsImage); + if (d->pix.isNull()) + return; + + bool oldAA = p->testRenderHint(QPainter::Antialiasing); + bool oldSmooth = p->testRenderHint(QPainter::SmoothPixmapTransform); + if (d->smooth) + p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth); + + if (width() != d->pix.width() || height() != d->pix.height()) { + if (d->fillMode >= Tile) { + if (d->fillMode == Tile) + p->drawTiledPixmap(QRectF(0,0,width(),height()), d->pix); + else if (d->fillMode == TileVertically) + p->drawTiledPixmap(QRectF(0,0,d->pix.width(),height()), d->pix); + else + p->drawTiledPixmap(QRectF(0,0,width(),d->pix.height()), d->pix); + } else { + qreal widthScale = width() / qreal(d->pix.width()); + qreal heightScale = height() / qreal(d->pix.height()); + + QTransform scale; + + if (d->fillMode == PreserveAspectFit) { + if (widthScale <= heightScale) { + heightScale = widthScale; + scale.translate(0, (height() - heightScale * d->pix.height()) / 2); + } else if(heightScale < widthScale) { + widthScale = heightScale; + scale.translate((width() - widthScale * d->pix.width()) / 2, 0); + } + } else if (d->fillMode == PreserveAspectCrop) { + if (widthScale < heightScale) { + widthScale = heightScale; + scale.translate((width() - widthScale * d->pix.width()) / 2, 0); + } else if(heightScale < widthScale) { + heightScale = widthScale; + scale.translate(0, (height() - heightScale * d->pix.height()) / 2); + } + } + if (clip()) { + p->save(); + p->setClipRect(boundingRect(), Qt::IntersectClip); + } + scale.scale(widthScale, heightScale); + QTransform old = p->transform(); + p->setWorldTransform(scale * old); + p->drawPixmap(0, 0, d->pix); + p->setWorldTransform(old); + if (clip()) { + p->restore(); + } + } + } else { + p->drawPixmap(0, 0, d->pix); + } + + if (d->smooth) { + p->setRenderHint(QPainter::Antialiasing, oldAA); + p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth); + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsimage_p.h b/src/declarative/graphicsitems/qmlgraphicsimage_p.h new file mode 100644 index 0000000..dde5d79 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsimage_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSIMAGE_H +#define QMLGRAPHICSIMAGE_H + +#include "qmlgraphicsimagebase_p.h" + +#include <QtNetwork/qnetworkreply.h> + +QT_BEGIN_HEADER +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsImagePrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsImage : public QmlGraphicsImageBase +{ + Q_OBJECT + Q_ENUMS(FillMode) + + Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap NOTIFY pixmapChanged DESIGNABLE false) + 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) + +public: + QmlGraphicsImage(QmlGraphicsItem *parent=0); + ~QmlGraphicsImage(); + + enum FillMode { Stretch, PreserveAspectFit, PreserveAspectCrop, Tile, TileVertically, TileHorizontally }; + FillMode fillMode() const; + void setFillMode(FillMode); + + QPixmap pixmap() const; + void setPixmap(const QPixmap &); + + qreal paintedWidth() const; + qreal paintedHeight() const; + + void setSource(const QUrl &url); + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + +Q_SIGNALS: + void fillModeChanged(); + void paintedGeometryChanged(); + +protected: + QmlGraphicsImage(QmlGraphicsImagePrivate &dd, QmlGraphicsItem *parent); + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + +protected Q_SLOTS: + void updatePaintedGeometry(); + +private: + Q_DISABLE_COPY(QmlGraphicsImage) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsImage) +}; + +QT_END_NAMESPACE +QML_DECLARE_TYPE(QmlGraphicsImage) +QT_END_HEADER + +#endif // QMLGRAPHICSIMAGE_H diff --git a/src/declarative/graphicsitems/qmlgraphicsimage_p_p.h b/src/declarative/graphicsitems/qmlgraphicsimage_p_p.h new file mode 100644 index 0000000..3a5acca --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsimage_p_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSIMAGE_P_H +#define QMLGRAPHICSIMAGE_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 "qmlgraphicsitem_p.h" +#include "qmlgraphicsimagebase_p_p.h" + +QT_BEGIN_NAMESPACE + +class QmlGraphicsImagePrivate : public QmlGraphicsImageBasePrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsImage) + +public: + QmlGraphicsImagePrivate() + : fillMode(QmlGraphicsImage::Stretch), paintedWidth(0), paintedHeight(0) + { + } + + QmlGraphicsImage::FillMode fillMode; + qreal paintedWidth; + qreal paintedHeight; + void setPixmap(const QPixmap &pix); +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSIMAGE_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsimagebase.cpp b/src/declarative/graphicsitems/qmlgraphicsimagebase.cpp new file mode 100644 index 0000000..08617ac --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsimagebase.cpp @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsimagebase_p.h" +#include "qmlgraphicsimagebase_p_p.h" + +#include <qmlengine.h> +#include <qmlpixmapcache_p.h> + +#include <QFile> + +QT_BEGIN_NAMESPACE + +QmlGraphicsImageBase::QmlGraphicsImageBase(QmlGraphicsImageBasePrivate &dd, QmlGraphicsItem *parent) + : QmlGraphicsItem(dd, parent) +{ + setFlag(QGraphicsItem::ItemHasNoContents, false); +} + +QmlGraphicsImageBase::~QmlGraphicsImageBase() +{ + Q_D(QmlGraphicsImageBase); + if (d->pendingPixmapCache) + QmlPixmapCache::cancel(d->url, this); +} + +QmlGraphicsImageBase::Status QmlGraphicsImageBase::status() const +{ + Q_D(const QmlGraphicsImageBase); + return d->status; +} + + +qreal QmlGraphicsImageBase::progress() const +{ + Q_D(const QmlGraphicsImageBase); + return d->progress; +} + +QUrl QmlGraphicsImageBase::source() const +{ + Q_D(const QmlGraphicsImageBase); + return d->url; +} + +void QmlGraphicsImageBase::setSource(const QUrl &url) +{ + Q_D(QmlGraphicsImageBase); + //equality is fairly expensive, so we bypass for simple, common case + if ((d->url.isEmpty() == url.isEmpty()) && url == d->url) + return; + + if (d->pendingPixmapCache) { + QmlPixmapCache::cancel(d->url, this); + d->pendingPixmapCache = false; + } + + d->url = url; + if (d->progress != 0.0) { + d->progress = 0.0; + emit progressChanged(d->progress); + } + + if (url.isEmpty()) { + d->pix = QPixmap(); + d->status = Null; + setImplicitWidth(0); + setImplicitHeight(0); + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit pixmapChanged(); + update(); + } else { + d->status = Loading; + QmlPixmapReply::Status status = QmlPixmapCache::get(d->url, &d->pix); + if (status != QmlPixmapReply::Ready && status != QmlPixmapReply::Error) { + QmlPixmapReply *reply = QmlPixmapCache::request(qmlEngine(this), d->url); + d->pendingPixmapCache = true; + + static int replyDownloadProgress = -1; + static int replyFinished = -1; + static int thisRequestProgress = -1; + static int thisRequestFinished = -1; + if (replyDownloadProgress == -1) { + replyDownloadProgress = + QmlPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); + replyFinished = + QmlPixmapReply::staticMetaObject.indexOfSignal("finished()"); + thisRequestProgress = + QmlGraphicsImageBase::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)"); + thisRequestFinished = + QmlGraphicsImageBase::staticMetaObject.indexOfSlot("requestFinished()"); + } + + QMetaObject::connect(reply, replyFinished, this, + thisRequestFinished, Qt::DirectConnection); + QMetaObject::connect(reply, replyDownloadProgress, this, + thisRequestProgress, Qt::DirectConnection); + } else { + //### should be unified with requestFinished + if (status == QmlPixmapReply::Ready) { + setImplicitWidth(d->pix.width()); + setImplicitHeight(d->pix.height()); + + if (d->status == Loading) + d->status = Ready; + } else { + d->status = Error; + } + d->progress = 1.0; + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit progressChanged(d->progress); + emit pixmapChanged(); + update(); + } + } + + emit statusChanged(d->status); +} + +void QmlGraphicsImageBase::requestFinished() +{ + Q_D(QmlGraphicsImageBase); + + d->pendingPixmapCache = false; + + if (QmlPixmapCache::get(d->url, &d->pix) != QmlPixmapReply::Ready) + d->status = Error; + setImplicitWidth(d->pix.width()); + setImplicitHeight(d->pix.height()); + + if (d->status == Loading) + d->status = Ready; + d->progress = 1.0; + emit statusChanged(d->status); + emit sourceChanged(d->url); + emit progressChanged(1.0); + emit pixmapChanged(); + update(); +} + +void QmlGraphicsImageBase::requestProgress(qint64 received, qint64 total) +{ + Q_D(QmlGraphicsImageBase); + if (d->status == Loading && total > 0) { + d->progress = qreal(received)/total; + emit progressChanged(d->progress); + } +} + + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsimagebase_p.h b/src/declarative/graphicsitems/qmlgraphicsimagebase_p.h new file mode 100644 index 0000000..61ea975 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsimagebase_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSIMAGEBASE_H +#define QMLGRAPHICSIMAGEBASE_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QmlGraphicsImageBasePrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsImageBase : public QmlGraphicsItem +{ + 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) + +public: + ~QmlGraphicsImageBase(); + enum Status { Null, Ready, Loading, Error }; + Status status() const; + qreal progress() const; + + QUrl source() const; + virtual void setSource(const QUrl &url); + +Q_SIGNALS: + void sourceChanged(const QUrl &); + void statusChanged(Status); + void progressChanged(qreal progress); + void pixmapChanged(); + +protected: + QmlGraphicsImageBase(QmlGraphicsImageBasePrivate &dd, QmlGraphicsItem *parent); + +private Q_SLOTS: + virtual void requestFinished(); + void requestProgress(qint64,qint64); + +private: + Q_DISABLE_COPY(QmlGraphicsImageBase) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsImageBase) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QMLGRAPHICSIMAGEBASE_H diff --git a/src/declarative/graphicsitems/qmlgraphicsimagebase_p_p.h b/src/declarative/graphicsitems/qmlgraphicsimagebase_p_p.h new file mode 100644 index 0000000..44b2332 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsimagebase_p_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSIMAGEBASE_P_H +#define QMLGRAPHICSIMAGEBASE_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 "qmlgraphicsitem_p.h" + +#include <QtCore/QPointer> + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QmlGraphicsImageBasePrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsImageBase) + +public: + QmlGraphicsImageBasePrivate() + : status(QmlGraphicsImageBase::Null), + progress(0.0), + pendingPixmapCache(false) + { + } + + QPixmap pix; + QmlGraphicsImageBase::Status status; + QUrl url; + qreal progress; + bool pendingPixmapCache; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsitem.cpp b/src/declarative/graphicsitems/qmlgraphicsitem.cpp new file mode 100644 index 0000000..8973cb4 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsitem.cpp @@ -0,0 +1,3099 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsitem_p.h" +#include "qmlgraphicsitem.h" + +#include "qmlgraphicsevents_p_p.h" + +#include <qfxperf_p_p.h> +#include <qmlengine.h> +#include <qmlopenmetaobject_p.h> +#include <qmlstate_p.h> +#include <qmlview.h> +#include <qmlstategroup_p.h> +#include <qmlcomponent.h> + +#include <QDebug> +#include <QPen> +#include <QFile> +#include <QEvent> +#include <QGraphicsSceneMouseEvent> +#include <QtCore/qnumeric.h> +#include <QtScript/qscriptengine.h> +#include <QtGui/qgraphicstransform.h> +#include <QtGui/qgraphicseffect.h> +#include <qlistmodelinterface_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef FLT_MAX +#define FLT_MAX 1E+37 +#endif + +QML_DEFINE_TYPE(Qt,4,6,Item,QmlGraphicsItem) + +QML_DEFINE_NOCREATE_TYPE(QGraphicsTransform); +QML_DEFINE_TYPE(Qt,4,6,Scale,QGraphicsScale) +QML_DEFINE_TYPE(Qt,4,6,Rotation,QGraphicsRotation) + +#include "qmlgraphicseffects.cpp" + +/*! + \qmlclass Transform QGraphicsTransform + \brief The Transform elements provide a way of building advanced transformations on Items. + + The Transform elements let you create and control advanced transformations that can be configured + independently using specialized properties. + + You can assign any number of Transform elements to an Item. Each Transform is applied in order, + one at a time, to the Item it's assigned to. + + \sa Rotation, Scale +*/ + +/*! + \qmlclass Scale QGraphicsScale + \brief The Scale object provides a way to scale an Item. + + The Scale object gives more control over scaling than using Item's scale property. Specifically, + it allows a different scale for the x and y axes, and allows the scale to be relative to an + arbitrary point. + + The following example scales the X axis of the Rectangle, relative to its interior point 25, 25: + \qml + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Scale { origin.x: 25; origin.y: 25; xScale: 3} + } + \endqml +*/ + +/*! + \qmlproperty real Scale::origin.x + \qmlproperty real Scale::origin.y + + The point that the item is scaled from (i.e., the point that stays fixed relative to the parent as + the rest of the item grows). By default the origin is 0, 0. +*/ + +/*! + \qmlproperty real Scale::xScale + + The scaling factor for the X axis. +*/ + +/*! + \qmlproperty real Scale::yScale + + The scaling factor for the Y axis. +*/ + +/*! + \qmlclass Rotation QGraphicsRotation + \brief The Rotation object provides a way to rotate an Item. + + The Rotation object gives more control over rotation than using Item's rotation property. + Specifically, it allows (z axis) rotation to be relative to an arbitrary point. + + The following example rotates a Rectangle around its interior point 25, 25: + \qml + Rectangle { + width: 100; height: 100 + color: "blue" + transform: Rotation { origin.x: 25; origin.y: 25; angle: 45} + } + \endqml + + Rotation also provides a way to specify 3D-like rotations for Items. For these types of + rotations you must specify the axis to rotate around in addition to the origin point. + + The following example shows various 3D-like rotations applied to an \l Image. + \snippet doc/src/snippets/declarative/rotation.qml 0 + + \image axisrotation.png +*/ + +/*! + \qmlproperty real Rotation::origin.x + \qmlproperty real Rotation::origin.y + + The origin point of the rotation (i.e., the point that stays fixed relative to the parent as + the rest of the item rotates). By default the origin is 0, 0. +*/ + +/*! + \qmlproperty real Rotation::axis.x + \qmlproperty real Rotation::axis.y + \qmlproperty real Rotation::axis.z + + The axis to rotate around. For simple (2D) rotation around a point, you do not need to specify an axis, + as the default axis is the z axis (\c{ axis { x: 0; y: 0; z: 1 } }). + + For a typical 3D-like rotation you will usually specify both the origin and the axis. + + \image 3d-rotation-axis.png +*/ + +/*! + \qmlproperty real Rotation::angle + + The angle to rotate, in degrees clockwise. +*/ + + +/*! + \group group_animation + \title Animation +*/ + +/*! + \group group_coreitems + \title Basic Items +*/ + +/*! + \group group_effects + \title Effects +*/ + +/*! + \group group_layouts + \title Layouts +*/ + +/*! + \group group_states + \title States and Transitions +*/ + +/*! + \group group_utility + \title Utility +*/ + +/*! + \group group_views + \title Views +*/ + +/*! + \group group_widgets + \title Widgets +*/ + +/*! + \internal + \class QmlGraphicsContents + \ingroup group_utility + \brief The QmlGraphicsContents class gives access to the height and width of an item's contents. + +*/ + +QmlGraphicsContents::QmlGraphicsContents() : m_x(0), m_y(0), m_width(0), m_height(0) +{ +} + +/*! + \qmlproperty real Item::childrenRect.x + \qmlproperty real Item::childrenRect.y + \qmlproperty real Item::childrenRect.width + \qmlproperty real Item::childrenRect.height + + The childrenRect properties allow an item access to the geometry of its + children. This property is useful if you have an item that needs to be + sized to fit its children. +*/ + +QRectF QmlGraphicsContents::rectF() const +{ + return QRectF(m_x, m_y, m_width, m_height); +} + +//TODO: optimization: only check sender(), if there is one +void QmlGraphicsContents::calcHeight() +{ + qreal oldy = m_y; + qreal oldheight = m_height; + + qreal top = FLT_MAX; + qreal bottom = 0; + + QList<QGraphicsItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QmlGraphicsItem *child = qobject_cast<QmlGraphicsItem *>(children.at(i)); + if(!child)//### Should this be ignoring non-QmlGraphicsItem graphicsobjects? + continue; + 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(); +} + +//TODO: optimization: only check sender(), if there is one +void QmlGraphicsContents::calcWidth() +{ + qreal oldx = m_x; + qreal oldwidth = m_width; + + qreal left = FLT_MAX; + qreal right = 0; + + QList<QGraphicsItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QmlGraphicsItem *child = qobject_cast<QmlGraphicsItem *>(children.at(i)); + if(!child)//### Should this be ignoring non-QmlGraphicsItem graphicsobjects? + continue; + 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(); +} + +void QmlGraphicsContents::setItem(QmlGraphicsItem *item) +{ + m_item = item; + + QList<QGraphicsItem *> children = m_item->childItems(); + for (int i = 0; i < children.count(); ++i) { + QmlGraphicsItem *child = qobject_cast<QmlGraphicsItem *>(children.at(i)); + if(!child)//### Should this be ignoring non-QmlGraphicsItem graphicsobjects? + continue; + connect(child, SIGNAL(heightChanged()), this, SLOT(calcHeight())); + connect(child, SIGNAL(yChanged()), this, SLOT(calcHeight())); + connect(child, SIGNAL(widthChanged()), this, SLOT(calcWidth())); + connect(child, SIGNAL(xChanged()), this, SLOT(calcWidth())); + connect(this, SIGNAL(rectChanged()), m_item, SIGNAL(childrenRectChanged())); + } + + calcHeight(); + calcWidth(); +} + +/* + Key filters can be installed on a QmlGraphicsItem, 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 QmlGraphicsItemKeyFilter +{ +public: + QmlGraphicsItemKeyFilter(QmlGraphicsItem * = 0); + virtual ~QmlGraphicsItemKeyFilter(); + + virtual void keyPressed(QKeyEvent *event); + virtual void keyReleased(QKeyEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *event); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + virtual void componentComplete(); + +private: + QmlGraphicsItemKeyFilter *m_next; +}; + +QmlGraphicsItemKeyFilter::QmlGraphicsItemKeyFilter(QmlGraphicsItem *item) +: m_next(0) +{ + QmlGraphicsItemPrivate *p = + item?static_cast<QmlGraphicsItemPrivate *>(QGraphicsItemPrivate::get(item)):0; + if (p) { + m_next = p->keyHandler; + p->keyHandler = this; + } +} + +QmlGraphicsItemKeyFilter::~QmlGraphicsItemKeyFilter() +{ +} + +void QmlGraphicsItemKeyFilter::keyPressed(QKeyEvent *event) +{ + if (m_next) m_next->keyPressed(event); +} + +void QmlGraphicsItemKeyFilter::keyReleased(QKeyEvent *event) +{ + if (m_next) m_next->keyReleased(event); +} + +void QmlGraphicsItemKeyFilter::inputMethodEvent(QInputMethodEvent *event) +{ + if (m_next) m_next->inputMethodEvent(event); +} + +QVariant QmlGraphicsItemKeyFilter::inputMethodQuery(Qt::InputMethodQuery query) const +{ + if (m_next) return m_next->inputMethodQuery(query); + return QVariant(); +} + +void QmlGraphicsItemKeyFilter::componentComplete() +{ + if (m_next) m_next->componentComplete(); +} + + +/*! + \qmlclass KeyNavigation + \brief The KeyNavigation attached property supports key navigation by arrow keys. + + It is common in key-based UIs to use arrow keys to navigate + between focussed items. The KeyNavigation property provides a + convenient way of specifying which item will gain focus + when an arrow key is pressed. The following example provides + key navigation for a 2x2 grid of items. + + \code + Grid { + columns: 2 + width: 100; height: 100 + Rectangle { + id: item1 + focus: true + width: 50; height: 50 + color: focus ? "red" : "lightgray" + KeyNavigation.right: item2 + KeyNavigation.down: item3 + } + Rectangle { + id: item2 + width: 50; height: 50 + color: focus ? "red" : "lightgray" + KeyNavigation.left: item1 + KeyNavigation.down: item4 + } + Rectangle { + id: item3 + width: 50; height: 50 + color: focus ? "red" : "lightgray" + KeyNavigation.right: item4 + KeyNavigation.up: item1 + } + Rectangle { + id: item4 + width: 50; height: 50 + color: focus ? "red" : "lightgray" + KeyNavigation.left: item3 + KeyNavigation.up: item2 + } + } + \endcode + + KeyNavigation receives key events after the item it is attached to. + If the item accepts an arrow key event, the KeyNavigation + attached property will not receive an event for that key. + + If an item has been set for a direction and the KeyNavigation + attached property receives the corresponding + key press and release events, the events will be accepted by + KeyNaviagtion and will not propagate any further. + + \sa {Keys}{Keys attached property} +*/ + +/*! + \qmlproperty Item KeyNavigation::left + \qmlproperty Item KeyNavigation::right + \qmlproperty Item KeyNavigation::up + \qmlproperty Item KeyNavigation::down + + These properties hold the item to assign focus to + when Key_Left, Key_Right, Key_Up or Key_Down are + pressed. +*/ + +class QmlGraphicsKeyNavigationAttachedPrivate : public QObjectPrivate +{ +public: + QmlGraphicsKeyNavigationAttachedPrivate() + : QObjectPrivate(), left(0), right(0), up(0), down(0) {} + + QmlGraphicsItem *left; + QmlGraphicsItem *right; + QmlGraphicsItem *up; + QmlGraphicsItem *down; +}; + +class QmlGraphicsKeyNavigationAttached : public QObject, public QmlGraphicsItemKeyFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QmlGraphicsKeyNavigationAttached) + + Q_PROPERTY(QmlGraphicsItem *left READ left WRITE setLeft NOTIFY changed) + Q_PROPERTY(QmlGraphicsItem *right READ right WRITE setRight NOTIFY changed) + Q_PROPERTY(QmlGraphicsItem *up READ up WRITE setUp NOTIFY changed) + Q_PROPERTY(QmlGraphicsItem *down READ down WRITE setDown NOTIFY changed) +public: + QmlGraphicsKeyNavigationAttached(QObject * = 0); + + QmlGraphicsItem *left() const; + void setLeft(QmlGraphicsItem *); + QmlGraphicsItem *right() const; + void setRight(QmlGraphicsItem *); + QmlGraphicsItem *up() const; + void setUp(QmlGraphicsItem *); + QmlGraphicsItem *down() const; + void setDown(QmlGraphicsItem *); + + static QmlGraphicsKeyNavigationAttached *qmlAttachedProperties(QObject *); + +Q_SIGNALS: + void changed(); + +private: + virtual void keyPressed(QKeyEvent *event); + virtual void keyReleased(QKeyEvent *event); +}; + +QmlGraphicsKeyNavigationAttached::QmlGraphicsKeyNavigationAttached(QObject *parent) +: QObject(*(new QmlGraphicsKeyNavigationAttachedPrivate), parent), + QmlGraphicsItemKeyFilter(qobject_cast<QmlGraphicsItem*>(parent)) +{ +} + +QmlGraphicsKeyNavigationAttached * +QmlGraphicsKeyNavigationAttached::qmlAttachedProperties(QObject *obj) +{ + return new QmlGraphicsKeyNavigationAttached(obj); +} + +QmlGraphicsItem *QmlGraphicsKeyNavigationAttached::left() const +{ + Q_D(const QmlGraphicsKeyNavigationAttached); + return d->left; +} + +void QmlGraphicsKeyNavigationAttached::setLeft(QmlGraphicsItem *i) +{ + Q_D(QmlGraphicsKeyNavigationAttached); + d->left = i; + emit changed(); +} + +QmlGraphicsItem *QmlGraphicsKeyNavigationAttached::right() const +{ + Q_D(const QmlGraphicsKeyNavigationAttached); + return d->right; +} + +void QmlGraphicsKeyNavigationAttached::setRight(QmlGraphicsItem *i) +{ + Q_D(QmlGraphicsKeyNavigationAttached); + d->right = i; + emit changed(); +} + +QmlGraphicsItem *QmlGraphicsKeyNavigationAttached::up() const +{ + Q_D(const QmlGraphicsKeyNavigationAttached); + return d->up; +} + +void QmlGraphicsKeyNavigationAttached::setUp(QmlGraphicsItem *i) +{ + Q_D(QmlGraphicsKeyNavigationAttached); + d->up = i; + emit changed(); +} + +QmlGraphicsItem *QmlGraphicsKeyNavigationAttached::down() const +{ + Q_D(const QmlGraphicsKeyNavigationAttached); + return d->down; +} + +void QmlGraphicsKeyNavigationAttached::setDown(QmlGraphicsItem *i) +{ + Q_D(QmlGraphicsKeyNavigationAttached); + d->down = i; + emit changed(); +} + +void QmlGraphicsKeyNavigationAttached::keyPressed(QKeyEvent *event) +{ + Q_D(QmlGraphicsKeyNavigationAttached); + + event->ignore(); + + switch(event->key()) { + case Qt::Key_Left: + if (d->left) { + d->left->setFocus(true); + event->accept(); + } + break; + case Qt::Key_Right: + if (d->right) { + d->right->setFocus(true); + event->accept(); + } + break; + case Qt::Key_Up: + if (d->up) { + d->up->setFocus(true); + event->accept(); + } + break; + case Qt::Key_Down: + if (d->down) { + d->down->setFocus(true); + event->accept(); + } + break; + default: + break; + } + + if (!event->isAccepted()) QmlGraphicsItemKeyFilter::keyPressed(event); +} + +void QmlGraphicsKeyNavigationAttached::keyReleased(QKeyEvent *event) +{ + Q_D(QmlGraphicsKeyNavigationAttached); + + event->ignore(); + + switch(event->key()) { + case Qt::Key_Left: + if (d->left) { + event->accept(); + } + break; + case Qt::Key_Right: + if (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; + default: + break; + } + + if (!event->isAccepted()) QmlGraphicsItemKeyFilter::keyReleased(event); +} + +/*! + \qmlclass Keys + \brief The Keys attached property provides key handling to Items. + + All visual primitives support key handling via the \e Keys + attached property. Keys can be handled via the \e onPressed + and \e onReleased signal properties. + + The signal properties have a \l KeyEvent parameter, named + \e event which contains details of the event. If a key is + handled \e event.accepted should be set to true to prevent the + event from propagating up the item heirarchy. + + \code + Item { + focus: true + Keys.onPressed: { + if (event.key == Qt.Key_Left) { + console.log("move left"); + event.accepted = true; + } + } + } + \endcode + + Some keys may alternatively be handled via specific signal properties, + for example \e onSelectPressed. These handlers automatically set + \e event.accepted to true. + + \code + Item { + focus: true + Keys.onLeftPressed: console.log("move left") + } + \endcode + + See \l {Qt::Key}{Qt.Key} for the list of keyboard codes. + + \sa KeyEvent, {KeyNavigation}{KeyNavigation attached property} +*/ + +/*! + \qmlproperty bool Keys::enabled + + This flags enables key handling if true (default); otherwise + no key handlers will be called. +*/ + +/*! + \qmlproperty List<Object> Keys::forwardTo + + This property provides a way to forward key presses, key releases, and keyboard input + coming from input methods to other items. This can be useful when you want + one item to handle some keys (e.g. the up and down arrow keys), and another item to + handle other keys (e.g. the left and right arrow keys). Once an item that has been + forwarded keys accepts the event it is no longer forwarded to items later in the + list. + + This example forwards key events to two lists: + \qml + ListView { id: list1 ... } + ListView { id: list2 ... } + Keys.forwardTo: [list1, list2] + focus: true + \endqml +*/ + +/*! + \qmlsignal Keys::onPressed(event) + + This handler is called when a key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onReleased(event) + + This handler is called when a key has been released. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit0Pressed(event) + + This handler is called when the digit '0' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit1Pressed(event) + + This handler is called when the digit '1' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit2Pressed(event) + + This handler is called when the digit '2' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit3Pressed(event) + + This handler is called when the digit '3' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit4Pressed(event) + + This handler is called when the digit '4' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit5Pressed(event) + + This handler is called when the digit '5' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit6Pressed(event) + + This handler is called when the digit '6' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit7Pressed(event) + + This handler is called when the digit '7' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit8Pressed(event) + + This handler is called when the digit '8' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDigit9Pressed(event) + + This handler is called when the digit '9' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onLeftPressed(event) + + This handler is called when the Left arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onRightPressed(event) + + This handler is called when the Right arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onUpPressed(event) + + This handler is called when the Up arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDownPressed(event) + + This handler is called when the Down arrow has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onAsteriskPressed(event) + + This handler is called when the Asterisk '*' has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onEscapePressed(event) + + This handler is called when the Escape key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onReturnPressed(event) + + This handler is called when the Return key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onEnterPressed(event) + + This handler is called when the Enter key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onDeletePressed(event) + + This handler is called when the Delete key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onSpacePressed(event) + + This handler is called when the Space key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onBackPressed(event) + + This handler is called when the Back key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onCancelPressed(event) + + This handler is called when the Cancel key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onSelectPressed(event) + + This handler is called when the Select key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onYesPressed(event) + + This handler is called when the Yes key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onNoPressed(event) + + This handler is called when the No key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onContext1Pressed(event) + + This handler is called when the Context1 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onContext2Pressed(event) + + This handler is called when the Context2 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onContext3Pressed(event) + + This handler is called when the Context3 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onContext4Pressed(event) + + This handler is called when the Context4 key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onCallPressed(event) + + This handler is called when the Call key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onHangupPressed(event) + + This handler is called when the Hangup key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onFlipPressed(event) + + This handler is called when the Flip key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onMenuPressed(event) + + This handler is called when the Menu key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onVolumeUpPressed(event) + + This handler is called when the VolumeUp key has been pressed. The \a event + parameter provides information about the event. +*/ + +/*! + \qmlsignal Keys::onVolumeDownPressed(event) + + This handler is called when the VolumeDown key has been pressed. The \a event + parameter provides information about the event. +*/ + + +class QmlGraphicsKeysAttachedPrivate : public QObjectPrivate +{ +public: + QmlGraphicsKeysAttachedPrivate() + : QObjectPrivate(), inPress(false), inRelease(false) + , inIM(false), enabled(true), imeItem(0), item(0) + {} + + bool isConnected(const char *signalName); + + QGraphicsItem *finalFocusProxy(QGraphicsItem *item) const + { + QGraphicsItem *fp; + while ((fp = item->focusProxy())) + item = fp; + return item; + } + + //loop detection + bool inPress:1; + bool inRelease:1; + bool inIM:1; + + bool enabled : 1; + + QGraphicsItem *imeItem; + QList<QmlGraphicsItem *> targets; + QmlGraphicsItem *item; +}; + +class QmlGraphicsKeysAttached : public QObject, public QmlGraphicsItemKeyFilter +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QmlGraphicsKeysAttached) + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QList<QmlGraphicsItem *> *forwardTo READ forwardTo) + +public: + QmlGraphicsKeysAttached(QObject *parent=0); + ~QmlGraphicsKeysAttached(); + + bool enabled() const { Q_D(const QmlGraphicsKeysAttached); return d->enabled; } + void setEnabled(bool enabled) { + Q_D(QmlGraphicsKeysAttached); + if (enabled != d->enabled) { + d->enabled = enabled; + emit enabledChanged(); + } + } + + QList<QmlGraphicsItem *> *forwardTo() { + Q_D(QmlGraphicsKeysAttached); + return &d->targets; + } + + virtual void componentComplete(); + + static QmlGraphicsKeysAttached *qmlAttachedProperties(QObject *); + +Q_SIGNALS: + void enabledChanged(); + void pressed(QmlGraphicsKeyEvent *event); + void released(QmlGraphicsKeyEvent *event); + void digit0Pressed(QmlGraphicsKeyEvent *event); + void digit1Pressed(QmlGraphicsKeyEvent *event); + void digit2Pressed(QmlGraphicsKeyEvent *event); + void digit3Pressed(QmlGraphicsKeyEvent *event); + void digit4Pressed(QmlGraphicsKeyEvent *event); + void digit5Pressed(QmlGraphicsKeyEvent *event); + void digit6Pressed(QmlGraphicsKeyEvent *event); + void digit7Pressed(QmlGraphicsKeyEvent *event); + void digit8Pressed(QmlGraphicsKeyEvent *event); + void digit9Pressed(QmlGraphicsKeyEvent *event); + + void leftPressed(QmlGraphicsKeyEvent *event); + void rightPressed(QmlGraphicsKeyEvent *event); + void upPressed(QmlGraphicsKeyEvent *event); + void downPressed(QmlGraphicsKeyEvent *event); + + void asteriskPressed(QmlGraphicsKeyEvent *event); + void numberSignPressed(QmlGraphicsKeyEvent *event); + void escapePressed(QmlGraphicsKeyEvent *event); + void returnPressed(QmlGraphicsKeyEvent *event); + void enterPressed(QmlGraphicsKeyEvent *event); + void deletePressed(QmlGraphicsKeyEvent *event); + void spacePressed(QmlGraphicsKeyEvent *event); + void backPressed(QmlGraphicsKeyEvent *event); + void cancelPressed(QmlGraphicsKeyEvent *event); + void selectPressed(QmlGraphicsKeyEvent *event); + void yesPressed(QmlGraphicsKeyEvent *event); + void noPressed(QmlGraphicsKeyEvent *event); + void context1Pressed(QmlGraphicsKeyEvent *event); + void context2Pressed(QmlGraphicsKeyEvent *event); + void context3Pressed(QmlGraphicsKeyEvent *event); + void context4Pressed(QmlGraphicsKeyEvent *event); + void callPressed(QmlGraphicsKeyEvent *event); + void hangupPressed(QmlGraphicsKeyEvent *event); + void flipPressed(QmlGraphicsKeyEvent *event); + void menuPressed(QmlGraphicsKeyEvent *event); + void volumeUpPressed(QmlGraphicsKeyEvent *event); + void volumeDownPressed(QmlGraphicsKeyEvent *event); + +private: + virtual void keyPressed(QKeyEvent *event); + virtual void keyReleased(QKeyEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *); + 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[]; +}; + +const QmlGraphicsKeysAttached::SigMap QmlGraphicsKeysAttached::sigMap[] = { + { Qt::Key_Left, "leftPressed" }, + { Qt::Key_Right, "rightPressed" }, + { Qt::Key_Up, "upPressed" }, + { Qt::Key_Down, "downPressed" }, + { 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 QmlGraphicsKeysAttachedPrivate::isConnected(const char *signalName) +{ + return isSignalConnected(signalIndex(signalName)); +} + +QmlGraphicsKeysAttached::QmlGraphicsKeysAttached(QObject *parent) +: QObject(*(new QmlGraphicsKeysAttachedPrivate), parent), + QmlGraphicsItemKeyFilter(qobject_cast<QmlGraphicsItem*>(parent)) +{ + Q_D(QmlGraphicsKeysAttached); + d->item = qobject_cast<QmlGraphicsItem*>(parent); +} + +QmlGraphicsKeysAttached::~QmlGraphicsKeysAttached() +{ +} + +void QmlGraphicsKeysAttached::componentComplete() +{ + Q_D(QmlGraphicsKeysAttached); + if (d->item) { + for (int ii = 0; ii < d->targets.count(); ++ii) { + QGraphicsItem *targetItem = d->finalFocusProxy(d->targets.at(ii)); + if (targetItem && (targetItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { + d->item->setFlag(QGraphicsItem::ItemAcceptsInputMethod); + break; + } + } + } +} + +void QmlGraphicsKeysAttached::keyPressed(QKeyEvent *event) +{ + Q_D(QmlGraphicsKeysAttached); + if (!d->enabled || d->inPress) { + event->ignore(); + return; + } + + // first process forwards + if (d->item && d->item->scene()) { + d->inPress = true; + for (int ii = 0; ii < d->targets.count(); ++ii) { + QGraphicsItem *i = d->finalFocusProxy(d->targets.at(ii)); + if (i) { + d->item->scene()->sendEvent(i, event); + if (event->isAccepted()) { + d->inPress = false; + return; + } + } + } + d->inPress = false; + } + + QmlGraphicsKeyEvent ke(*event); + QByteArray keySignal = keyToSignal(event->key()); + if (!keySignal.isEmpty()) { + keySignal += "(QmlGraphicsKeyEvent*)"; + if (d->isConnected(keySignal)) { + // If we specifically handle a key then default to accepted + ke.setAccepted(true); + int idx = QmlGraphicsKeysAttached::staticMetaObject.indexOfSignal(keySignal); + metaObject()->method(idx).invoke(this, Qt::DirectConnection, Q_ARG(QmlGraphicsKeyEvent*, &ke)); + } + } + if (!ke.isAccepted()) + emit pressed(&ke); + event->setAccepted(ke.isAccepted()); + + if (!event->isAccepted()) QmlGraphicsItemKeyFilter::keyPressed(event); +} + +void QmlGraphicsKeysAttached::keyReleased(QKeyEvent *event) +{ + Q_D(QmlGraphicsKeysAttached); + if (!d->enabled || d->inRelease) { + event->ignore(); + return; + } + + if (d->item && d->item->scene()) { + d->inRelease = true; + for (int ii = 0; ii < d->targets.count(); ++ii) { + QGraphicsItem *i = d->finalFocusProxy(d->targets.at(ii)); + if (i) { + d->item->scene()->sendEvent(i, event); + if (event->isAccepted()) { + d->inRelease = false; + return; + } + } + } + d->inRelease = false; + } + + QmlGraphicsKeyEvent ke(*event); + emit released(&ke); + event->setAccepted(ke.isAccepted()); + + if (!event->isAccepted()) QmlGraphicsItemKeyFilter::keyReleased(event); +} + +void QmlGraphicsKeysAttached::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QmlGraphicsKeysAttached); + if (d->item && !d->inIM && d->item->scene()) { + d->inIM = true; + for (int ii = 0; ii < d->targets.count(); ++ii) { + QGraphicsItem *i = d->finalFocusProxy(d->targets.at(ii)); + if (i && (i->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { + d->item->scene()->sendEvent(i, event); + if (event->isAccepted()) { + d->imeItem = i; + d->inIM = false; + return; + } + } + } + d->inIM = false; + } + if (!event->isAccepted()) QmlGraphicsItemKeyFilter::inputMethodEvent(event); +} + +class QmlGraphicsItemAccessor : public QGraphicsItem +{ +public: + QVariant doInputMethodQuery(Qt::InputMethodQuery query) const { + return QGraphicsItem::inputMethodQuery(query); + } +}; + +QVariant QmlGraphicsKeysAttached::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QmlGraphicsKeysAttached); + if (d->item) { + for (int ii = 0; ii < d->targets.count(); ++ii) { + QGraphicsItem *i = d->finalFocusProxy(d->targets.at(ii)); + if (i && (i->flags() & QGraphicsItem::ItemAcceptsInputMethod) && i == d->imeItem) { //### how robust is i == d->imeItem check? + QVariant v = static_cast<QmlGraphicsItemAccessor *>(i)->doInputMethodQuery(query); + if (v.userType() == QVariant::RectF) + v = d->item->mapRectFromItem(i, v.toRectF()); //### cost? + return v; + } + } + } + return QmlGraphicsItemKeyFilter::inputMethodQuery(query); +} + +QmlGraphicsKeysAttached *QmlGraphicsKeysAttached::qmlAttachedProperties(QObject *obj) +{ + return new QmlGraphicsKeysAttached(obj); +} + +/*! + \class QmlGraphicsItem + \brief The QmlGraphicsItem class provides the most basic of all visual items in QML. + + All visual items in Qt Declarative inherit from QmlGraphicsItem. Although QmlGraphicsItem + has no visual appearance, it defines all the properties that are + common across visual items - such as the x and y position, the + width and height, \l {anchor-layout}{anchoring} and key handling. + + You can subclass QmlGraphicsItem to provide your own custom visual item that inherits + these features. +*/ + +/*! + \qmlclass Item QmlGraphicsItem + \brief The Item is the most basic of all visual items in QML. + + All visual items in Qt Declarative inherit from Item. Although Item + has no visual appearance, it defines all the properties that are + common across visual items - such as the x and y position, the + width and height, \l {anchor-layout}{anchoring} and key handling. + + Item is also useful for grouping items together. + + \qml + Item { + Image { + source: "tile.png" + } + Image { + x: 80 + width: 100 + height: 100 + source: "tile.png" + } + Image { + x: 190 + width: 100 + height: 100 + fillMode: Image.Tile + source: "tile.png" + } + } + \endqml + + \section1 Identity + + Each item has an "id" - the identifier of the Item. + + The identifier can be used in bindings and other expressions to + refer to the item. For example: + + \qml + Text { id: myText; ... } + Text { text: myText.text } + \endqml + + The identifier is available throughout to the \l {components}{component} + where it is declared. The identifier must be unique in the component. + + The id should not be thought of as a "property" - it makes no sense + to write \c myText.id, for example. + + \section1 Key Handling + + Key handling is available to all Item-based visual elements via the \l {Keys}{Keys} + attached property. The \e Keys attached property provides basic handlers such + as \l {Keys::onPressed}{onPressed} and \l {Keys::onReleased}{onReleased}, + as well as handlers for specific keys, such as + \l {Keys::onCancelPressed}{onCancelPressed}. The example below + assigns \l {qmlfocus}{focus} to the item and handles + the Left key via the general \e onPressed handler and the Select key via the + onSelectPressed handler: + + \qml + Item { + focus: true + Keys.onPressed: { + if (event.key == Qt.Key_Left) { + console.log("move left"); + event.accepted = true; + } + } + Keys.onSelectPressed: console.log("Selected"); + } + \endqml + + See the \l {Keys}{Keys} attached property for detailed documentation. + + \ingroup group_coreitems +*/ + +/*! + \property QmlGraphicsItem::baseline + \internal +*/ + +/*! + \property QmlGraphicsItem::effect + \internal +*/ + +/*! + \property QmlGraphicsItem::focus + \internal +*/ + +/*! + \property QmlGraphicsItem::wantsFocus + \internal +*/ + +/*! + \property QmlGraphicsItem::transformOrigin + \internal +*/ + +/*! + \fn void QmlGraphicsItem::childrenRectChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::baselineOffsetChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::widthChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::heightChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::stateChanged(const QString &state) + \internal +*/ + +/*! + \fn void QmlGraphicsItem::parentChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::childrenChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::focusChanged() + \internal +*/ + +/*! + \fn void QmlGraphicsItem::wantsFocusChanged() + \internal +*/ + +// ### Must fix +struct RegisterAnchorLineAtStartup { + RegisterAnchorLineAtStartup() { + qRegisterMetaType<QmlGraphicsAnchorLine>("QmlGraphicsAnchorLine"); + } +}; +static RegisterAnchorLineAtStartup registerAnchorLineAtStartup; + + +/*! + \fn QmlGraphicsItem::QmlGraphicsItem(QmlGraphicsItem *parent) + + Constructs a QmlGraphicsItem with the given \a parent. +*/ +QmlGraphicsItem::QmlGraphicsItem(QmlGraphicsItem* parent) + : QGraphicsObject(*(new QmlGraphicsItemPrivate), parent, 0) +{ + Q_D(QmlGraphicsItem); + d->init(parent); +} + +/*! \internal +*/ +QmlGraphicsItem::QmlGraphicsItem(QmlGraphicsItemPrivate &dd, QmlGraphicsItem *parent) + : QGraphicsObject(dd, parent, 0) +{ + Q_D(QmlGraphicsItem); + d->init(parent); +} + +/*! + Destroys the QmlGraphicsItem. +*/ +QmlGraphicsItem::~QmlGraphicsItem() +{ + Q_D(QmlGraphicsItem); + for (int ii = 0; ii < d->changeListeners.count(); ++ii) { + QmlGraphicsAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate(); + if (anchor) + anchor->clearItem(this); + } + if (!d->parent || (parentItem() && !parentItem()->QGraphicsItem::d_ptr->inDestructor)) { + for (int ii = 0; ii < d->changeListeners.count(); ++ii) { + QmlGraphicsAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate(); + if (anchor && anchor->item && anchor->item->parentItem() != this) //child will be deleted anyway + anchor->updateOnComplete(); + } + } + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QmlGraphicsItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QmlGraphicsItemPrivate::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; +} + +/*! + \qmlproperty enum Item::transformOrigin + This property holds the origin point around which scale and rotation transform. + + Nine transform origins are available, as shown in the image below. + + \image declarative-transformorigin.png + + This example rotates an image around its bottom-right corner. + \qml + Image { + source: "myimage.png" + transformOrigin: Item.BottomRight + rotate: 45 + } + \endqml + + The default transform origin is \c Center. +*/ + +/*! + \qmlproperty Item Item::parent + This property holds the parent of the item. +*/ + +/*! + \property QmlGraphicsItem::parent + This property holds the parent of the item. +*/ +void QmlGraphicsItem::setParentItem(QmlGraphicsItem *parent) +{ + QmlGraphicsItem *oldParent = parentItem(); + if (parent == oldParent || !parent) return; + + Q_D(QmlGraphicsItem); + QObject::setParent(parent); + d->setParentItemHelper(parent, /*newParentVariant=*/0, /*thisPointerVariant=*/0); + if (oldParent) + emit oldParent->childrenChanged(); + emit parentChanged(); +} + +/*! + \fn void QmlGraphicsItem::setParent(QmlGraphicsItem *parent) + \overload + Sets both the parent object and parent item to \a parent. This + function avoids the programming error of calling setParent() + when you mean setParentItem(). +*/ + +/*! + Returns the QmlGraphicsItem parent of this item. +*/ +QmlGraphicsItem *QmlGraphicsItem::parentItem() const +{ + return qobject_cast<QmlGraphicsItem *>(QGraphicsObject::parentItem()); +} + +/*! + \qmlproperty list<Item> Item::children + \qmlproperty list<Object> Item::resources + + The children property contains the list of visual children of this item. + The resources property contains non-visual resources that you want to + reference by name. + + Generally you can rely on Item's default property to handle all this for + you, but it can come in handy in some cases. + + \qml + Item { + children: [ + Text {}, + Rectangle {} + ] + resources: [ + Component { + id: myComponent + Text {} + } + ] + } + \endqml +*/ + +/*! + \property QmlGraphicsItem::children + \internal +*/ + +/*! + \property QmlGraphicsItem::resources + \internal +*/ + +/*! + Returns true if construction of the QML component is complete; otherwise + returns false. + + It is often desireable to delay some processing until the component is + completed. + + \sa componentComplete() +*/ +bool QmlGraphicsItem::isComponentComplete() const +{ + Q_D(const QmlGraphicsItem); + return d->_componentComplete; +} + +/*! + \property QmlGraphicsItem::anchors + \internal +*/ + +/*! \internal */ +QmlGraphicsAnchors *QmlGraphicsItem::anchors() +{ + Q_D(QmlGraphicsItem); + return d->anchors(); +} + +void QmlGraphicsItemPrivate::data_removeAt(int) +{ + // ### +} + +int QmlGraphicsItemPrivate::data_count() const +{ + // ### + return 0; +} + +void QmlGraphicsItemPrivate::data_append(QObject *o) +{ + Q_Q(QmlGraphicsItem); + QmlGraphicsItem *i = qobject_cast<QmlGraphicsItem *>(o); + if (i) + q->fxChildren()->append(i); + else + resources_append(o); +} + +void QmlGraphicsItemPrivate::data_insert(int, QObject *) +{ + // ### +} + +QObject *QmlGraphicsItemPrivate::data_at(int) const +{ + // ### + return 0; +} + +void QmlGraphicsItemPrivate::data_clear() +{ + // ### +} + +void QmlGraphicsItemPrivate::resources_removeAt(int) +{ + // ### +} + +int QmlGraphicsItemPrivate::resources_count() const +{ + Q_Q(const QmlGraphicsItem); + return q->children().count(); +} + +void QmlGraphicsItemPrivate::resources_append(QObject *o) +{ + Q_Q(QmlGraphicsItem); + o->setParent(q); +} + +void QmlGraphicsItemPrivate::resources_insert(int, QObject *) +{ + // ### +} + +QObject *QmlGraphicsItemPrivate::resources_at(int idx) const +{ + Q_Q(const QmlGraphicsItem); + QObjectList children = q->children(); + if (idx < children.count()) + return children.at(idx); + else + return 0; +} + +void QmlGraphicsItemPrivate::resources_clear() +{ + // ### +} + +void QmlGraphicsItemPrivate::children_removeAt(int) +{ + // ### +} + +int QmlGraphicsItemPrivate::children_count() const +{ + Q_Q(const QmlGraphicsItem); + return q->childItems().count(); +} + +void QmlGraphicsItemPrivate::children_append(QmlGraphicsItem *i) +{ + Q_Q(QmlGraphicsItem); + i->setParentItem(q); +} + +void QmlGraphicsItemPrivate::children_insert(int, QmlGraphicsItem *) +{ + // ### +} + +QmlGraphicsItem *QmlGraphicsItemPrivate::children_at(int idx) const +{ + Q_Q(const QmlGraphicsItem); + QList<QGraphicsItem *> children = q->childItems(); + if (idx < children.count()) + return qobject_cast<QmlGraphicsItem *>(children.at(idx)); + else + return 0; +} + +void QmlGraphicsItemPrivate::children_clear() +{ + // ### +} + + +void QmlGraphicsItemPrivate::transform_removeAt(int i) +{ + if (!transformData) + return; + transformData->graphicsTransforms.removeAt(i); + dirtySceneTransform = 1; +} + +int QmlGraphicsItemPrivate::transform_count() const +{ + return transformData ? transformData->graphicsTransforms.size() : 0; +} + +void QmlGraphicsItemPrivate::transform_append(QGraphicsTransform *item) +{ + appendGraphicsTransform(item); +} + +void QmlGraphicsItemPrivate::transform_insert(int, QGraphicsTransform *) +{ + // ### +} + +QGraphicsTransform *QmlGraphicsItemPrivate::transform_at(int idx) const +{ + if (!transformData) + return 0; + return transformData->graphicsTransforms.at(idx); +} + +void QmlGraphicsItemPrivate::transform_clear() +{ + if (!transformData) + return; + Q_Q(QmlGraphicsItem); + q->setTransformations(QList<QGraphicsTransform *>()); +} + +/*! + \qmlproperty list<Object> Item::data + \default + + The data property is allows you to freely mix visual children and resources + of an item. If you assign a visual item to the data list it becomes + a child and if you assign any other object type, it is added as a resource. + + So you can write: + \qml + Item { + Text {} + Rectangle {} + Script {} + } + \endqml + + instead of: + \qml + Item { + children: [ + Text {}, + Rectangle {} + ] + resources: [ + Script {} + ] + } + \endqml + + data is a behind-the-scenes property: you should never need to explicitly + specify it. + */ + +/*! + \property QmlGraphicsItem::data + \internal +*/ + +/*! \internal */ +QmlList<QObject *> *QmlGraphicsItem::data() +{ + Q_D(QmlGraphicsItem); + return &d->data; +} + +/*! + \property QmlGraphicsItem::childrenRect + \brief The geometry of an item's children. + + childrenRect provides an easy way to access the (collective) position and size of the item's children. +*/ +QRectF QmlGraphicsItem::childrenRect() +{ + Q_D(QmlGraphicsItem); + if (!d->_contents) { + d->_contents = new QmlGraphicsContents; + d->_contents->setParent(this); + d->_contents->setItem(this); + } + return d->_contents->rectF(); +} + +bool QmlGraphicsItem::clip() const +{ + return flags() & ItemClipsChildrenToShape; +} + +void QmlGraphicsItem::setClip(bool c) +{ + setFlag(ItemClipsChildrenToShape, c); +} + +/*! + \qmlproperty real Item::x + \qmlproperty real Item::y + \qmlproperty real Item::width + \qmlproperty real Item::height + + Defines the item's position and size relative to its parent. + + \qml + Item { x: 100; y: 100; width: 100; height: 100 } + \endqml + */ + +/*! + \property QmlGraphicsItem::width + + Defines the item's width relative to its parent. + */ + +/*! + \property QmlGraphicsItem::height + + Defines the item's height relative to its parent. + */ + +/*! + \qmlproperty real Item::z + + Sets the stacking order of the item. By default the stacking order is 0. + + Items with a higher stacking value are drawn on top of items with a + lower stacking order. Items with the same stacking value are drawn + bottom up in the order they appear. Items with a negative stacking + value are drawn under their parent's content. + + The following example shows the various effects of stacking order. + + \table + \row + \o \image declarative-item_stacking1.png + \o Same \c z - later children above earlier children: + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + } + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + \endqml + \row + \o \image declarative-item_stacking2.png + \o Higher \c z on top: + \qml + Item { + Rectangle { + z: 1 + color: "red" + width: 100; height: 100 + } + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + \endqml + \row + \o \image declarative-item_stacking3.png + \o Same \c z - children above parents: + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \row + \o \image declarative-item_stacking4.png + \o Lower \c z below: + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + Rectangle { + z: -1 + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \endtable + */ + +/*! + \qmlproperty bool Item::visible + + Whether the item is visible. By default this is true. + + \note visible is not linked to actual visibility; if an item + moves off screen, or the opacity changes to 0, this will + not affect the visible property. +*/ + + +/*! + This function is called to handle this item's changes in + geometry from \a oldGeometry to \a newGeometry. If the two + geometries are the same, it doesn't do anything. + */ +void QmlGraphicsItem::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + Q_D(QmlGraphicsItem); + + if (d->_anchors) + d->_anchors->d_func()->updateMe(); + + if (transformOrigin() != QmlGraphicsItem::TopLeft) + setTransformOriginPoint(d->computeTransformOrigin()); + + if (newGeometry.x() != oldGeometry.x()) + emit xChanged(); + if (newGeometry.width() != oldGeometry.width()) + emit widthChanged(); + if (newGeometry.y() != oldGeometry.y()) + emit yChanged(); + if (newGeometry.height() != oldGeometry.height()) + emit heightChanged(); + + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QmlGraphicsItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QmlGraphicsItemPrivate::Geometry) + change.listener->itemGeometryChanged(this, newGeometry, oldGeometry); + } +} + +void QmlGraphicsItemPrivate::removeItemChangeListener(QmlGraphicsItemChangeListener *listener, ChangeTypes types) +{ + ChangeListener change(listener, types); + changeListeners.removeOne(change); +} + +/*! \internal */ +void QmlGraphicsItem::keyPressEvent(QKeyEvent *event) +{ + Q_D(QmlGraphicsItem); + if (d->keyHandler) + d->keyHandler->keyPressed(event); + else + event->ignore(); +} + +/*! \internal */ +void QmlGraphicsItem::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QmlGraphicsItem); + if (d->keyHandler) + d->keyHandler->keyReleased(event); + else + event->ignore(); +} + +/*! \internal */ +void QmlGraphicsItem::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QmlGraphicsItem); + if (d->keyHandler) + d->keyHandler->inputMethodEvent(event); + else + event->ignore(); +} + +/*! \internal */ +QVariant QmlGraphicsItem::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q_D(const QmlGraphicsItem); + QVariant v; + if (d->keyHandler) + v = d->keyHandler->inputMethodQuery(query); + + if (!v.isValid()) + v = QGraphicsObject::inputMethodQuery(query); + + return v; +} + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::left() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->left; +} + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::right() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->right; +} + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::horizontalCenter() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->hCenter; +} + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::top() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->top; +} + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::bottom() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->bottom; +} + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::verticalCenter() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->vCenter; +} + + +/*! + \internal +*/ +QmlGraphicsAnchorLine QmlGraphicsItem::baseline() const +{ + Q_D(const QmlGraphicsItem); + return d->anchorLines()->baseline; +} + +/*! + \property QmlGraphicsItem::top + \internal +*/ + +/*! + \property QmlGraphicsItem::bottom + \internal +*/ + +/*! + \property QmlGraphicsItem::left + \internal +*/ + +/*! + \property QmlGraphicsItem::right + \internal +*/ + +/*! + \property QmlGraphicsItem::horizontalCenter + \internal +*/ + +/*! + \property QmlGraphicsItem::verticalCenter + \internal +*/ + +/*! + \qmlproperty AnchorLine Item::top + \qmlproperty AnchorLine Item::bottom + \qmlproperty AnchorLine Item::left + \qmlproperty AnchorLine Item::right + \qmlproperty AnchorLine Item::horizontalCenter + \qmlproperty AnchorLine Item::verticalCenter + \qmlproperty AnchorLine Item::baseline + + The anchor lines of the item. + + For more information see \l {anchor-layout}{Anchor Layouts}. +*/ + +/*! + \qmlproperty AnchorLine Item::anchors.top + \qmlproperty AnchorLine Item::anchors.bottom + \qmlproperty AnchorLine Item::anchors.left + \qmlproperty AnchorLine Item::anchors.right + \qmlproperty AnchorLine Item::anchors.horizontalCenter + \qmlproperty AnchorLine Item::anchors.verticalCenter + \qmlproperty AnchorLine Item::anchors.baseline + + \qmlproperty Item Item::anchors.fill + \qmlproperty Item Item::anchors.centerIn + + \qmlproperty real Item::anchors.margins + \qmlproperty real Item::anchors.topMargin + \qmlproperty real Item::anchors.bottomMargin + \qmlproperty real Item::anchors.leftMargin + \qmlproperty real Item::anchors.rightMargin + \qmlproperty real Item::anchors.horizontalCenterOffset + \qmlproperty real Item::anchors.verticalCenterOffset + \qmlproperty real Item::anchors.baselineOffset + + Anchors provide a way to position an item by specifying its + relationship with other items. + + Margins apply to top, bottom, left, right, and fill anchors. + The margins property can be used to set all of the various margins at once, to the same value. + + Offsets apply for horizontal center, vertical center, and baseline anchors. + + \table + \row + \o \image declarative-anchors_example.png + \o Text anchored to Image, horizontally centered and vertically below, with a margin. + \qml + Image { id: pic; ... } + Text { + id: label + anchors.horizontalCenter: pic.horizontalCenter + anchors.top: pic.bottom + anchors.topMargin: 5 + ... + } + \endqml + \row + \o \image declarative-anchors_example2.png + \o + Left of Text anchored to right of Image, with a margin. The y + property of both defaults to 0. + + \qml + Image { id: pic; ... } + Text { + id: label + anchors.left: pic.right + anchors.leftMargin: 5 + ... + } + \endqml + \endtable + + anchors.fill provides a convenient way for one item to have the + same geometry as another item, and is equivalent to connecting all + four directional anchors. + + \note You can only anchor an item to siblings or a parent. + + For more information see \l {anchor-layout}{Anchor Layouts}. +*/ + +/*! + \property QmlGraphicsItem::baselineOffset + \brief The position of the item's baseline in local coordinates. + + The baseline of a Text item is the imaginary line on which the text + sits. Controls containing text usually set their baseline to the + baseline of their text. + + For non-text items, a default baseline offset of 0 is used. +*/ +qreal QmlGraphicsItem::baselineOffset() const +{ + Q_D(const QmlGraphicsItem); + if (!d->_baselineOffset.isValid()) { + return 0.0; + } else + return d->_baselineOffset; +} + +void QmlGraphicsItem::setBaselineOffset(qreal offset) +{ + Q_D(QmlGraphicsItem); + if (offset == d->_baselineOffset) + return; + + d->_baselineOffset = offset; + emit baselineOffsetChanged(); + + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QmlGraphicsItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QmlGraphicsItemPrivate::Geometry) { + QmlGraphicsAnchorsPrivate *anchor = change.listener->anchorPrivate(); + if (anchor) + anchor->updateVerticalAnchors(); + } + } +} + +/*! + \qmlproperty real Item::rotation + This property holds the rotation of the item in degrees clockwise. + + This specifies how many degrees to rotate the item around its transformOrigin. + The default rotation is 0 degrees (i.e. not rotated at all). + + \table + \row + \o \image declarative-rotation.png + \o + \qml + Rectangle { + color: "blue" + width: 100; height: 100 + Rectangle { + color: "green" + width: 25; height: 25 + } + Rectangle { + color: "red" + x: 25; y: 25; width: 50; height: 50 + rotation: 30 + } + } + \endqml + \endtable +*/ + +/*! + \qmlproperty real Item::scale + This property holds the scale of the item. + + A scale of less than 1 means the item will be displayed smaller than + normal, and a scale of greater than 1 means the item will be + displayed larger than normal. A negative scale means the item will + be mirrored. + + By default, items are displayed at a scale of 1 (i.e. at their + normal size). + + Scaling is from the item's transformOrigin. + + \table + \row + \o \image declarative-scale.png + \o + \qml + Rectangle { + color: "blue" + width: 100; height: 100 + Rectangle { + color: "green" + width: 25; height: 25 + } + Rectangle { + color: "red" + x: 25; y: 25; width: 50; height: 50 + scale: 1.4 + } + } + \endqml + \endtable +*/ + +/*! + \qmlproperty real Item::opacity + + The opacity of the item. Opacity is specified as a number between 0 + (fully transparent) and 1 (fully opaque). The default is 1. + + Opacity is an \e inherited attribute. That is, the opacity is + also applied individually to child items. In almost all cases this + is what you want. If you can spot the issue in the following + example, you might need to use an \l Opacity effect instead. + + \table + \row + \o \image declarative-item_opacity1.png + \o + \qml + Item { + Rectangle { + color: "red" + width: 100; height: 100 + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \row + \o \image declarative-item_opacity2.png + \o + \qml + Item { + Rectangle { + opacity: 0.5 + color: "red" + width: 100; height: 100 + Rectangle { + color: "blue" + x: 50; y: 50; width: 100; height: 100 + } + } + } + \endqml + \endtable +*/ + +/*! + Returns a value indicating whether mouse input should + remain with this item exclusively. + + \sa setKeepMouseGrab() + */ +bool QmlGraphicsItem::keepMouseGrab() const +{ + Q_D(const QmlGraphicsItem); + return d->_keepMouse; +} + +/*! + The flag indicating whether the mouse should remain + with this item is set to \a keep. + + This is useful for items that wish to grab and keep mouse + interaction following a predefined gesture. For example, + an item that is interested in horizontal mouse movement + may set keepMouseGrab to true once a threshold has been + exceeded. Once keepMouseGrab has been set to true, filtering + items will not react to mouse events. + + If the item does not indicate that it wishes to retain mouse grab, + a filtering item may steal the grab. For example, Flickable may attempt + to steal a mouse grab if it detects that the user has begun to + move the viewport. + + \sa keepMouseGrab() + */ +void QmlGraphicsItem::setKeepMouseGrab(bool keep) +{ + Q_D(QmlGraphicsItem); + d->_keepMouse = keep; +} + +/*! + \internal + + This function emits the \e focusChanged signal. + + Subclasses overriding this function should call up + to their base class. +*/ +void QmlGraphicsItem::focusChanged(bool flag) +{ + Q_UNUSED(flag); + emit focusChanged(); +} + +/*! \internal */ +QmlList<QmlGraphicsItem *> *QmlGraphicsItem::fxChildren() +{ + Q_D(QmlGraphicsItem); + return &(d->children); +} + +/*! \internal */ +QmlList<QObject *> *QmlGraphicsItem::resources() +{ + Q_D(QmlGraphicsItem); + return &(d->resources); +} + +/*! + \qmlproperty list<State> Item::states + This property holds a list of states defined by the item. + + \qml + Item { + states: [ + State { ... }, + State { ... } + ... + ] + } + \endqml + + \sa {qmlstate}{States} +*/ + +/*! + \property QmlGraphicsItem::states + \internal +*/ +/*! \internal */ +QmlList<QmlState *>* QmlGraphicsItem::states() +{ + Q_D(QmlGraphicsItem); + return d->states()->statesProperty(); +} + +/*! + \qmlproperty list<Transition> Item::transitions + This property holds a list of transitions defined by the item. + + \qml + Item { + transitions: [ + Transition { ... }, + Transition { ... } + ... + ] + } + \endqml + + \sa {state-transitions}{Transitions} +*/ + +/*! + \property QmlGraphicsItem::transitions + \internal +*/ + +/*! \internal */ +QmlList<QmlTransition *>* QmlGraphicsItem::transitions() +{ + Q_D(QmlGraphicsItem); + return d->states()->transitionsProperty(); +} + +/* + \qmlproperty list<Filter> Item::filter + This property holds a list of graphical filters to be applied to the item. + + \l {Filter}{Filters} include things like \l {Blur}{blurring} + the item, or giving it a \l Reflection. Some + filters may not be available on all canvases; if a filter is not + available on a certain canvas, it will simply not be applied for + that canvas (but the QML will still be considered valid). + + \qml + Item { + filter: [ + Blur { ... }, + Relection { ... } + ... + ] + } + \endqml +*/ + +/*! + \qmlproperty bool Item::clip + This property holds whether clipping is enabled. + + if clipping is enabled, an item will clip its own painting, as well + as the painting of its children, to its bounding rectangle. + + Non-rectangular clipping regions are not supported for performance reasons. +*/ + +/*! + \property QmlGraphicsItem::clip + This property holds whether clipping is enabled. + + if clipping is enabled, an item will clip its own painting, as well + as the painting of its children, to its bounding rectangle. + + Non-rectangular clipping regions are not supported for performance reasons. +*/ + +/*! + \qmlproperty string Item::state + + This property holds the name of the current state of the item. + + This property is often used in scripts to change between states. For + example: + + \qml + Script { + function toggle() { + if (button.state == 'On') + button.state = 'Off'; + else + button.state = 'On'; + } + } + \endqml + + If the item is in its base state (i.e. no explicit state has been + set), \c state will be a blank string. Likewise, you can return an + item to its base state by setting its current state to \c ''. + + \sa {qmlstates}{States} +*/ + +/*! + \property QmlGraphicsItem::state + \internal +*/ + +/*! \internal */ +QString QmlGraphicsItem::state() const +{ + Q_D(const QmlGraphicsItem); + if (!d->_stateGroup) + return QString(); + else + return d->_stateGroup->state(); +} + +/*! \internal */ +void QmlGraphicsItem::setState(const QString &state) +{ + Q_D(QmlGraphicsItem); + d->states()->setState(state); +} + +/*! + \qmlproperty list<Transform> Item::transform + This property holds the list of transformations to apply. + + For more information see \l Transform. +*/ + +/*! + \property QmlGraphicsItem::transform + \internal +*/ + +/*! \internal */ +QmlList<QGraphicsTransform *>* QmlGraphicsItem::transform() +{ + Q_D(QmlGraphicsItem); + return &(d->transform); +} + +/*! + \internal + + classBegin() is called when the item is constructed, but its + properties have not yet been set. + + \sa componentComplete(), isComponentComplete() +*/ +void QmlGraphicsItem::classBegin() +{ + Q_D(QmlGraphicsItem); + d->_componentComplete = false; + if (d->_stateGroup) + d->_stateGroup->classBegin(); + if (d->_anchors) + d->_anchors->classBegin(); +} + +/*! + \internal + + componentComplete() is called when all items in the component + have been constructed. It is often desireable to delay some + processing until the component is complete an all bindings in the + component have been resolved. +*/ +void QmlGraphicsItem::componentComplete() +{ +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::ItemComponentComplete> cc; +#endif + + Q_D(QmlGraphicsItem); + d->_componentComplete = true; + if (d->_stateGroup) + d->_stateGroup->componentComplete(); + if (d->_anchors) { + d->_anchors->componentComplete(); + d->_anchors->d_func()->updateOnComplete(); + } + if (d->keyHandler) + d->keyHandler->componentComplete(); +} + +QmlStateGroup *QmlGraphicsItemPrivate::states() +{ + Q_Q(QmlGraphicsItem); + if (!_stateGroup) { + _stateGroup = new QmlStateGroup; + if (!_componentComplete) + _stateGroup->classBegin(); + QObject::connect(_stateGroup, SIGNAL(stateChanged(QString)), + q, SIGNAL(stateChanged(QString))); + } + + return _stateGroup; +} + +QmlGraphicsItemPrivate::AnchorLines::AnchorLines(QmlGraphicsItem *q) +{ + left.item = q; + left.anchorLine = QmlGraphicsAnchorLine::Left; + right.item = q; + right.anchorLine = QmlGraphicsAnchorLine::Right; + hCenter.item = q; + hCenter.anchorLine = QmlGraphicsAnchorLine::HCenter; + top.item = q; + top.anchorLine = QmlGraphicsAnchorLine::Top; + bottom.item = q; + bottom.anchorLine = QmlGraphicsAnchorLine::Bottom; + vCenter.item = q; + vCenter.anchorLine = QmlGraphicsAnchorLine::VCenter; + baseline.item = q; + baseline.anchorLine = QmlGraphicsAnchorLine::Baseline; +} + +QPointF QmlGraphicsItemPrivate::computeTransformOrigin() const +{ + Q_Q(const QmlGraphicsItem); + + QRectF br = q->boundingRect(); + + switch(origin) { + default: + case QmlGraphicsItem::TopLeft: + return QPointF(0, 0); + case QmlGraphicsItem::Top: + return QPointF(br.width() / 2., 0); + case QmlGraphicsItem::TopRight: + return QPointF(br.width(), 0); + case QmlGraphicsItem::Left: + return QPointF(0, br.height() / 2.); + case QmlGraphicsItem::Center: + return QPointF(br.width() / 2., br.height() / 2.); + case QmlGraphicsItem::Right: + return QPointF(br.width(), br.height() / 2.); + case QmlGraphicsItem::BottomLeft: + return QPointF(0, br.height()); + case QmlGraphicsItem::Bottom: + return QPointF(br.width() / 2., br.height()); + case QmlGraphicsItem::BottomRight: + return QPointF(br.width(), br.height()); + } +} + +/*! \internal */ +bool QmlGraphicsItem::sceneEvent(QEvent *event) +{ + bool rv = QGraphicsItem::sceneEvent(event); + + if (event->type() == QEvent::FocusIn || + event->type() == QEvent::FocusOut) { + focusChanged(hasFocus()); + } + + return rv; +} + +/*! \internal */ +QVariant QmlGraphicsItem::itemChange(GraphicsItemChange change, + const QVariant &value) +{ + Q_D(const QmlGraphicsItem); + switch (change) { + case ItemParentHasChanged: + emit parentChanged(); + break; + case ItemChildAddedChange: + case ItemChildRemovedChange: + emit childrenChanged(); + break; + case ItemVisibleHasChanged: { + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QmlGraphicsItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QmlGraphicsItemPrivate::Visibility) { + change.listener->itemVisibilityChanged(this); + } + } + } + break; + case ItemOpacityHasChanged: { + for(int ii = 0; ii < d->changeListeners.count(); ++ii) { + const QmlGraphicsItemPrivate::ChangeListener &change = d->changeListeners.at(ii); + if (change.types & QmlGraphicsItemPrivate::Opacity) { + change.listener->itemOpacityChanged(this); + } + } + } + break; + default: + break; + } + + return QGraphicsItem::itemChange(change, value); +} + +/*! \internal */ +QRectF QmlGraphicsItem::boundingRect() const +{ + Q_D(const QmlGraphicsItem); + return QRectF(0, 0, d->width, d->height); +} + +/*! + \enum QmlGraphicsItem::TransformOrigin + + Controls the point about which simple transforms like scale apply. + + \value TopLeft The top-left corner of the item. + \value Top The center point of the top of the item. + \value TopRight The top-right corner of the item. + \value Left The left most point of the vertical middle. + \value Center The center of the item. + \value Right The right most point of the vertical middle. + \value BottomLeft The bottom-left corner of the item. + \value Bottom The center point of the bottom of the item. + \value BottomRight The bottom-right corner of the item. +*/ + +/*! + Returns the current transform origin. +*/ +QmlGraphicsItem::TransformOrigin QmlGraphicsItem::transformOrigin() const +{ + Q_D(const QmlGraphicsItem); + return d->origin; +} + +/*! + Set the transform \a origin. +*/ +void QmlGraphicsItem::setTransformOrigin(TransformOrigin origin) +{ + Q_D(QmlGraphicsItem); + if (origin != d->origin) { + d->origin = origin; + QGraphicsItem::setTransformOriginPoint(d->computeTransformOrigin()); + emit transformOriginChanged(d->origin); + } +} + +/*! + \property QmlGraphicsItem::smooth + \brief whether the item is smoothly transformed. + + This property is provided purely for the purpose of optimization. Turning + smooth transforms off is faster, but looks worse; turning smooth + transformations on is slower, but looks better. + + By default smooth transformations are off. +*/ + +/*! + Returns true if the item should be drawn with antialiasing and + smooth pixmap filtering, false otherwise. + + The default is false. + + \sa setSmooth() +*/ +bool QmlGraphicsItem::smooth() const +{ + Q_D(const QmlGraphicsItem); + return d->smooth; +} + +/*! + Sets whether the item should be drawn with antialiasing and + smooth pixmap filtering to \a smooth. + + \sa smooth() +*/ +void QmlGraphicsItem::setSmooth(bool smooth) +{ + Q_D(QmlGraphicsItem); + if (d->smooth == smooth) + return; + d->smooth = smooth; + update(); +} + +qreal QmlGraphicsItem::width() const +{ + Q_D(const QmlGraphicsItem); + return d->width; +} + +void QmlGraphicsItem::setWidth(qreal w) +{ + Q_D(QmlGraphicsItem); + if (qIsNaN(w)) + return; + + d->widthValid = true; + if (d->width == w) + return; + + qreal oldWidth = d->width; + + prepareGeometryChange(); + d->width = w; + update(); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), oldWidth, height())); +} + +void QmlGraphicsItem::resetWidth() +{ + Q_D(QmlGraphicsItem); + d->widthValid = false; + setImplicitWidth(implicitWidth()); +} + +/*! + Returns the width of the item that is implied by other properties that determine the content. +*/ +qreal QmlGraphicsItem::implicitWidth() const +{ + Q_D(const QmlGraphicsItem); + return d->implicitWidth; +} + +/*! + Sets the implied width of the item to \a w. + This is the width implied by other properties that determine the content. +*/ +void QmlGraphicsItem::setImplicitWidth(qreal w) +{ + Q_D(QmlGraphicsItem); + d->implicitWidth = w; + if (d->width == w || widthValid()) + return; + + qreal oldWidth = d->width; + + prepareGeometryChange(); + d->width = w; + update(); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), oldWidth, height())); +} + +/*! + Returns whether the width property has been set explicitly. +*/ +bool QmlGraphicsItem::widthValid() const +{ + Q_D(const QmlGraphicsItem); + return d->widthValid; +} + +qreal QmlGraphicsItem::height() const +{ + Q_D(const QmlGraphicsItem); + return d->height; +} + +void QmlGraphicsItem::setHeight(qreal h) +{ + Q_D(QmlGraphicsItem); + if (qIsNaN(h)) + return; + + d->heightValid = true; + if (d->height == h) + return; + + qreal oldHeight = d->height; + + prepareGeometryChange(); + d->height = h; + update(); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), width(), oldHeight)); +} + +void QmlGraphicsItem::resetHeight() +{ + Q_D(QmlGraphicsItem); + d->heightValid = false; + setImplicitHeight(implicitHeight()); +} + +/*! + Returns the height of the item that is implied by other properties that determine the content. +*/ +qreal QmlGraphicsItem::implicitHeight() const +{ + Q_D(const QmlGraphicsItem); + return d->implicitHeight; +} + +/*! + Sets the implied height of the item to \a h. + This is the height implied by other properties that determine the content. +*/ +void QmlGraphicsItem::setImplicitHeight(qreal h) +{ + Q_D(QmlGraphicsItem); + d->implicitHeight = h; + if (d->height == h || heightValid()) + return; + + qreal oldHeight = d->height; + + prepareGeometryChange(); + d->height = h; + update(); + + geometryChanged(QRectF(x(), y(), width(), height()), + QRectF(x(), y(), width(), oldHeight)); +} + +/*! + Returns whether the height property has been set explicitly. +*/ +bool QmlGraphicsItem::heightValid() const +{ + Q_D(const QmlGraphicsItem); + return d->heightValid; +} + +/*! + \qmlproperty bool Item::wantsFocus + + This property indicates whether the item has has an active focus request. + + \sa {qmlfocus}{Keyboard Focus} +*/ + +/*! \internal */ +bool QmlGraphicsItem::wantsFocus() const +{ + return focusItem() != 0; +} + +/*! + \qmlproperty bool Item::focus + This property indicates whether the item has keyboard input focus. Set this + property to true to request focus. + + \sa {qmlfocus}{Keyboard Focus} +*/ + +/*! \internal */ +bool QmlGraphicsItem::hasFocus() const +{ + return QGraphicsItem::hasFocus(); +} + +/*! \internal */ +void QmlGraphicsItem::setFocus(bool focus) +{ + if (focus) + QGraphicsItem::setFocus(Qt::OtherFocusReason); + else + QGraphicsItem::clearFocus(); +} + +/*! + \reimp + \internal +*/ +void QmlGraphicsItem::paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) +{ +} + +/*! + \reimp + \internal +*/ +bool QmlGraphicsItem::event(QEvent *ev) +{ + return QGraphicsObject::event(ev); +} + +QDebug operator<<(QDebug debug, QmlGraphicsItem *item) +{ + if (!item) { + debug << "QmlGraphicsItem(0)"; + return debug; + } + + debug << item->metaObject()->className() << "(this =" << ((void*)item) + << ", parent =" << ((void*)item->parentItem()) + << ", geometry =" << QRectF(item->pos(), QSizeF(item->width(), item->height())) + << ", z =" << item->zValue() << ')'; + return debug; +} + +int QmlGraphicsItemPrivate::consistentTime = -1; +void QmlGraphicsItemPrivate::setConsistentTime(int t) +{ + consistentTime = t; +} + +QTime QmlGraphicsItemPrivate::currentTime() +{ + if (consistentTime == -1) + return QTime::currentTime(); + else + return QTime(0, 0).addMSecs(consistentTime); +} + +void QmlGraphicsItemPrivate::start(QTime &t) +{ + t = currentTime(); +} + +int QmlGraphicsItemPrivate::elapsed(QTime &t) +{ + int n = t.msecsTo(currentTime()); + if (n < 0) // passed midnight + n += 86400 * 1000; + return n; +} + +int QmlGraphicsItemPrivate::restart(QTime &t) +{ + QTime time = currentTime(); + int n = t.msecsTo(time); + if (n < 0) // passed midnight + n += 86400*1000; + t = time; + return n; +} + +#include <qmlgraphicsitem.moc> +#include <moc_qmlgraphicsitem.cpp> + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsKeysAttached) +QML_DECLARE_TYPEINFO(QmlGraphicsKeysAttached, QML_HAS_ATTACHED_PROPERTIES) +QML_DEFINE_TYPE(Qt,4,6,Keys,QmlGraphicsKeysAttached) +QML_DECLARE_TYPE(QmlGraphicsKeyNavigationAttached) +QML_DECLARE_TYPEINFO(QmlGraphicsKeyNavigationAttached, QML_HAS_ATTACHED_PROPERTIES) +QML_DEFINE_TYPE(Qt,4,6,KeyNavigation,QmlGraphicsKeyNavigationAttached) diff --git a/src/declarative/graphicsitems/qmlgraphicsitem.h b/src/declarative/graphicsitems/qmlgraphicsitem.h new file mode 100644 index 0000000..8ae2d5c --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsitem.h @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSITEM_H +#define QMLGRAPHICSITEM_H + +#include <qml.h> +#include <qmlcomponent.h> + +#include <QtCore/QObject> +#include <QtCore/QList> +#include <QtGui/qgraphicsitem.h> +#include <QtGui/qgraphicstransform.h> +#include <QtGui/qfont.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlState; +class QmlGraphicsAnchorLine; +class QmlTransition; +class QmlGraphicsKeyEvent; +class QmlGraphicsAnchors; +class QmlGraphicsItemPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsItem : public QGraphicsObject, public QmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QmlParserStatus) + + Q_PROPERTY(QmlGraphicsItem * parent READ parentItem WRITE setParentItem NOTIFY parentChanged DESIGNABLE false FINAL) + Q_PROPERTY(QmlList<QObject *> *data READ data DESIGNABLE false) + Q_PROPERTY(QmlList<QmlGraphicsItem *>* children READ fxChildren DESIGNABLE false NOTIFY childrenChanged) + Q_PROPERTY(QmlList<QObject *>* resources READ resources DESIGNABLE false) + Q_PROPERTY(QmlList<QmlState *>* states READ states DESIGNABLE false) + Q_PROPERTY(QmlList<QmlTransition *>* transitions READ transitions DESIGNABLE false) + Q_PROPERTY(QString state READ state WRITE setState NOTIFY stateChanged) + 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(QRectF childrenRect READ childrenRect NOTIFY childrenRectChanged DESIGNABLE false FINAL) + Q_PROPERTY(QmlGraphicsAnchors * anchors READ anchors DESIGNABLE false CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine left READ left CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine right READ right CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine horizontalCenter READ horizontalCenter CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine top READ top CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine bottom READ bottom CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine verticalCenter READ verticalCenter CONSTANT FINAL) + Q_PROPERTY(QmlGraphicsAnchorLine baseline READ baseline CONSTANT FINAL) + Q_PROPERTY(qreal baselineOffset READ baselineOffset WRITE setBaselineOffset NOTIFY baselineOffsetChanged) + Q_PROPERTY(bool clip READ clip WRITE setClip) // ### move to QGI/QGO, NOTIFY + Q_PROPERTY(bool focus READ hasFocus WRITE setFocus NOTIFY focusChanged FINAL) + Q_PROPERTY(bool wantsFocus READ wantsFocus NOTIFY wantsFocusChanged) + Q_PROPERTY(QmlList<QGraphicsTransform *>* transform READ transform DESIGNABLE false FINAL) + Q_PROPERTY(TransformOrigin transformOrigin READ transformOrigin WRITE setTransformOrigin NOTIFY transformOriginChanged) + Q_PROPERTY(bool smooth READ smooth WRITE setSmooth) + Q_PROPERTY(QGraphicsEffect *effect READ graphicsEffect WRITE setGraphicsEffect) + Q_ENUMS(TransformOrigin) + Q_CLASSINFO("DefaultProperty", "data") + +public: + enum TransformOrigin { + TopLeft, Top, TopRight, + Left, Center, Right, + BottomLeft, Bottom, BottomRight + }; + + QmlGraphicsItem(QmlGraphicsItem *parent = 0); + virtual ~QmlGraphicsItem(); + + QmlGraphicsItem *parentItem() const; + void setParentItem(QmlGraphicsItem *parent); + void setParent(QmlGraphicsItem *parent) { setParentItem(parent); } + + QmlList<QObject *> *data(); + QmlList<QmlGraphicsItem *> *fxChildren(); + QmlList<QObject *> *resources(); + + QmlGraphicsAnchors *anchors(); + QRectF childrenRect(); + + bool clip() const; + void setClip(bool); + + QmlList<QmlState *>* states(); + QmlList<QmlTransition *>* transitions(); + + QString state() const; + void setState(const QString &); + + qreal baselineOffset() const; + void setBaselineOffset(qreal); + + QmlList<QGraphicsTransform *> *transform(); + + qreal width() const; + void setWidth(qreal); + void resetWidth(); + qreal implicitWidth() const; + + qreal height() const; + void setHeight(qreal); + void resetHeight(); + qreal implicitHeight() const; + + TransformOrigin transformOrigin() const; + void setTransformOrigin(TransformOrigin); + + bool smooth() const; + void setSmooth(bool); + + QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + bool wantsFocus() const; + bool hasFocus() const; + void setFocus(bool); + + bool keepMouseGrab() const; + void setKeepMouseGrab(bool); + + QmlGraphicsAnchorLine left() const; + QmlGraphicsAnchorLine right() const; + QmlGraphicsAnchorLine horizontalCenter() const; + QmlGraphicsAnchorLine top() const; + QmlGraphicsAnchorLine bottom() const; + QmlGraphicsAnchorLine verticalCenter() const; + QmlGraphicsAnchorLine baseline() const; + +Q_SIGNALS: + void widthChanged(); + void heightChanged(); + void childrenChanged(); + void childrenRectChanged(); + void baselineOffsetChanged(); + void stateChanged(const QString &); + void focusChanged(); + void wantsFocusChanged(); + void parentChanged(); + void transformOriginChanged(TransformOrigin); + +protected: + bool isComponentComplete() const; + virtual bool sceneEvent(QEvent *); + virtual bool event(QEvent *); + virtual QVariant itemChange(GraphicsItemChange, const QVariant &); + + void setImplicitWidth(qreal); + bool widthValid() const; // ### better name? + void setImplicitHeight(qreal); + bool heightValid() const; // ### better name? + + virtual void classBegin(); + virtual void componentComplete(); + virtual void focusChanged(bool); + virtual void keyPressEvent(QKeyEvent *event); + virtual void keyReleaseEvent(QKeyEvent *event); + virtual void inputMethodEvent(QInputMethodEvent *); + virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + +protected: + QmlGraphicsItem(QmlGraphicsItemPrivate &dd, QmlGraphicsItem *parent = 0); + +private: + friend class QmlStatePrivate; + friend class QmlGraphicsAnchors; + Q_DISABLE_COPY(QmlGraphicsItem) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsItem) +}; + +template<typename T> + T qobject_cast(QGraphicsObject *o) +{ + QObject *obj = o; + return qobject_cast<T>(obj); +} + +// ### move to QGO +template<typename T> +T qobject_cast(QGraphicsItem *item) +{ + if (!item) return 0; + QObject *o = item->toGraphicsObject(); + return qobject_cast<T>(o); +} + +QDebug Q_DECLARATIVE_EXPORT operator<<(QDebug debug, QmlGraphicsItem *item); + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsItem) +QML_DECLARE_TYPE(QGraphicsTransform) +QML_DECLARE_TYPE(QGraphicsScale) +QML_DECLARE_TYPE(QGraphicsRotation) + +QT_END_HEADER + +#endif // QMLGRAPHICSITEM_H diff --git a/src/declarative/graphicsitems/qmlgraphicsitem_p.h b/src/declarative/graphicsitems/qmlgraphicsitem_p.h new file mode 100644 index 0000000..1741808 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsitem_p.h @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSITEM_P_H +#define QMLGRAPHICSITEM_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 "qmlgraphicsitem.h" + +#include "qmlgraphicsanchors_p.h" +#include "qmlgraphicsanchors_p_p.h" +#include "qmlgraphicsitemchangelistener_p.h" +#include <private/qpodvector_p.h> + +#include "../util/qmlstate_p.h" +#include "../util/qmlnullablevalue_p_p.h" +#include <qml.h> +#include <qmlcontext.h> + +#include <QtCore/qlist.h> +#include <QtCore/qdebug.h> + +#include <private/qgraphicsitem_p.h> + +QT_BEGIN_NAMESPACE + +class QNetworkReply; +class QmlGraphicsItemKeyFilter; + +//### merge into private? +class QmlGraphicsContents : public QObject +{ + Q_OBJECT +public: + QmlGraphicsContents(); + + QRectF rectF() const; + + void setItem(QmlGraphicsItem *item); + +public Q_SLOTS: + void calcHeight(); + void calcWidth(); + +Q_SIGNALS: + void rectChanged(); + +private: + QmlGraphicsItem *m_item; + qreal m_x; + qreal m_y; + qreal m_width; + qreal m_height; +}; + +class QmlGraphicsItemPrivate : public QGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsItem) + +public: + QmlGraphicsItemPrivate() + : _anchors(0), _contents(0), + _baselineOffset(0), + _anchorLines(0), + _stateGroup(0), origin(QmlGraphicsItem::Center), + widthValid(false), heightValid(false), + _componentComplete(true), _keepMouse(false), + smooth(false), keyHandler(0), + width(0), height(0), implicitWidth(0), implicitHeight(0) + { + QGraphicsItemPrivate::acceptedMouseButtons = 0; + QGraphicsItemPrivate::flags = QGraphicsItem::GraphicsItemFlags( + QGraphicsItem::ItemHasNoContents + | QGraphicsItem::ItemIsFocusable + | QGraphicsItem::ItemNegativeZStacksBehindParent); + + } + + void init(QmlGraphicsItem *parent) + { + Q_Q(QmlGraphicsItem); + + if (parent) + q->setParentItem(parent); + _baselineOffset.invalidate(); + mouseSetsFocus = false; + } + + QString _id; + + // data property + void data_removeAt(int); + int data_count() const; + void data_append(QObject *); + void data_insert(int, QObject *); + QObject *data_at(int) const; + void data_clear(); + QML_DECLARE_LIST_PROXY(QmlGraphicsItemPrivate, QObject *, data) + + // resources property + void resources_removeAt(int); + int resources_count() const; + void resources_append(QObject *); + void resources_insert(int, QObject *); + QObject *resources_at(int) const; + void resources_clear(); + QML_DECLARE_LIST_PROXY(QmlGraphicsItemPrivate, QObject *, resources) + + // children property + void children_removeAt(int); + int children_count() const; + void children_append(QmlGraphicsItem *); + void children_insert(int, QmlGraphicsItem *); + QmlGraphicsItem *children_at(int) const; + void children_clear(); + QML_DECLARE_LIST_PROXY(QmlGraphicsItemPrivate, QmlGraphicsItem *, children) + + // transform property + void transform_removeAt(int); + int transform_count() const; + void transform_append(QGraphicsTransform *); + void transform_insert(int, QGraphicsTransform *); + QGraphicsTransform *transform_at(int) const; + void transform_clear(); + QML_DECLARE_LIST_PROXY(QmlGraphicsItemPrivate, QGraphicsTransform *, transform) + + QmlGraphicsAnchors *anchors() { + if (!_anchors) { + Q_Q(QmlGraphicsItem); + _anchors = new QmlGraphicsAnchors(q); + if (!_componentComplete) + _anchors->classBegin(); + } + return _anchors; + } + QmlGraphicsAnchors *_anchors; + QmlGraphicsContents *_contents; + + QmlNullableValue<qreal> _baselineOffset; + + struct AnchorLines { + AnchorLines(QmlGraphicsItem *); + QmlGraphicsAnchorLine left; + QmlGraphicsAnchorLine right; + QmlGraphicsAnchorLine hCenter; + QmlGraphicsAnchorLine top; + QmlGraphicsAnchorLine bottom; + QmlGraphicsAnchorLine vCenter; + QmlGraphicsAnchorLine baseline; + }; + mutable AnchorLines *_anchorLines; + AnchorLines *anchorLines() const { + Q_Q(const QmlGraphicsItem); + if (!_anchorLines) _anchorLines = + new AnchorLines(const_cast<QmlGraphicsItem *>(q)); + return _anchorLines; + } + + enum ChangeType { + Geometry = 0x01, + SiblingOrder = 0x02, + Visibility = 0x04, + Opacity = 0x08, + Destroyed = 0x10 + }; + + Q_DECLARE_FLAGS(ChangeTypes, ChangeType) + + struct ChangeListener { + ChangeListener(QmlGraphicsItemChangeListener *l, QmlGraphicsItemPrivate::ChangeTypes t) : listener(l), types(t) {} + QmlGraphicsItemChangeListener *listener; + QmlGraphicsItemPrivate::ChangeTypes types; + bool operator==(const ChangeListener &other) const { return listener == other.listener && types == other.types; } + }; + + void addItemChangeListener(QmlGraphicsItemChangeListener *listener, ChangeTypes types) { + changeListeners.append(ChangeListener(listener, types)); + } + void removeItemChangeListener(QmlGraphicsItemChangeListener *, ChangeTypes types); + QPODVector<ChangeListener,4> changeListeners; + + QmlStateGroup *states(); + QmlStateGroup *_stateGroup; + + QmlGraphicsItem::TransformOrigin origin:4; + bool widthValid:1; + bool heightValid:1; + bool _componentComplete:1; + bool _keepMouse:1; + bool smooth:1; + + QmlGraphicsItemKeyFilter *keyHandler; + + qreal width; + qreal height; + qreal implicitWidth; + qreal implicitHeight; + + QPointF computeTransformOrigin() const; + + virtual void setPosHelper(const QPointF &pos) + { + Q_Q(QmlGraphicsItem); + QRectF oldGeometry(this->pos.x(), this->pos.y(), width, height); + QGraphicsItemPrivate::setPosHelper(pos); + q->geometryChanged(QRectF(this->pos.x(), this->pos.y(), width, height), oldGeometry); + } + + // Reimplemented from QGraphicsItemPrivate + virtual void subFocusItemChange() + { + emit q_func()->wantsFocusChanged(); + } + + // Reimplemented from QGraphicsItemPrivate + virtual void siblingOrderChange() + { + Q_Q(QmlGraphicsItem); + for(int ii = 0; ii < changeListeners.count(); ++ii) { + const QmlGraphicsItemPrivate::ChangeListener &change = changeListeners.at(ii); + if (change.types & QmlGraphicsItemPrivate::SiblingOrder) { + change.listener->itemSiblingOrderChanged(q); + } + } + } + + static int consistentTime; + static QTime currentTime(); + static void Q_DECLARATIVE_EXPORT setConsistentTime(int t); + static void start(QTime &); + static int elapsed(QTime &); + static int restart(QTime &); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QmlGraphicsItemPrivate::ChangeTypes); + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSITEM_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsitemchangelistener_p.h b/src/declarative/graphicsitems/qmlgraphicsitemchangelistener_p.h new file mode 100644 index 0000000..f430df0 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsitemchangelistener_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSITEMCHANGELISTENER +#define QMLGRAPHICSITEMCHANGELISTENER + +// +// 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 QmlGraphicsItem; +class QmlGraphicsAnchorsPrivate; +class QmlGraphicsItemChangeListener +{ +public: + virtual void itemGeometryChanged(QmlGraphicsItem *, const QRectF &, const QRectF &) {} + virtual void itemSiblingOrderChanged(QmlGraphicsItem *) {} + virtual void itemVisibilityChanged(QmlGraphicsItem *) {} + virtual void itemOpacityChanged(QmlGraphicsItem *) {} + virtual void itemDestroyed(QmlGraphicsItem *) {} + virtual QmlGraphicsAnchorsPrivate *anchorPrivate() { return 0; } +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSITEMCHANGELISTENER diff --git a/src/declarative/graphicsitems/qmlgraphicslayoutitem.cpp b/src/declarative/graphicsitems/qmlgraphicslayoutitem.cpp new file mode 100644 index 0000000..7227eb0 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicslayoutitem.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 "qmlgraphicslayoutitem_p.h" + +#include <QDebug> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_TYPE(Qt,4,6,LayoutItem,QmlGraphicsLayoutItem) + +/*! + \qmlclass LayoutItem QmlGraphicsLayoutItem + \brief The LayoutItem element allows you to place your Fluid UI elements inside a classical Qt layout. +*/ + +/*! + \internal + \class QmlGraphicsLayoutItem + \brief The QmlGraphicsLayoutItem class allows you to place your Fluid UI elements inside a classical Qt layout. +*/ + + +/*! + \qmlproperty QSizeF LayoutItem::maximumSize + + The maximumSize property can be set to specify the maximum desired size of this LayoutItem +*/ + +/*! + \qmlproperty QSizeF LayoutItem::minimumSize + + The minimumSize property can be set to specify the minimum desired size of this LayoutItem +*/ + +/*! + \qmlproperty QSizeF LayoutItem::preferredSize + + The preferredSize property can be set to specify the preferred size of this LayoutItem +*/ + +QmlGraphicsLayoutItem::QmlGraphicsLayoutItem(QmlGraphicsItem* parent) + : QmlGraphicsItem(parent), m_maximumSize(INT_MAX,INT_MAX), m_minimumSize(0,0), m_preferredSize(0,0) +{ + setGraphicsItem(this); +} + +void QmlGraphicsLayoutItem::setGeometry(const QRectF & rect) +{ + setX(rect.x()); + setY(rect.y()); + setWidth(rect.width()); + setHeight(rect.height()); +} + +QSizeF QmlGraphicsLayoutItem::sizeHint(Qt::SizeHint w, const QSizeF &constraint) const +{ + Q_UNUSED(constraint); + if(w == Qt::MinimumSize){ + return m_minimumSize; + }else if(w == Qt::MaximumSize){ + return m_maximumSize; + }else{ + return m_preferredSize; + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicslayoutitem_p.h b/src/declarative/graphicsitems/qmlgraphicslayoutitem_p.h new file mode 100644 index 0000000..3278b63 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicslayoutitem_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSGRAPHICSLAYOUTITEM_H +#define QMLGRAPHICSGRAPHICSLAYOUTITEM_H +#include "qmlgraphicsitem.h" + +#include <QGraphicsLayoutItem> +#include <QSizeF> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsLayoutItem : public QmlGraphicsItem, public QGraphicsLayoutItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsLayoutItem) + Q_PROPERTY(QSizeF maximumSize READ maximumSize WRITE setMaximumSize NOTIFY maximumSizeChanged) + Q_PROPERTY(QSizeF minimumSize READ minimumSize WRITE setMinimumSize NOTIFY minimumSizeChanged) + Q_PROPERTY(QSizeF preferredSize READ preferredSize WRITE setPreferredSize NOTIFY preferredSizeChanged) +public: + QmlGraphicsLayoutItem(QmlGraphicsItem* parent=0); + + QSizeF maximumSize() const { return m_maximumSize; } + void setMaximumSize(const QSizeF &s) { if(s==m_maximumSize) return; m_maximumSize = s; emit maximumSizeChanged(); } + + QSizeF minimumSize() const { return m_minimumSize; } + void setMinimumSize(const QSizeF &s) { if(s==m_minimumSize) return; m_minimumSize = s; emit minimumSizeChanged(); } + + QSizeF preferredSize() const { return m_preferredSize; } + void setPreferredSize(const QSizeF &s) { if(s==m_preferredSize) return; m_preferredSize = s; emit preferredSizeChanged(); } + + virtual void setGeometry(const QRectF & rect); +protected: + virtual QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + +Q_SIGNALS: + void maximumSizeChanged(); + void minimumSizeChanged(); + void preferredSizeChanged(); + +private: + QSizeF m_maximumSize; + QSizeF m_minimumSize; + QSizeF m_preferredSize; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsLayoutItem) + +QT_END_HEADER +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicslistview.cpp b/src/declarative/graphicsitems/qmlgraphicslistview.cpp new file mode 100644 index 0000000..d0b3739 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicslistview.cpp @@ -0,0 +1,2748 @@ +/**************************************************************************** +** +** 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 "qmlgraphicslistview_p.h" + +#include "qmlgraphicsflickable_p_p.h" +#include "qmlgraphicsvisualitemmodel_p.h" + +#include <qmleasefollow_p.h> +#include <qmlexpression.h> +#include <qmlengine.h> + +#include <qlistmodelinterface_p.h> +#include <QKeyEvent> + +QT_BEGIN_NAMESPACE + +void QmlGraphicsViewSection::setProperty(const QString &property) +{ + if (property != m_property) { + m_property = property; + emit changed(); + } +} + +void QmlGraphicsViewSection::setCriteria(QmlGraphicsViewSection::SectionCriteria criteria) +{ + if (criteria != m_criteria) { + m_criteria = criteria; + emit changed(); + } +} + +void QmlGraphicsViewSection::setDelegate(QmlComponent *delegate) +{ + if (delegate != m_delegate) { + m_delegate = delegate; + emit delegateChanged(); + } +} + +QString QmlGraphicsViewSection::sectionString(const QString &value) +{ + if (m_criteria == FirstCharacter) + return value.isEmpty() ? QString() : value.at(0); + else + return value; +} + +class QmlGraphicsListViewAttached : public QObject +{ + Q_OBJECT +public: + QmlGraphicsListViewAttached(QObject *parent) + : QObject(parent), m_view(0), m_isCurrent(false), m_delayRemove(false) {} + ~QmlGraphicsListViewAttached() {} + + Q_PROPERTY(QmlGraphicsListView *view READ view CONSTANT) + QmlGraphicsListView *view() { return m_view; } + + 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 prevSection 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 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 delayRemoveChanged(); + void add(); + void remove(); + +public: + QmlGraphicsListView *m_view; + bool m_isCurrent; + mutable QString m_section; + QString m_prevSection; + bool m_delayRemove; +}; + +//---------------------------------------------------------------------------- + +class FxListItem +{ +public: + FxListItem(QmlGraphicsItem *i, QmlGraphicsListView *v) : item(i), section(0), view(v) { + attached = static_cast<QmlGraphicsListViewAttached*>(qmlAttachedPropertiesObject<QmlGraphicsListView>(item)); + if (attached) + attached->m_view = view; + } + ~FxListItem() {} + qreal position() const { + if (section) + return (view->orientation() == QmlGraphicsListView::Vertical ? section->y() : section->x()); + else + return (view->orientation() == QmlGraphicsListView::Vertical ? item->y() : item->x()); + } + int size() const { + if (section) + return (view->orientation() == QmlGraphicsListView::Vertical ? item->height()+section->height() : item->width()+section->height()); + else + return (view->orientation() == QmlGraphicsListView::Vertical ? item->height() : item->width()); + } + qreal endPosition() const { + return (view->orientation() == QmlGraphicsListView::Vertical + ? item->y() + (item->height() > 0 ? item->height() : 1) + : item->x() + (item->width() > 0 ? item->width() : 1)) - 1; + } + void setPosition(qreal pos) { + if (view->orientation() == QmlGraphicsListView::Vertical) { + if (section) { + section->setY(pos); + pos += section->height(); + } + item->setY(pos); + } else { + if (section) { + section->setX(pos); + pos += section->width(); + } + item->setX(pos); + } + } + + QmlGraphicsItem *item; + QmlGraphicsItem *section; + QmlGraphicsListView *view; + QmlGraphicsListViewAttached *attached; + int index; +}; + +//---------------------------------------------------------------------------- + +class QmlGraphicsListViewPrivate : public QmlGraphicsFlickablePrivate, private QmlGraphicsItemChangeListener +{ + Q_DECLARE_PUBLIC(QmlGraphicsListView) + +public: + QmlGraphicsListViewPrivate() + : currentItem(0), orient(QmlGraphicsListView::Vertical) + , visiblePos(0), visibleIndex(0) + , averageSize(100.0), currentIndex(-1), requestedIndex(-1) + , 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), highlightResizeSpeed(400), highlightRange(QmlGraphicsListView::NoHighlightRange) + , snapMode(QmlGraphicsListView::NoSnap), overshootDist(0.0) + , footerComponent(0), footer(0), headerComponent(0), header(0) + , bufferMode(NoBuffer) + , ownModel(false), wrap(false), autoHighlight(true), haveHighlightRange(false) + , correctFlick(true), inFlickCorrection(false), lazyRelease(false) + , deferredRelease(false), minExtentDirty(true), maxExtentDirty(true) + {} + + void init(); + void clear(); + FxListItem *createItem(int modelIndex); + void releaseItem(FxListItem *item); + + FxListItem *visibleItem(int modelIndex) const { + if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.count()) { + for (int i = modelIndex - visibleIndex; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems.at(i); + if (item->index == modelIndex) + return item; + } + } + return 0; + } + + FxListItem *firstVisibleItem() const { + const qreal pos = position(); + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems.at(i); + if (item->index != -1 && item->endPosition() > pos) + return item; + } + return visibleItems.count() ? visibleItems.first() : 0; + } + + FxListItem *nextVisibleItem() const { + const qreal pos = position(); + bool foundFirst = false; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems.at(i); + if (item->index != -1) { + if (foundFirst) + return item; + else if (item->position() < pos && item->endPosition() > pos) + foundFirst = true; + } + } + return 0; + } + + qreal position() const { + Q_Q(const QmlGraphicsListView); + return orient == QmlGraphicsListView::Vertical ? q->viewportY() : q->viewportX(); + } + void setPosition(qreal pos) { + Q_Q(QmlGraphicsListView); + if (orient == QmlGraphicsListView::Vertical) + q->setViewportY(pos); + else + q->setViewportX(pos); + } + qreal size() const { + Q_Q(const QmlGraphicsListView); + return orient == QmlGraphicsListView::Vertical ? q->height() : q->width(); + } + + qreal startPosition() const { + qreal pos = 0; + if (!visibleItems.isEmpty()) { + pos = (*visibleItems.constBegin())->position(); + if (visibleIndex > 0) + pos -= visibleIndex * (averageSize + spacing) - spacing; + } + return pos; + } + + qreal endPosition() 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); + } + return pos; + } + + qreal positionAt(int modelIndex) const { + if (FxListItem *item = visibleItem(modelIndex)) + return item->position(); + if (!visibleItems.isEmpty()) { + if (modelIndex < visibleIndex) { + int count = visibleIndex - modelIndex; + return (*visibleItems.constBegin())->position() - count * (averageSize + spacing); + } 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; + } + + QString sectionAt(int modelIndex) { + if (FxListItem *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(); + } + + int snapIndex() { + int index = currentIndex; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if (itemTop >= highlight->position()-item->size()/2 && itemTop < highlight->position()+item->size()/2) + return item->index; + } + return index; + } + + qreal snapPosAt(qreal pos) { + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if (item->index == model->count()-1 || (itemTop+item->size()/2 >= pos)) + return item->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 - startPosition()) / averageSize) * averageSize + startPosition(); + } + + FxListItem *snapItemAt(qreal pos) { + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems[i]; + if (item->index == -1) + continue; + qreal itemTop = item->position(); + if (item->index == model->count()-1 || (itemTop+item->size()/2 >= pos)) + return item; + } + if (visibleItems.count() && visibleItems.first()->position() <= pos) + return visibleItems.first(); + return 0; + } + + int lastVisibleIndex() const { + int lastIndex = -1; + for (int i = visibleItems.count()-1; i >= 0; --i) { + FxListItem *listItem = visibleItems.at(i); + if (listItem->index != -1) { + lastIndex = listItem->index; + break; + } + } + return lastIndex; + } + + // map a model index to visibleItems 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) { + FxListItem *listItem = visibleItems.at(i); + if (listItem->index == modelIndex) + return i + visibleIndex; + if (listItem->index > modelIndex) + return -1; + } + return -1; // Not in visibleList + } + + bool mapRangeFromModel(int &index, int &count) const { + if (index + count < visibleIndex) + return false; + + int lastIndex = -1; + for (int i = visibleItems.count()-1; i >= 0; --i) { + FxListItem *listItem = visibleItems.at(i); + if (listItem->index != -1) { + lastIndex = listItem->index; + break; + } + } + + if (index > lastIndex) + return false; + + int last = qMin(index + count - 1, lastIndex); + index = qMax(index, visibleIndex); + count = last - index + 1; + + return true; + } + + void updateViewport() { + Q_Q(QmlGraphicsListView); + if (orient == QmlGraphicsListView::Vertical) { + q->setViewportHeight(endPosition() - startPosition() + 1); + } else { + q->setViewportWidth(endPosition() - startPosition() + 1); + } + } + + void itemGeometryChanged(QmlGraphicsItem *, const QRectF &newGeometry, const QRectF &oldGeometry) { + if ((orient == QmlGraphicsListView::Vertical && newGeometry.height() != oldGeometry.height()) + || newGeometry.width() != oldGeometry.width()) { + layout(); + fixupPosition(); + } + } + + // for debugging only + void checkVisible() const { + int skip = 0; + for (int i = 0; i < visibleItems.count(); ++i) { + FxListItem *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 layout(); + void updateUnrequestedIndexes(); + void updateUnrequestedPositions(); + void updateTrackedItem(); + void createHighlight(); + void updateHighlight(); + void createSection(FxListItem *); + void updateSections(); + void updateCurrentSection(); + void updateCurrent(int); + void updateAverage(); + void updateHeader(); + void updateFooter(); + void fixupPosition(); + virtual void fixupY(); + virtual void fixupX(); + virtual void flickX(qreal velocity); + virtual void flickY(qreal velocity); + + QGuard<QmlGraphicsVisualModel> model; + QVariant modelVariant; + QList<FxListItem*> visibleItems; + QHash<QmlGraphicsItem*,int> unrequestedItems; + FxListItem *currentItem; + QmlGraphicsListView::Orientation orient; + int visiblePos; + int visibleIndex; + qreal averageSize; + int currentIndex; + int requestedIndex; + qreal highlightRangeStart; + qreal highlightRangeEnd; + QmlComponent *highlightComponent; + FxListItem *highlight; + FxListItem *trackedItem; + enum MovementReason { Other, SetIndex, Mouse }; + MovementReason moveReason; + int buffer; + QmlEaseFollow *highlightPosAnimator; + QmlEaseFollow *highlightSizeAnimator; + QmlGraphicsViewSection *sectionCriteria; + QString currentSection; + static const int sectionCacheSize = 3; + QmlGraphicsItem *sectionCache[sectionCacheSize]; + qreal spacing; + qreal highlightMoveSpeed; + qreal highlightResizeSpeed; + QmlGraphicsListView::HighlightRangeMode highlightRange; + QmlGraphicsListView::SnapMode snapMode; + qreal overshootDist; + QmlComponent *footerComponent; + FxListItem *footer; + QmlComponent *headerComponent; + FxListItem *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; + mutable bool minExtentDirty : 1; + mutable bool maxExtentDirty : 1; +}; + +void QmlGraphicsListViewPrivate::init() +{ + Q_Q(QmlGraphicsListView); + q->setFlag(QGraphicsItem::ItemIsFocusScope); + addItemChangeListener(this, Geometry); + QObject::connect(q, SIGNAL(movementEnded()), q, SLOT(animStopped())); + q->setFlickDirection(QmlGraphicsFlickable::VerticalFlick); + ::memset(sectionCache, 0, sizeof(QmlGraphicsItem*) * sectionCacheSize); +} + +void QmlGraphicsListViewPrivate::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; +} + +FxListItem *QmlGraphicsListViewPrivate::createItem(int modelIndex) +{ + Q_Q(QmlGraphicsListView); + // create object + requestedIndex = modelIndex; + FxListItem *listItem = 0; + if (QmlGraphicsItem *item = model->item(modelIndex, false)) { + listItem = new FxListItem(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 (FxListItem *item = visibleItem(modelIndex-1)) + listItem->attached->m_prevSection = item->attached->section(); + else + listItem->attached->m_prevSection = sectionAt(modelIndex-1); + } + } + // complete + model->completeItem(); + listItem->item->setZValue(1); + listItem->item->setParent(q->viewport()); + QmlGraphicsItemPrivate *itemPrivate = static_cast<QmlGraphicsItemPrivate*>(QGraphicsItemPrivate::get(item)); + itemPrivate->addItemChangeListener(this, QmlGraphicsItemPrivate::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 QmlGraphicsListViewPrivate::releaseItem(FxListItem *item) +{ + Q_Q(QmlGraphicsListView); + if (!item || !model) + return; + if (trackedItem == item) { + const char *notifier1 = orient == QmlGraphicsListView::Vertical ? SIGNAL(yChanged()) : SIGNAL(xChanged()); + const char *notifier2 = orient == QmlGraphicsListView::Vertical ? SIGNAL(heightChanged()) : SIGNAL(widthChanged()); + QObject::disconnect(trackedItem->item, notifier1, q, SLOT(trackedPositionChanged())); + QObject::disconnect(trackedItem->item, notifier2, q, SLOT(trackedPositionChanged())); + trackedItem = 0; + } + QmlGraphicsItemPrivate *itemPrivate = static_cast<QmlGraphicsItemPrivate*>(QGraphicsItemPrivate::get(item->item)); + itemPrivate->removeItemChangeListener(this, QmlGraphicsItemPrivate::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 QmlGraphicsListViewPrivate::refill(qreal from, qreal to, bool doBuffer) +{ + Q_Q(QmlGraphicsListView); + if (!isValid() || !q->isComponentComplete()) + return; + 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; + modelIndex = visibleItems.at(i)->index + 1; + } + + bool changed = false; + FxListItem *item = 0; + int 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 > 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; + 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 (sectionCriteria) + updateCurrentSection(); + if (header) + updateHeader(); + if (footer) + updateFooter(); + updateViewport(); + updateUnrequestedPositions(); + } else if (!doBuffer && buffer && bufferMode != NoBuffer) { + refill(from, to, true); + } + lazyRelease = false; +} + +void QmlGraphicsListViewPrivate::layout() +{ + Q_Q(QmlGraphicsListView); + updateSections(); + if (!visibleItems.isEmpty()) { + int oldEnd = visibleItems.last()->endPosition(); + int pos = visibleItems.first()->endPosition() + spacing + 1; + for (int i=1; i < visibleItems.count(); ++i) { + FxListItem *item = visibleItems.at(i); + item->setPosition(pos); + pos += item->size() + spacing; + } + // move current item if it is after the visible items. + if (currentItem && currentIndex > lastVisibleIndex()) + currentItem->setPosition(currentItem->position() + (visibleItems.last()->endPosition() - oldEnd)); + } + if (!isValid()) + return; + q->refill(); + minExtentDirty = true; + maxExtentDirty = true; + updateHighlight(); + fixupPosition(); + q->refill(); + if (header) + updateHeader(); + if (footer) + updateFooter(); + updateViewport(); +} + +void QmlGraphicsListViewPrivate::updateUnrequestedIndexes() +{ + Q_Q(QmlGraphicsListView); + QHash<QmlGraphicsItem*,int>::iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) + *it = model->indexOf(it.key(), q); +} + +void QmlGraphicsListViewPrivate::updateUnrequestedPositions() +{ + Q_Q(QmlGraphicsListView); + if (unrequestedItems.count()) { + qreal pos = position(); + QHash<QmlGraphicsItem*,int>::const_iterator it; + for (it = unrequestedItems.begin(); it != unrequestedItems.end(); ++it) { + QmlGraphicsItem *item = it.key(); + if (orient == QmlGraphicsListView::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()) + item->setX(positionAt(*it)); + } + } + } +} + +void QmlGraphicsListViewPrivate::updateTrackedItem() +{ + Q_Q(QmlGraphicsListView); + FxListItem *item = currentItem; + if (highlight) + item = highlight; + + FxListItem *oldTracked = trackedItem; + + const char *notifier1 = orient == QmlGraphicsListView::Vertical ? SIGNAL(yChanged()) : SIGNAL(xChanged()); + const char *notifier2 = orient == QmlGraphicsListView::Vertical ? SIGNAL(heightChanged()) : SIGNAL(widthChanged()); + + if (trackedItem && item != trackedItem) { + QObject::disconnect(trackedItem->item, notifier1, q, SLOT(trackedPositionChanged())); + QObject::disconnect(trackedItem->item, notifier2, q, SLOT(trackedPositionChanged())); + trackedItem = 0; + } + + if (!trackedItem && item) { + trackedItem = item; + QObject::connect(trackedItem->item, notifier1, q, SLOT(trackedPositionChanged())); + QObject::connect(trackedItem->item, notifier2, q, SLOT(trackedPositionChanged())); + } + if (trackedItem && trackedItem != oldTracked) + q->trackedPositionChanged(); +} + +void QmlGraphicsListViewPrivate::createHighlight() +{ + Q_Q(QmlGraphicsListView); + 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) { + QmlGraphicsItem *item = 0; + if (highlightComponent) { + QmlContext *highlightContext = new QmlContext(qmlContext(q)); + QObject *nobj = highlightComponent->create(highlightContext); + if (nobj) { + highlightContext->setParent(nobj); + item = qobject_cast<QmlGraphicsItem *>(nobj); + if (!item) + delete nobj; + } else { + delete highlightContext; + } + } else { + item = new QmlGraphicsItem; + } + if (item) { + item->setParent(q->viewport()); + highlight = new FxListItem(item, q); + if (orient == QmlGraphicsListView::Vertical) + highlight->item->setHeight(currentItem->item->height()); + else + highlight->item->setWidth(currentItem->item->width()); + const QLatin1String posProp(orient == QmlGraphicsListView::Vertical ? "y" : "x"); + highlightPosAnimator = new QmlEaseFollow(q); + highlightPosAnimator->setTarget(QmlMetaProperty(highlight->item, posProp)); + highlightPosAnimator->setVelocity(highlightMoveSpeed); + highlightPosAnimator->setEnabled(autoHighlight); + const QLatin1String sizeProp(orient == QmlGraphicsListView::Vertical ? "height" : "width"); + highlightSizeAnimator = new QmlEaseFollow(q); + highlightSizeAnimator->setVelocity(highlightResizeSpeed); + highlightSizeAnimator->setTarget(QmlMetaProperty(highlight->item, sizeProp)); + highlightSizeAnimator->setEnabled(autoHighlight); + changed = true; + } + } + if (changed) + emit q->highlightChanged(); +} + +void QmlGraphicsListViewPrivate::updateHighlight() +{ + if ((!currentItem && highlight) || (currentItem && !highlight)) + createHighlight(); + if (currentItem && autoHighlight && highlight && !moving) { + // auto-update highlight + highlightPosAnimator->setSourceValue(currentItem->position()); + highlightSizeAnimator->setSourceValue(currentItem->size()); + if (orient == QmlGraphicsListView::Vertical) { + if (highlight->item->width() == 0) + highlight->item->setWidth(currentItem->item->width()); + } else { + if (highlight->item->height() == 0) + highlight->item->setHeight(currentItem->item->height()); + } + } + updateTrackedItem(); +} + +void QmlGraphicsListViewPrivate::createSection(FxListItem *listItem) +{ + Q_Q(QmlGraphicsListView); + if (!sectionCriteria || !sectionCriteria->delegate()) + return; + if (listItem->attached->m_prevSection != listItem->attached->m_section) { + if (!listItem->section) { + int i = sectionCacheSize-1; + while (i >= 0 && !sectionCache[i]) + --i; + if (i >= 0) { + listItem->section = sectionCache[i]; + sectionCache[i] = 0; + listItem->section->setVisible(true); + QmlContext *context = QmlEngine::contextForObject(listItem->section)->parentContext(); + context->setContextProperty(QLatin1String("section"), listItem->attached->m_section); + } else { + QmlContext *context = new QmlContext(qmlContext(q)); + context->setContextProperty(QLatin1String("section"), listItem->attached->m_section); + QObject *nobj = sectionCriteria->delegate()->create(context); + if (nobj) { + context->setParent(nobj); + listItem->section = qobject_cast<QmlGraphicsItem *>(nobj); + if (!listItem->section) { + delete nobj; + } else { + listItem->section->setZValue(1); + listItem->section->setParent(q->viewport()); + } + } else { + delete context; + } + } + } + } else if (listItem->section) { + 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; + } +} + +void QmlGraphicsListViewPrivate::updateSections() +{ + if (sectionCriteria) { + QString prevSection; + if (visibleIndex > 0) + prevSection = sectionAt(visibleIndex-1); + for (int i = 0; i < visibleItems.count(); ++i) { + if (visibleItems.at(i)->index != -1) { + QmlGraphicsListViewAttached *attached = visibleItems.at(i)->attached; + attached->setPrevSection(prevSection); + createSection(visibleItems.at(i)); + prevSection = attached->section(); + } + } + } +} + +void QmlGraphicsListViewPrivate::updateCurrentSection() +{ + if (!sectionCriteria || visibleItems.isEmpty()) { + currentSection = QString(); + return; + } + int index = 0; + while (visibleItems.at(index)->endPosition() < position() && index < visibleItems.count()) + ++index; + + if (index < visibleItems.count()) + currentSection = visibleItems.at(index)->attached->section(); + else + currentSection = visibleItems.first()->attached->section(); +} + +void QmlGraphicsListViewPrivate::updateCurrent(int modelIndex) +{ + Q_Q(QmlGraphicsListView); + if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) { + if (currentItem) { + currentItem->attached->setIsCurrentItem(false); + releaseItem(currentItem); + currentItem = 0; + currentIndex = -1; + updateHighlight(); + emit q->currentIndexChanged(); + } + return; + } + + if (currentItem && currentIndex == modelIndex) { + updateHighlight(); + return; + } + FxListItem *oldCurrentItem = currentItem; + currentIndex = modelIndex; + currentItem = createItem(modelIndex); + if (oldCurrentItem && (!currentItem || oldCurrentItem->item != currentItem->item)) + oldCurrentItem->attached->setIsCurrentItem(false); + if (currentItem) { + if (modelIndex == visibleIndex - 1) { + // 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); + } + updateHighlight(); + emit q->currentIndexChanged(); + // Release the old current item + releaseItem(oldCurrentItem); +} + +void QmlGraphicsListViewPrivate::updateAverage() +{ + if (!visibleItems.count()) + return; + qreal sum = 0.0; + for (int i = 0; i < visibleItems.count(); ++i) + sum += visibleItems.at(i)->size(); + averageSize = sum / visibleItems.count(); +} + +void QmlGraphicsListViewPrivate::updateFooter() +{ + Q_Q(QmlGraphicsListView); + if (!footer && footerComponent) { + QmlGraphicsItem *item = 0; + QmlContext *context = new QmlContext(qmlContext(q)); + QObject *nobj = footerComponent->create(context); + if (nobj) { + context->setParent(nobj); + item = qobject_cast<QmlGraphicsItem *>(nobj); + if (!item) + delete nobj; + } else { + delete context; + } + if (item) { + item->setParent(q->viewport()); + item->setZValue(1); + footer = new FxListItem(item, q); + } + } + if (footer) { + if (visibleItems.count()) { + qreal endPos = endPosition(); + 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 QmlGraphicsListViewPrivate::updateHeader() +{ + Q_Q(QmlGraphicsListView); + if (!header && headerComponent) { + QmlGraphicsItem *item = 0; + QmlContext *context = new QmlContext(qmlContext(q)); + QObject *nobj = headerComponent->create(context); + if (nobj) { + context->setParent(nobj); + item = qobject_cast<QmlGraphicsItem *>(nobj); + if (!item) + delete nobj; + } else { + delete context; + } + if (item) { + item->setParent(q->viewport()); + item->setZValue(1); + header = new FxListItem(item, q); + if (visibleItems.isEmpty()) + visiblePos = header->size(); + } + } + if (header) { + if (visibleItems.count()) { + qreal startPos = startPosition(); + if (visibleIndex == 0) { + header->setPosition(startPos - header->size()); + } else { + if (position() <= startPos || header->position() > startPos - header->size()) + header->setPosition(startPos - header->size()); + } + } else { + header->setPosition(0); + } + } +} + +void QmlGraphicsListViewPrivate::fixupPosition() +{ + moveReason = Other; + if (orient == QmlGraphicsListView::Vertical) + fixupY(); + else + fixupX(); +} + +void QmlGraphicsListViewPrivate::fixupY() +{ + Q_Q(QmlGraphicsListView); + if (orient == QmlGraphicsListView::Horizontal) + return; + if (!q->yflick() || _moveY.timeLine()) + return; + + int oldDuration = fixupDuration; + fixupDuration = moveReason == Mouse ? fixupDuration : 0; + + if (haveHighlightRange && highlightRange == QmlGraphicsListView::StrictlyEnforceRange) { + if (currentItem && highlight && currentItem->position() != highlight->position()) { + timeline.reset(_moveY); + timeline.move(_moveY, -(currentItem->position() - highlightRangeStart), QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + vTime = timeline.time(); + } + } else if (snapMode != QmlGraphicsListView::NoSnap) { + if (FxListItem *item = snapItemAt(position())) { + qreal pos = qMin(item->position() - highlightRangeStart, -q->maxYExtent()); + qreal dist = qAbs(_moveY + pos); + if (dist > 0) { + timeline.reset(_moveY); + timeline.move(_moveY, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + vTime = timeline.time(); + } + } + } else { + QmlGraphicsFlickablePrivate::fixupY(); + } + fixupDuration = oldDuration; +} + +void QmlGraphicsListViewPrivate::fixupX() +{ + Q_Q(QmlGraphicsListView); + if (orient == QmlGraphicsListView::Vertical) + return; + if (!q->xflick() || _moveX.timeLine()) + return; + + int oldDuration = fixupDuration; + fixupDuration = moveReason == Mouse ? fixupDuration : 0; + + if (haveHighlightRange && highlightRange == QmlGraphicsListView::StrictlyEnforceRange) { + if (currentItem && highlight && currentItem->position() != highlight->position()) { + timeline.reset(_moveX); + timeline.move(_moveX, -(currentItem->position() - highlightRangeStart), QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + vTime = timeline.time(); + } + } else if (snapMode != QmlGraphicsListView::NoSnap) { + if (FxListItem *item = snapItemAt(position())) { + qreal pos = qMin(item->position() - highlightRangeStart, -q->maxXExtent()); + qreal dist = qAbs(_moveX + pos); + if (dist > 0) { + timeline.reset(_moveX); + timeline.move(_moveX, -pos, QEasingCurve(QEasingCurve::InOutQuad), fixupDuration); + vTime = timeline.time(); + } + } + } else { + QmlGraphicsFlickablePrivate::fixupX(); + } + fixupDuration = oldDuration; +} + +void QmlGraphicsListViewPrivate::flickX(qreal velocity) +{ + Q_Q(QmlGraphicsListView); + + moveReason = Mouse; + if ((!haveHighlightRange || highlightRange != QmlGraphicsListView::StrictlyEnforceRange) && snapMode == QmlGraphicsListView::NoSnap) { + QmlGraphicsFlickablePrivate::flickX(velocity); + return; + } + qreal maxDistance = -1; + const qreal maxX = q->maxXExtent(); + const qreal minX = q->minXExtent(); + // -ve velocity means list is moving up + if (velocity > 0) { + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = firstVisibleItem()) + maxDistance = qAbs(item->position() + _moveX.value()); + } else if (_moveX.value() < minX) { + maxDistance = qAbs(minX -_moveX.value() + (overShoot?30:0)); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetX = minX; + } else { + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = nextVisibleItem()) + maxDistance = qAbs(item->position() + _moveX.value()); + } else if (_moveX.value() > maxX) { + maxDistance = qAbs(maxX - _moveX.value()) + (overShoot?30:0); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetX = maxX; + } + if (maxDistance > 0 && (snapMode != QmlGraphicsListView::NoSnap || highlightRange == QmlGraphicsListView::StrictlyEnforceRange)) { + // 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 (!flicked) { + // the initial flick - estimate boundary + qreal accel = deceleration; + qreal v2 = v * v; + qreal maxAccel = v2 / (2.0f * maxDistance); + if (maxAccel < accel) { + qreal dist = v2 / (accel * 2.0); + if (v > 0) + dist = -dist; + flickTargetX = -snapPosAt(-(_moveX.value() - highlightRangeStart) + dist) + highlightRangeStart; + dist = -flickTargetX + _moveX.value(); + accel = v2 / (2.0f * qAbs(dist)); + overshootDist = 0.0; + } else { + flickTargetX = velocity > 0 ? minX : maxX; + overshootDist = overShoot ? 30 : 0; + } + timeline.reset(_moveX); + timeline.accel(_moveX, v, accel, maxDistance + overshootDist); + timeline.execute(fixupXEvent); + flicked = true; + emit q->flickingChanged(); + emit q->flickStarted(); + correctFlick = true; + } else { + // reevaluate the target boundary. + qreal newtarget = flickTargetX; + if (snapMode != QmlGraphicsListView::NoSnap || highlightRange == QmlGraphicsListView::StrictlyEnforceRange) + newtarget = -snapPosAt(-(flickTargetX - highlightRangeStart)) + highlightRangeStart; + if (velocity < 0 && newtarget < maxX) + newtarget = maxX; + else if (velocity > 0 && newtarget > minX) + newtarget = minX; + if (newtarget == flickTargetX) // boundary unchanged - nothing to do + return; + flickTargetX = newtarget; + qreal dist = -newtarget + _moveX.value(); + if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) { + correctFlick = false; + timeline.reset(_moveX); + fixupX(); + return; + } + timeline.reset(_moveX); + timeline.accelDistance(_moveX, v, -dist + (v < 0 ? -overshootDist : overshootDist)); + timeline.execute(fixupXEvent); + } + } else { + correctFlick = false; + timeline.reset(_moveX); + fixupX(); + } +} + +void QmlGraphicsListViewPrivate::flickY(qreal velocity) +{ + Q_Q(QmlGraphicsListView); + + moveReason = Mouse; + if ((!haveHighlightRange || highlightRange != QmlGraphicsListView::StrictlyEnforceRange) && snapMode == QmlGraphicsListView::NoSnap) { + QmlGraphicsFlickablePrivate::flickY(velocity); + return; + } + qreal maxDistance = -1; + const qreal maxY = q->maxYExtent(); + const qreal minY = q->minYExtent(); + // -ve velocity means list is moving up + if (velocity > 0) { + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = firstVisibleItem()) + maxDistance = qAbs(item->position() + _moveY.value()); + } else if (_moveY.value() < minY) { + maxDistance = qAbs(minY -_moveY.value() + (overShoot?30:0)); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetY = minY; + } else { + if (snapMode == QmlGraphicsListView::SnapOneItem) { + if (FxListItem *item = nextVisibleItem()) + maxDistance = qAbs(item->position() + _moveY.value()); + } else if (_moveY.value() > maxY) { + maxDistance = qAbs(maxY - _moveY.value()) + (overShoot?30:0); + } + if (snapMode != QmlGraphicsListView::SnapToItem && highlightRange != QmlGraphicsListView::StrictlyEnforceRange) + flickTargetY = maxY; + } + if (maxDistance > 0 && (snapMode != QmlGraphicsListView::NoSnap || highlightRange == QmlGraphicsListView::StrictlyEnforceRange)) { + // 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 (!flicked) { + // the initial flick - estimate boundary + qreal accel = deceleration; + qreal v2 = v * v; + qreal maxAccel = v2 / (2.0f * maxDistance); + if (maxAccel < accel) { + qreal dist = v2 / (accel * 2.0); + if (v > 0) + dist = -dist; + flickTargetY = -snapPosAt(-(_moveY.value() - highlightRangeStart) + dist) + highlightRangeStart; + dist = -flickTargetY + _moveY.value(); + accel = v2 / (2.0f * qAbs(dist)); + overshootDist = 0.0; + } else { + flickTargetY = velocity > 0 ? minY : maxY; + overshootDist = overShoot ? 30 : 0; + } + timeline.reset(_moveY); + timeline.accel(_moveY, v, accel, maxDistance + overshootDist); + timeline.execute(fixupYEvent); + flicked = true; + emit q->flickingChanged(); + emit q->flickStarted(); + correctFlick = true; + } else { + // reevaluate the target boundary. + qreal newtarget = flickTargetY; + if (snapMode != QmlGraphicsListView::NoSnap || highlightRange == QmlGraphicsListView::StrictlyEnforceRange) + newtarget = -snapPosAt(-(flickTargetY - highlightRangeStart)) + highlightRangeStart; + if (velocity < 0 && newtarget < maxY) + newtarget = maxY; + else if (velocity > 0 && newtarget > minY) + newtarget = minY; + if (newtarget == flickTargetY) // boundary unchanged - nothing to do + return; + flickTargetY = newtarget; + qreal dist = -newtarget + _moveY.value(); + if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) { + correctFlick = false; + timeline.reset(_moveY); + fixupY(); + return; + } + timeline.reset(_moveY); + timeline.accelDistance(_moveY, v, -dist + (v < 0 ? -overshootDist : overshootDist)); + timeline.execute(fixupYEvent); + } + } else { + correctFlick = false; + timeline.reset(_moveY); + fixupY(); + } +} + +//---------------------------------------------------------------------------- + +/*! + \qmlclass ListView QmlGraphicsListView + \inherits Flickable + \brief The ListView item provides a list view of items provided by a model. + + The model is typically provided by a QAbstractListModel "C++ model object", + but can also be created directly in QML. The items are laid out vertically + or horizontally and may be flicked to scroll. + + The below example creates a very simple vertical list, using a QML model. + \image trivialListView.png + + The user interface defines a delegate to display an item, a highlight, + and the ListView which uses the above. + + \snippet doc/src/snippets/declarative/listview/listview.qml 3 + + The model is defined as a ListModel using QML: + \quotefile doc/src/snippets/declarative/listview/dummydata/ContactModel.qml + + In this case ListModel is a handy way for us to test our UI. In practice + the model would be implemented in C++, or perhaps via a SQL data source. +*/ + +QmlGraphicsListView::QmlGraphicsListView(QmlGraphicsItem *parent) + : QmlGraphicsFlickable(*(new QmlGraphicsListViewPrivate), parent) +{ + Q_D(QmlGraphicsListView); + d->init(); +} + +QmlGraphicsListView::~QmlGraphicsListView() +{ + Q_D(QmlGraphicsListView); + d->clear(); + if (d->ownModel) + delete d->model; + delete d->header; + delete d->footer; +} + +/*! + \qmlattachedproperty bool ListView::isCurrentItem + This attached property is true if this delegate is the current item; otherwise false. + + It is attached to each instance of the delegate. + + This property may be used to adjust the appearance of the current item, for example: + + \snippet doc/src/snippets/declarative/listview/highlight.qml 0 +*/ + +/*! + \qmlattachedproperty ListView ListView::view + This attached property holds the view that manages this delegate instance. + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlattachedproperty string ListView::prevSection + This attached property holds the section of the previous element. + + It is attached to each instance of the delegate. + + The section is evaluated using the \l {ListView::section.property}{section} properties. +*/ + +/*! + \qmlattachedproperty string ListView::section + This attached property holds the section of this element. + + It is attached to each instance of the delegate. + + The section is evaluated using the \l {ListView::section.property}{section} properties. +*/ + +/*! + \qmlattachedproperty bool ListView::delayRemove + This attached property holds whether the delegate may be destroyed. + + It is attached to each instance of the delegate. + + It is sometimes necessary to delay the destruction of an item + until an animation completes. + + The example below ensures that the animation completes before + the item is removed from the list. + + \code + Component { + id: myDelegate + Item { + id: wrapper + ListView.onRemove: SequentialAnimation { + PropertyAction { target: wrapper.ListView; property: "delayRemove"; value: true } + NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 250; easing: "easeInOutQuad" } + PropertyAction { target: wrapper.ListView; property: "delayRemove"; value: false } + } + } + } + \endcode +*/ + +/*! + \qmlattachedsignal ListView::onAdd() + This attached handler is called immediately after an item is added to the view. +*/ + +/*! + \qmlattachedsignal ListView::onRemove() + This attached handler is called immediately before an item is removed from the view. +*/ + +/*! + \qmlproperty model ListView::model + This property holds the model providing data for the list. + + The model provides a set of data that is used to create the items + for the view. For large or dynamic datasets the model is usually + provided by a C++ model object. The C++ model object must be a \l + {QAbstractItemModel} subclass or a simple list. + + Models can also be created directly in QML, using a \l{ListModel}, + \l{XmlListModel} or \l{VisualItemModel}. + + \sa {qmlmodels}{Data Models} +*/ +QVariant QmlGraphicsListView::model() const +{ + Q_D(const QmlGraphicsListView); + return d->modelVariant; +} + +void QmlGraphicsListView::setModel(const QVariant &model) +{ + Q_D(QmlGraphicsListView); + 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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + disconnect(d->model, SIGNAL(destroyingItem(QmlGraphicsItem*)), this, SLOT(destroyingItem(QmlGraphicsItem*))); + } + d->clear(); + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QmlGraphicsVisualModel *vim = 0; + if (object && (vim = qobject_cast<QmlGraphicsVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) + dataModel->setModel(model); + } + if (d->model) { + if (isComponentComplete()) { + refill(); + if (d->currentIndex >= d->model->count() || d->currentIndex < 0) { + setCurrentIndex(0); + } else { + d->moveReason = QmlGraphicsListViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + } + } + 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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + connect(d->model, SIGNAL(destroyingItem(QmlGraphicsItem*)), this, SLOT(destroyingItem(QmlGraphicsItem*))); + emit countChanged(); + } +} + +/*! + \qmlproperty component ListView::delegate + + The delegate provides a template defining each item instantiated by the view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + + Note that the ListView will layout the items based on the size of the root item + in the delegate. + + Here is an example delegate: + \snippet doc/src/snippets/declarative/listview/listview.qml 0 +*/ +QmlComponent *QmlGraphicsListView::delegate() const +{ + Q_D(const QmlGraphicsListView); + if (d->model) { + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QmlGraphicsListView::setDelegate(QmlComponent *delegate) +{ + Q_D(QmlGraphicsListView); + if (delegate == this->delegate()) + return; + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(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(); + refill(); + d->moveReason = QmlGraphicsListViewPrivate::SetIndex; + d->updateCurrent(d->currentIndex); + } + } +} + +/*! + \qmlproperty int ListView::currentIndex + \qmlproperty Item ListView::currentItem + + \c currentIndex holds the index of the current item. + \c currentItem is the current item. Note that the position of the current item + may only be approximate until it becomes visible in the view. +*/ +int QmlGraphicsListView::currentIndex() const +{ + Q_D(const QmlGraphicsListView); + return d->currentIndex; +} + +void QmlGraphicsListView::setCurrentIndex(int index) +{ + Q_D(QmlGraphicsListView); + if (isComponentComplete() && d->isValid() && index != d->currentIndex && index < d->model->count() && index >= 0) { + d->moveReason = QmlGraphicsListViewPrivate::SetIndex; + cancelFlick(); + d->updateCurrent(index); + } else { + d->currentIndex = index; + } +} + +QmlGraphicsItem *QmlGraphicsListView::currentItem() +{ + Q_D(QmlGraphicsListView); + if (!d->currentItem) + return 0; + return d->currentItem->item; +} + +/*! + \qmlproperty Item ListView::highlightItem + + \c highlightItem holds the highlight item, which was created + from the \l highlight component. + + The highlightItem is managed by the view unless + \l highlightFollowsCurrentItem is set to false. + + \sa highlight, highlightFollowsCurrentItem +*/ +QmlGraphicsItem *QmlGraphicsListView::highlightItem() +{ + Q_D(QmlGraphicsListView); + if (!d->highlight) + return 0; + return d->highlight->item; +} + +/*! + \qmlproperty int ListView::count + This property holds the number of items in the view. +*/ +int QmlGraphicsListView::count() const +{ + Q_D(const QmlGraphicsListView); + if (d->model) + return d->model->count(); + return 0; +} + +/*! + \qmlproperty component ListView::highlight + This property holds the component to use as the highlight. + + An instance of the highlight component will be created for each list. + The geometry of the resultant component instance will be managed by the list + so as to stay with the current item, unless the highlightFollowsCurrentItem + property is false. + + The below example demonstrates how to make a simple highlight + for a vertical list. + + \snippet doc/src/snippets/declarative/listview/listview.qml 1 + \image trivialListView.png + + \sa highlightItem, highlightFollowsCurrentItem +*/ +QmlComponent *QmlGraphicsListView::highlight() const +{ + Q_D(const QmlGraphicsListView); + return d->highlightComponent; +} + +void QmlGraphicsListView::setHighlight(QmlComponent *highlight) +{ + Q_D(QmlGraphicsListView); + if (highlight != d->highlightComponent) { + d->highlightComponent = highlight; + d->createHighlight(); + if (d->currentItem) + d->updateHighlight(); + } +} + +/*! + \qmlproperty bool ListView::highlightFollowsCurrentItem + This property holds whether the highlight is managed by the view. + + If highlightFollowsCurrentItem is true, the highlight will be moved smoothly + to follow the current item. If highlightFollowsCurrentItem is false, the + highlight will not be moved by the view, and must be implemented + by the highlight. The following example creates a highlight with + its motion defined by the spring \l {SpringFollow}: + + \snippet doc/src/snippets/declarative/listview/highlight.qml 1 + + Note that the highlight animation also affects the way that the view + is scrolled. This is because the view moves to maintain the + highlight within the preferred highlight range (or visible viewport). + + \sa highlight, highlightMoveSpeed +*/ +bool QmlGraphicsListView::highlightFollowsCurrentItem() const +{ + Q_D(const QmlGraphicsListView); + return d->autoHighlight; +} + +void QmlGraphicsListView::setHighlightFollowsCurrentItem(bool autoHighlight) +{ + Q_D(QmlGraphicsListView); + if (d->autoHighlight != autoHighlight) { + d->autoHighlight = autoHighlight; + if (d->highlightPosAnimator) { + d->highlightPosAnimator->setEnabled(d->autoHighlight); + d->highlightSizeAnimator->setEnabled(d->autoHighlight); + } + d->updateHighlight(); + } +} + +//###Possibly rename these properties, since they are very useful even without a highlight? +/*! + \qmlproperty real ListView::preferredHighlightBegin + \qmlproperty real ListView::preferredHighlightEnd + \qmlproperty enumeration ListView::highlightRangeMode + + These properties set the preferred range of the highlight (current item) + within the view. + + Note that this is the correct way to influence where the + current item ends up when the list scrolls. For example, if you want the + currently selected item to be in the middle of the list, then set the + highlight range to be where the middle item would go. Then, when the list scrolls, + the currently selected item will be the item at that spot. This also applies to + when the currently selected item changes - it will scroll to within the preferred + highlight range. Furthermore, the behaviour of the current item index will occur + whether or not a highlight exists. + + If highlightRangeMode is set to \e ApplyRange the view will + attempt to maintain the highlight within the range, however + the highlight can move outside of the range at the ends of the list + or due to a mouse interaction. + + If highlightRangeMode is set to \e StrictlyEnforceRange the highlight will never + move outside of the range. This means that the current item will change + if a keyboard or mouse action would cause the highlight to move + outside of the range. + + The default value is \e NoHighlightRange. + + Note that a valid range requires preferredHighlightEnd to be greater + than or equal to preferredHighlightBegin. +*/ +qreal QmlGraphicsListView::preferredHighlightBegin() const +{ + Q_D(const QmlGraphicsListView); + return d->highlightRangeStart; +} + +void QmlGraphicsListView::setPreferredHighlightBegin(qreal start) +{ + Q_D(QmlGraphicsListView); + d->highlightRangeStart = start; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; +} + +qreal QmlGraphicsListView::preferredHighlightEnd() const +{ + Q_D(const QmlGraphicsListView); + return d->highlightRangeEnd; +} + +void QmlGraphicsListView::setPreferredHighlightEnd(qreal end) +{ + Q_D(QmlGraphicsListView); + d->highlightRangeEnd = end; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; +} + +QmlGraphicsListView::HighlightRangeMode QmlGraphicsListView::highlightRangeMode() const +{ + Q_D(const QmlGraphicsListView); + return d->highlightRange; +} + +void QmlGraphicsListView::setHighlightRangeMode(HighlightRangeMode mode) +{ + Q_D(QmlGraphicsListView); + d->highlightRange = mode; + d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd; +} + +/*! + \qmlproperty real ListView::spacing + + This property holds the spacing to leave between items. +*/ +qreal QmlGraphicsListView::spacing() const +{ + Q_D(const QmlGraphicsListView); + return d->spacing; +} + +void QmlGraphicsListView::setSpacing(qreal spacing) +{ + Q_D(QmlGraphicsListView); + if (spacing != d->spacing) { + d->spacing = spacing; + d->layout(); + emit spacingChanged(); + } +} + +/*! + \qmlproperty enumeration ListView::orientation + This property holds the orientation of the list. + + Possible values are \c Vertical (default) and \c Horizontal. + + Vertical Example: + \image trivialListView.png + Horizontal Example: + \image ListViewHorizontal.png +*/ +QmlGraphicsListView::Orientation QmlGraphicsListView::orientation() const +{ + Q_D(const QmlGraphicsListView); + return d->orient; +} + +void QmlGraphicsListView::setOrientation(QmlGraphicsListView::Orientation orientation) +{ + Q_D(QmlGraphicsListView); + if (d->orient != orientation) { + d->orient = orientation; + if (d->orient == QmlGraphicsListView::Vertical) { + setViewportWidth(-1); + setFlickDirection(VerticalFlick); + } else { + setViewportHeight(-1); + setFlickDirection(HorizontalFlick); + } + d->clear(); + refill(); + emit orientationChanged(); + d->updateCurrent(d->currentIndex); + } +} + +/*! + \qmlproperty bool ListView::keyNavigationWraps + This property holds whether the list wraps key navigation + + If this property is true then key presses to move off of one end of the list will cause the + current item to jump to the other end. +*/ +bool QmlGraphicsListView::isWrapEnabled() const +{ + Q_D(const QmlGraphicsListView); + return d->wrap; +} + +void QmlGraphicsListView::setWrapEnabled(bool wrap) +{ + Q_D(QmlGraphicsListView); + d->wrap = wrap; +} + +/*! + \qmlproperty int ListView::cacheBuffer + This property holds the number of off-screen pixels to cache. + + This property determines the number of pixels above the top of the list + and below the bottom of the list to cache. Setting this value can make + scrolling the list smoother at the expense of additional memory usage. +*/ +int QmlGraphicsListView::cacheBuffer() const +{ + Q_D(const QmlGraphicsListView); + return d->buffer; +} + +void QmlGraphicsListView::setCacheBuffer(int b) +{ + Q_D(QmlGraphicsListView); + if (d->buffer != b) { + d->buffer = b; + if (isComponentComplete()) { + d->bufferMode = QmlGraphicsListViewPrivate::BufferBefore | QmlGraphicsListViewPrivate::BufferAfter; + refill(); + } + } +} + +/*! + \qmlproperty string ListView::section.property + \qmlproperty enumeration ListView::section.criteria + These properties hold the expression to be evaluated for the section attached property. + + section.property hold the name of the property to use to determine + the section the item is in. + + section.criteria holds the criteria to use to get the section. It + can be either: + \list + \o ViewSection.FullString (default) - section is the value of the property. + \o ViewSection.FirstCharacter - section is the first character of the property value. + \endlist + + Each item in the list has attached properties named \c ListView.section and + \c ListView.prevSection. These may be used to place a section header for + related items. The example below assumes that the model is sorted by size of + pet. The section expression is the size property. If \c ListView.section and + \c ListView.prevSection differ, the item will display a section header. + + \snippet examples/declarative/listview/sections.qml 0 + + \image ListViewSections.png +*/ +QmlGraphicsViewSection *QmlGraphicsListView::sectionCriteria() +{ + Q_D(QmlGraphicsListView); + if (!d->sectionCriteria) + d->sectionCriteria = new QmlGraphicsViewSection(this); + return d->sectionCriteria; +} + +/*! + \qmlproperty string ListView::currentSection + This property holds the section that is currently at the beginning of the view. +*/ +QString QmlGraphicsListView::currentSection() const +{ + Q_D(const QmlGraphicsListView); + return d->currentSection; +} + +/*! + \qmlproperty real ListView::highlightMoveSpeed + \qmlproperty real ListView::highlightResizeSpeed + These properties hold the move and resize animation speed of the highlight delegate. + + highlightFollowsCurrentItem must be true for these properties + to have effect. + + The default value for these properties is 400 pixels/second. + + \sa highlightFollowsCurrentItem +*/ +qreal QmlGraphicsListView::highlightMoveSpeed() const +{ + Q_D(const QmlGraphicsListView);\ + return d->highlightMoveSpeed; +} + +void QmlGraphicsListView::setHighlightMoveSpeed(qreal speed) +{ + Q_D(QmlGraphicsListView);\ + if (d->highlightMoveSpeed != speed) { + d->highlightMoveSpeed = speed; + if (d->highlightPosAnimator) + d->highlightPosAnimator->setVelocity(d->highlightMoveSpeed); + emit highlightMoveSpeedChanged(); + } +} + +qreal QmlGraphicsListView::highlightResizeSpeed() const +{ + Q_D(const QmlGraphicsListView);\ + return d->highlightResizeSpeed; +} + +void QmlGraphicsListView::setHighlightResizeSpeed(qreal speed) +{ + Q_D(QmlGraphicsListView);\ + if (d->highlightResizeSpeed != speed) { + d->highlightResizeSpeed = speed; + if (d->highlightSizeAnimator) + d->highlightSizeAnimator->setVelocity(d->highlightResizeSpeed); + emit highlightResizeSpeedChanged(); + } +} + +/*! + \qmlproperty enumeration ListView::snapMode + + This property determines where the view will settle following a drag or flick. + The allowed values are: + + \list + \o NoSnap (default) - the view will stop anywhere within the visible area. + \o SnapToItem - the view will settle with an item aligned with the start of + the view. + \o SnapOneItem - the view will settle no more than one item away from the first + visible item at the time the mouse button is released. This mode is particularly + useful for moving one page at a time. + \endlist +*/ +QmlGraphicsListView::SnapMode QmlGraphicsListView::snapMode() const +{ + Q_D(const QmlGraphicsListView); + return d->snapMode; +} + +void QmlGraphicsListView::setSnapMode(SnapMode mode) +{ + Q_D(QmlGraphicsListView); + if (d->snapMode != mode) { + d->snapMode = mode; + } +} + +QmlComponent *QmlGraphicsListView::footer() const +{ + Q_D(const QmlGraphicsListView); + return d->footerComponent; +} + +void QmlGraphicsListView::setFooter(QmlComponent *footer) +{ + Q_D(QmlGraphicsListView); + if (d->footerComponent != footer) { + if (d->footer) { + delete d->footer; + d->footer = 0; + } + d->footerComponent = footer; + d->minExtentDirty = true; + d->maxExtentDirty = true; + d->updateFooter(); + d->updateViewport(); + } +} + +QmlComponent *QmlGraphicsListView::header() const +{ + Q_D(const QmlGraphicsListView); + return d->headerComponent; +} + +void QmlGraphicsListView::setHeader(QmlComponent *header) +{ + Q_D(QmlGraphicsListView); + if (d->headerComponent != header) { + if (d->header) { + delete d->header; + d->header = 0; + } + d->headerComponent = header; + d->minExtentDirty = true; + d->maxExtentDirty = true; + d->updateHeader(); + d->updateFooter(); + d->updateViewport(); + } +} + +void QmlGraphicsListView::viewportMoved() +{ + Q_D(QmlGraphicsListView); + QmlGraphicsFlickable::viewportMoved(); + d->lazyRelease = true; + refill(); + if (isFlicking() || d->moving) + d->moveReason = QmlGraphicsListViewPrivate::Mouse; + if (d->moveReason != QmlGraphicsListViewPrivate::SetIndex) { + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) { + // reposition highlight + qreal pos = d->highlight->position(); + if (pos > d->position() + d->highlightRangeEnd - 1 - d->highlight->size()) + pos = d->position() + d->highlightRangeEnd - 1 - d->highlight->size(); + if (pos < d->position() + d->highlightRangeStart) + pos = d->position() + d->highlightRangeStart; + d->highlight->setPosition(pos); + + // update current index + int idx = d->snapIndex(); + if (idx >= 0 && idx != d->currentIndex) + d->updateCurrent(idx); + } + } + + if (d->flicked && 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()) { + if (d->velocityY > 0) { + const qreal minY = minYExtent(); + if ((minY - d->_moveY.value() < height()/2 || d->flickTargetY - d->_moveY.value() < height()/2) + && minY != d->flickTargetY) + d->flickY(-d->verticalVelocity.value()); + d->bufferMode = QmlGraphicsListViewPrivate::BufferBefore; + } else if (d->velocityY < 0) { + const qreal maxY = maxYExtent(); + if ((d->_moveY.value() - maxY < height()/2 || d->_moveY.value() - d->flickTargetY < height()/2) + && maxY != d->flickTargetY) + d->flickY(-d->verticalVelocity.value()); + d->bufferMode = QmlGraphicsListViewPrivate::BufferAfter; + } + } + + if (xflick()) { + if (d->velocityX > 0) { + const qreal minX = minXExtent(); + if ((minX - d->_moveX.value() < height()/2 || d->flickTargetX - d->_moveX.value() < height()/2) + && minX != d->flickTargetX) + d->flickX(-d->horizontalVelocity.value()); + d->bufferMode = QmlGraphicsListViewPrivate::BufferBefore; + } else if (d->velocityX < 0) { + const qreal maxX = maxXExtent(); + if ((d->_moveX.value() - maxX < height()/2 || d->_moveX.value() - d->flickTargetX < height()/2) + && maxX != d->flickTargetX) + d->flickX(-d->horizontalVelocity.value()); + d->bufferMode = QmlGraphicsListViewPrivate::BufferAfter; + } + } + d->inFlickCorrection = false; + } +} + +qreal QmlGraphicsListView::minYExtent() const +{ + Q_D(const QmlGraphicsListView); + if (d->orient == QmlGraphicsListView::Horizontal) + return QmlGraphicsFlickable::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; + d->minExtentDirty = false; + } + + return d->minExtent; +} + +qreal QmlGraphicsListView::maxYExtent() const +{ + Q_D(const QmlGraphicsListView); + if (d->orient == QmlGraphicsListView::Horizontal) + return height(); + if (d->maxExtentDirty) { + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) + d->maxExtent = -(d->positionAt(count()-1) - d->highlightRangeEnd); + 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 QmlGraphicsListView::minXExtent() const +{ + Q_D(const QmlGraphicsListView); + if (d->orient == QmlGraphicsListView::Vertical) + return QmlGraphicsFlickable::minXExtent(); + if (d->minExtentDirty) { + d->minExtent = -d->startPosition(); + if (d->header) + d->minExtent += d->header->size(); + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) + d->minExtent += d->highlightRangeStart; + d->minExtentDirty = false; + } + + return d->minExtent; +} + +qreal QmlGraphicsListView::maxXExtent() const +{ + Q_D(const QmlGraphicsListView); + if (d->orient == QmlGraphicsListView::Vertical) + return width(); + if (d->maxExtentDirty) { + if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange) + d->maxExtent = -(d->positionAt(count()-1) - d->highlightRangeEnd); + else + d->maxExtent = -(d->endPosition() - width() + 1); + 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 QmlGraphicsListView::keyPressEvent(QKeyEvent *event) +{ + Q_D(QmlGraphicsListView); + QmlGraphicsFlickable::keyPressEvent(event); + if (event->isAccepted()) + return; + + if (d->model && d->model->count() && d->interactive) { + if ((d->orient == QmlGraphicsListView::Horizontal && event->key() == Qt::Key_Left) + || (d->orient == QmlGraphicsListView::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 == QmlGraphicsListView::Horizontal && event->key() == Qt::Key_Right) + || (d->orient == QmlGraphicsListView::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(); +} + +/*! + \qmlmethod ListView::incrementCurrentIndex() + + Increments the current index. The current index will wrap + if keyNavigationWraps is true and it is currently at the end. +*/ +void QmlGraphicsListView::incrementCurrentIndex() +{ + Q_D(QmlGraphicsListView); + if (currentIndex() < d->model->count() - 1 || d->wrap) { + d->moveReason = QmlGraphicsListViewPrivate::SetIndex; + int index = currentIndex()+1; + cancelFlick(); + d->updateCurrent(index < d->model->count() ? index : 0); + } +} + +/*! + \qmlmethod ListView::decrementCurrentIndex() + + Decrements the current index. The current index will wrap + if keyNavigationWraps is true and it is currently at the beginning. +*/ +void QmlGraphicsListView::decrementCurrentIndex() +{ + Q_D(QmlGraphicsListView); + if (currentIndex() > 0 || d->wrap) { + d->moveReason = QmlGraphicsListViewPrivate::SetIndex; + int index = currentIndex()-1; + cancelFlick(); + d->updateCurrent(index >= 0 ? index : d->model->count()-1); + } +} + +/*! + \qmlmethod ListView::positionViewAtIndex(int index) + + Positions the view such that the \a index is at the top (or left for horizontal orientation) of the view. + If positioning the view at the index would cause empty space to be displayed at + the end of the view, the view will be positioned at the end. +*/ +void QmlGraphicsListView::positionViewAtIndex(int index) +{ + Q_D(QmlGraphicsListView); + if (!d->isValid() || index < 0 || index >= d->model->count()) + return; + + qreal maxExtent = d->orient == QmlGraphicsListView::Vertical ? -maxYExtent() : -maxXExtent(); + FxListItem *item = d->visibleItem(index); + if (item) { + // Already created - just move to top of view + int pos = qMin(item->position(), maxExtent); + d->setPosition(pos); + } else { + int pos = d->positionAt(index); + // save the currently visible items in case any of them end up visible again + QList<FxListItem*> oldVisible = d->visibleItems; + d->visibleItems.clear(); + d->visiblePos = pos; + d->visibleIndex = index; + d->setPosition(pos); + // setPosition() will cause refill. Adjust if we have moved beyond range. + if (d->position() > maxExtent) + d->setPosition(maxExtent); + // now release the reference to all the old visible items. + for (int i = 0; i < oldVisible.count(); ++i) + d->releaseItem(oldVisible.at(i)); + } +} + + +void QmlGraphicsListView::componentComplete() +{ + Q_D(QmlGraphicsListView); + QmlGraphicsFlickable::componentComplete(); + refill(); + d->moveReason = QmlGraphicsListViewPrivate::SetIndex; + if (d->currentIndex < 0) + d->updateCurrent(0); + else + d->updateCurrent(d->currentIndex); + d->fixupPosition(); +} + +void QmlGraphicsListView::refill() +{ + Q_D(QmlGraphicsListView); + d->refill(d->position(), d->position()+d->size()-1); +} + +void QmlGraphicsListView::trackedPositionChanged() +{ + Q_D(QmlGraphicsListView); + if (!d->trackedItem || !d->currentItem) + return; + if (!isFlicking() && !d->moving && d->moveReason == QmlGraphicsListViewPrivate::SetIndex) { + const qreal trackedPos = d->trackedItem->position(); + const qreal viewPos = d->position(); + if (d->haveHighlightRange) { + if (d->highlightRange == StrictlyEnforceRange) { + qreal pos = viewPos; + if (trackedPos > pos + d->highlightRangeEnd - d->trackedItem->size()) + pos = trackedPos - d->highlightRangeEnd + d->trackedItem->size(); + if (trackedPos < pos + d->highlightRangeStart) + pos = trackedPos - d->highlightRangeStart; + d->setPosition(pos); + } else { + qreal pos = viewPos; + if (trackedPos < d->startPosition() + d->highlightRangeStart) { + pos = d->startPosition(); + } else if (d->trackedItem->endPosition() > d->endPosition() - d->size() + d->highlightRangeEnd) { + pos = d->endPosition() - d->size(); + if (pos < d->startPosition()) + pos = d->startPosition(); + } else { + if (trackedPos < viewPos + d->highlightRangeStart) { + pos = trackedPos - d->highlightRangeStart; + } else if (trackedPos > viewPos + d->highlightRangeEnd - d->trackedItem->size()) { + pos = trackedPos - d->highlightRangeEnd + d->trackedItem->size(); + } + } + d->setPosition(pos); + } + } else { + if (trackedPos < viewPos && d->currentItem->position() < viewPos) { + d->setPosition(d->currentItem->position() < trackedPos ? trackedPos : d->currentItem->position()); + } else if (d->trackedItem->endPosition() > viewPos + d->size() + && d->currentItem->endPosition() > viewPos + d->size()) { + qreal pos; + if (d->trackedItem->endPosition() < d->currentItem->endPosition()) { + pos = d->trackedItem->endPosition() - d->size(); + if (d->trackedItem->size() > d->size()) + pos = trackedPos; + } else { + pos = d->currentItem->endPosition() - d->size(); + if (d->currentItem->size() > d->size()) + pos = d->currentItem->position(); + } + d->setPosition(pos); + } + } + } +} + +void QmlGraphicsListView::itemsInserted(int modelIndex, int count) +{ + Q_D(QmlGraphicsListView); + d->updateUnrequestedIndexes(); + d->moveReason = QmlGraphicsListViewPrivate::Other; + if (!d->visibleItems.count() || d->model->count() <= 1) { + d->layout(); + d->updateCurrent(qMax(0, qMin(d->currentIndex, d->model->count()-1))); + emit countChanged(); + return; + } + + int overlapCount = count; + if (!d->mapRangeFromModel(modelIndex, overlapCount)) { + 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. + modelIndex = 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) { + FxListItem *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; + } + d->layout(); + emit countChanged(); + return; + } + } + + // At least some of the added items will be visible + + int index = modelIndex - d->visibleIndex; + // index can be the next item past the end of the visible items list (i.e. appended) + int pos = index < d->visibleItems.count() ? d->visibleItems.at(index)->position() + : d->visibleItems.at(index-1)->endPosition()+d->spacing+1; + int initialPos = pos; + int diff = 0; + QList<FxListItem*> added; + bool addedVisible = false; + FxListItem *firstVisible = d->firstVisibleItem(); + if (firstVisible && pos < firstVisible->position()) { + // Insert items before the visible item. + int insertionIdx = index; + int i = 0; + int from = d->position() - d->buffer; + for (i = count-1; i >= 0 && pos > from; --i) { + addedVisible = true; + FxListItem *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--) { + FxListItem *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--) { + FxListItem *listItem = d->visibleItems.at(i); + listItem->setPosition(listItem->position() - (initialPos - pos)); + } + } + } else { + int i = 0; + int to = d->buffer+d->position()+d->size()-1; + for (i = 0; i < count && pos <= to; ++i) { + addedVisible = true; + FxListItem *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->currentIndex >= modelIndex) { + // adjust current item index + d->currentIndex += count; + if (d->currentItem) { + d->currentItem->index = d->currentIndex; + d->currentItem->setPosition(d->currentItem->position() + diff); + } + } + // Update the indexes of the following visible items. + for (; index < d->visibleItems.count(); ++index) { + FxListItem *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(); + + if (addedVisible) + d->layout(); + emit countChanged(); +} + +void QmlGraphicsListView::itemsRemoved(int modelIndex, int count) +{ + Q_D(QmlGraphicsListView); + d->moveReason = QmlGraphicsListViewPrivate::Other; + d->updateUnrequestedIndexes(); + + FxListItem *firstVisible = d->firstVisibleItem(); + int preRemovedSize = 0; + bool removedVisible = false; + // Remove the items from the visible list, skipping anything already marked for removal + QList<FxListItem*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxListItem *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 + 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; + } else if (d->currentIndex >= modelIndex && d->currentIndex < modelIndex + count) { + // current item has been removed. + d->currentItem->attached->setIsCurrentItem(false); + d->releaseItem(d->currentItem); + d->currentItem = 0; + d->currentIndex = -1; + d->updateCurrent(qMin(modelIndex, d->model->count()-1)); + } + + // update visibleIndex + for (it = d->visibleItems.begin(); it != d->visibleItems.end(); ++it) { + if ((*it)->index != -1) { + d->visibleIndex = (*it)->index; + break; + } + } + + if (removedVisible) { + if (d->visibleItems.isEmpty()) { + d->visibleIndex = 0; + d->visiblePos = d->header ? d->header->size() : 0; + d->timeline.clear(); + d->setPosition(0); + if (d->model->count() == 0) + update(); + else + refill(); + } else { + // Correct the positioning of the items + d->layout(); + } + } + + emit countChanged(); +} + +void QmlGraphicsListView::destroyRemoved() +{ + Q_D(QmlGraphicsListView); + for (QList<FxListItem*>::Iterator it = d->visibleItems.begin(); + it != d->visibleItems.end();) { + FxListItem *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 QmlGraphicsListView::itemsMoved(int from, int to, int count) +{ + Q_D(QmlGraphicsListView); + d->updateUnrequestedIndexes(); + + if (d->visibleItems.isEmpty()) { + refill(); + return; + } + + d->moveReason = QmlGraphicsListViewPrivate::Other; + FxListItem *firstVisible = d->firstVisibleItem(); + qreal firstItemPos = firstVisible->position(); + QHash<int,FxListItem*> moved; + int moveBy = 0; + + QList<FxListItem*>::Iterator it = d->visibleItems.begin(); + while (it != d->visibleItems.end()) { + FxListItem *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()) { + FxListItem *item = *it; + if (remaining && item->index >= to && item->index < to + count) { + // place items in the target position, reusing any existing items + FxListItem *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 (FxListItem *item = moved.take(endIndex+1)) { + d->visibleItems.append(item); + ++endIndex; + } + + // Whatever moved items remain are no longer visible items. + while (moved.count()) + d->releaseItem(moved.take(moved.begin().key())); + + // Ensure we don't cause an ugly list scroll. + d->visibleItems.first()->setPosition(d->visibleItems.first()->position() + moveBy); + + d->layout(); +} + +void QmlGraphicsListView::createdItem(int index, QmlGraphicsItem *item) +{ + Q_D(QmlGraphicsListView); + if (d->requestedIndex != index) { + item->setParentItem(viewport()); + d->unrequestedItems.insert(item, index); + if (d->orient == QmlGraphicsListView::Vertical) + item->setY(d->positionAt(index)); + else + item->setX(d->positionAt(index)); + } +} + +void QmlGraphicsListView::destroyingItem(QmlGraphicsItem *item) +{ + Q_D(QmlGraphicsListView); + d->unrequestedItems.remove(item); +} + +void QmlGraphicsListView::animStopped() +{ + Q_D(QmlGraphicsListView); + d->moveReason = QmlGraphicsListViewPrivate::Other; + d->bufferMode = QmlGraphicsListViewPrivate::NoBuffer; +} + +QmlGraphicsListViewAttached *QmlGraphicsListView::qmlAttachedProperties(QObject *obj) +{ + return new QmlGraphicsListViewAttached(obj); +} + +QML_DEFINE_TYPE(Qt,4,6,ListView,QmlGraphicsListView) +QML_DEFINE_TYPE(Qt,4,6,ViewSection,QmlGraphicsViewSection) + +QT_END_NAMESPACE + +#include <qmlgraphicslistview.moc> diff --git a/src/declarative/graphicsitems/qmlgraphicslistview_p.h b/src/declarative/graphicsitems/qmlgraphicslistview_p.h new file mode 100644 index 0000000..79d678a --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicslistview_p.h @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSLISTVIEW_H +#define QMLGRAPHICSLISTVIEW_H + +#include "qmlgraphicsflickable_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QmlGraphicsViewSection : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY changed) + Q_PROPERTY(SectionCriteria criteria READ criteria WRITE setCriteria NOTIFY changed) + Q_PROPERTY(QmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_ENUMS(SectionCriteria) +public: + QmlGraphicsViewSection(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); + + QmlComponent *delegate() const { return m_delegate; } + void setDelegate(QmlComponent *delegate); + + QString sectionString(const QString &value); + +Q_SIGNALS: + void changed(); + void delegateChanged(); + +private: + QString m_property; + SectionCriteria m_criteria; + QmlComponent *m_delegate; +}; + + +class QmlGraphicsVisualModel; +class QmlGraphicsListViewAttached; +class QmlGraphicsListViewPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsListView : public QmlGraphicsFlickable +{ + Q_OBJECT + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsListView) + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(QmlGraphicsItem *currentItem READ currentItem NOTIFY currentIndexChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + + Q_PROPERTY(QmlComponent *highlight READ highlight WRITE setHighlight) + Q_PROPERTY(QmlGraphicsItem *highlightItem READ highlightItem NOTIFY highlightChanged) + Q_PROPERTY(bool highlightFollowsCurrentItem READ highlightFollowsCurrentItem WRITE setHighlightFollowsCurrentItem) + Q_PROPERTY(qreal highlightMoveSpeed READ highlightMoveSpeed WRITE setHighlightMoveSpeed NOTIFY highlightMoveSpeedChanged) + Q_PROPERTY(qreal highlightResizeSpeed READ highlightResizeSpeed WRITE setHighlightResizeSpeed NOTIFY highlightResizeSpeedChanged) + + Q_PROPERTY(qreal preferredHighlightBegin READ preferredHighlightBegin WRITE setPreferredHighlightBegin) + Q_PROPERTY(qreal preferredHighlightEnd READ preferredHighlightEnd WRITE setPreferredHighlightEnd) + Q_PROPERTY(HighlightRangeMode highlightRangeMode READ highlightRangeMode WRITE setHighlightRangeMode) + + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY(Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) + Q_PROPERTY(bool keyNavigationWraps READ isWrapEnabled WRITE setWrapEnabled) + Q_PROPERTY(int cacheBuffer READ cacheBuffer WRITE setCacheBuffer) + Q_PROPERTY(QmlGraphicsViewSection *section READ sectionCriteria CONSTANT) + Q_PROPERTY(QString currentSection READ currentSection NOTIFY currentSectionChanged) + + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode) + + Q_PROPERTY(QmlComponent *header READ header WRITE setHeader) + Q_PROPERTY(QmlComponent *footer READ footer WRITE setFooter) + + Q_ENUMS(HighlightRangeMode) + Q_ENUMS(Orientation) + Q_ENUMS(SnapMode) + Q_CLASSINFO("DefaultProperty", "data") + +public: + QmlGraphicsListView(QmlGraphicsItem *parent=0); + ~QmlGraphicsListView(); + + QVariant model() const; + void setModel(const QVariant &); + + QmlComponent *delegate() const; + void setDelegate(QmlComponent *); + + int currentIndex() const; + void setCurrentIndex(int idx); + + QmlGraphicsItem *currentItem(); + QmlGraphicsItem *highlightItem(); + int count() const; + + QmlComponent *highlight() const; + void setHighlight(QmlComponent *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); + + qreal preferredHighlightEnd() const; + void setPreferredHighlightEnd(qreal); + + qreal spacing() const; + void setSpacing(qreal spacing); + + enum Orientation { Horizontal = Qt::Horizontal, Vertical = Qt::Vertical }; + Orientation orientation() const; + void setOrientation(Orientation); + + bool isWrapEnabled() const; + void setWrapEnabled(bool); + + int cacheBuffer() const; + void setCacheBuffer(int); + + QmlGraphicsViewSection *sectionCriteria(); + QString currentSection() const; + + qreal highlightMoveSpeed() const; + void setHighlightMoveSpeed(qreal); + + qreal highlightResizeSpeed() const; + void setHighlightResizeSpeed(qreal); + + enum SnapMode { NoSnap, SnapToItem, SnapOneItem }; + SnapMode snapMode() const; + void setSnapMode(SnapMode mode); + + QmlComponent *footer() const; + void setFooter(QmlComponent *); + + QmlComponent *header() const; + void setHeader(QmlComponent *); + + static QmlGraphicsListViewAttached *qmlAttachedProperties(QObject *); + +public Q_SLOTS: + void incrementCurrentIndex(); + void decrementCurrentIndex(); + void positionViewAtIndex(int index); + +Q_SIGNALS: + void countChanged(); + void spacingChanged(); + void orientationChanged(); + void currentIndexChanged(); + void currentSectionChanged(); + void highlightMoveSpeedChanged(); + void highlightResizeSpeedChanged(); + void highlightChanged(); + +protected: + 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 refill(); + void trackedPositionChanged(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void itemsMoved(int from, int to, int count); + void destroyRemoved(); + void createdItem(int index, QmlGraphicsItem *item); + void destroyingItem(QmlGraphicsItem *item); + void animStopped(); +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPEINFO(QmlGraphicsListView, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QmlGraphicsListView) +QML_DECLARE_TYPE(QmlGraphicsViewSection) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsloader.cpp b/src/declarative/graphicsitems/qmlgraphicsloader.cpp new file mode 100644 index 0000000..b3486ef --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsloader.cpp @@ -0,0 +1,488 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsloader_p_p.h" + +#include <qmlengine_p.h> + +QT_BEGIN_NAMESPACE + +QmlGraphicsLoaderPrivate::QmlGraphicsLoaderPrivate() + : item(0), component(0), ownComponent(false) + , resizeMode(QmlGraphicsLoader::SizeLoaderToItem) +{ +} + +QmlGraphicsLoaderPrivate::~QmlGraphicsLoaderPrivate() +{ +} + +void QmlGraphicsLoaderPrivate::itemGeometryChanged(QmlGraphicsItem *resizeItem, const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (resizeItem == item && resizeMode == QmlGraphicsLoader::SizeLoaderToItem) { + _q_updateSize(); + } + QmlGraphicsItemChangeListener::itemGeometryChanged(resizeItem, newGeometry, oldGeometry); +} + +void QmlGraphicsLoaderPrivate::clear() +{ + if (ownComponent) { + delete component; + component = 0; + ownComponent = false; + } + source = QUrl(); + + if (item) { + if (QmlGraphicsItem *qmlItem = qobject_cast<QmlGraphicsItem*>(item)) { + if (resizeMode == QmlGraphicsLoader::SizeLoaderToItem) { + QmlGraphicsItemPrivate *p = + static_cast<QmlGraphicsItemPrivate *>(QGraphicsItemPrivate::get(qmlItem)); + p->removeItemChangeListener(this, QmlGraphicsItemPrivate::Geometry); + } + } + + // We can't delete immediately because our item may have triggered + // the Loader to load a different item. + item->setVisible(false); + item->setParentItem(0); + item->deleteLater(); + item = 0; + } +} + +void QmlGraphicsLoaderPrivate::initResize() +{ + Q_Q(QmlGraphicsLoader); + if (QmlGraphicsItem *qmlItem = qobject_cast<QmlGraphicsItem*>(item)) { + if (resizeMode == QmlGraphicsLoader::SizeLoaderToItem) { + QmlGraphicsItemPrivate *p = + static_cast<QmlGraphicsItemPrivate *>(QGraphicsItemPrivate::get(qmlItem)); + p->addItemChangeListener(this, QmlGraphicsItemPrivate::Geometry); + } + } else if (item && item->isWidget()) { + QGraphicsWidget *widget = static_cast<QGraphicsWidget*>(item); + if (resizeMode == QmlGraphicsLoader::SizeLoaderToItem) { + widget->installEventFilter(q); + } + } + _q_updateSize(); +} + +QML_DEFINE_TYPE(Qt,4,6,Loader,QmlGraphicsLoader) + +/*! + \qmlclass Loader QmlGraphicsLoader + \inherits Item + + \brief The Loader item allows dynamically loading an Item-based + subtree from a QML URL or Component. + + Loader instantiates an item from a component. The component to + instantiate may be specified directly by the \c sourceComponent + property, or loaded from a URL via the \c source property. + + It is also an effective means of delaying the creation of a component + until it is required: + \code + Loader { id: pageLoader } + Rectangle { + MouseRegion { anchors.fill: parent; onClicked: pageLoader.source = "Page1.qml" } + } + \endcode + + If the Loader source is changed, any previous items instantiated + will be destroyed. Setting \c source to an empty string + will destroy the currently instantiated items, freeing resources + and leaving the Loader empty. For example: + + \code + pageLoader.source = "" + \endcode + + unloads "Page1.qml" and frees resources consumed by it. + + \sa {dynamic-object-creation}{Dynamic Object Creation} +*/ + +/*! + \internal + \class QmlGraphicsLoader + \qmlclass Loader + */ + +/*! + Create a new QmlGraphicsLoader instance. + */ +QmlGraphicsLoader::QmlGraphicsLoader(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsLoaderPrivate), parent) +{ +} + +/*! + Destroy the loader instance. + */ +QmlGraphicsLoader::~QmlGraphicsLoader() +{ +} + +/*! + \qmlproperty url Loader::source + This property holds the URL of the QML component to + instantiate. + + \sa sourceComponent, status, progress +*/ +QUrl QmlGraphicsLoader::source() const +{ + Q_D(const QmlGraphicsLoader); + return d->source; +} + +void QmlGraphicsLoader::setSource(const QUrl &url) +{ + Q_D(QmlGraphicsLoader); + 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 QmlComponent(qmlEngine(this), d->source, this); + d->ownComponent = true; + if (!d->component->isLoading()) { + d->_q_sourceLoaded(); + } else { + connect(d->component, SIGNAL(statusChanged(QmlComponent::Status)), + this, SLOT(_q_sourceLoaded())); + connect(d->component, SIGNAL(progressChanged(qreal)), + this, SIGNAL(progressChanged())); + emit statusChanged(); + emit progressChanged(); + emit sourceChanged(); + emit itemChanged(); + } +} + +/*! + \qmlproperty Component Loader::sourceComponent + The sourceComponent property holds the \l{Component} to instantiate. + + \qml + Item { + Component { + id: redSquare + Rectangle { color: "red"; width: 10; height: 10 } + } + + Loader { sourceComponent: redSquare } + Loader { sourceComponent: redSquare; x: 10 } + } + \endqml + + \sa source, progress +*/ + +QmlComponent *QmlGraphicsLoader::sourceComponent() const +{ + Q_D(const QmlGraphicsLoader); + return d->component; +} + +void QmlGraphicsLoader::setSourceComponent(QmlComponent *comp) +{ + Q_D(QmlGraphicsLoader); + 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 (!d->component->isLoading()) { + d->_q_sourceLoaded(); + } else { + connect(d->component, SIGNAL(statusChanged(QmlComponent::Status)), + this, SLOT(_q_sourceLoaded())); + connect(d->component, SIGNAL(progressChanged(qreal)), + this, SIGNAL(progressChanged())); + emit progressChanged(); + emit sourceChanged(); + emit statusChanged(); + emit itemChanged(); + } +} + +void QmlGraphicsLoaderPrivate::_q_sourceLoaded() +{ + Q_Q(QmlGraphicsLoader); + + if (component) { + QmlContext *ctxt = new QmlContext(qmlContext(q)); + ctxt->addDefaultObject(q); + + if (!component->errors().isEmpty()) { + qWarning() << component->errors(); + emit q->sourceChanged(); + emit q->statusChanged(); + emit q->progressChanged(); + return; + } + + QObject *obj = component->create(ctxt); + if (obj) { + ctxt->setParent(obj); + item = qobject_cast<QGraphicsObject *>(obj); + if (item) { + if (QmlGraphicsItem* qmlItem = qobject_cast<QmlGraphicsItem *>(item)) { + qmlItem->setParentItem(q); + } else { + item->setParentItem(q); + item->setParent(q); + } +// item->setFocus(true); + initResize(); + } + } else { + delete obj; + delete ctxt; + source = QUrl(); + } + emit q->sourceChanged(); + emit q->statusChanged(); + emit q->progressChanged(); + emit q->itemChanged(); + } +} + +/*! + \qmlproperty enum Loader::status + + This property holds the status of QML loading. It can be one of: + \list + \o Null - no QML source has been set + \o Ready - the QML source has been loaded + \o Loading - the QML source is currently being loaded + \o Error - an error occurred while loading the QML source + \endlist + + \sa progress +*/ + +QmlGraphicsLoader::Status QmlGraphicsLoader::status() const +{ + Q_D(const QmlGraphicsLoader); + + if (d->component) + return static_cast<QmlGraphicsLoader::Status>(d->component->status()); + + if (d->item) + return Ready; + + return d->source.isEmpty() ? Null : Error; +} + +/*! + \qmlproperty real Loader::progress + + This property holds the progress of QML data loading, from 0.0 (nothing loaded) + to 1.0 (finished). + + \sa status +*/ +qreal QmlGraphicsLoader::progress() const +{ + Q_D(const QmlGraphicsLoader); + + if (d->item) + return 1.0; + + if (d->component) + return d->component->progress(); + + return 0.0; +} + +/*! + \qmlproperty enum Loader::resizeMode + + This property determines how the Loader or item are resized: + \list + \o NoResize - no item will be resized + \o SizeLoaderToItem - the Loader will be sized to the size of the item, unless the size of the Loader has been otherwise specified. + \o SizeItemToLoader - the item will be sized to the size of the Loader. + \endlist + + Note that changing from SizeItemToLoader to SizeLoaderToItem + after the component is loaded will not return the item or Loader + to it's original size. This is due to the item size being adjusted + to the Loader size, thereby losing the original size of the item. + Future changes to the item's size will affect the loader, however. + + The default resizeMode is SizeLoaderToItem. +*/ +QmlGraphicsLoader::ResizeMode QmlGraphicsLoader::resizeMode() const +{ + Q_D(const QmlGraphicsLoader); + return d->resizeMode; +} + +void QmlGraphicsLoader::setResizeMode(ResizeMode mode) +{ + Q_D(QmlGraphicsLoader); + if (mode == d->resizeMode) + return; + + if (QmlGraphicsItem *qmlItem = qobject_cast<QmlGraphicsItem*>(d->item)) { + if (d->resizeMode == SizeLoaderToItem) { + QmlGraphicsItemPrivate *p = + static_cast<QmlGraphicsItemPrivate *>(QGraphicsItemPrivate::get(qmlItem)); + p->removeItemChangeListener(d, QmlGraphicsItemPrivate::Geometry); + } + } else if (d->item && d->item->isWidget()) { + if (d->resizeMode == SizeLoaderToItem) + d->item->removeEventFilter(this); + } + + d->resizeMode = mode; + d->initResize(); +} + +void QmlGraphicsLoaderPrivate::_q_updateSize() +{ + Q_Q(QmlGraphicsLoader); + if (!item) + return; + if (QmlGraphicsItem *qmlItem = qobject_cast<QmlGraphicsItem*>(item)) { + if (resizeMode == QmlGraphicsLoader::SizeLoaderToItem) { + q->setWidth(qmlItem->width()); + q->setHeight(qmlItem->height()); + } else if (resizeMode == QmlGraphicsLoader::SizeItemToLoader) { + qmlItem->setWidth(q->width()); + qmlItem->setHeight(q->height()); + } + } else if (item && item->isWidget()) { + QGraphicsWidget *widget = static_cast<QGraphicsWidget*>(item); + if (resizeMode == QmlGraphicsLoader::SizeLoaderToItem) { + QSizeF newSize = widget->size(); + if (newSize.isValid()) { + q->setWidth(newSize.width()); + q->setHeight(newSize.height()); + } + } else if (resizeMode == QmlGraphicsLoader::SizeItemToLoader) { + QSizeF oldSize = widget->size(); + QSizeF newSize = oldSize; + if (q->heightValid()) + newSize.setHeight(q->height()); + if (q->widthValid()) + newSize.setWidth(q->width()); + if (oldSize != newSize) + widget->resize(newSize); + } + } +} + +/*! + \qmlproperty Item Loader::item + This property holds the top-level item created from source. +*/ +QGraphicsObject *QmlGraphicsLoader::item() const +{ + Q_D(const QmlGraphicsLoader); + return d->item; +} + +void QmlGraphicsLoader::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QmlGraphicsLoader); + if (newGeometry != oldGeometry) { + if (d->resizeMode == SizeItemToLoader) { + d->_q_updateSize(); + } + } + QmlGraphicsItem::geometryChanged(newGeometry, oldGeometry); +} + +QVariant QmlGraphicsLoader::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_D(QmlGraphicsLoader); + if (change == ItemSceneHasChanged) { + if (d->item && d->item->isWidget()) { + if (d->resizeMode == SizeLoaderToItem) { + d->item->removeEventFilter(this); + d->item->installEventFilter(this); + } + } + } + return QmlGraphicsItem::itemChange(change, value); +} + +bool QmlGraphicsLoader::eventFilter(QObject *watched, QEvent *e) +{ + Q_D(QmlGraphicsLoader); + if (watched == d->item && e->type() == QEvent::GraphicsSceneResize) { + if (d->item && d->item->isWidget() && d->resizeMode == SizeLoaderToItem) { + d->_q_updateSize(); + } + } + return QmlGraphicsItem::eventFilter(watched, e); +} + +#include <moc_qmlgraphicsloader_p.cpp> + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsloader_p.h b/src/declarative/graphicsitems/qmlgraphicsloader_p.h new file mode 100644 index 0000000..87b6a52 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsloader_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSLOADER_H +#define QMLGRAPHICSLOADER_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsLoaderPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsLoader : public QmlGraphicsItem +{ + Q_OBJECT + Q_ENUMS(Status) + Q_ENUMS(ResizeMode) + + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(QmlComponent *sourceComponent READ sourceComponent WRITE setSourceComponent NOTIFY sourceChanged) + Q_PROPERTY(ResizeMode resizeMode READ resizeMode WRITE setResizeMode) + Q_PROPERTY(QGraphicsObject *item READ item NOTIFY itemChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged) + +public: + QmlGraphicsLoader(QmlGraphicsItem *parent=0); + virtual ~QmlGraphicsLoader(); + + QUrl source() const; + void setSource(const QUrl &); + + QmlComponent *sourceComponent() const; + void setSourceComponent(QmlComponent *); + + enum Status { Null, Ready, Loading, Error }; + Status status() const; + qreal progress() const; + + enum ResizeMode { NoResize, SizeLoaderToItem, SizeItemToLoader }; + ResizeMode resizeMode() const; + void setResizeMode(ResizeMode mode); + + QGraphicsObject *item() const; + +Q_SIGNALS: + void itemChanged(); + void sourceChanged(); + void statusChanged(); + void progressChanged(); +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + bool eventFilter(QObject *watched, QEvent *e); +private: + Q_DISABLE_COPY(QmlGraphicsLoader) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsLoader) + Q_PRIVATE_SLOT(d_func(), void _q_sourceLoaded()) + Q_PRIVATE_SLOT(d_func(), void _q_updateSize()) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsLoader) + +QT_END_HEADER + +#endif // QMLGRAPHICSLOADER_H diff --git a/src/declarative/graphicsitems/qmlgraphicsloader_p_p.h b/src/declarative/graphicsitems/qmlgraphicsloader_p_p.h new file mode 100644 index 0000000..7f10eff --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsloader_p_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSLOADER_P_H +#define QMLGRAPHICSLOADER_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 "qmlgraphicsloader_p.h" + +#include "qmlgraphicsitem_p.h" +#include "qmlgraphicsitemchangelistener_p.h" + +QT_BEGIN_NAMESPACE + +class QmlContext; +class QmlGraphicsLoaderPrivate : public QmlGraphicsItemPrivate, public QmlGraphicsItemChangeListener +{ + Q_DECLARE_PUBLIC(QmlGraphicsLoader) + +public: + QmlGraphicsLoaderPrivate(); + ~QmlGraphicsLoaderPrivate(); + + void itemGeometryChanged(QmlGraphicsItem *item, const QRectF &newGeometry, const QRectF &oldGeometry); + void clear(); + void initResize(); + + QUrl source; + QGraphicsObject *item; + QmlComponent *component; + bool ownComponent : 1; + QmlGraphicsLoader::ResizeMode resizeMode; + + void _q_sourceLoaded(); + void _q_updateSize(); +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSLOADER_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsmouseregion.cpp b/src/declarative/graphicsitems/qmlgraphicsmouseregion.cpp new file mode 100644 index 0000000..bd21e7a --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsmouseregion.cpp @@ -0,0 +1,653 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsmouseregion_p.h" +#include "qmlgraphicsmouseregion_p_p.h" + +#include "qmlgraphicsevents_p_p.h" + +#include <QGraphicsSceneMouseEvent> + +QT_BEGIN_NAMESPACE +static const int PressAndHoldDelay = 800; + +QML_DEFINE_TYPE(Qt,4,6,Drag,QmlGraphicsDrag) +QmlGraphicsDrag::QmlGraphicsDrag(QObject *parent) +: QObject(parent), _target(0), _axis(XandYAxis), _xmin(0), _xmax(0), _ymin(0), _ymax(0) +{ +} + +QmlGraphicsDrag::~QmlGraphicsDrag() +{ +} + +QmlGraphicsItem *QmlGraphicsDrag::target() const +{ + return _target; +} + +void QmlGraphicsDrag::setTarget(QmlGraphicsItem *t) +{ + _target = t; +} + +QmlGraphicsDrag::Axis QmlGraphicsDrag::axis() const +{ + return _axis; +} + +void QmlGraphicsDrag::setAxis(QmlGraphicsDrag::Axis a) +{ + _axis = a; +} + +qreal QmlGraphicsDrag::xmin() const +{ + return _xmin; +} + +void QmlGraphicsDrag::setXmin(qreal m) +{ + _xmin = m; +} + +qreal QmlGraphicsDrag::xmax() const +{ + return _xmax; +} + +void QmlGraphicsDrag::setXmax(qreal m) +{ + _xmax = m; +} + +qreal QmlGraphicsDrag::ymin() const +{ + return _ymin; +} + +void QmlGraphicsDrag::setYmin(qreal m) +{ + _ymin = m; +} + +qreal QmlGraphicsDrag::ymax() const +{ + return _ymax; +} + +void QmlGraphicsDrag::setYmax(qreal m) +{ + _ymax = m; +} + +QmlGraphicsMouseRegionPrivate::~QmlGraphicsMouseRegionPrivate() +{ + delete drag; +} + + +/*! + \qmlclass MouseRegion QmlGraphicsMouseRegion + \brief The MouseRegion item enables simple mouse handling. + \inherits Item + + A MouseRegion is typically used in conjunction with a visible item, + where the MouseRegion effectively 'proxies' mouse handling for that + item. For example, we can put a MouseRegion in a Rectangle that changes + the Rectangle color to red when clicked: + \snippet doc/src/snippets/declarative/mouseregion.qml 0 + + Many MouseRegion signals pass a \l {MouseEvent}{mouse} parameter that contains + additional information about the mouse event, such as the position, button, + and any key modifiers. + + Below we have the previous + example extended so as to give a different color when you right click. + \snippet doc/src/snippets/declarative/mouseregion.qml 1 + + For basic key handling, see the \l {Keys}{Keys attached property}. + + MouseRegion is an invisible item: it is never painted. + + \sa MouseEvent +*/ + +/*! + \qmlsignal MouseRegion::onEntered() + + This handler is called when the mouse enters the mouse region. +*/ + +/*! + \qmlsignal MouseRegion::onExited() + + This handler is called when the mouse exists the mouse region. +*/ + +/*! + \qmlsignal MouseRegion::onPositionChanged(MouseEvent mouse) + + This handler is called when the mouse position changes. + + The \l {MouseEvent}{mouse} parameter provides information about the mouse, including the x and y + position, and any buttons currently pressed. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +/*! + \qmlsignal MouseRegion::onClicked(mouse) + + This handler is called when there is a click. A click is defined as a press followed by a release, + both inside the MouseRegion (pressing, moving outside the MouseRegion, and then moving back inside and + releasing is also considered a click). + + The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y + position of the release of the click, and whether the click wasHeld. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +/*! + \qmlsignal MouseRegion::onPressed(mouse) + + This handler is called when there is a press. + The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y + position and which button was pressed. + + The \e accepted property of the MouseEvent parameter determines whether this MouseRegion + will handle the press and all future mouse events until release. The default is to accept + the event and not allow other MouseRegions beneath this one to handle the event. If \e accepted + is set to false, no further events will be sent to this MouseRegion until the button is next + pressed. +*/ + +/*! + \qmlsignal MouseRegion::onReleased(mouse) + + This handler is called when there is a release. + The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y + position of the release of the click, and whether the click wasHeld. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +/*! + \qmlsignal MouseRegion::onPressAndHold(mouse) + + This handler is called when there is a long press (currently 800ms). + The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y + position of the press, and which button is pressed. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +/*! + \qmlsignal MouseRegion::onDoubleClicked(mouse) + + This handler is called when there is a double-click (a press followed by a release followed by a press). + The \l {MouseEvent}{mouse} parameter provides information about the click, including the x and y + position of the release of the click, and whether the click wasHeld. + + The \e accepted property of the MouseEvent parameter is ignored in this handler. +*/ + +QML_DEFINE_TYPE(Qt,4,6,MouseRegion,QmlGraphicsMouseRegion) + +/*! + \internal + \class QmlGraphicsMouseRegion + \brief The QmlGraphicsMouseRegion class provides a simple mouse handling abstraction for use within Qml. + + \ingroup group_coreitems + + All QmlGraphicsItem derived classes can do mouse handling but the QmlGraphicsMouseRegion class exposes mouse + handling data as properties and tracks flicking and dragging of the mouse. + + A QmlGraphicsMouseRegion object can be instantiated in Qml using the tag \l MouseRegion. + */ +QmlGraphicsMouseRegion::QmlGraphicsMouseRegion(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsMouseRegionPrivate), parent) +{ + Q_D(QmlGraphicsMouseRegion); + d->init(); +} + +QmlGraphicsMouseRegion::~QmlGraphicsMouseRegion() +{ +} + +/*! + \qmlproperty real MouseRegion::mouseX + \qmlproperty real MouseRegion::mouseY + These properties hold the coordinates of the mouse. + + If the hoverEnabled property is false then these properties will only be valid + while a button is pressed, and will remain valid as long as the button is held + even if the mouse is moved outside the region. + + If hoverEnabled is true then these properties will be valid: + \list + \i when no button is pressed, but the mouse is within the MouseRegion (containsMouse is true). + \i if a button is pressed and held, even if it has since moved out of the region. + \endlist + + The coordinates are relative to the MouseRegion. +*/ +qreal QmlGraphicsMouseRegion::mouseX() const +{ + Q_D(const QmlGraphicsMouseRegion); + return d->lastPos.x(); +} + +qreal QmlGraphicsMouseRegion::mouseY() const +{ + Q_D(const QmlGraphicsMouseRegion); + return d->lastPos.y(); +} + +/*! + \qmlproperty bool MouseRegion::enabled + This property holds whether the item accepts mouse events. +*/ +bool QmlGraphicsMouseRegion::isEnabled() const +{ + Q_D(const QmlGraphicsMouseRegion); + return d->absorb; +} + +void QmlGraphicsMouseRegion::setEnabled(bool a) +{ + Q_D(QmlGraphicsMouseRegion); + if (a != d->absorb) { + d->absorb = a; + emit enabledChanged(); + } +} +/*! + \qmlproperty MouseButtons MouseRegion::pressedButtons + This property holds the mouse buttons currently pressed. + + It contains a bitwise combination of: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MidButton + \endlist + + The code below displays "right" when the right mouse buttons is pressed: + \code + Text { + text: mr.pressedButtons & Qt.RightButton ? "right" : "" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + MouseRegion { + id: mr + acceptedButtons: Qt.LeftButton | Qt.RightButton + anchors.fill: parent + } + } + \endcode + + \sa acceptedButtons +*/ +Qt::MouseButtons QmlGraphicsMouseRegion::pressedButtons() const +{ + Q_D(const QmlGraphicsMouseRegion); + return d->lastButtons; +} + +void QmlGraphicsMouseRegion::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + d->moved = false; + if (!d->absorb) + QmlGraphicsItem::mousePressEvent(event); + else { + d->longPress = false; + d->saveEvent(event); + if (d->drag) { + d->dragX = drag()->axis() & QmlGraphicsDrag::XAxis; + d->dragY = drag()->axis() & QmlGraphicsDrag::YAxis; + } + d->dragged = false; + setHovered(true); + d->start = event->pos(); + d->startScene = event->scenePos(); + // we should only start timer if pressAndHold is connected to. + if (d->isConnected("pressAndHold(QmlGraphicsMouseEvent*)")) + d->pressAndHoldTimer.start(PressAndHoldDelay, this); + setKeepMouseGrab(false); + event->setAccepted(setPressed(true)); + } +} + +void QmlGraphicsMouseRegion::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->absorb) { + QmlGraphicsItem::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) { + if (d->dragX) d->startX = drag()->target()->x(); + if (d->dragY) d->startY = drag()->target()->y(); + } + + QPointF startLocalPos; + QPointF curLocalPos; + if (drag()->target()->parent()) { + 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 ((d->dragX && !(dx < dragThreshold)) || (d->dragY && !(dy < dragThreshold))) + d->dragged = true; + if (!keepMouseGrab()) { + if ((!d->dragY && dy < dragThreshold && d->dragX && dx > dragThreshold) + || (!d->dragX && dx < dragThreshold && d->dragY && dy > dragThreshold) + || (d->dragX && d->dragY)) { + setKeepMouseGrab(true); + } + } + + if (d->dragX) { + 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) { + 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); + } + } + d->moved = true; + QmlGraphicsMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, false, d->longPress); + emit positionChanged(&me); +} + + +void QmlGraphicsMouseRegion::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->absorb) { + QmlGraphicsItem::mouseReleaseEvent(event); + } else { + d->saveEvent(event); + setPressed(false); + // If we don't accept hover, we need to reset containsMouse. + if (!acceptHoverEvents()) + setHovered(false); + setKeepMouseGrab(false); + } +} + +void QmlGraphicsMouseRegion::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->absorb) { + QmlGraphicsItem::mouseDoubleClickEvent(event); + } else { + QmlGraphicsItem::mouseDoubleClickEvent(event); + if (event->isAccepted()) { + // Only deliver the event if we have accepted the press. + d->saveEvent(event); + QmlGraphicsMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, true, false); + emit this->doubleClicked(&me); + } + } +} + +void QmlGraphicsMouseRegion::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->absorb) + QmlGraphicsItem::hoverEnterEvent(event); + else + setHovered(true); +} + +void QmlGraphicsMouseRegion::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->absorb) { + QmlGraphicsItem::hoverEnterEvent(event); + } else { + d->lastPos = event->pos(); + QmlGraphicsMouseEvent me(d->lastPos.x(), d->lastPos.y(), Qt::NoButton, d->lastButtons, d->lastModifiers, false, d->longPress); + emit positionChanged(&me); + } +} + +void QmlGraphicsMouseRegion::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->absorb) + QmlGraphicsItem::hoverLeaveEvent(event); + else + setHovered(false); +} + +bool QmlGraphicsMouseRegion::sceneEvent(QEvent *event) +{ + bool rv = QmlGraphicsItem::sceneEvent(event); + if (event->type() == QEvent::UngrabMouse) { + Q_D(QmlGraphicsMouseRegion); + if (d->pressed) { + // if our mouse grab has been removed (probably by Flickable), fix our + // state + d->pressed = false; + setKeepMouseGrab(false); + emit pressedChanged(); + //emit hoveredChanged(); + } + } + return rv; +} + +void QmlGraphicsMouseRegion::timerEvent(QTimerEvent *event) +{ + Q_D(QmlGraphicsMouseRegion); + if (event->timerId() == d->pressAndHoldTimer.timerId()) { + d->pressAndHoldTimer.stop(); + if (d->pressed && d->dragged == false && d->hovered == true) { + d->longPress = true; + QmlGraphicsMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, false, d->longPress); + emit pressAndHold(&me); + } + } +} + +/*! + \qmlproperty bool MouseRegion::hoverEnabled + This property holds whether hover events are handled. + + By default, mouse events are only handled in response to a button event, or when a button is + pressed. Hover enables handling of all mouse events even when no mouse button is + pressed. + + This property affects the containsMouse property and the onEntered, onExited and onPositionChanged signals. +*/ + +/*! + \qmlproperty bool MouseRegion::containsMouse + This property holds whether the mouse is currently inside the mouse region. + + \warning This property is not updated if the region moves under the mouse: \e containsMouse will not change. + In addition, if hoverEnabled is false, containsMouse will only be valid when the mouse is pressed. +*/ +bool QmlGraphicsMouseRegion::hovered() const +{ + Q_D(const QmlGraphicsMouseRegion); + return d->hovered; +} + +/*! + \qmlproperty bool MouseRegion::pressed + This property holds whether the mouse region is currently pressed. +*/ +bool QmlGraphicsMouseRegion::pressed() const +{ + Q_D(const QmlGraphicsMouseRegion); + return d->pressed; +} + +void QmlGraphicsMouseRegion::setHovered(bool h) +{ + Q_D(QmlGraphicsMouseRegion); + if (d->hovered != h) { + d->hovered = h; + emit hoveredChanged(); + d->hovered ? emit entered() : emit exited(); + } +} + +/*! + \qmlproperty Qt::MouseButtons MouseRegion::acceptedButtons + This property holds the mouse buttons that the mouse region reacts to. + + The available buttons are: + \list + \o Qt.LeftButton + \o Qt.RightButton + \o Qt.MidButton + \endlist + + To accept more than one button the flags can be combined with the + "|" (or) operator: + + \code + MouseRegion { acceptedButtons: Qt.LeftButton | Qt.RightButton } + \endcode + + The default is to accept the Left button. +*/ +Qt::MouseButtons QmlGraphicsMouseRegion::acceptedButtons() const +{ + return acceptedMouseButtons(); +} + +void QmlGraphicsMouseRegion::setAcceptedButtons(Qt::MouseButtons buttons) +{ + if (buttons != acceptedMouseButtons()) { + setAcceptedMouseButtons(buttons); + emit acceptedButtonsChanged(); + } +} + +bool QmlGraphicsMouseRegion::setPressed(bool p) +{ + Q_D(QmlGraphicsMouseRegion); + bool isclick = d->pressed == true && p == false && d->dragged == false && d->hovered == true; + + if (d->pressed != p) { + d->pressed = p; + QmlGraphicsMouseEvent me(d->lastPos.x(), d->lastPos.y(), d->lastButton, d->lastButtons, d->lastModifiers, isclick, d->longPress); + if (d->pressed) { + emit positionChanged(&me); + emit pressed(&me); + } else { + emit released(&me); + if (isclick) + emit clicked(&me); + } + + emit pressedChanged(); + return me.isAccepted(); + } + return false; +} + +QmlGraphicsDrag *QmlGraphicsMouseRegion::drag() +{ + Q_D(QmlGraphicsMouseRegion); + if (!d->drag) + d->drag = new QmlGraphicsDrag; + return d->drag; +} + +/*! + \qmlproperty Item MouseRegion::drag.target + \qmlproperty Axis MouseRegion::drag.axis + \qmlproperty real MouseRegion::drag.minimumX + \qmlproperty real MouseRegion::drag.maximumX + \qmlproperty real MouseRegion::drag.minimumY + \qmlproperty real MouseRegion::drag.maximumY + + drag provides a convenient way to make an item draggable. + + \list + \i \c target specifies the item to drag. + \i \c axis specifies whether dragging can be done horizontally (XAxis), vertically (YAxis), or both (XandYAxis) + \i the minimum and maximum properties limit how far the target can be dragged along the corresponding axes. + \endlist + + The following example uses drag to reduce the opacity of an image as it moves to the right: + \snippet doc/src/snippets/declarative/drag.qml 0 +*/ + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsmouseregion_p.h b/src/declarative/graphicsitems/qmlgraphicsmouseregion_p.h new file mode 100644 index 0000000..0ddad1b --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsmouseregion_p.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSMOUSEREGION_H +#define QMLGRAPHICSMOUSEREGION_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QmlGraphicsDrag : public QObject +{ + Q_OBJECT + + Q_ENUMS(Axis) + Q_PROPERTY(QmlGraphicsItem *target READ target WRITE setTarget) + Q_PROPERTY(Axis axis READ axis WRITE setAxis) + Q_PROPERTY(qreal minimumX READ xmin WRITE setXmin) + Q_PROPERTY(qreal maximumX READ xmax WRITE setXmax) + Q_PROPERTY(qreal minimumY READ ymin WRITE setYmin) + Q_PROPERTY(qreal maximumY READ ymax WRITE setYmax) + //### consider drag and drop + +public: + QmlGraphicsDrag(QObject *parent=0); + ~QmlGraphicsDrag(); + + QmlGraphicsItem *target() const; + void setTarget(QmlGraphicsItem *); + + 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); + +private: + QmlGraphicsItem *_target; + Axis _axis; + qreal _xmin; + qreal _xmax; + qreal _ymin; + qreal _ymax; + Q_DISABLE_COPY(QmlGraphicsDrag) +}; + +class QmlGraphicsMouseEvent; +class QmlGraphicsMouseRegionPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsMouseRegion : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(qreal mouseX READ mouseX NOTIFY positionChanged) + Q_PROPERTY(qreal mouseY READ mouseY NOTIFY positionChanged) + 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 acceptHoverEvents WRITE setAcceptHoverEvents) + Q_PROPERTY(QmlGraphicsDrag *drag READ drag) //### add flicking to QmlGraphicsDrag or add a QmlGraphicsFlick ??? + +public: + QmlGraphicsMouseRegion(QmlGraphicsItem *parent=0); + ~QmlGraphicsMouseRegion(); + + 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); + + QmlGraphicsDrag *drag(); + +Q_SIGNALS: + void hoveredChanged(); + void pressedChanged(); + void enabledChanged(); + void acceptedButtonsChanged(); + void positionChanged(QmlGraphicsMouseEvent *mouse); + + void pressed(QmlGraphicsMouseEvent *mouse); + void pressAndHold(QmlGraphicsMouseEvent *mouse); + void released(QmlGraphicsMouseEvent *mouse); + void clicked(QmlGraphicsMouseEvent *mouse); + void doubleClicked(QmlGraphicsMouseEvent *mouse); + void entered(); + void exited(); + +protected: + void setHovered(bool); + bool setPressed(bool); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverMoveEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + bool sceneEvent(QEvent *); + void timerEvent(QTimerEvent *event); + +private: + void handlePress(); + void handleRelease(); + +private: + Q_DISABLE_COPY(QmlGraphicsMouseRegion) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsMouseRegion) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsDrag) +QML_DECLARE_TYPE(QmlGraphicsMouseRegion) + +QT_END_HEADER + +#endif // QMLGRAPHICSMOUSEREGION_H diff --git a/src/declarative/graphicsitems/qmlgraphicsmouseregion_p_p.h b/src/declarative/graphicsitems/qmlgraphicsmouseregion_p_p.h new file mode 100644 index 0000000..0f1b0d4 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsmouseregion_p_p.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSMOUSEREGION_P_H +#define QMLGRAPHICSMOUSEREGION_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 "qmlgraphicsitem_p.h" + +#include <qdatetime.h> +#include <qbasictimer.h> +#include <qgraphicssceneevent.h> + +QT_BEGIN_NAMESPACE + +class QmlGraphicsMouseRegionPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsMouseRegion) + +public: + QmlGraphicsMouseRegionPrivate() + : absorb(true), hovered(false), pressed(false), longPress(false), drag(0) + { + } + + ~QmlGraphicsMouseRegionPrivate(); + + void init() + { + Q_Q(QmlGraphicsMouseRegion); + q->setAcceptedMouseButtons(Qt::LeftButton); + } + + void saveEvent(QGraphicsSceneMouseEvent *event) { + lastPos = event->pos(); + lastButton = event->button(); + lastButtons = event->buttons(); + lastModifiers = event->modifiers(); + } + + bool isConnected(const char *signal) { + Q_Q(QmlGraphicsMouseRegion); + int idx = QObjectPrivate::get(q)->signalIndex(signal); + return QObjectPrivate::get(q)->isSignalConnected(idx); + } + + bool absorb : 1; + bool hovered : 1; + bool pressed : 1; + bool longPress : 1; + bool moved : 1; + bool dragX : 1; + bool dragY : 1; + bool dragged : 1; + QmlGraphicsDrag *drag; + QPointF start; + QPointF startScene; + qreal startX; + qreal startY; + QPointF lastPos; + Qt::MouseButton lastButton; + Qt::MouseButtons lastButtons; + Qt::KeyboardModifiers lastModifiers; + QBasicTimer pressAndHoldTimer; +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSMOUSEREGION_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicspainteditem.cpp b/src/declarative/graphicsitems/qmlgraphicspainteditem.cpp new file mode 100644 index 0000000..e50e3e4 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspainteditem.cpp @@ -0,0 +1,466 @@ +/**************************************************************************** +** +** 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 "qmlgraphicspainteditem_p.h" +#include "qmlgraphicspainteditem_p_p.h" + +#include <QDebug> +#include <QPen> +#include <QFile> +#include <QEvent> +#include <QApplication> +#include <QGraphicsSceneMouseEvent> +#include <QPainter> +#include <QPaintEngine> +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QmlGraphicsPaintedItem + \brief The QmlGraphicsPaintedItem class is an abstract base class for QmlView items that want cached painting. + \internal + + This is a convenience class for implementing items that paint their contents + using a QPainter. The contents of the item are cached behind the scenes. + The dirtyCache() function should be called if the contents change to + ensure the cache is refreshed the next time painting occurs. + + To subclass QmlGraphicsPaintedItem, you must reimplement drawContents() to draw + the contents of the item. +*/ + +/*! + \fn void QmlGraphicsPaintedItem::drawContents(QPainter *painter, const QRect &rect) + + This function is called when the cache needs to be refreshed. When + sub-classing QmlGraphicsPaintedItem this function should be implemented so as to + paint the contents of the item using the given \a painter for the + area of the contents specified by \a rect. +*/ + +/*! + \property QmlGraphicsPaintedItem::contentsSize + \brief The size of the contents + + The contents size is the size of the item in regards to how it is painted + using the drawContents() function. This is distinct from the size of the + item in regards to height() and width(). +*/ + +// XXX bug in WebKit - can call repaintRequested and other cache-changing functions from within render! +static int inpaint=0; +static int inpaint_clearcache=0; + +/*! + Marks areas of the cache that intersect with the given \a rect as dirty and + in need of being refreshed. + + \sa clearCache() +*/ +void QmlGraphicsPaintedItem::dirtyCache(const QRect& rect) +{ + Q_D(QmlGraphicsPaintedItem); + QRect srect(qCeil(rect.x()*d->contentsScale), + qCeil(rect.y()*d->contentsScale), + qCeil(rect.width()*d->contentsScale), + qCeil(rect.height()*d->contentsScale)); + for (int i=0; i < d->imagecache.count(); ) { + QmlGraphicsPaintedItemPrivate::ImageCacheItem *c = d->imagecache[i]; + QRect isect = (c->area & srect) | c->dirty; + if (isect == c->area && !inpaint) { + delete d->imagecache.takeAt(i); + } else { + c->dirty = isect; + ++i; + } + } +} + +/*! + Marks the entirety of the contents cache as dirty. + + \sa dirtyCache() +*/ +void QmlGraphicsPaintedItem::clearCache() +{ + if (inpaint) { + inpaint_clearcache=1; + return; + } + Q_D(QmlGraphicsPaintedItem); + qDeleteAll(d->imagecache); + d->imagecache.clear(); +} + +/*! + Returns the size of the contents. + + \sa setContentsSize() +*/ +QSize QmlGraphicsPaintedItem::contentsSize() const +{ + Q_D(const QmlGraphicsPaintedItem); + return d->contentsSize; +} + +/*! + Sets the size of the contents to the given \a size. + + \sa contentsSize() +*/ +void QmlGraphicsPaintedItem::setContentsSize(const QSize &size) +{ + Q_D(QmlGraphicsPaintedItem); + if (d->contentsSize == size) return; + d->contentsSize = size; + setImplicitWidth(size.width()*d->contentsScale); + setImplicitHeight(size.height()*d->contentsScale); + clearCache(); + update(); + emit contentsSizeChanged(); +} + +qreal QmlGraphicsPaintedItem::contentsScale() const +{ + Q_D(const QmlGraphicsPaintedItem); + return d->contentsScale; +} + +void QmlGraphicsPaintedItem::setContentsScale(qreal scale) +{ + Q_D(QmlGraphicsPaintedItem); + if (d->contentsScale == scale) return; + d->contentsScale = scale; + setImplicitWidth(d->contentsSize.width()*scale); + setImplicitHeight(d->contentsSize.height()*scale); + clearCache(); + update(); + emit contentsScaleChanged(); +} + + +/*! + Constructs a new QmlGraphicsPaintedItem with the given \a parent. +*/ +QmlGraphicsPaintedItem::QmlGraphicsPaintedItem(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsPaintedItemPrivate), parent) +{ + init(); +} + +/*! + \internal + Constructs a new QmlGraphicsPaintedItem with the given \a parent and + initialized private data member \a dd. +*/ +QmlGraphicsPaintedItem::QmlGraphicsPaintedItem(QmlGraphicsPaintedItemPrivate &dd, QmlGraphicsItem *parent) + : QmlGraphicsItem(dd, parent) +{ + init(); +} + +/*! + Destroys the image item. +*/ +QmlGraphicsPaintedItem::~QmlGraphicsPaintedItem() +{ + clearCache(); +} + +/*! + \internal +*/ +void QmlGraphicsPaintedItem::init() +{ + connect(this,SIGNAL(widthChanged()),this,SLOT(clearCache())); + connect(this,SIGNAL(heightChanged()),this,SLOT(clearCache())); + connect(this,SIGNAL(visibleChanged()),this,SLOT(clearCache())); +} + +void QmlGraphicsPaintedItem::setCacheFrozen(bool frozen) +{ + Q_D(QmlGraphicsPaintedItem); + if (d->cachefrozen == frozen) + return; + d->cachefrozen = frozen; + // XXX clear cache? +} + +/*! + \reimp +*/ +void QmlGraphicsPaintedItem::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + Q_D(QmlGraphicsPaintedItem); + const QRect content(0,0,qCeil(d->contentsSize.width()*d->contentsScale), + qCeil(d->contentsSize.height()*d->contentsScale)); + if (content.width() <= 0 || content.height() <= 0) + return; + + ++inpaint; + + const QTransform &x = p->deviceTransform(); + QTransform xinv = x.inverted(); + QRegion effectiveClip; + QRegion sysClip = p->paintEngine()->systemClip(); + if (xinv.type() <= QTransform::TxScale && sysClip.numRects() < 5) { + // simple transform, region gets no more complicated... + effectiveClip = xinv.map(sysClip); + } else { + // do not make complicated regions... + effectiveClip = xinv.mapRect(sysClip.boundingRect()); + } + + QRegion topaint = p->clipRegion(); + if (topaint.isEmpty()) { + if (effectiveClip.isEmpty()) + topaint = QRect(0,0,p->device()->width(),p->device()->height()); + else + topaint = effectiveClip; + } else if (!effectiveClip.isEmpty()) { + topaint &= effectiveClip; + } + + topaint &= content; + QRegion uncached(content); + p->setRenderHints(QPainter::SmoothPixmapTransform, d->smooth); + + int cachesize=0; + for (int i=0; i<d->imagecache.count(); ++i) { + QRect area = d->imagecache[i]->area; + if (topaint.contains(area)) { + QRectF target(area.x(), area.y(), area.width(), area.height()); + if (!d->cachefrozen) { + if (!d->imagecache[i]->dirty.isNull() && topaint.contains(d->imagecache[i]->dirty)) { + QPainter qp(&d->imagecache[i]->image); + qp.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, d->smoothCache); + qp.translate(-area.x(), -area.y()); + qp.scale(d->contentsScale,d->contentsScale); + QRect clip = d->imagecache[i]->dirty; + QRect sclip(qFloor(clip.x()/d->contentsScale), + qFloor(clip.y()/d->contentsScale), + qCeil(clip.width()/d->contentsScale+clip.x()/d->contentsScale-qFloor(clip.x()/d->contentsScale)), + qCeil(clip.height()/d->contentsScale+clip.y()/d->contentsScale-qFloor(clip.y()/d->contentsScale))); + qp.setClipRect(sclip); + if (d->fillColor.isValid()){ + if(d->fillColor.alpha() < 255){ + // ### Might not work outside of raster paintengine + QPainter::CompositionMode prev = qp.compositionMode(); + qp.setCompositionMode(QPainter::CompositionMode_Source); + qp.fillRect(sclip,d->fillColor); + qp.setCompositionMode(prev); + }else{ + qp.fillRect(sclip,d->fillColor); + } + } + drawContents(&qp, sclip); + d->imagecache[i]->dirty = QRect(); + } + } + p->drawPixmap(target.toRect(), d->imagecache[i]->image); + topaint -= area; + d->imagecache[i]->age=0; + } else { + d->imagecache[i]->age++; + } + cachesize += area.width()*area.height(); + uncached -= area; + } + + if (!topaint.isEmpty()) { + if (!d->cachefrozen) { + // Find a sensible larger area, otherwise will paint lots of tiny images. + QRect biggerrect = topaint.boundingRect().adjusted(-64,-64,128,128); + cachesize += biggerrect.width() * biggerrect.height(); + while (d->imagecache.count() && cachesize > d->max_imagecache_size) { + int oldest=-1; + int age=-1; + for (int i=0; i<d->imagecache.count(); ++i) { + int a = d->imagecache[i]->age; + if (a > age) { + oldest = i; + age = a; + } + } + cachesize -= d->imagecache[oldest]->area.width()*d->imagecache[oldest]->area.height(); + uncached += d->imagecache[oldest]->area; + delete d->imagecache.takeAt(oldest); + } + const QRegion bigger = QRegion(biggerrect) & uncached; + const QVector<QRect> rects = bigger.rects(); + for (int i = 0; i < rects.count(); ++i) { + const QRect &r = rects.at(i); + QPixmap img(r.size()); + if (d->fillColor.isValid()) + img.fill(d->fillColor); + { + QPainter qp(&img); + qp.setRenderHints(QPainter::HighQualityAntialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, d->smoothCache); + + qp.translate(-r.x(),-r.y()); + qp.scale(d->contentsScale,d->contentsScale); + QRect sclip(qFloor(r.x()/d->contentsScale), + qFloor(r.y()/d->contentsScale), + qCeil(r.width()/d->contentsScale+r.x()/d->contentsScale-qFloor(r.x()/d->contentsScale)), + qCeil(r.height()/d->contentsScale+r.y()/d->contentsScale-qFloor(r.y()/d->contentsScale))); + drawContents(&qp, sclip); + } + QmlGraphicsPaintedItemPrivate::ImageCacheItem *newitem = new QmlGraphicsPaintedItemPrivate::ImageCacheItem; + newitem->area = r; + newitem->image = img; + d->imagecache.append(newitem); + p->drawPixmap(r, newitem->image); + } + } else { + const QVector<QRect> rects = uncached.rects(); + for (int i = 0; i < rects.count(); ++i) + p->fillRect(rects.at(i), Qt::lightGray); + } + } + + if (inpaint_clearcache) { + clearCache(); + inpaint_clearcache = 0; + } + + --inpaint; +} + +/*! + \qmlproperty int PaintedItem::pixelCacheSize + + This property holds the maximum number of pixels of image cache to + allow. The default is 0.1 megapixels. The cache will not be larger + than the (unscaled) size of the WebView. +*/ +/*! + \property QmlGraphicsPaintedItem::pixelCacheSize + + The maximum number of pixels of image cache to allow. The default + is 0.1 megapixels. The cache will not be larger than the (unscaled) + size of the QmlGraphicsPaintedItem. +*/ +int QmlGraphicsPaintedItem::pixelCacheSize() const +{ + Q_D(const QmlGraphicsPaintedItem); + return d->max_imagecache_size; +} + +void QmlGraphicsPaintedItem::setPixelCacheSize(int pixels) +{ + Q_D(QmlGraphicsPaintedItem); + if (pixels < d->max_imagecache_size) { + int cachesize=0; + for (int i=0; i<d->imagecache.count(); ++i) { + QRect area = d->imagecache[i]->area; + cachesize += area.width()*area.height(); + } + while (d->imagecache.count() && cachesize > pixels) { + int oldest=-1; + int age=-1; + for (int i=0; i<d->imagecache.count(); ++i) { + int a = d->imagecache[i]->age; + if (a > age) { + oldest = i; + age = a; + } + } + cachesize -= d->imagecache[oldest]->area.width()*d->imagecache[oldest]->area.height(); + delete d->imagecache.takeAt(oldest); + } + } + d->max_imagecache_size = pixels; +} + + + +/*! + \property QmlGraphicsPaintedItem::fillColor + + The color to be used to fill the item prior to calling drawContents(). + By default, this is Qt::transparent. + + Performance improvements can be achieved if subclasses call this with either an + invalid color (QColor()), or an appropriate solid color. +*/ +void QmlGraphicsPaintedItem::setFillColor(const QColor& c) +{ + Q_D(QmlGraphicsPaintedItem); + if (d->fillColor == c) + return; + d->fillColor = c; + emit fillColorChanged(); + update(); +} + +QColor QmlGraphicsPaintedItem::fillColor() const +{ + Q_D(const QmlGraphicsPaintedItem); + return d->fillColor; +} + +/*! + \qmlproperty bool PaintedItem::smoothCache + + Controls whether the cached tiles of which the item is composed are + rendered smoothly when they are generated. + + This is in addition toe Item::smooth, which controls the smooth painting of + the already-painted cached tiles under transformation. +*/ +bool QmlGraphicsPaintedItem::smoothCache() const +{ + Q_D(const QmlGraphicsPaintedItem); + return d->smoothCache; +} + +void QmlGraphicsPaintedItem::setSmoothCache(bool on) +{ + Q_D(QmlGraphicsPaintedItem); + if (d->smoothCache != on) { + d->smoothCache = on; + clearCache(); + } +} + + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicspainteditem_p.h b/src/declarative/graphicsitems/qmlgraphicspainteditem_p.h new file mode 100644 index 0000000..ab21f36 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspainteditem_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSIMAGEITEM_H +#define QMLGRAPHICSIMAGEITEM_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsPaintedItemPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsPaintedItem : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(QSize contentsSize READ contentsSize WRITE setContentsSize NOTIFY contentsSizeChanged) + Q_PROPERTY(QColor fillColor READ fillColor WRITE setFillColor NOTIFY fillColorChanged) + Q_PROPERTY(int pixelCacheSize READ pixelCacheSize WRITE setPixelCacheSize) + Q_PROPERTY(bool smoothCache READ smoothCache WRITE setSmoothCache) + Q_PROPERTY(qreal contentsScale READ contentsScale WRITE setContentsScale NOTIFY contentsScaleChanged) + + +public: + QmlGraphicsPaintedItem(QmlGraphicsItem *parent=0); + ~QmlGraphicsPaintedItem(); + + QSize contentsSize() const; + void setContentsSize(const QSize &); + + qreal contentsScale() const; + void setContentsScale(qreal); + + int pixelCacheSize() const; + void setPixelCacheSize(int pixels); + + bool smoothCache() const; + void setSmoothCache(bool on); + + QColor fillColor() const; + void setFillColor(const QColor&); + + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + +protected: + QmlGraphicsPaintedItem(QmlGraphicsPaintedItemPrivate &dd, QmlGraphicsItem *parent); + + virtual void drawContents(QPainter *p, const QRect &) = 0; + + void setCacheFrozen(bool); + +Q_SIGNALS: + void fillColorChanged(); + void contentsSizeChanged(); + void contentsScaleChanged(); + +protected Q_SLOTS: + void dirtyCache(const QRect &); + void clearCache(); + +private: + void init(); + Q_DISABLE_COPY(QmlGraphicsPaintedItem) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsPaintedItem) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsPaintedItem) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicspainteditem_p_p.h b/src/declarative/graphicsitems/qmlgraphicspainteditem_p_p.h new file mode 100644 index 0000000..6bcc51a --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspainteditem_p_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSIMAGEITEM_P_H +#define QMLGRAPHICSIMAGEITEM_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 "qmlgraphicsitem_p.h" + +QT_BEGIN_NAMESPACE + +class QmlGraphicsPaintedItemPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsPaintedItem) + +public: + QmlGraphicsPaintedItemPrivate() + : max_imagecache_size(100000), contentsScale(1.0), fillColor(Qt::transparent), cachefrozen(false), smoothCache(true) + { + } + + struct ImageCacheItem { + ImageCacheItem() : age(0) {} + ~ImageCacheItem() { } + int age; + QRect area; + QRect dirty; // one dirty area (allows optimization of common cases) + QPixmap image; + }; + + QList<ImageCacheItem*> imagecache; + + int max_imagecache_size; + QSize contentsSize; + qreal contentsScale; + QColor fillColor; + bool cachefrozen; + bool smoothCache; +}; + +QT_END_NAMESPACE +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsparticles.cpp b/src/declarative/graphicsitems/qmlgraphicsparticles.cpp new file mode 100644 index 0000000..8c5fb4f --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsparticles.cpp @@ -0,0 +1,1241 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsparticles_p.h" + +#include "qmlgraphicsitem_p.h" + +#include <qmlpixmapcache_p.h> +#include <qfxperf_p_p.h> +#include <qmlanimation_p_p.h> + +#include <QNetworkReply> +#include <QPainter> +#include <QtGui/qdrawutil.h> +#include <QVarLengthArray> + +#include <stdlib.h> +#include <math.h> + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#define M_PI_2 (M_PI / 2.) +#endif +#ifndef INT_MAX +#define INT_MAX 2147483647 +#endif + +QT_BEGIN_NAMESPACE +#define PI_SQR 9.8696044 +// parabolic approximation +inline qreal fastSin(qreal theta) +{ + const qreal b = 4 / M_PI; + const qreal c = -4 / PI_SQR; + + qreal y = b * theta + c * theta * qAbs(theta); + return y; +} + +inline qreal fastCos(qreal theta) +{ + theta += M_PI_2; + if (theta > M_PI) + theta -= 2 * M_PI; + + return fastSin(theta); +} + +class QmlGraphicsParticle +{ +public: + QmlGraphicsParticle(int time) : lifeSpan(1000), fadeOutAge(800) + , opacity(0), birthTime(time), x_velocity(0), y_velocity(0) + , state(FadeIn), data(0) + { + } + + int lifeSpan; + int fadeOutAge; + qreal x; + qreal y; + qreal opacity; + int birthTime; + qreal x_velocity; + qreal y_velocity; + enum State { FadeIn, Solid, FadeOut }; + State state; + void *data; +}; + +//--------------------------------------------------------------------------- + +QML_DEFINE_TYPE(Qt,4,6,ParticleMotion,QmlGraphicsParticleMotion) + +/*! + \class QmlGraphicsParticleMotion + \ingroup group_effects + \brief The QmlGraphicsParticleMotion class is the base class for particle motion. + \internal + + This class causes the particles to remain static. +*/ + +/*! + Constructs a QmlGraphicsParticleMotion with parent object \a parent. +*/ +QmlGraphicsParticleMotion::QmlGraphicsParticleMotion(QObject *parent) : + QObject(parent) +{ +} + +/*! + Move the \a particle to its new position. \a interval is the number of + milliseconds elapsed since it was last moved. +*/ +void QmlGraphicsParticleMotion::advance(QmlGraphicsParticle &particle, int interval) +{ + Q_UNUSED(particle); + Q_UNUSED(interval); +} + +/*! + The \a particle has just been created. Some motion strategies require + additional state information. This can be allocated by this function. +*/ +void QmlGraphicsParticleMotion::created(QmlGraphicsParticle &particle) +{ + Q_UNUSED(particle); +} + +/*! + The \a particle is about to be destroyed. Any additional memory + that has been allocated for the particle should be freed. +*/ +void QmlGraphicsParticleMotion::destroy(QmlGraphicsParticle &particle) +{ + Q_UNUSED(particle); +} + +/*! + \qmlclass ParticleMotionLinear + \brief The ParticleMotionLinear object moves particles linearly. + + \sa Particles +*/ + +/*! + \internal + \class QmlGraphicsParticleMotionLinear + \ingroup group_effects + \brief The QmlGraphicsParticleMotionLinear class moves the particles linearly. +*/ + +QML_DEFINE_TYPE(Qt,4,6,ParticleMotionLinear,QmlGraphicsParticleMotionLinear) + +void QmlGraphicsParticleMotionLinear::advance(QmlGraphicsParticle &p, int interval) +{ + p.x += interval * p.x_velocity; + p.y += interval * p.y_velocity; +} + +/*! + \qmlclass ParticleMotionGravity + \brief The ParticleMotionGravity object moves particles towards a point. + + \sa Particles +*/ + +/*! + \internal + \class QmlGraphicsParticleMotionGravity + \ingroup group_effects + \brief The QmlGraphicsParticleMotionGravity class moves the particles towards a point. +*/ + +QML_DEFINE_TYPE(Qt,4,6,ParticleMotionGravity,QmlGraphicsParticleMotionGravity) + +/*! + \qmlproperty int ParticleMotionGravity::xattractor + \qmlproperty int ParticleMotionGravity::yattractor + These properties hold the x and y coordinates of the point attracting the particles. +*/ + +/*! + \qmlproperty int ParticleMotionGravity::acceleration + This property holds the acceleration to apply to the particles. +*/ + +/*! + \property QmlGraphicsParticleMotionGravity::xattractor + \brief the x coordinate of the point attracting the particles. +*/ + +/*! + \property QmlGraphicsParticleMotionGravity::yattractor + \brief the y coordinate of the point attracting the particles. +*/ + +/*! + \property QmlGraphicsParticleMotionGravity::acceleration + \brief the acceleration to apply to the particles. +*/ + +void QmlGraphicsParticleMotionGravity::advance(QmlGraphicsParticle &p, int interval) +{ + qreal xdiff = p.x - _xAttr; + qreal ydiff = p.y - _yAttr; + + qreal xcomp = xdiff / (xdiff + ydiff); + qreal ycomp = ydiff / (xdiff + ydiff); + + p.x_velocity += xcomp * _accel * interval; + p.y_velocity += ycomp * _accel * interval; + + p.x += interval * p.x_velocity; + p.y += interval * p.y_velocity; +} + +/*! + \qmlclass ParticleMotionWander + \brief The ParticleMotionWander object moves particles in a somewhat random fashion. + + The particles will continue roughly in the original direction, however will randomly + drift to each side. + + The code below produces an effect similar to falling snow. + + \qml +Rectangle { + width: 240 + height: 320 + color: "black" + + Particles { + y: 0 + width: parent.width + height: 30 + source: "star.png" + lifeSpan: 5000 + count: 50 + angle: 70 + angleDeviation: 36 + velocity: 30 + velocityDeviation: 10 + ParticleMotionWander { + xvariance: 30 + pace: 100 + } + } +} + \endqml + + \sa Particles +*/ + +/*! + \internal + \class QmlGraphicsParticleMotionWander + \ingroup group_effects + \brief The QmlGraphicsParticleMotionWander class moves particles in a somewhat random fashion. + + The particles will continue roughly in the original direction, however will randomly + drift to each side. +*/ + +/*! + \qmlproperty int QmlGraphicsParticleMotionWander::xvariance + \qmlproperty int QmlGraphicsParticleMotionWander::yvariance + + These properties set the amount to wander in the x and y directions. +*/ + +/*! + \qmlproperty int QmlGraphicsParticleMotionWander::pace + This property holds how quickly the paricles will move from side to side. +*/ + +QML_DEFINE_TYPE(Qt,4,6,ParticleMotionWander,QmlGraphicsParticleMotionWander) + +void QmlGraphicsParticleMotionWander::advance(QmlGraphicsParticle &p, int interval) +{ + if (!particles) + particles = qobject_cast<QmlGraphicsParticles*>(parent()); + if (particles) { + Data *d = (Data*)p.data; + if (_xvariance != 0.) { + qreal xdiff = p.x_velocity - d->x_targetV; + if ((xdiff > d->x_peak && d->x_var > 0.0) || (xdiff < -d->x_peak && d->x_var < 0.0)) { + d->x_var = -d->x_var; + d->x_peak = _xvariance + _xvariance * qreal(qrand()) / RAND_MAX; + } + p.x_velocity += d->x_var * interval; + } + p.x += interval * p.x_velocity; + + if (_yvariance != 0.) { + qreal ydiff = p.y_velocity - d->y_targetV; + if ((ydiff > d->y_peak && d->y_var > 0.0) || (ydiff < -d->y_peak && d->y_var < 0.0)) { + d->y_var = -d->y_var; + d->y_peak = _yvariance + _yvariance * qreal(qrand()) / RAND_MAX; + } + p.y_velocity += d->y_var * interval; + } + p.y += interval * p.y_velocity; + } +} + +void QmlGraphicsParticleMotionWander::created(QmlGraphicsParticle &p) +{ + if (!p.data) { + Data *d = new Data; + p.data = (void*)d; + d->x_targetV = p.x_velocity; + d->y_targetV = p.y_velocity; + d->x_peak = _xvariance; + d->y_peak = _yvariance; + d->x_var = _pace * qreal(qrand()) / RAND_MAX / 1000.0; + d->y_var = _pace * qreal(qrand()) / RAND_MAX / 1000.0; + } +} + +void QmlGraphicsParticleMotionWander::destroy(QmlGraphicsParticle &p) +{ + if (p.data) + delete (Data*)p.data; +} + +//--------------------------------------------------------------------------- +class QmlGraphicsParticlesPainter : public QmlGraphicsItem +{ +public: + QmlGraphicsParticlesPainter(QmlGraphicsParticlesPrivate *p, QmlGraphicsItem* parent) + : QmlGraphicsItem(parent), d(p) + { + setFlag(QGraphicsItem::ItemHasNoContents, false); + maxX = minX = maxY = minY = 0; + } + + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + void updateSize(); + + qreal maxX; + qreal minX; + qreal maxY; + qreal minY; + QmlGraphicsParticlesPrivate* d; +}; + +//--------------------------------------------------------------------------- +class QmlGraphicsParticlesPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsParticles) +public: + QmlGraphicsParticlesPrivate() + : count(1), emissionRate(-1), emissionVariance(0.5), lifeSpan(1000) + , lifeSpanDev(1000), fadeInDur(200), fadeOutDur(300) + , angle(0), angleDev(0), velocity(0), velocityDev(0), emissionCarry(0.) + , addParticleTime(0), addParticleCount(0), lastAdvTime(0) + , motion(0), pendingPixmapCache(false), clock(this) + { + } + + ~QmlGraphicsParticlesPrivate() + { + } + + void init() + { + Q_Q(QmlGraphicsParticles); + paintItem = new QmlGraphicsParticlesPainter(this, q); + } + + void tick(int time); + void createParticle(int time); + void updateOpacity(QmlGraphicsParticle &p, int age); + + QUrl url; + QPixmap image; + int count; + int emissionRate; + qreal emissionVariance; + int lifeSpan; + int lifeSpanDev; + int fadeInDur; + int fadeOutDur; + qreal angle; + qreal angleDev; + qreal velocity; + qreal velocityDev; + qreal emissionCarry; + int addParticleTime; + int addParticleCount; + int lastAdvTime; + QmlGraphicsParticleMotion *motion; + QmlGraphicsParticlesPainter *paintItem; + + bool pendingPixmapCache; + + QList<QPair<int, int> > bursts;//countLeft, emissionRate pairs + QList<QmlGraphicsParticle> particles; + QTickAnimationProxy<QmlGraphicsParticlesPrivate, &QmlGraphicsParticlesPrivate::tick> clock; + +}; + +void QmlGraphicsParticlesPrivate::tick(int time) +{ + Q_Q(QmlGraphicsParticles); + if (!motion) + motion = new QmlGraphicsParticleMotionLinear(q); + + int oldCount = particles.count(); + int removed = 0; + int interval = time - lastAdvTime; + for (int i = 0; i < particles.count(); ) { + QmlGraphicsParticle &particle = particles[i]; + int age = time - particle.birthTime; + if (age >= particle.lifeSpan) { + QmlGraphicsParticle part = particles.takeAt(i); + motion->destroy(part); + ++removed; + } else { + updateOpacity(particle, age); + motion->advance(particle, interval); + ++i; + } + } + + if(emissionRate == -1)//Otherwise leave emission to the emission rate + while(removed-- && ((count == -1) || particles.count() < count)) + createParticle(time); + + if (!addParticleTime) + addParticleTime = time; + + //Possibly emit new particles + if (((count == -1) || particles.count() < count) && emissionRate + && !(count==-1 && emissionRate==-1)) { + int emissionCount = -1; + if (emissionRate != -1){ + qreal variance = 1.; + if (emissionVariance > 0.){ + variance += (qreal(qrand())/RAND_MAX) * emissionVariance * (qrand()%2?-1.:1.); + } + qreal emission = emissionRate * (qreal(interval)/1000.); + emission = emission * variance + emissionCarry; + double tmpDbl; + emissionCarry = modf(emission, &tmpDbl); + emissionCount = (int)tmpDbl; + emissionCount = qMax(0,emissionCount); + } + while(((count == -1) || particles.count() < count) && + (emissionRate==-1 || emissionCount--)) + createParticle(time); + } + + //Deal with emissions from requested bursts + for(int i=0; i<bursts.size(); i++){ + int emission = 0; + if(bursts[i].second == -1){ + emission = bursts[i].first; + }else{ + qreal variance = 1.; + if (emissionVariance > 0.){ + variance += (qreal(qrand())/RAND_MAX) * emissionVariance * (qrand()%2?-1.:1.); + } + qreal workingEmission = bursts[i].second * (qreal(interval)/1000.); + workingEmission *= variance; + emission = (int)workingEmission; + emission = qMax(emission, 0); + } + emission = qMin(emission, bursts[i].first); + bursts[i].first -= emission; + while(emission--) + createParticle(time); + } + for(int i=bursts.size()-1; i>=0; i--) + if(bursts[i].first <= 0) + bursts.removeAt(i); + + lastAdvTime = time; + paintItem->updateSize(); + paintItem->update(); + if (!(oldCount || particles.count()) && (!count || !emissionRate) && bursts.isEmpty()) { + lastAdvTime = 0; + clock.stop(); + } +} + +void QmlGraphicsParticlesPrivate::createParticle(int time) +{ +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::CreateParticle> x; +#endif + Q_Q(QmlGraphicsParticles); + QmlGraphicsParticle p(time); + p.x = q->x() + q->width() * qreal(qrand()) / RAND_MAX - image.width()/2.0; + p.y = q->y() + q->height() * qreal(qrand()) / RAND_MAX - image.height()/2.0; + p.lifeSpan = lifeSpan; + if (lifeSpanDev) + p.lifeSpan += int(lifeSpanDev/2 - lifeSpanDev * qreal(qrand()) / RAND_MAX); + p.fadeOutAge = p.lifeSpan - fadeOutDur; + if (fadeInDur == 0.) { + p.state= QmlGraphicsParticle::Solid; + p.opacity = 1.0; + } + qreal a = angle; + if (angleDev) + a += angleDev/2 - angleDev * qreal(qrand()) / RAND_MAX; + if (a > M_PI) + a = a - 2 * M_PI; + qreal v = velocity; + if (velocityDev) + v += velocityDev/2 - velocityDev * qreal(qrand()) / RAND_MAX; + p.x_velocity = v * fastCos(a); + p.y_velocity = v * fastSin(a); + particles.append(p); + motion->created(particles.last()); +} + +void QmlGraphicsParticlesPrivate::updateOpacity(QmlGraphicsParticle &p, int age) +{ + switch (p.state) { + case QmlGraphicsParticle::FadeIn: + if (age <= fadeInDur) { + p.opacity = qreal(age) / fadeInDur; + break; + } else { + p.opacity = 1.0; + p.state = QmlGraphicsParticle::Solid; + // Fall through + } + case QmlGraphicsParticle::Solid: + if (age <= p.fadeOutAge) { + break; + } else { + p.state = QmlGraphicsParticle::FadeOut; + // Fall through + } + case QmlGraphicsParticle::FadeOut: + p.opacity = qreal(p.lifeSpan - age) / fadeOutDur; + break; + } +} + +QML_DEFINE_TYPE(Qt,4,6,Particles,QmlGraphicsParticles) + +/*! + \qmlclass Particles + \brief The Particles object generates and moves particles. + \inherits Item + + This element provides preliminary support for particles in QML, and may be heavily changed or removed in later versions. + + The particles created by this object cannot be dealt with directly, they can only be controlled through the parameters of the Particles object. The particles are all the same pixmap, specified by the user. + + The particles are painted relative to the parent of the Particles object. Moving the + Particles object will not move the particles already emitted. + + The below example creates two differently behaving particle sources. + The top one has particles falling from the top like snow, + the lower one has particles expelled up like a fountain. + + \qml +Rectangle { + width: 240 + height: 320 + color: "black" + Particles { + y: 0 + width: parent.width + height: 30 + source: "star.png" + lifeSpan: 5000 + count: 50 + angle: 70 + angleDeviation: 36 + velocity: 30 + velocityDeviation: 10 + ParticleMotionWander { + xvariance: 30 + pace: 100 + } + } + Particles { + y: 300 + x: 120 + width: 1 + height: 1 + source: "star.png" + lifeSpan: 5000 + count: 200 + angle: 270 + angleDeviation: 45 + velocity: 50 + velocityDeviation: 30 + ParticleMotionGravity { + yattractor: 1000 + xattractor: 0 + acceleration: 25 + } + } +} + \endqml + \image particles.gif +*/ + +/*! + \internal + \class QmlGraphicsParticles + \ingroup group_effects + \brief The QmlGraphicsParticles class generates and moves particles. +*/ + +QmlGraphicsParticles::QmlGraphicsParticles(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsParticlesPrivate), parent) +{ + Q_D(QmlGraphicsParticles); + d->init(); +} + +QmlGraphicsParticles::~QmlGraphicsParticles() +{ + Q_D(QmlGraphicsParticles); + if (d->pendingPixmapCache) + QmlPixmapCache::cancel(d->url, this); +} + +/*! + \qmlproperty string Particles::src + This property holds the URL of the particle image. +*/ + +/*! + \property QmlGraphicsParticles::source + \brief the URL of the particle image. +*/ +QUrl QmlGraphicsParticles::source() const +{ + Q_D(const QmlGraphicsParticles); + return d->url; +} + +void QmlGraphicsParticles::imageLoaded() +{ + Q_D(QmlGraphicsParticles); + d->pendingPixmapCache = false; + QmlPixmapCache::get(d->url, &d->image); + d->paintItem->updateSize(); + d->paintItem->update(); +} + +void QmlGraphicsParticles::setSource(const QUrl &name) +{ + Q_D(QmlGraphicsParticles); + + if ((d->url.isEmpty() == name.isEmpty()) && name == d->url) + return; + + if (d->pendingPixmapCache) { + QmlPixmapCache::cancel(d->url, this); + d->pendingPixmapCache = false; + } + if (name.isEmpty()) { + d->url = name; + d->image = QPixmap(); + d->paintItem->updateSize(); + d->paintItem->update(); + } else { + d->url = name; + Q_ASSERT(!name.isRelative()); + QmlPixmapReply::Status status = QmlPixmapCache::get(d->url, &d->image); + if (status != QmlPixmapReply::Ready && status != QmlPixmapReply::Error) { + QmlPixmapReply *reply = QmlPixmapCache::request(qmlEngine(this), d->url); + connect(reply, SIGNAL(finished()), this, SLOT(imageLoaded())); + d->pendingPixmapCache = true; + } else { + //### unify with imageLoaded + d->paintItem->updateSize(); + d->paintItem->update(); + } + } + emit sourceChanged(); +} + +/*! + \qmlproperty int Particles::count + This property holds the maximum number of particles + + The particles element emits particles until it has count active + particles. When this number is reached, new particles are not emitted until + some of the current particles reach theend of their lifespan. + + If count is -1 then there is no maximum number of active particles, and + particles will be constantly emitted at the rate specified by emissionRate. + + If both count and emissionRate are set to -1, nothing will be emitted. + +*/ + +/*! + \property QmlGraphicsParticles::count + \brief the maximum number of particles +*/ +int QmlGraphicsParticles::count() const +{ + Q_D(const QmlGraphicsParticles); + return d->count; +} + +void QmlGraphicsParticles::setCount(int cnt) +{ + Q_D(QmlGraphicsParticles); + if (cnt == d->count) + return; + + int oldCount = d->count; + d->count = cnt; + d->addParticleTime = 0; + d->addParticleCount = d->particles.count(); + if (!oldCount && d->clock.state() != QAbstractAnimation::Running && d->count && d->emissionRate) { + d->clock.start(); + } + d->paintItem->updateSize(); + d->paintItem->update(); + emit countChanged(); +} + + +/*! + \qmlproperty int Particles::emissionRate + This property holds the target number of particles to emit every second. + + The particles element will emit up to emissionRate particles every + second. Fewer particles may be emitted per second if the maximum number of + particles has been reached. + + If emissionRate is set to -1 there is no limit to the number of + particles emitted per second, and particles will be instantly emitted to + reach the maximum number of particles specified by count. + + The default value for emissionRate is -1. + + If both count and emissionRate are set to -1, nothing will be emitted. +*/ + +/*! + \property QmlGraphicsParticles::emissionRate + \brief the emission rate of particles +*/ +int QmlGraphicsParticles::emissionRate() const +{ + Q_D(const QmlGraphicsParticles); + return d->emissionRate; +} +void QmlGraphicsParticles::setEmissionRate(int er) +{ + Q_D(QmlGraphicsParticles); + if(er == d->emissionRate) + return; + d->emissionRate = er; + if (d->clock.state() != QAbstractAnimation::Running && d->count && d->emissionRate) { + d->clock.start(); + } + emit emissionRateChanged(); +} + +/*! + \qmlproperty qreal Particles::emissionVariance + This property holds how inconsistent the rate of particle emissions are. + It is a number between 0 (no variance) and 1 (some variance). + + The expected number of particles emitted per second is emissionRate. If + emissionVariance is 0 then particles will be emitted consistently throughout + each second to reach that number. If emissionVariance is greater than 0 the + rate of particle emission will vary randomly throughout the second, with the + consequence that the actual number of particles emitted in one second will + vary randomly as well. + + emissionVariance is the maximum deviation from emitting + emissionRate particles per second. An emissionVariance of 0 means you should + get exactly emissionRate particles emitted per second, + and an emissionVariance of 1 means you will get between zero and two times + emissionRate particles per second, but you should get emissionRate particles + per second on average. + + Note that even with an emissionVariance of 0 there may be some variance due + to performance and hardware constraints. + + The default value of emissionVariance is 0.5 +*/ + +/*! + \property QmlGraphicsParticles::emissionVariance + \brief how much the particle emission amounts vary per tick +*/ + +qreal QmlGraphicsParticles::emissionVariance() const +{ + Q_D(const QmlGraphicsParticles); + return d->emissionVariance; +} + +void QmlGraphicsParticles::setEmissionVariance(qreal ev) +{ + Q_D(QmlGraphicsParticles); + if(d->emissionVariance == ev) + return; + d->emissionVariance = ev; + emit emissionVarianceChanged(); +} + +/*! + \qmlproperty int Particles::lifeSpan + \qmlproperty int Particles::lifeSpanDeviation + + These properties describe the life span of each particle. + + The default lifespan for a particle is 1000ms. + + lifeSpanDeviation randomly varies the lifeSpan up to the specified variation. For + example, the following creates particles whose lifeSpan will vary + from 150ms to 250ms: + + \qml +Particles { + source: "star.png" + lifeSpan: 200 + lifeSpanDeviation: 100 +} + \endqml +*/ + +/*! + \property QmlGraphicsParticles::lifeSpan + \brief the life span of each particle. + + Default value is 1000ms. + + \sa QmlGraphicsParticles::lifeSpanDeviation +*/ +int QmlGraphicsParticles::lifeSpan() const +{ + Q_D(const QmlGraphicsParticles); + return d->lifeSpan; +} + +void QmlGraphicsParticles::setLifeSpan(int ls) +{ + Q_D(QmlGraphicsParticles); + if(d->lifeSpan == ls) + return; + d->lifeSpan = ls; + emit lifeSpanChanged(); +} + +/*! + \property QmlGraphicsParticles::lifeSpanDeviation + \brief the maximum possible deviation from the set lifeSpan. + + Randomly varies the lifeSpan up to the specified variation. For + example, the following creates particles whose lifeSpan will vary + from 150ms to 250ms: + +\qml +Particles { + source: "star.png" + lifeSpan: 200 + lifeSpanDeviation: 100 +} +\endqml + + \sa QmlGraphicsParticles::lifeSpan +*/ +int QmlGraphicsParticles::lifeSpanDeviation() const +{ + Q_D(const QmlGraphicsParticles); + return d->lifeSpanDev; +} + +void QmlGraphicsParticles::setLifeSpanDeviation(int dev) +{ + Q_D(QmlGraphicsParticles); + if(d->lifeSpanDev == dev) + return; + d->lifeSpanDev = dev; + emit lifeSpanDeviationChanged(); +} + +/*! + \qmlproperty int Particles::fadeInDuration + \qmlproperty int Particles::fadeOutDuration + These properties hold the time taken to fade the particles in and out. + + By default fade in is 200ms and fade out is 300ms. +*/ + +/*! + \property QmlGraphicsParticles::fadeInDuration + \brief the time taken to fade in the particles. + + Default value is 200ms. +*/ +int QmlGraphicsParticles::fadeInDuration() const +{ + Q_D(const QmlGraphicsParticles); + return d->fadeInDur; +} + +void QmlGraphicsParticles::setFadeInDuration(int dur) +{ + Q_D(QmlGraphicsParticles); + if (dur < 0.0 || dur == d->fadeInDur) + return; + d->fadeInDur = dur; + emit fadeInDurationChanged(); +} + +/*! + \property QmlGraphicsParticles::fadeOutDuration + \brief the time taken to fade out the particles. + + Default value is 300ms. +*/ +int QmlGraphicsParticles::fadeOutDuration() const +{ + Q_D(const QmlGraphicsParticles); + return d->fadeOutDur; +} + +void QmlGraphicsParticles::setFadeOutDuration(int dur) +{ + Q_D(QmlGraphicsParticles); + if (dur < 0.0 || d->fadeOutDur == dur) + return; + d->fadeOutDur = dur; + emit fadeOutDurationChanged(); +} + +/*! + \qmlproperty real Particles::angle + \qmlproperty real Particles::angleDeviation + + These properties control particle direction. + + angleDeviation randomly varies the direction up to the specified variation. For + example, the following creates particles whose initial direction will + vary from 15 degrees to 105 degrees: + + \qml +Particles { + source: "star.png" + angle: 60 + angleDeviation: 90 +} + \endqml +*/ + +/*! + \property QmlGraphicsParticles::angle + \brief the initial angle of direction. + + \sa QmlGraphicsParticles::angleDeviation +*/ +qreal QmlGraphicsParticles::angle() const +{ + Q_D(const QmlGraphicsParticles); + return d->angle * 180.0 / M_PI; +} + +void QmlGraphicsParticles::setAngle(qreal angle) +{ + Q_D(QmlGraphicsParticles); + qreal radAngle = angle * M_PI / 180.0; + if(radAngle == d->angle) + return; + d->angle = radAngle; + emit angleChanged(); +} + +/*! + \property QmlGraphicsParticles::angleDeviation + \brief the maximum possible deviation from the set angle. + + Randomly varies the direction up to the specified variation. For + example, the following creates particles whose initial direction will + vary from 15 degrees to 105 degrees: + +\qml +Particles { + source: "star.png" + angle: 60 + angleDeviation: 90 +} +\endqml + + \sa QmlGraphicsParticles::angle +*/ +qreal QmlGraphicsParticles::angleDeviation() const +{ + Q_D(const QmlGraphicsParticles); + return d->angleDev * 180.0 / M_PI; +} + +void QmlGraphicsParticles::setAngleDeviation(qreal dev) +{ + Q_D(QmlGraphicsParticles); + qreal radDev = dev * M_PI / 180.0; + if(radDev == d->angleDev) + return; + d->angleDev = radDev; + emit angleDeviationChanged(); +} + +/*! + \qmlproperty real Particles::velocity + \qmlproperty real Particles::velocityDeviation + + These properties control the velocity of the particles. + + velocityDeviation randomly varies the velocity up to the specified variation. For + example, the following creates particles whose initial velocity will + vary from 40 to 60. + + \qml +Particles { + source: "star.png" + velocity: 50 + velocityDeviation: 20 +} + \endqml +*/ + +/*! + \property QmlGraphicsParticles::velocity + \brief the initial velocity of the particles. + + \sa QmlGraphicsParticles::velocityDeviation +*/ +qreal QmlGraphicsParticles::velocity() const +{ + Q_D(const QmlGraphicsParticles); + return d->velocity * 1000.0; +} + +void QmlGraphicsParticles::setVelocity(qreal velocity) +{ + Q_D(QmlGraphicsParticles); + qreal realVel = velocity / 1000.0; + if(realVel == d->velocity) + return; + d->velocity = realVel; + emit velocityChanged(); +} + +/*! + \property QmlGraphicsParticles::velocityDeviation + \brief the maximum possible deviation from the set velocity. + + Randomly varies the velocity up to the specified variation. For + example, the following creates particles whose initial velocity will + vary from 40 to 60. + +\qml +Particles { + source: "star.png" + velocity: 50 + velocityDeviation: 20 +} +\endqml + + \sa QmlGraphicsParticles::velocity +*/ +qreal QmlGraphicsParticles::velocityDeviation() const +{ + Q_D(const QmlGraphicsParticles); + return d->velocityDev * 1000.0; +} + +void QmlGraphicsParticles::setVelocityDeviation(qreal velocity) +{ + Q_D(QmlGraphicsParticles); + qreal realDev = velocity / 1000.0; + if(realDev == d->velocityDev) + return; + d->velocityDev = realDev; + emit velocityDeviationChanged(); +} + +/*! + \qmlproperty ParticleMotion Particles::motion + This property sets the type of motion to apply to the particles. + + When a particle is created it will have an initial direction and velocity. + The motion of the particle during its lifeSpan is then influenced by the + motion property. + + Default motion is ParticleMotionLinear. +*/ + +/*! + \property QmlGraphicsParticles::motion + \brief sets the type of motion to apply to the particles. + + When a particle is created it will have an initial direction and velocity. + The motion of the particle during its lifeSpan is then influenced by the + motion property. + + Default motion is QmlGraphicsParticleMotionLinear. +*/ +QmlGraphicsParticleMotion *QmlGraphicsParticles::motion() const +{ + Q_D(const QmlGraphicsParticles); + return d->motion; +} + +void QmlGraphicsParticles::setMotion(QmlGraphicsParticleMotion *motion) +{ + Q_D(QmlGraphicsParticles); + d->motion = motion; +} + +/*! + \qmlmethod Particles::burst(int count, int emissionRate) + + Initiates a burst of particles. + + This method takes two arguments. The first argument is the number + of particles to emit and the second argument is the emissionRate for the + burst. If the second argument is omitted, it is treated as -1. The burst + of particles has a separate emissionRate and count to the normal emission of + particles. The burst uses the same values as normal emission for all other + properties, including emissionVariance. + + The normal emission of particles will continue during the burst, however + the particles created by the burst count towards the maximum number used by + normal emission. To avoid this behavior, use two Particles elements. + +*/ +void QmlGraphicsParticles::burst(int count, int emissionRate) +{ + Q_D(QmlGraphicsParticles); + d->bursts << qMakePair(count, emissionRate); + if (d->clock.state() != QAbstractAnimation::Running) + d->clock.start(); +} + +void QmlGraphicsParticlesPainter::updateSize() +{ + if (!isComponentComplete()) + return; + + const int parentX = parentItem()->x(); + const int parentY = parentItem()->y(); + for (int i = 0; i < d->particles.count(); ++i) { + const QmlGraphicsParticle &particle = d->particles.at(i); + if(particle.x > maxX) + maxX = particle.x; + if(particle.x < minX) + minX = particle.x; + if(particle.y > maxY) + maxY = particle.y; + if(particle.y < minY) + minY = particle.y; + } + + int myWidth = (int)(maxX-minX+0.5)+d->image.width(); + int myX = (int)(minX - parentX); + int myHeight = (int)(maxY-minY+0.5)+d->image.height(); + int myY = (int)(minY - parentY); + setWidth(myWidth); + setHeight(myHeight); + setX(myX); + setY(myY); +} + +void QmlGraphicsParticles::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + Q_UNUSED(p); + //painting is done by the ParticlesPainter, so it can have the right size +} + +void QmlGraphicsParticlesPainter::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + if (d->image.isNull() || d->particles.isEmpty()) + return; + + const int myX = x() + parentItem()->x(); + const int myY = y() + parentItem()->y(); + + QVarLengthArray<QDrawPixmaps::Data, 256> pixmapData; + pixmapData.resize(d->particles.count()); + + const QRectF sourceRect = d->image.rect(); + qreal halfPWidth = sourceRect.width()/2.; + qreal halfPHeight = sourceRect.height()/2.; + for (int i = 0; i < d->particles.count(); ++i) { + const QmlGraphicsParticle &particle = d->particles.at(i); + pixmapData[i].point = QPointF(particle.x - myX + halfPWidth, particle.y - myY + halfPHeight); + pixmapData[i].opacity = particle.opacity; + + //these never change + pixmapData[i].rotation = 0; + pixmapData[i].scaleX = 1; + pixmapData[i].scaleY = 1; + pixmapData[i].source = sourceRect; + } + qDrawPixmaps(p, pixmapData.data(), d->particles.count(), d->image); +} + +void QmlGraphicsParticles::componentComplete() +{ + Q_D(QmlGraphicsParticles); + QmlGraphicsItem::componentComplete(); + if (d->count) { + d->paintItem->updateSize(); + d->clock.start(); + } + if (d->lifeSpanDev > d->lifeSpan) + d->lifeSpanDev = d->lifeSpan; +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsparticles_p.h b/src/declarative/graphicsitems/qmlgraphicsparticles_p.h new file mode 100644 index 0000000..c34d55b --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsparticles_p.h @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSPARTICLES_H +#define QMLGRAPHICSPARTICLES_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsParticle; +class QmlGraphicsParticles; +class Q_DECLARATIVE_EXPORT QmlGraphicsParticleMotion : public QObject +{ + Q_OBJECT +public: + QmlGraphicsParticleMotion(QObject *parent=0); + + virtual void advance(QmlGraphicsParticle &, int interval); + virtual void created(QmlGraphicsParticle &); + virtual void destroy(QmlGraphicsParticle &); +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsParticleMotionLinear : public QmlGraphicsParticleMotion +{ + Q_OBJECT +public: + QmlGraphicsParticleMotionLinear(QObject *parent=0) + : QmlGraphicsParticleMotion(parent) {} + + virtual void advance(QmlGraphicsParticle &, int interval); +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsParticleMotionGravity : public QmlGraphicsParticleMotion +{ + Q_OBJECT + + Q_PROPERTY(int xattractor READ xAttractor WRITE setXAttractor) + Q_PROPERTY(int yattractor READ yAttractor WRITE setYAttractor) + Q_PROPERTY(int acceleration READ acceleration WRITE setAcceleration) +public: + QmlGraphicsParticleMotionGravity(QObject *parent=0) + : QmlGraphicsParticleMotion(parent), _xAttr(0), _yAttr(0), _accel(0.00005) {} + + int xAttractor() const { return _xAttr; } + void setXAttractor(int x) { _xAttr = x; } + + int yAttractor() const { return _yAttr; } + void setYAttractor(int y) { _yAttr = y; } + + int acceleration() const { return int(_accel * 1000000); } + void setAcceleration(int accel) { _accel = qreal(accel)/1000000.0; } + + virtual void advance(QmlGraphicsParticle &, int interval); + +private: + int _xAttr; + int _yAttr; + qreal _accel; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsParticleMotionWander : public QmlGraphicsParticleMotion +{ + Q_OBJECT +public: + QmlGraphicsParticleMotionWander() + : QmlGraphicsParticleMotion(), particles(0), _xvariance(0), _yvariance(0) {} + + virtual void advance(QmlGraphicsParticle &, int interval); + virtual void created(QmlGraphicsParticle &); + virtual void destroy(QmlGraphicsParticle &); + + struct Data { + qreal x_targetV; + qreal y_targetV; + qreal x_peak; + qreal y_peak; + qreal x_var; + qreal y_var; + }; + + Q_PROPERTY(int xvariance READ xVariance WRITE setXVariance) + int xVariance() const { return int(_xvariance * 1000); } + void setXVariance(int var) { _xvariance = var / 1000.0; } + + Q_PROPERTY(int yvariance READ yVariance WRITE setYVariance) + int yVariance() const { return int(_yvariance * 1000); } + void setYVariance(int var) { _yvariance = var / 1000.0; } + + Q_PROPERTY(int pace READ pace WRITE setPace) + int pace() const { return int(_pace * 1000); } + void setPace(int pace) { _pace = pace / 1000.0; } + +private: + QmlGraphicsParticles *particles; + qreal _xvariance; + qreal _yvariance; + qreal _pace; +}; + +class QmlGraphicsParticlesPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsParticles : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged) + Q_PROPERTY(int emissionRate READ emissionRate WRITE setEmissionRate NOTIFY emissionRateChanged) + Q_PROPERTY(qreal emissionVariance READ emissionVariance WRITE setEmissionVariance NOTIFY emissionVarianceChanged) + Q_PROPERTY(int lifeSpan READ lifeSpan WRITE setLifeSpan NOTIFY lifeSpanChanged) + Q_PROPERTY(int lifeSpanDeviation READ lifeSpanDeviation WRITE setLifeSpanDeviation NOTIFY lifeSpanDeviationChanged) + Q_PROPERTY(int fadeInDuration READ fadeInDuration WRITE setFadeInDuration NOTIFY fadeInDurationChanged) + Q_PROPERTY(int fadeOutDuration READ fadeOutDuration WRITE setFadeOutDuration NOTIFY fadeOutDurationChanged) + Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged) + Q_PROPERTY(qreal angleDeviation READ angleDeviation WRITE setAngleDeviation NOTIFY angleDeviationChanged) + Q_PROPERTY(qreal velocity READ velocity WRITE setVelocity NOTIFY velocityChanged) + Q_PROPERTY(qreal velocityDeviation READ velocityDeviation WRITE setVelocityDeviation NOTIFY velocityDeviationChanged) + Q_PROPERTY(QmlGraphicsParticleMotion *motion READ motion WRITE setMotion) + Q_CLASSINFO("DefaultProperty", "motion") + +public: + QmlGraphicsParticles(QmlGraphicsItem *parent=0); + ~QmlGraphicsParticles(); + + QUrl source() const; + void setSource(const QUrl &); + + int count() const; + void setCount(int cnt); + + int emissionRate() const; + void setEmissionRate(int); + + qreal emissionVariance() const; + void setEmissionVariance(qreal); + + int lifeSpan() const; + void setLifeSpan(int); + + int lifeSpanDeviation() const; + void setLifeSpanDeviation(int); + + int fadeInDuration() const; + void setFadeInDuration(int); + + int fadeOutDuration() const; + void setFadeOutDuration(int); + + qreal angle() const; + void setAngle(qreal); + + qreal angleDeviation() const; + void setAngleDeviation(qreal); + + qreal velocity() const; + void setVelocity(qreal); + + qreal velocityDeviation() const; + void setVelocityDeviation(qreal); + + QmlGraphicsParticleMotion *motion() const; + void setMotion(QmlGraphicsParticleMotion *); + + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + +public Q_SLOTS: + void burst(int count, int emissionRate=-1); + +protected: + virtual void componentComplete(); + +Q_SIGNALS: + void sourceChanged(); + void countChanged(); + void emissionRateChanged(); + void emissionVarianceChanged(); + void lifeSpanChanged(); + void lifeSpanDeviationChanged(); + void fadeInDurationChanged(); + void fadeOutDurationChanged(); + void angleChanged(); + void angleDeviationChanged(); + void velocityChanged(); + void velocityDeviationChanged(); + void emittingChanged(); + +private Q_SLOTS: + void imageLoaded(); + +private: + Q_DISABLE_COPY(QmlGraphicsParticles) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsParticles) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsParticleMotion) +QML_DECLARE_TYPE(QmlGraphicsParticleMotionLinear) +QML_DECLARE_TYPE(QmlGraphicsParticleMotionGravity) +QML_DECLARE_TYPE(QmlGraphicsParticleMotionWander) +QML_DECLARE_TYPE(QmlGraphicsParticles) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicspath.cpp b/src/declarative/graphicsitems/qmlgraphicspath.cpp new file mode 100644 index 0000000..18f27af --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspath.cpp @@ -0,0 +1,850 @@ +/**************************************************************************** +** +** 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 "qmlgraphicspath_p.h" +#include "qmlgraphicspath_p_p.h" + +#include <qfxperf_p_p.h> + +#include <QSet> + +#include <private/qbezier_p.h> + +QT_BEGIN_NAMESPACE +QML_DEFINE_TYPE(Qt,4,6,Path,QmlGraphicsPath) +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsPathElement) +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsCurve) +QML_DEFINE_TYPE(Qt,4,6,PathAttribute,QmlGraphicsPathAttribute) +QML_DEFINE_TYPE(Qt,4,6,PathPercent,QmlGraphicsPathPercent) +QML_DEFINE_TYPE(Qt,4,6,PathLine,QmlGraphicsPathLine) +QML_DEFINE_TYPE(Qt,4,6,PathQuad,QmlGraphicsPathQuad) +QML_DEFINE_TYPE(Qt,4,6,PathCubic,QmlGraphicsPathCubic) + +/*! + \qmlclass PathElement QmlGraphicsPathElement + \brief PathElement is the base path type. + + This type is the base for all path types. It cannot + be instantiated. + + \sa Path, PathAttribute, PathPercent, PathLine, PathQuad, PathCubic +*/ + +/*! + \internal + \class QmlGraphicsPathElement + \ingroup group_utility +*/ + +/*! + \qmlclass Path QmlGraphicsPath + \brief A Path object defines a path for use by \l PathView. + + A Path is composed of one or more path segments - PathLine, PathQuad, + PathCubic. + + The spacing of the items along the Path can be adjusted via a + PathPercent object. + + PathAttribute allows named attributes with values to be defined + along the path. + + \sa PathView, PathAttribute, PathPercent, PathLine, PathQuad, PathCubic +*/ + +/*! + \internal + \class QmlGraphicsPath + \ingroup group_utility + \brief The QmlGraphicsPath class defines a path. + \sa QmlGraphicsPathView +*/ +QmlGraphicsPath::QmlGraphicsPath(QObject *parent) + : QObject(*(new QmlGraphicsPathPrivate), parent) +{ +} + +QmlGraphicsPath::~QmlGraphicsPath() +{ +} + +/*! + \qmlproperty real Path::startX + \qmlproperty real Path::startY + These properties hold the starting position of the path. +*/ +qreal QmlGraphicsPath::startX() const +{ + Q_D(const QmlGraphicsPath); + return d->startX; +} + +void QmlGraphicsPath::setStartX(qreal x) +{ + Q_D(QmlGraphicsPath); + d->startX = x; +} + +qreal QmlGraphicsPath::startY() const +{ + Q_D(const QmlGraphicsPath); + return d->startY; +} + +void QmlGraphicsPath::setStartY(qreal y) +{ + Q_D(QmlGraphicsPath); + d->startY = y; +} + +/*! + \qmlproperty bool Path::closed + This property holds whether the start and end of the path are identical. +*/ +bool QmlGraphicsPath::isClosed() const +{ + Q_D(const QmlGraphicsPath); + return d->closed; +} + +/*! + \qmlproperty list<PathElement> Path::pathElements + This property holds the objects composing the path. + + \default + + A path can contain the following path objects: + \list + \i \l PathLine - a straight line to a given position. + \i \l PathQuad - a quadratic Bezier curve to a given position with a control point. + \i \l PathCubic - a cubic Bezier curve to a given position with two control points. + \i \l PathAttribute - an attribute at a given position in the path. + \i \l PathPercent - a way to spread out items along various segments of the path. + \endlist + + \snippet doc/src/snippets/declarative/pathview/pathattributes.qml 2 +*/ + +QList<QmlGraphicsPathElement *>* QmlGraphicsPath::pathElements() +{ + Q_D(QmlGraphicsPath); + return &(d->_pathElements); +} + +void QmlGraphicsPath::interpolate(int idx, const QString &name, qreal value) +{ + Q_D(QmlGraphicsPath); + if (!idx) + return; + + qreal lastValue = 0; + qreal lastPercent = 0; + int search = idx - 1; + while(search >= 0) { + const AttributePoint &point = d->_attributePoints.at(search); + if (point.values.contains(name)) { + lastValue = point.values.value(name); + lastPercent = point.origpercent; + break; + } + --search; + } + + ++search; + + const AttributePoint &curPoint = d->_attributePoints.at(idx); + + for (int ii = search; ii < idx; ++ii) { + AttributePoint &point = d->_attributePoints[ii]; + + qreal val = lastValue + (value - lastValue) * (point.origpercent - lastPercent) / (curPoint.origpercent - lastPercent); + point.values.insert(name, val); + } +} + +void QmlGraphicsPath::endpoint(const QString &name) +{ + Q_D(QmlGraphicsPath); + const AttributePoint &first = d->_attributePoints.first(); + qreal val = first.values.value(name); + for (int ii = d->_attributePoints.count() - 1; ii >= 0; ii--) { + const AttributePoint &point = d->_attributePoints.at(ii); + if (point.values.contains(name)) { + for (int jj = ii + 1; jj < d->_attributePoints.count(); ++jj) { + AttributePoint &setPoint = d->_attributePoints[jj]; + setPoint.values.insert(name, val); + } + return; + } + } +} + +void QmlGraphicsPath::processPath() +{ + Q_D(QmlGraphicsPath); + + d->_pointCache.clear(); + d->_attributePoints.clear(); + d->_path = QPainterPath(); + + AttributePoint first; + for (int ii = 0; ii < d->_attributes.count(); ++ii) + first.values[d->_attributes.at(ii)] = 0; + d->_attributePoints << first; + + d->_path.moveTo(d->startX, d->startY); + + QmlGraphicsCurve *lastCurve = 0; + foreach (QmlGraphicsPathElement *pathElement, d->_pathElements) { + if (QmlGraphicsCurve *curve = qobject_cast<QmlGraphicsCurve *>(pathElement)) { + curve->addToPath(d->_path); + AttributePoint p; + p.origpercent = d->_path.length(); + d->_attributePoints << p; + lastCurve = curve; + } else if (QmlGraphicsPathAttribute *attribute = qobject_cast<QmlGraphicsPathAttribute *>(pathElement)) { + AttributePoint &point = d->_attributePoints.last(); + point.values[attribute->name()] = attribute->value(); + interpolate(d->_attributePoints.count() - 1, attribute->name(), attribute->value()); + } else if (QmlGraphicsPathPercent *percent = qobject_cast<QmlGraphicsPathPercent *>(pathElement)) { + AttributePoint &point = d->_attributePoints.last(); + point.values[QLatin1String("_qfx_percent")] = percent->value(); + interpolate(d->_attributePoints.count() - 1, QLatin1String("_qfx_percent"), percent->value()); + } + } + + // Fixup end points + const AttributePoint &last = d->_attributePoints.last(); + for (int ii = 0; ii < d->_attributes.count(); ++ii) { + if (!last.values.contains(d->_attributes.at(ii))) + endpoint(d->_attributes.at(ii)); + } + + // Adjust percent + qreal length = d->_path.length(); + qreal prevpercent = 0; + qreal prevorigpercent = 0; + for (int ii = 0; ii < d->_attributePoints.count(); ++ii) { + const AttributePoint &point = d->_attributePoints.at(ii); + if (point.values.contains(QLatin1String("_qfx_percent"))) { //special string for QmlGraphicsPathPercent + if ( ii > 0) { + qreal scale = (d->_attributePoints[ii].origpercent/length - prevorigpercent) / + (point.values.value(QLatin1String("_qfx_percent"))-prevpercent); + d->_attributePoints[ii].scale = scale; + } + d->_attributePoints[ii].origpercent /= length; + d->_attributePoints[ii].percent = point.values.value(QLatin1String("_qfx_percent")); + prevorigpercent = d->_attributePoints[ii].origpercent; + prevpercent = d->_attributePoints[ii].percent; + } else { + d->_attributePoints[ii].origpercent /= length; + d->_attributePoints[ii].percent = d->_attributePoints[ii].origpercent; + } + } + + d->closed = lastCurve && d->startX == lastCurve->x() && d->startY == lastCurve->y(); + + emit changed(); +} + +void QmlGraphicsPath::componentComplete() +{ + Q_D(QmlGraphicsPath); + QSet<QString> attrs; + // First gather up all the attributes + foreach (QmlGraphicsPathElement *pathElement, d->_pathElements) { + if (QmlGraphicsPathAttribute *attribute = + qobject_cast<QmlGraphicsPathAttribute *>(pathElement)) + attrs.insert(attribute->name()); + } + d->_attributes = attrs.toList(); + + processPath(); + + foreach (QmlGraphicsPathElement *pathElement, d->_pathElements) + connect(pathElement, SIGNAL(changed()), this, SLOT(processPath())); +} + +QPainterPath QmlGraphicsPath::path() const +{ + Q_D(const QmlGraphicsPath); + return d->_path; +} + +QStringList QmlGraphicsPath::attributes() const +{ + Q_D(const QmlGraphicsPath); + return d->_attributes; +} +#include <QTime> + +static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bezLength) +{ + const int lastElement = path.elementCount() - 1; + for (int i=*from; i <= lastElement; ++i) { + const QPainterPath::Element &e = path.elementAt(i); + + switch (e.type) { + case QPainterPath::MoveToElement: + break; + case QPainterPath::LineToElement: + { + QLineF line(path.elementAt(i-1), e); + *bezLength = line.length(); + QPointF a = path.elementAt(i-1); + QPointF delta = e - a; + *from = i+1; + return QBezier::fromPoints(a, a + delta / 3, a + 2 * delta / 3, e); + } + case QPainterPath::CurveToElement: + { + QBezier b = QBezier::fromPoints(path.elementAt(i-1), + e, + path.elementAt(i+1), + path.elementAt(i+2)); + *bezLength = b.length(); + *from = i+3; + return b; + } + default: + break; + } + } + *from = lastElement; + *bezLength = 0; + return QBezier(); +} + +void QmlGraphicsPath::createPointCache() const +{ + Q_D(const QmlGraphicsPath); +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::QmlGraphicsPathViewPathCache> pc; +#endif + qreal pathLength = d->_path.length(); + const int points = int(pathLength*2); + const int lastElement = d->_path.elementCount() - 1; + d->_pointCache.resize(points+1); + + int currElement = 0; + qreal bezLength = 0; + QBezier currBez = nextBezier(d->_path, &currElement, &bezLength); + qreal currLength = bezLength; + qreal epc = currLength / pathLength; + + for (int i = 0; i < d->_pointCache.size(); i++) { + //find which set we are in + qreal prevPercent = 0; + qreal prevOrigPercent = 0; + for (int ii = 0; ii < d->_attributePoints.count(); ++ii) { + qreal percent = qreal(i)/points; + const AttributePoint &point = d->_attributePoints.at(ii); + if (percent < point.percent || ii == d->_attributePoints.count() - 1) { //### || is special case for very last item + qreal elementPercent = (percent - prevPercent); + + qreal spc = prevOrigPercent + elementPercent * point.scale; + + while (spc > epc) { + if (currElement > lastElement) + break; + currBez = nextBezier(d->_path, &currElement, &bezLength); + if (bezLength == 0.0) { + currLength = pathLength; + epc = 1.0; + break; + } + currLength += bezLength; + epc = currLength / pathLength; + } + qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength; + d->_pointCache[i] = currBez.pointAt(qBound(qreal(0), realT, qreal(1))); + break; + } + prevOrigPercent = point.origpercent; + prevPercent = point.percent; + } + } +} + +QPointF QmlGraphicsPath::pointAt(qreal p) const +{ + Q_D(const QmlGraphicsPath); + if (d->_pointCache.isEmpty()) { + createPointCache(); + } + int idx = qRound(p*d->_pointCache.size()); + if (idx >= d->_pointCache.size()) + idx = d->_pointCache.size() - 1; + else if (idx < 0) + idx = 0; + return d->_pointCache.at(idx); +} + +qreal QmlGraphicsPath::attributeAt(const QString &name, qreal percent) const +{ + Q_D(const QmlGraphicsPath); + if (percent < 0 || percent > 1) + return 0; + + for (int ii = 0; ii < d->_attributePoints.count(); ++ii) { + const AttributePoint &point = d->_attributePoints.at(ii); + + if (point.percent == percent) { + return point.values.value(name); + } else if (point.percent > percent) { + qreal lastValue = + ii?(d->_attributePoints.at(ii - 1).values.value(name)):0; + qreal lastPercent = + ii?(d->_attributePoints.at(ii - 1).percent):0; + qreal curValue = point.values.value(name); + qreal curPercent = point.percent; + + return lastValue + (curValue - lastValue) * (percent - lastPercent) / (curPercent - lastPercent); + } + } + + return 0; +} + +/****************************************************************************/ + +qreal QmlGraphicsCurve::x() const +{ + return _x; +} + +void QmlGraphicsCurve::setX(qreal x) +{ + if (_x != x) { + _x = x; + emit changed(); + } +} + +qreal QmlGraphicsCurve::y() const +{ + return _y; +} + +void QmlGraphicsCurve::setY(qreal y) +{ + if (_y != y) { + _y = y; + emit changed(); + } +} + +/****************************************************************************/ + +/*! + \qmlclass PathAttribute QmlGraphicsPathAttribute + \brief The PathAttribute allows setting an attribute at a given position in a Path. + + The PathAttribute object allows attibutes consisting of a name and + a value to be specified for the endpoints of path segments. The + attributes are exposed to the delegate as + \l{qmlintroduction.html#attached-properties} {Attached Properties}. + The value of an attribute at any particular point is interpolated + from the PathAttributes bounding the point. + + The example below shows a path with the items scaled to 30% with + opacity 50% at the top of the path and scaled 100% with opacity + 100% at the bottom. Note the use of the PathView.scale and + PathView.opacity attached properties to set the scale and opacity + of the delegate. + + \table + \row + \o \image declarative-pathattribute.png + \o + \snippet doc/src/snippets/declarative/pathview/pathattributes.qml 0 + \endtable + + \sa Path +*/ + +/*! + \internal + \class QmlGraphicsPathAttribute + \ingroup group_utility + \brief The QmlGraphicsPathAttribute class allows to set the value of an attribute at a given position in the path. + + \sa QmlGraphicsPath +*/ + + +/*! + \qmlproperty string PathAttribute::name + the name of the attribute to change. +*/ + +/*! + the name of the attribute to change. +*/ + +QString QmlGraphicsPathAttribute::name() const +{ + return _name; +} + +void QmlGraphicsPathAttribute::setName(const QString &name) +{ + _name = name; +} + +/*! + \qmlproperty string PathAttribute::value + the new value of the attribute. +*/ + +/*! + the new value of the attribute. +*/ +qreal QmlGraphicsPathAttribute::value() const +{ + return _value; +} + +void QmlGraphicsPathAttribute::setValue(qreal value) +{ + if (_value != value) { + _value = value; + emit changed(); + } +} + +/****************************************************************************/ + +/*! + \qmlclass PathLine QmlGraphicsPathLine + \brief The PathLine defines a straight line. + + The example below creates a path consisting of a straight line from + 0,100 to 200,100: + + \qml + Path { + startX: 0; startY: 100 + PathLine { x: 200; y: 100 } + } + \endqml + + \sa Path, PathQuad, PathCubic +*/ + +/*! + \internal + \class QmlGraphicsPathLine + \ingroup group_utility + \brief The QmlGraphicsPathLine class defines a straight line. + + \sa QmlGraphicsPath +*/ + +/*! + \qmlproperty real PathLine::x + \qmlproperty real PathLine::y + + Defines the end point of the line. +*/ + +void QmlGraphicsPathLine::addToPath(QPainterPath &path) +{ + path.lineTo(x(), y()); +} + +/****************************************************************************/ + +/*! + \qmlclass PathQuad QmlGraphicsPathQuad + \brief The PathQuad defines a quadratic Bezier curve with a control point. + + The following QML produces the path shown below: + \table + \row + \o \image declarative-pathquad.png + \o + \qml + Path { + startX: 0; startY: 0 + PathQuad x: 200; y: 0; controlX: 100; controlY: 150 } + } + \endqml + \endtable + + \sa Path, PathCubic, PathLine +*/ + +/*! + \internal + \class QmlGraphicsPathQuad + \ingroup group_utility + \brief The QmlGraphicsPathQuad class defines a quadratic Bezier curve with a control point. + + \sa QmlGraphicsPath +*/ + + +/*! + \qmlproperty real PathQuad::x + \qmlproperty real PathQuad::y + + Defines the end point of the curve. +*/ + +/*! + \qmlproperty real PathQuad::controlX + \qmlproperty real PathQuad::controlY + + Defines the position of the control point. +*/ + +/*! + the x position of the control point. +*/ +qreal QmlGraphicsPathQuad::controlX() const +{ + return _controlX; +} + +void QmlGraphicsPathQuad::setControlX(qreal x) +{ + if (_controlX != x) { + _controlX = x; + emit changed(); + } +} + + +/*! + the y position of the control point. +*/ +qreal QmlGraphicsPathQuad::controlY() const +{ + return _controlY; +} + +void QmlGraphicsPathQuad::setControlY(qreal y) +{ + if (_controlY != y) { + _controlY = y; + emit changed(); + } +} + +void QmlGraphicsPathQuad::addToPath(QPainterPath &path) +{ + path.quadTo(controlX(), controlY(), x(), y()); +} + +/****************************************************************************/ + +/*! + \qmlclass PathCubic QmlGraphicsPathCubic + \brief The PathCubic defines a cubic Bezier curve with two control points. + + The following QML produces the path shown below: + \table + \row + \o \image declarative-pathcubic.png + \o + \qml + Path { + startX: 20; startY: 0 + PathCubic { + x: 180; y: 0; control1X: -10; control1Y: 90 + control2X: 210; control2Y: 90 + } + } + \endqml + \endtable + + \sa Path, PathQuad, PathLine +*/ + +/*! + \internal + \class QmlGraphicsPathCubic + \ingroup group_utility + \brief The QmlGraphicsPathCubic class defines a cubic Bezier curve with two control points. + + \sa QmlGraphicsPath +*/ + +/*! + \qmlproperty real PathCubic::x + \qmlproperty real PathCubic::y + + Defines the end point of the curve. +*/ + +/*! + \qmlproperty real PathCubic::control1X + \qmlproperty real PathCubic::control1Y + + Defines the position of the first control point. +*/ +qreal QmlGraphicsPathCubic::control1X() const +{ + return _control1X; +} + +void QmlGraphicsPathCubic::setControl1X(qreal x) +{ + if (_control1X != x) { + _control1X = x; + emit changed(); + } +} + +qreal QmlGraphicsPathCubic::control1Y() const +{ + return _control1Y; +} + +void QmlGraphicsPathCubic::setControl1Y(qreal y) +{ + if (_control1Y != y) { + _control1Y = y; + emit changed(); + } +} + +/*! + \qmlproperty real PathCubic::control2X + \qmlproperty real PathCubic::control2Y + + Defines the position of the second control point. +*/ +qreal QmlGraphicsPathCubic::control2X() const +{ + return _control2X; +} + +void QmlGraphicsPathCubic::setControl2X(qreal x) +{ + if (_control2X != x) { + _control2X = x; + emit changed(); + } +} + +qreal QmlGraphicsPathCubic::control2Y() const +{ + return _control2Y; +} + +void QmlGraphicsPathCubic::setControl2Y(qreal y) +{ + if (_control2Y != y) { + _control2Y = y; + emit changed(); + } +} + +void QmlGraphicsPathCubic::addToPath(QPainterPath &path) +{ + path.cubicTo(control1X(), control1Y(), control2X(), control2Y(), x(), y()); +} + +/****************************************************************************/ + +/*! + \qmlclass PathPercent QmlGraphicsPathPercent + \brief The PathPercent manipulates the way a path is interpreted. + + The examples below show the normal distrubution of items along a path + compared to a distribution which places 50% of the items along the + PathLine section of the path. + \table + \row + \o \image declarative-nopercent.png + \o + \qml + Path { + startX: 20; startY: 0 + PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 } + PathLine { x: 150; y: 80 } + PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 } + } + \endqml + \row + \o \image declarative-percent.png + \o + \qml + Path { + startX: 20; startY: 0 + PathQuad { x: 50; y: 80; controlX: 0; controlY: 80 } + PathPercent { value: 0.25 } + PathLine { x: 150; y: 80 } + PathPercent { value: 0.75 } + PathQuad { x: 180; y: 0; controlX: 200; controlY: 80 } + PathPercent { value: 1 } + } + \endqml + \endtable + + \sa Path +*/ + +/*! + \internal + \class QmlGraphicsPathPercent + \ingroup group_utility + \brief The QmlGraphicsPathPercent class manipulates the way a path is interpreted. + + QmlGraphicsPathPercent allows you to bunch up items (or spread out items) along various + segments of a QmlGraphicsPathView's path. + + \sa QmlGraphicsPath + +*/ + +qreal QmlGraphicsPathPercent::value() const +{ + return _value; +} + +void QmlGraphicsPathPercent::setValue(qreal value) +{ + _value = value; +} +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicspath_p.h b/src/declarative/graphicsitems/qmlgraphicspath_p.h new file mode 100644 index 0000000..2b4b0fd --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspath_p.h @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSPATH_H +#define QMLGRAPHICSPATH_H + +#include "qmlgraphicsitem.h" + +#include <qml.h> + +#include <QtCore/QObject> +#include <QtGui/QPainterPath> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class Q_DECLARATIVE_EXPORT QmlGraphicsPathElement : public QObject +{ + Q_OBJECT +public: + QmlGraphicsPathElement(QObject *parent=0) : QObject(parent) {} +Q_SIGNALS: + void changed(); +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsPathAttribute : public QmlGraphicsPathElement +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY changed) +public: + QmlGraphicsPathAttribute(QObject *parent=0) : QmlGraphicsPathElement(parent), _value(0) {} + + + QString name() const; + void setName(const QString &name); + + qreal value() const; + void setValue(qreal value); + +private: + QString _name; + qreal _value; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsCurve : public QmlGraphicsPathElement +{ + Q_OBJECT + + Q_PROPERTY(qreal x READ x WRITE setX NOTIFY changed) + Q_PROPERTY(qreal y READ y WRITE setY NOTIFY changed) +public: + QmlGraphicsCurve(QObject *parent=0) : QmlGraphicsPathElement(parent), _x(0), _y(0) {} + + qreal x() const; + void setX(qreal x); + + qreal y() const; + void setY(qreal y); + + virtual void addToPath(QPainterPath &) {} + +private: + qreal _x; + qreal _y; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsPathLine : public QmlGraphicsCurve +{ + Q_OBJECT +public: + QmlGraphicsPathLine(QObject *parent=0) : QmlGraphicsCurve(parent) {} + + void addToPath(QPainterPath &path); +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsPathQuad : public QmlGraphicsCurve +{ + Q_OBJECT + + Q_PROPERTY(qreal controlX READ controlX WRITE setControlX NOTIFY changed) + Q_PROPERTY(qreal controlY READ controlY WRITE setControlY NOTIFY changed) +public: + QmlGraphicsPathQuad(QObject *parent=0) : QmlGraphicsCurve(parent), _controlX(0), _controlY(0) {} + + qreal controlX() const; + void setControlX(qreal x); + + qreal controlY() const; + void setControlY(qreal y); + + void addToPath(QPainterPath &path); + +private: + qreal _controlX; + qreal _controlY; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsPathCubic : public QmlGraphicsCurve +{ + Q_OBJECT + + Q_PROPERTY(qreal control1X READ control1X WRITE setControl1X NOTIFY changed) + Q_PROPERTY(qreal control1Y READ control1Y WRITE setControl1Y NOTIFY changed) + Q_PROPERTY(qreal control2X READ control2X WRITE setControl2X NOTIFY changed) + Q_PROPERTY(qreal control2Y READ control2Y WRITE setControl2Y NOTIFY changed) +public: + QmlGraphicsPathCubic(QObject *parent=0) : QmlGraphicsCurve(parent), _control1X(0), _control1Y(0), _control2X(0), _control2Y(0) {} + + qreal control1X() const; + void setControl1X(qreal x); + + qreal control1Y() const; + void setControl1Y(qreal y); + + qreal control2X() const; + void setControl2X(qreal x); + + qreal control2Y() const; + void setControl2Y(qreal y); + + void addToPath(QPainterPath &path); + +private: + int _control1X; + int _control1Y; + int _control2X; + int _control2Y; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsPathPercent : public QmlGraphicsPathElement +{ + Q_OBJECT + Q_PROPERTY(qreal value READ value WRITE setValue) +public: + QmlGraphicsPathPercent(QObject *parent=0) : QmlGraphicsPathElement(parent) {} + + qreal value() const; + void setValue(qreal value); + +private: + qreal _value; +}; + +class QmlGraphicsPathPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsPath : public QObject, public QmlParserStatus +{ + Q_OBJECT + + Q_INTERFACES(QmlParserStatus) + Q_PROPERTY(QList<QmlGraphicsPathElement *>* pathElements READ pathElements) + Q_PROPERTY(qreal startX READ startX WRITE setStartX) + Q_PROPERTY(qreal startY READ startY WRITE setStartY) + Q_PROPERTY(bool closed READ isClosed NOTIFY changed) + Q_CLASSINFO("DefaultProperty", "pathElements") + Q_INTERFACES(QmlParserStatus) +public: + QmlGraphicsPath(QObject *parent=0); + ~QmlGraphicsPath(); + + QList<QmlGraphicsPathElement *>* pathElements(); + + qreal startX() const; + void setStartX(qreal x); + + qreal startY() const; + void setStartY(qreal y); + + bool isClosed() const; + + QPainterPath path() const; + QStringList attributes() const; + qreal attributeAt(const QString &, qreal) const; + QPointF pointAt(qreal) const; + +Q_SIGNALS: + void changed(); + +protected: + virtual void componentComplete(); + +private Q_SLOTS: + void processPath(); + +private: + struct AttributePoint { + AttributePoint() : percent(0), scale(1), origpercent(0) {} + AttributePoint(const AttributePoint &other) + : percent(other.percent), scale(other.scale), origpercent(other.origpercent), values(other.values) {} + AttributePoint &operator=(const AttributePoint &other) { + percent = other.percent; scale = other.scale; origpercent = other.origpercent; values = other.values; return *this; + } + qreal percent; //massaged percent along the painter path + qreal scale; + qreal origpercent; //'real' percent along the painter path + QHash<QString, qreal> values; + }; + + void interpolate(int idx, const QString &name, qreal value); + void endpoint(const QString &name); + void createPointCache() const; + +private: + Q_DISABLE_COPY(QmlGraphicsPath) + Q_DECLARE_PRIVATE(QmlGraphicsPath) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsPathElement) +QML_DECLARE_TYPE(QmlGraphicsPathAttribute) +QML_DECLARE_TYPE(QmlGraphicsCurve) +QML_DECLARE_TYPE(QmlGraphicsPathLine) +QML_DECLARE_TYPE(QmlGraphicsPathQuad) +QML_DECLARE_TYPE(QmlGraphicsPathCubic) +QML_DECLARE_TYPE(QmlGraphicsPathPercent) +QML_DECLARE_TYPE(QmlGraphicsPath) + +QT_END_HEADER + +#endif // QMLGRAPHICSPATH_H diff --git a/src/declarative/graphicsitems/qmlgraphicspath_p_p.h b/src/declarative/graphicsitems/qmlgraphicspath_p_p.h new file mode 100644 index 0000000..04342a8 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspath_p_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSPATH_P_H +#define QMLGRAPHICSPATH_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 "qmlgraphicspath_p.h" + +#include <qml.h> + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE +class QmlGraphicsPathPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsPath) + +public: + QmlGraphicsPathPrivate() : startX(0), startY(0), closed(false) { } + + QPainterPath _path; + QList<QmlGraphicsPathElement*> _pathElements; + mutable QVector<QPointF> _pointCache; + QList<QmlGraphicsPath::AttributePoint> _attributePoints; + QStringList _attributes; + int startX; + int startY; + bool closed; +}; + +QT_END_NAMESPACE +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicspathview.cpp b/src/declarative/graphicsitems/qmlgraphicspathview.cpp new file mode 100644 index 0000000..6718d25 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspathview.cpp @@ -0,0 +1,976 @@ +/**************************************************************************** +** +** 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 "qmlgraphicspathview_p.h" +#include "qmlgraphicspathview_p_p.h" + +#include <qmlstate_p.h> +#include <qmlopenmetaobject_p.h> + +#include <QDebug> +#include <QEvent> +#include <qlistmodelinterface_p.h> +#include <QGraphicsSceneEvent> + +#include <math.h> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_TYPE(Qt,4,6,PathView,QmlGraphicsPathView) + + +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); +} + + +class QmlGraphicsPathViewAttached : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool onPath READ isOnPath NOTIFY onPathChanged) +public: + QmlGraphicsPathViewAttached(QObject *parent) + : QObject(parent), mo(new QmlOpenMetaObject(this)), onPath(false) + { + } + + ~QmlGraphicsPathViewAttached() + { + QmlGraphicsPathView::attachedProperties.remove(parent()); + } + + QVariant value(const QByteArray &name) const + { + return mo->value(name); + } + void setValue(const QByteArray &name, const QVariant &val) + { + mo->setValue(name, val); + } + + bool isOnPath() const { return onPath; } + void setOnPath(bool on) { + if (on != onPath) { + onPath = on; + emit onPathChanged(); + } + } + +Q_SIGNALS: + void onPathChanged(); + +private: + QmlOpenMetaObject *mo; + bool onPath; +}; + + +QmlGraphicsItem *QmlGraphicsPathViewPrivate::getItem(int modelIndex) +{ + Q_Q(QmlGraphicsPathView); + requestedIndex = modelIndex; + QmlGraphicsItem *item = model->item(modelIndex); + if (item) { + if (QObject *obj = QmlGraphicsPathView::qmlAttachedProperties(item)) + static_cast<QmlGraphicsPathViewAttached *>(obj)->setOnPath(true); + item->setParentItem(q); + } + requestedIndex = -1; + return item; +} + +void QmlGraphicsPathViewPrivate::releaseItem(QmlGraphicsItem *item) +{ + if (!item || !model) + return; + if (QObject *obj = QmlGraphicsPathView::qmlAttachedProperties(item)) + static_cast<QmlGraphicsPathViewAttached *>(obj)->setOnPath(false); + if (model->release(item) == 0) { + if (QObject *obj = QmlGraphicsPathView::qmlAttachedProperties(item)) + static_cast<QmlGraphicsPathViewAttached *>(obj)->setOnPath(false); + } +} + +/*! + \qmlclass PathView QmlGraphicsPathView + \brief The PathView element lays out model-provided items on a path. + \inherits Item + + The model is typically provided by a QAbstractListModel "C++ model object", but can also be created directly in QML. + + The items are laid out along a path defined by a \l Path and may be flicked to scroll. + + \snippet doc/src/snippets/declarative/pathview/pathview.qml 0 + + \image pathview.gif + + \sa Path +*/ + +QmlGraphicsPathView::QmlGraphicsPathView(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsPathViewPrivate), parent) +{ + Q_D(QmlGraphicsPathView); + d->init(); +} + +QmlGraphicsPathView::~QmlGraphicsPathView() +{ + Q_D(QmlGraphicsPathView); + if (d->ownModel) + delete d->model; +} + +/*! + \qmlattachedproperty bool PathView::onPath + This attached property holds whether the item is currently on the path. + + If a pathItemCount has been set, it is possible that some items may + be instantiated, but not considered to be currently on the path. + Usually, these items would be set invisible, for example: + + \code + Component { + Rectangle { + visible: PathView.onPath + ... + } + } + \endcode + + It is attached to each instance of the delegate. +*/ + +/*! + \qmlproperty model PathView::model + This property holds the model providing data for the view. + + The model provides a set of data that is used to create the items for the view. + For large or dynamic datasets the model is usually provided by a C++ model object. + Models can also be created directly in XML, using the ListModel element. + + \sa {qmlmodels}{Data Models} +*/ +QVariant QmlGraphicsPathView::model() const +{ + Q_D(const QmlGraphicsPathView); + return d->modelVariant; +} + +void QmlGraphicsPathView::setModel(const QVariant &model) +{ + Q_D(QmlGraphicsPathView); + 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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + for (int i=0; i<d->items.count(); i++){ + QmlGraphicsItem *p = d->items[i]; + d->model->release(p); + } + d->items.clear(); + } + + d->modelVariant = model; + QObject *object = qvariant_cast<QObject*>(model); + QmlGraphicsVisualModel *vim = 0; + if (object && (vim = qobject_cast<QmlGraphicsVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + } + d->firstIndex = 0; + d->pathOffset = 0; + d->regenerate(); + d->fixOffset(); +} + +/*! + \qmlproperty int PathView::count + This property holds the number of items in the model. +*/ +int QmlGraphicsPathView::count() const +{ + Q_D(const QmlGraphicsPathView); + return d->model ? d->model->count() : 0; +} + +/*! + \qmlproperty Path PathView::path + \default + This property holds the path used to lay out the items. + For more information see the \l Path documentation. +*/ +QmlGraphicsPath *QmlGraphicsPathView::path() const +{ + Q_D(const QmlGraphicsPathView); + return d->path; +} + +void QmlGraphicsPathView::setPath(QmlGraphicsPath *path) +{ + Q_D(QmlGraphicsPathView); + d->path = path; + connect(d->path, SIGNAL(changed()), this, SLOT(refill())); + d->regenerate(); +} + +/*! + \qmlproperty int PathView::currentIndex + This property holds the index of the current item. +*/ +int QmlGraphicsPathView::currentIndex() const +{ + Q_D(const QmlGraphicsPathView); + return d->currentIndex; +} + +void QmlGraphicsPathView::setCurrentIndex(int idx) +{ + Q_D(QmlGraphicsPathView); + if (d->model && d->model->count()) + idx = qAbs(idx % d->model->count()); + if (d->model && idx != d->currentIndex) { + d->currentIndex = idx; + if (d->model->count()) { + d->snapToCurrent(); + int itemIndex = (idx - d->firstIndex + d->model->count()) % d->model->count(); + if (itemIndex < d->items.count()) + d->items.at(itemIndex)->setFocus(true); + } + emit currentIndexChanged(); + } +} + +/*! + \qmlproperty real PathView::offset + + The offset specifies how far along the path the items are from their initial positions. +*/ +qreal QmlGraphicsPathView::offset() const +{ + Q_D(const QmlGraphicsPathView); + return d->_offset; +} + +void QmlGraphicsPathView::setOffset(qreal offset) +{ + Q_D(QmlGraphicsPathView); + d->setOffset(offset); + d->updateCurrent(); +} + +void QmlGraphicsPathViewPrivate::setOffset(qreal o) +{ + Q_Q(QmlGraphicsPathView); + if (_offset != o) { + _offset = qmlMod(o, qreal(100.0)); + if (_offset < 0) + _offset = 100.0 + _offset; + q->refill(); + } +} + +/*! + \qmlproperty real PathView::snapPosition + + This property determines the position (0-100) the nearest item will snap to. +*/ +qreal QmlGraphicsPathView::snapPosition() const +{ + Q_D(const QmlGraphicsPathView); + return d->snapPos; +} + +void QmlGraphicsPathView::setSnapPosition(qreal pos) +{ + Q_D(QmlGraphicsPathView); + d->snapPos = pos/100; + d->fixOffset(); +} + +/*! + \qmlproperty real PathView::dragMargin + This property holds the maximum distance from the path that initiate mouse dragging. + + By default the path can only be dragged by clicking on an item. If + dragMargin is greater than zero, a drag can be initiated by clicking + within dragMargin pixels of the path. +*/ +qreal QmlGraphicsPathView::dragMargin() const +{ + Q_D(const QmlGraphicsPathView); + return d->dragMargin; +} + +void QmlGraphicsPathView::setDragMargin(qreal dragMargin) +{ + Q_D(QmlGraphicsPathView); + d->dragMargin = dragMargin; +} + +/*! + \qmlproperty component PathView::delegate + + The delegate provides a template defining each item instantiated by the view. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + + Note that the PathView will layout the items based on the size of the root + item in the delegate. + + Here is an example delegate: + \snippet doc/src/snippets/declarative/pathview/pathview.qml 1 +*/ +QmlComponent *QmlGraphicsPathView::delegate() const +{ + Q_D(const QmlGraphicsPathView); + if (d->model) { + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QmlGraphicsPathView::setDelegate(QmlComponent *c) +{ + Q_D(QmlGraphicsPathView); + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) { + dataModel->setDelegate(c); + d->regenerate(); + } +} + +/*! + \qmlproperty int PathView::pathItemCount + This property holds the number of items visible on the path at any one time +*/ +int QmlGraphicsPathView::pathItemCount() const +{ + Q_D(const QmlGraphicsPathView); + return d->pathItems; +} + +void QmlGraphicsPathView::setPathItemCount(int i) +{ + Q_D(QmlGraphicsPathView); + if (i == d->pathItems) + return; + d->pathItems = i; + d->regenerate(); +} + +QPointF QmlGraphicsPathViewPrivate::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 / 10.0; + + return nearPoint; +} + + +void QmlGraphicsPathView::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsPathView); + if (!d->items.count()) + return; + QPointF scenePoint = mapToScene(event->pos()); + int idx = 0; + for (; idx < d->items.count(); ++idx) { + QRectF rect = d->items.at(idx)->boundingRect(); + rect = d->items.at(idx)->mapToScene(rect).boundingRect(); + if (rect.contains(scenePoint)) + break; + } + if (idx == d->items.count() && d->dragMargin == 0.) // didn't click on an item + return; + + d->startPoint = d->pointNear(event->pos(), &d->startPc); + if (idx == d->items.count()) { + qreal distance = qAbs(event->pos().x() - d->startPoint.x()) + qAbs(event->pos().y() - d->startPoint.y()); + if (distance > d->dragMargin) + return; + } + + d->stealMouse = false; + d->lastElapsed = 0; + d->lastDist = 0; + QmlGraphicsItemPrivate::start(d->lastPosTime); + d->tl.clear(); +} + +void QmlGraphicsPathView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsPathView); + if (d->lastPosTime.isNull()) + return; + + if (!d->stealMouse) { + QPointF delta = event->pos() - d->startPoint; + if (qAbs(delta.x()) > QApplication::startDragDistance() && qAbs(delta.y()) > QApplication::startDragDistance()) + d->stealMouse = true; + } + + if (d->stealMouse) { + d->moveReason = QmlGraphicsPathViewPrivate::Mouse; + qreal newPc; + d->pointNear(event->pos(), &newPc); + qreal diff = newPc - d->startPc; + if (diff) { + setOffset(d->_offset + diff); + + if (diff > 50) + diff -= 100; + else if (diff < -50) + diff += 100; + + d->lastElapsed = QmlGraphicsItemPrivate::restart(d->lastPosTime); + d->lastDist = diff; + d->startPc = newPc; + } + } +} + +void QmlGraphicsPathView::mouseReleaseEvent(QGraphicsSceneMouseEvent *) +{ + Q_D(QmlGraphicsPathView); + if (d->lastPosTime.isNull()) + return; + + qreal elapsed = qreal(d->lastElapsed + QmlGraphicsItemPrivate::elapsed(d->lastPosTime)) / 1000.; + qreal velocity = elapsed > 0. ? d->lastDist / elapsed : 0; + if (d->model && d->model->count() && qAbs(velocity) > 5) { + if (velocity > 100) + velocity = 100; + else if (velocity < -100) + velocity = -100; + qreal inc = qmlMod(d->_offset - d->snapPos, qreal(100.0 / d->model->count())); + qreal dist = qAbs(velocity/2 - qmlMod(velocity/2, qreal(100.0 / d->model->count()) - inc)); + d->moveOffset.setValue(d->_offset); + d->tl.accel(d->moveOffset, velocity, 10, dist); + d->tl.execute(d->fixupOffsetEvent); + } else { + d->fixOffset(); + } + + d->lastPosTime = QTime(); + d->stealMouse = false; + ungrabMouse(); +} + +bool QmlGraphicsPathView::sendMouseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsPathView); + QGraphicsSceneMouseEvent mouseEvent(event->type()); + QRectF myRect = mapToScene(QRectF(0, 0, width(), height())).boundingRect(); + QGraphicsScene *s = scene(); + QmlGraphicsItem *grabber = s ? qobject_cast<QmlGraphicsItem*>(s->mouseGrabberItem()) : 0; + if ((d->stealMouse || 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 = qobject_cast<QmlGraphicsItem*>(s->mouseGrabberItem()); + if (grabber && d->stealMouse && !grabber->keepMouseGrab() && grabber != this) + grabMouse(); + + return d->stealMouse; + } else if (!d->lastPosTime.isNull()) { + d->lastPosTime = QTime(); + } + return false; +} + +bool QmlGraphicsPathView::sceneEventFilter(QGraphicsItem *i, QEvent *e) +{ + if (!isVisible()) + return QmlGraphicsItem::sceneEventFilter(i, e); + + switch (e->type()) { + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseMove: + case QEvent::GraphicsSceneMouseRelease: + { + bool ret = sendMouseEvent(static_cast<QGraphicsSceneMouseEvent *>(e)); + if (e->type() == QEvent::GraphicsSceneMouseRelease) + return ret; + break; + } + default: + break; + } + + return QmlGraphicsItem::sceneEventFilter(i, e); +} + +void QmlGraphicsPathView::componentComplete() +{ + Q_D(QmlGraphicsPathView); + QmlGraphicsItem::componentComplete(); + d->regenerate(); + + // move to correct offset + if (d->items.count()) { + int itemIndex = (d->currentIndex - d->firstIndex + d->model->count()) % d->model->count(); + + itemIndex += d->pathOffset; + itemIndex %= d->items.count(); + qreal targetOffset = qmlMod(100 + (d->snapPos*100) - 100.0 * itemIndex / d->items.count(), qreal(100.0)); + + if (targetOffset < 0) + targetOffset = 100.0 + targetOffset; + if (targetOffset != d->_offset) { + d->moveOffset.setValue(targetOffset); + } + } +} + +void QmlGraphicsPathViewPrivate::regenerate() +{ + Q_Q(QmlGraphicsPathView); + if (!q->isComponentComplete()) + return; + + for (int i=0; i<items.count(); i++){ + QmlGraphicsItem *p = items[i]; + releaseItem(p); + } + items.clear(); + + if (!isValid()) + return; + + if (firstIndex >= model->count()) + firstIndex = model->count()-1; + if (pathOffset >= model->count()) + pathOffset = model->count()-1; + + int numItems = pathItems >= 0 ? pathItems : model->count(); + for (int i=0; i < numItems && i < model->count(); ++i){ + int index = (i + firstIndex) % model->count(); + QmlGraphicsItem *item = getItem(index); + if (!item) { + qWarning() << "PathView: Cannot create item, index" << (i + firstIndex) % model->count(); + return; + } + items.append(item); + item->setZValue(i); + if (currentIndex == index) + item->setFocus(true); + } + q->refill(); +} + +void QmlGraphicsPathViewPrivate::updateItem(QmlGraphicsItem *item, qreal percent) +{ + if (QObject *obj = QmlGraphicsPathView::qmlAttachedProperties(item)) { + foreach(const QString &attr, path->attributes()) + static_cast<QmlGraphicsPathViewAttached *>(obj)->setValue(attr.toUtf8(), path->attributeAt(attr, percent)); + } + QPointF pf = path->pointAt(percent); + item->setX(pf.x() - item->width()*item->scale()/2); + item->setY(pf.y() - item->height()*item->scale()/2); +} + +void QmlGraphicsPathView::refill() +{ + Q_D(QmlGraphicsPathView); + if (!d->isValid() || !isComponentComplete()) + return; + + QList<qreal> positions; + for (int i=0; i<d->items.count(); i++){ + qreal percent = i * (100. / d->items.count()); + percent = percent + d->_offset; + percent = qmlMod(percent, qreal(100.0)); + positions << qAbs(percent/100.0); + } + + if (d->pathItems==-1) { + for (int i=0; i<positions.count(); i++) + d->updateItem(d->items.at(i), positions[i]); + return; + } + + QList<qreal> rotatedPositions; + for (int i=0; i<d->items.count(); i++) + rotatedPositions << positions[(i + d->pathOffset + d->items.count()) % d->items.count()]; + + int wrapIndex= -1; + for (int i=0; i<d->items.count()-1; i++) { + if (rotatedPositions[i] > rotatedPositions[i+1]){ + wrapIndex = i; + break; + } + } + if (wrapIndex != -1 ){ + //A wraparound has occured + if (wrapIndex < d->items.count()/2){ + while(wrapIndex-- >= 0){ + QmlGraphicsItem* p = d->items.takeFirst(); + d->updateItem(p, 0.0); + d->releaseItem(p); + d->firstIndex++; + d->firstIndex %= d->model->count(); + int index = (d->firstIndex + d->items.count())%d->model->count(); + QmlGraphicsItem *item = d->getItem(index); + item->setZValue(wrapIndex); + if (d->currentIndex == index) + item->setFocus(true); + d->items << item; + d->pathOffset++; + d->pathOffset=d->pathOffset % d->items.count(); + } + } else { + while(wrapIndex++ < d->items.count()-1){ + QmlGraphicsItem* p = d->items.takeLast(); + d->updateItem(p, 1.0); + d->releaseItem(p); + d->firstIndex--; + if (d->firstIndex < 0) + d->firstIndex = d->model->count() - 1; + QmlGraphicsItem *item = d->getItem(d->firstIndex); + item->setZValue(d->firstIndex); + if (d->currentIndex == d->firstIndex) + item->setFocus(true); + d->items.prepend(item); + d->pathOffset--; + if (d->pathOffset < 0) + d->pathOffset = d->items.count() - 1; + } + } + for (int i=0; i<d->items.count(); i++) + rotatedPositions[i] = positions[(i + d->pathOffset + d->items.count()) + % d->items.count()]; + } + for (int i=0; i<d->items.count(); i++) + d->updateItem(d->items.at(i), rotatedPositions[i]); +} + +void QmlGraphicsPathView::itemsInserted(int modelIndex, int count) +{ + //XXX support animated insertion + Q_D(QmlGraphicsPathView); + if (!d->isValid() || !isComponentComplete()) + return; + if (d->pathItems == -1) { + for (int i = 0; i < count; ++i) { + QmlGraphicsItem *item = d->getItem(modelIndex + i); + item->setZValue(modelIndex + i); + d->items.insert(modelIndex + i, item); + } + refill(); + } else { + //XXX This is pretty heavy handed until we reference count items. + d->regenerate(); + } + + // make sure the current item is still at the snap position + int itemIndex = (d->currentIndex - d->firstIndex + d->model->count())%d->model->count(); + itemIndex += d->pathOffset; + itemIndex %= d->items.count(); + qreal targetOffset = qmlMod(100 + (d->snapPos*100) - 100.0 * itemIndex / d->items.count(), qreal(100.0)); + + if (targetOffset < 0) + targetOffset = 100.0 + targetOffset; + if (targetOffset != d->_offset) + d->moveOffset.setValue(targetOffset); +} + +void QmlGraphicsPathView::itemsRemoved(int modelIndex, int count) +{ + //XXX support animated removal + Q_D(QmlGraphicsPathView); + if (!d->isValid() || !isComponentComplete()) + return; + if (d->pathItems == -1) { + for (int i = 0; i < count; ++i) { + QmlGraphicsItem* p = d->items.takeAt(modelIndex); + d->model->release(p); + } + d->snapToCurrent(); + refill(); + } else { + d->regenerate(); + } + + if (d->model->count() == 0) { + d->currentIndex = -1; + d->moveOffset.setValue(0); + return; + } + + // make sure the current item is still at the snap position + if (d->currentIndex >= d->model->count()) + d->currentIndex = d->model->count() - 1; + int itemIndex = (d->currentIndex - d->firstIndex + d->model->count())%d->model->count(); + itemIndex += d->pathOffset; + itemIndex %= d->items.count(); + qreal targetOffset = qmlMod(100 + (d->snapPos*100) - 100.0 * itemIndex / d->items.count(), qreal(100.0)); + + if (targetOffset < 0) + targetOffset = 100.0 + targetOffset; + if (targetOffset != d->_offset) + d->moveOffset.setValue(targetOffset); +} + +void QmlGraphicsPathView::createdItem(int index, QmlGraphicsItem *item) +{ + Q_D(QmlGraphicsPathView); + if (d->requestedIndex != index) { + item->setParentItem(this); + d->updateItem(item, index < d->firstIndex ? 0.0 : 1.0); + } +} + +void QmlGraphicsPathView::destroyingItem(QmlGraphicsItem *item) +{ + Q_UNUSED(item); +} + +void QmlGraphicsPathView::ticked() +{ + Q_D(QmlGraphicsPathView); + d->updateCurrent(); +} + +// find the item closest to the snap position +int QmlGraphicsPathViewPrivate::calcCurrentIndex() +{ + int current = -1; + if (model && items.count()) { + _offset = qmlMod(_offset, qreal(100.0)); + if (_offset < 0) + _offset += 100.0; + + if (pathItems == -1) { + qreal delta = qmlMod(_offset - snapPos, qreal(100.0)); + if (delta < 0) + delta = 100.0 + delta; + int ii = model->count() - qRound(delta * model->count() / 100); + if (ii < 0) + ii = 0; + current = ii; + } else { + qreal bestDiff=1e9; + int bestI=-1; + for (int i=0; i<items.count(); i++){ + qreal percent = i * (100. / items.count()); + percent = percent + _offset; + percent = qmlMod(percent, qreal(100.0)); + qreal diff = qAbs(snapPos - (percent/100.0)); + if (diff < bestDiff){ + bestDiff = diff; + bestI = i; + } + } + int modelIndex = (bestI - pathOffset + items.count())%items.count(); + modelIndex += firstIndex; + current = modelIndex; + } + current = qAbs(current % model->count()); + } + + return current; +} + +void QmlGraphicsPathViewPrivate::updateCurrent() +{ + Q_Q(QmlGraphicsPathView); + if (moveReason != Mouse) + return; + int idx = calcCurrentIndex(); + if (model && idx != currentIndex) { + currentIndex = idx; + int itemIndex = (idx - firstIndex + model->count()) % model->count(); + if (itemIndex < items.count()) + items.at(itemIndex)->setFocus(true); + emit q->currentIndexChanged(); + } +} + +void QmlGraphicsPathViewPrivate::fixOffset() +{ + Q_Q(QmlGraphicsPathView); + if (model && items.count()) { + int curr = calcCurrentIndex(); + if (curr != currentIndex) + q->setCurrentIndex(curr); + else + snapToCurrent(); + } +} + +void QmlGraphicsPathViewPrivate::snapToCurrent() +{ + if (!model || model->count() <= 0) + return; + + int itemIndex = (currentIndex - firstIndex + model->count()) % model->count(); + + //Rounds is the number of times round to make the current item visible + int rounds = itemIndex / items.count(); + int otherWayRounds = (model->count() - (itemIndex)) / items.count(); + if (otherWayRounds < rounds) + rounds = -otherWayRounds; + + itemIndex += pathOffset; + if(model->count() % items.count() && itemIndex - model->count() + items.count() > 0){ + //When model.count() is not a multiple of pathItemCount we need to manually + //fix the index so that going backwards one step works correctly. + itemIndex = itemIndex - model->count() + items.count(); + } + itemIndex %= items.count(); + qreal targetOffset = qmlMod(100 + (snapPos*100) - 100.0 * itemIndex / items.count(), qreal(100.0)); + + if (targetOffset < 0) + targetOffset = 100.0 + targetOffset; + if (targetOffset == _offset && rounds == 0) + return; + + moveReason = Other; + tl.clear(); + moveOffset.setValue(_offset); + + if (rounds!=0){ + //Compensate if the targetOffset would bring the target in from off the screen + qreal distance = targetOffset - _offset; + if (distance <= -50) + rounds--; + if (distance > 50) + rounds++; + tl.move(moveOffset, targetOffset + 100.0*(-rounds), QEasingCurve(QEasingCurve::InOutQuad), + int(100*items.count()*qMax((qreal)(2.0/items.count()),(qreal)qAbs(rounds)))); + tl.execute(fixupOffsetEvent); + return; + } + + if (targetOffset - _offset > 50.0) { + qreal distance = 100 - targetOffset + _offset; + tl.move(moveOffset, 0.0, QEasingCurve(QEasingCurve::OutQuad), int(200 * _offset / distance)); + tl.set(moveOffset, 100.0); + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InQuad), int(200 * (100-targetOffset) / distance)); + } else if (targetOffset - _offset <= -50.0) { + qreal distance = 100 - _offset + targetOffset; + tl.move(moveOffset, 100.0, QEasingCurve(QEasingCurve::OutQuad), int(200 * (100-_offset) / distance)); + tl.set(moveOffset, 0.0); + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InQuad), int(200 * targetOffset / distance)); + } else { + tl.move(moveOffset, targetOffset, QEasingCurve(QEasingCurve::InOutQuad), 200); + } +} + +QHash<QObject*, QObject*> QmlGraphicsPathView::attachedProperties; +QObject *QmlGraphicsPathView::qmlAttachedProperties(QObject *obj) +{ + QObject *rv = attachedProperties.value(obj); + if (!rv) { + rv = new QmlGraphicsPathViewAttached(obj); + attachedProperties.insert(obj, rv); + } + return rv; +} + +QT_END_NAMESPACE + +#include <qmlgraphicspathview.moc> diff --git a/src/declarative/graphicsitems/qmlgraphicspathview_p.h b/src/declarative/graphicsitems/qmlgraphicspathview_p.h new file mode 100644 index 0000000..17106a2 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspathview_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSPATHVIEW_H +#define QMLGRAPHICSPATHVIEW_H + +#include "qmlgraphicsitem.h" +#include "qmlgraphicspath_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsPathViewPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsPathView : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QmlGraphicsPath *path READ path WRITE setPath) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(qreal offset READ offset WRITE setOffset NOTIFY offsetChanged) + Q_PROPERTY(qreal snapPosition READ snapPosition WRITE setSnapPosition) + Q_PROPERTY(qreal dragMargin READ dragMargin WRITE setDragMargin) + Q_PROPERTY(int count READ count) + Q_PROPERTY(QmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(int pathItemCount READ pathItemCount WRITE setPathItemCount) + +public: + QmlGraphicsPathView(QmlGraphicsItem *parent=0); + virtual ~QmlGraphicsPathView(); + + QVariant model() const; + void setModel(const QVariant &); + + QmlGraphicsPath *path() const; + void setPath(QmlGraphicsPath *); + + int currentIndex() const; + void setCurrentIndex(int idx); + + qreal offset() const; + void setOffset(qreal offset); + + qreal snapPosition() const; + void setSnapPosition(qreal pos); + + qreal dragMargin() const; + void setDragMargin(qreal margin); + + int count() const; + + QmlComponent *delegate() const; + void setDelegate(QmlComponent *); + + int pathItemCount() const; + void setPathItemCount(int); + + static QObject *qmlAttachedProperties(QObject *); + +Q_SIGNALS: + void currentIndexChanged(); + void offsetChanged(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *); + bool sendMouseEvent(QGraphicsSceneMouseEvent *event); + bool sceneEventFilter(QGraphicsItem *, QEvent *); + void componentComplete(); + +private Q_SLOTS: + void refill(); + void ticked(); + void itemsInserted(int index, int count); + void itemsRemoved(int index, int count); + void createdItem(int index, QmlGraphicsItem *item); + void destroyingItem(QmlGraphicsItem *item); + +private: + friend class QmlGraphicsPathViewAttached; + static QHash<QObject*, QObject*> attachedProperties; + Q_DISABLE_COPY(QmlGraphicsPathView) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsPathView) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsPathView) +QML_DECLARE_TYPEINFO(QmlGraphicsPathView, QML_HAS_ATTACHED_PROPERTIES) +QT_END_HEADER + +#endif // QMLGRAPHICSPATHVIEW_H diff --git a/src/declarative/graphicsitems/qmlgraphicspathview_p_p.h b/src/declarative/graphicsitems/qmlgraphicspathview_p_p.h new file mode 100644 index 0000000..18cb205 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspathview_p_p.h @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSPATHVIEW_P_H +#define QMLGRAPHICSPATHVIEW_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 "qmlgraphicspathview_p.h" + +#include "qmlgraphicsitem_p.h" +#include "qmlgraphicsvisualitemmodel_p.h" + +#include <qml.h> +#include <qmlanimation_p_p.h> + +#include <qdatetime.h> + +QT_BEGIN_NAMESPACE + +typedef struct PathViewItem{ + int index; + QmlGraphicsItem* item; +}PathViewItem; + +class QmlGraphicsPathViewPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsPathView) + +public: + QmlGraphicsPathViewPrivate() + : path(0), currentIndex(0), startPc(0), lastDist(0) + , lastElapsed(0), stealMouse(false), ownModel(false), activeItem(0) + , snapPos(0), dragMargin(0), moveOffset(this, &QmlGraphicsPathViewPrivate::setOffset) + , firstIndex(0), pathItems(-1), pathOffset(0), requestedIndex(-1) + , moveReason(Other) + { + fixupOffsetEvent = QmlTimeLineEvent::timeLineEvent<QmlGraphicsPathViewPrivate, &QmlGraphicsPathViewPrivate::fixOffset>(&moveOffset, this); + } + + void init() + { + Q_Q(QmlGraphicsPathView); + _offset = 0; + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QGraphicsItem::ItemIsFocusScope); + q->setFiltersChildEvents(true); + q->connect(&tl, SIGNAL(updated()), q, SLOT(ticked())); + } + + QmlGraphicsItem *getItem(int modelIndex); + void releaseItem(QmlGraphicsItem *item); + + bool isValid() const { + return model && model->count() > 0 && model->isValid() && path; + } + + int calcCurrentIndex(); + void updateCurrent(); + void fixOffset(); + void setOffset(qreal offset); + void regenerate(); + void updateItem(QmlGraphicsItem *, qreal); + void snapToCurrent(); + QPointF pointNear(const QPointF &point, qreal *nearPercent=0) const; + + QmlGraphicsPath *path; + int currentIndex; + qreal startPc; + QPointF startPoint; + qreal lastDist; + int lastElapsed; + qreal _offset; + bool stealMouse : 1; + bool ownModel : 1; + QTime lastPosTime; + QPointF lastPos; + QmlGraphicsItem *activeItem; + qreal snapPos; + qreal dragMargin; + QmlTimeLine tl; + QmlTimeLineValueProxy<QmlGraphicsPathViewPrivate> moveOffset; + QmlTimeLineEvent fixupOffsetEvent; + int firstIndex; + int pathItems; + int pathOffset; + int requestedIndex; + QList<QmlGraphicsItem *> items; + QGuard<QmlGraphicsVisualModel> model; + QVariant modelVariant; + enum MovementReason { Other, Key, Mouse }; + MovementReason moveReason; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicspositioners.cpp b/src/declarative/graphicsitems/qmlgraphicspositioners.cpp new file mode 100644 index 0000000..5b081a2 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspositioners.cpp @@ -0,0 +1,851 @@ +/**************************************************************************** +** +** 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 "qmlgraphicspositioners_p.h" +#include "qmlgraphicspositioners_p_p.h" + +#include <qml.h> +#include <qmlstate_p.h> +#include <qmlstategroup_p.h> +#include <qmlstateoperations_p.h> +#include <qfxperf_p_p.h> +#include <QtCore/qmath.h> + +#include <QDebug> +#include <QCoreApplication> + +QT_BEGIN_NAMESPACE + +static const QmlGraphicsItemPrivate::ChangeTypes watchedChanges + = QmlGraphicsItemPrivate::Geometry + | QmlGraphicsItemPrivate::SiblingOrder + | QmlGraphicsItemPrivate::Visibility + | QmlGraphicsItemPrivate::Opacity + | QmlGraphicsItemPrivate::Destroyed; + +void QmlGraphicsBasePositionerPrivate::watchChanges(QmlGraphicsItem *other) +{ + QmlGraphicsItemPrivate *otherPrivate = static_cast<QmlGraphicsItemPrivate*>(QGraphicsItemPrivate::get(other)); + otherPrivate->addItemChangeListener(this, watchedChanges); +} + +void QmlGraphicsBasePositionerPrivate::unwatchChanges(QmlGraphicsItem* other) +{ + QmlGraphicsItemPrivate *otherPrivate = static_cast<QmlGraphicsItemPrivate*>(QGraphicsItemPrivate::get(other)); + otherPrivate->removeItemChangeListener(this, watchedChanges); +} + +/*! + \internal + \class QmlGraphicsBasePositioner + \ingroup group_layouts + \brief The QmlGraphicsBasePositioner class provides a base for QmlGraphics layouts. + + To create a QmlGraphics Positioner, simply subclass QmlGraphicsBasePositioner and implement + doLayout(), which is automatically called when the layout might need + updating. In doLayout() use the setX and setY functions from QmlBasePositioner, and the + base class will apply the positions along with the appropriate transitions. The items to + position are provided in order as the protected member positionedItems. + + You also need to set a PositionerType, to declare whether you are positioning the x, y or both + for the child items. Depending on the chosen type, only x or y changes will be applied. + + Note that the subclass is responsible for adding the + spacing in between items. +*/ +QmlGraphicsBasePositioner::QmlGraphicsBasePositioner(PositionerType at, QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsBasePositionerPrivate), parent) +{ + Q_D(QmlGraphicsBasePositioner); + d->init(at); +} + +QmlGraphicsBasePositioner::QmlGraphicsBasePositioner(QmlGraphicsBasePositionerPrivate &dd, PositionerType at, QmlGraphicsItem *parent) + : QmlGraphicsItem(dd, parent) +{ + Q_D(QmlGraphicsBasePositioner); + d->init(at); +} + +QmlGraphicsBasePositioner::~QmlGraphicsBasePositioner() +{ + Q_D(QmlGraphicsBasePositioner); + for (int i = 0; i < positionedItems.count(); ++i) + d->unwatchChanges(positionedItems.at(i).item); + positionedItems.clear(); +} + +int QmlGraphicsBasePositioner::spacing() const +{ + Q_D(const QmlGraphicsBasePositioner); + return d->spacing; +} + +void QmlGraphicsBasePositioner::setSpacing(int s) +{ + Q_D(QmlGraphicsBasePositioner); + if (s==d->spacing) + return; + d->spacing = s; + prePositioning(); + emit spacingChanged(); +} + +QmlTransition *QmlGraphicsBasePositioner::move() const +{ + Q_D(const QmlGraphicsBasePositioner); + return d->moveTransition; +} + +void QmlGraphicsBasePositioner::setMove(QmlTransition *mt) +{ + Q_D(QmlGraphicsBasePositioner); + d->moveTransition = mt; +} + +QmlTransition *QmlGraphicsBasePositioner::add() const +{ + Q_D(const QmlGraphicsBasePositioner); + return d->addTransition; +} + +void QmlGraphicsBasePositioner::setAdd(QmlTransition *add) +{ + Q_D(QmlGraphicsBasePositioner); + d->addTransition = add; +} + +void QmlGraphicsBasePositioner::componentComplete() +{ + Q_D(QmlGraphicsBasePositioner); + QmlGraphicsItem::componentComplete(); +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::BasepositionerComponentComplete> cc; +#endif + positionedItems.reserve(d->QGraphicsItemPrivate::children.count()); + prePositioning(); +} + +QVariant QmlGraphicsBasePositioner::itemChange(GraphicsItemChange change, + const QVariant &value) +{ + Q_D(QmlGraphicsBasePositioner); + if (change == ItemChildAddedChange){ + QGraphicsItem* item = value.value<QGraphicsItem*>(); + QmlGraphicsItem* child = 0; + if(item) + child = qobject_cast<QmlGraphicsItem*>(item->toGraphicsObject()); + if (child) + prePositioning(); + } else if (change == ItemChildRemovedChange) { + QGraphicsItem* item = value.value<QGraphicsItem*>(); + QmlGraphicsItem* child = 0; + if(item) + child = qobject_cast<QmlGraphicsItem*>(item->toGraphicsObject()); + if (child) { + QmlGraphicsBasePositioner::PositionedItem posItem(child); + int idx = positionedItems.find(posItem); + if (idx >= 0) { + d->unwatchChanges(child); + positionedItems.remove(idx); + } + prePositioning(); + } + } + + return QmlGraphicsItem::itemChange(change, value); +} + +void QmlGraphicsBasePositioner::prePositioning() +{ + Q_D(QmlGraphicsBasePositioner); + if (!isComponentComplete()) + return; + + d->queuedPositioning = false; + //Need to order children by creation order modified by stacking order + QList<QGraphicsItem *> children = d->QGraphicsItemPrivate::children; + qSort(children.begin(), children.end(), d->insertionOrder); + + for (int ii = 0; ii < children.count(); ++ii) { + QmlGraphicsItem *child = qobject_cast<QmlGraphicsItem *>(children.at(ii)); + if (!child) + continue; + PositionedItem *item = 0; + PositionedItem posItem(child); + int wIdx = positionedItems.find(posItem); + if (wIdx < 0) { + d->watchChanges(child); + positionedItems.append(posItem); + item = &positionedItems[positionedItems.count()-1]; + } else { + item = &positionedItems[wIdx]; + } + if (child->opacity() <= 0.0 || !child->isVisible()) { + item->isVisible = false; + continue; + } + if (!item->isVisible) { + item->isVisible = true; + item->isNew = true; + } else { + item->isNew = false; + } + } + doPositioning(); + if(d->addTransition || d->moveTransition) + finishApplyTransitions(); + //Set implicit size to the size of its children + qreal h = 0.0f; + qreal w = 0.0f; + for (int i = 0; i < positionedItems.count(); ++i) { + const PositionedItem &posItem = positionedItems.at(i); + if (posItem.isVisible) { + h = qMax(h, posItem.item->y() + posItem.item->height()); + w = qMax(w, posItem.item->x() + posItem.item->width()); + } + } + setImplicitHeight(h); + setImplicitWidth(w); +} + +void QmlGraphicsBasePositioner::positionX(int x, const PositionedItem &target) +{ + Q_D(QmlGraphicsBasePositioner); + if(d->type == Horizontal || d->type == Both){ + if(!d->addTransition && !d->moveTransition){ + target.item->setX(x); + }else{ + if(target.isNew) + d->addActions << QmlAction(target.item, QLatin1String("x"), QVariant(x)); + else + d->moveActions << QmlAction(target.item, QLatin1String("x"), QVariant(x)); + } + } +} + +void QmlGraphicsBasePositioner::positionY(int y, const PositionedItem &target) +{ + Q_D(QmlGraphicsBasePositioner); + if(d->type == Vertical || d->type == Both){ + if(!d->addTransition && !d->moveTransition){ + target.item->setY(y); + }else{ + if(target.isNew) + d->addActions << QmlAction(target.item, QLatin1String("y"), QVariant(y)); + else + d->moveActions << QmlAction(target.item, QLatin1String("y"), QVariant(y)); + } + } +} + +void QmlGraphicsBasePositioner::finishApplyTransitions() +{ + Q_D(QmlGraphicsBasePositioner); + // 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(); +} + +QML_DEFINE_TYPE(Qt,4,6,Column,QmlGraphicsColumn) +/*! + \qmlclass Column QmlGraphicsColumn + \brief The Column item lines up its children vertically. + \inherits Item + + The Column item positions its child items so that they are vertically + aligned and not overlapping. Spacing between items can be added. + + The below example positions differently shaped rectangles using a Column. + \table + \row + \o \image verticalpositioner_example.png + \o + \qml +Column { + spacing: 2 + Rectangle { color: "red"; width: 50; height: 50 } + Rectangle { color: "green"; width: 20; height: 50 } + Rectangle { color: "blue"; width: 50; height: 20 } +} + \endqml + \endtable + + Column also provides for transitions to be set when items are added, moved, + or removed in the positioner. Adding and removing apply both to items which are deleted + or have their position in the document changed so as to no longer be children of the positioner, + as well as to items which have their opacity set to or from zero so as to appear or disappear. + + \table + \row + \o \image verticalpositioner_transition.gif + \o + \qml +Column { + spacing: 2 + remove: ... + add: ... + move: ... + ... +} + \endqml + \endtable + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, or use anchors on a child of a positioner, then the + positioner may exhibit strange behaviour. + +*/ +/*! + \qmlproperty Transition Column::add + This property holds the transition to be applied when adding an item to the positioner. The transition will only be applied to the added item(s). + Positioner transitions will only affect the position (x,y) of items. + + Added can mean that either the object has been created or reparented, and thus is now a child or the positioner, or that the object has had its opacity increased from zero, and thus is now visible. + + +*/ +/*! + \qmlproperty Transition Column::move + This property holds the transition to apply when moving an item within the positioner. + Positioner transitions will only affect the position (x,y) of items. + + This can happen when other items are added or removed from the positioner, or when items resize themselves. + + \table + \row + \o \image positioner-move.gif + \o + \qml +Column { + move: Transition { + NumberAnimation { + matchProperties: "y" + ease: "easeOutBounce" + } + } +} + \endqml + \endtable +*/ +/*! + \qmlproperty int Column::spacing + + spacing is the amount in pixels left empty between each adjacent + item, and defaults to 0. + + The below example places a Grid containing a red, a blue and a + green rectangle on a gray background. The area the grid positioner + occupies is colored white. The top positioner has the default of no spacing, + and the bottom positioner has its spacing set to 2. + + \image spacing_a.png + \image spacing_b.png + +*/ +/*! + \internal + \class QmlGraphicsColumn + \brief The QmlGraphicsColumn class lines up items vertically. + \ingroup group_positioners +*/ +QmlGraphicsColumn::QmlGraphicsColumn(QmlGraphicsItem *parent) +: QmlGraphicsBasePositioner(Vertical, parent) +{ +} + +static inline bool isInvisible(QmlGraphicsItem *child) +{ + return child->opacity() == 0.0 || !child->isVisible() || !child->width() || !child->height(); +} + +void QmlGraphicsColumn::doPositioning() +{ + int voffset = 0; + + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (!child.item || isInvisible(child.item)) + continue; + + if(child.item->y() != voffset) + positionY(voffset, child); + + voffset += child.item->height(); + voffset += spacing(); + } +} + +QML_DEFINE_TYPE(Qt,4,6,Row,QmlGraphicsRow) +/*! + \qmlclass Row QmlGraphicsRow + \brief The Row item lines up its children horizontally. + \inherits Item + + The Row item positions its child items so that they are + horizontally aligned and not overlapping. Spacing can be added between the + items, and a margin around all items can also be added. It also provides for + transitions to be set when items are added, moved, or removed in the + positioner. Adding and removing apply both to items which are deleted or have + their position in the document changed so as to no longer be children of the + positioner, as well as to items which have their opacity set to or from zero + so as to appear or disappear. + + The below example lays out differently shaped rectangles using a Row. + \qml +Row { + spacing: 2 + Rectangle { color: "red"; width: 50; height: 50 } + Rectangle { color: "green"; width: 20; height: 50 } + Rectangle { color: "blue"; width: 50; height: 20 } +} + \endqml + \image horizontalpositioner_example.png + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, or use anchors on a child of a positioner, then the + positioner may exhibit strange behaviour. + +*/ +/*! + \qmlproperty Transition Row::add + This property holds the transition to apply when adding an item to the positioner. + The transition will only be applied to the added item(s). + Positioner transitions will only affect the position (x,y) of items. + + Added can mean that either the object has been created or reparented, and thus is now a child or the positioner, or that the object has had its opacity increased from zero, and thus is now visible. + + +*/ +/*! + \qmlproperty Transition Row::move + This property holds the transition to apply when moving an item within the positioner. + Positioner transitions will only affect the position (x,y) of items. + + This can happen when other items are added or removed from the positioner, or when items resize themselves. + + \qml +Row { + id: positioner + move: Transition { + NumberAnimation { + matchProperties: "x" + ease: "easeOutBounce" + } + } +} + \endqml + +*/ +/*! + \qmlproperty int Row::spacing + + spacing is the amount in pixels left empty between each adjacent + item, and defaults to 0. + + The below example places a Grid containing a red, a blue and a + green rectangle on a gray background. The area the grid positioner + occupies is colored white. The top positioner has the default of no spacing, + and the bottom positioner has its spacing set to 2. + + \image spacing_a.png + \image spacing_b.png + +*/ +/*! + \internal + \class QmlGraphicsRow + \brief The QmlGraphicsRow class lines up items horizontally. + \ingroup group_positioners +*/ +QmlGraphicsRow::QmlGraphicsRow(QmlGraphicsItem *parent) +: QmlGraphicsBasePositioner(Horizontal, parent) +{ +} + +void QmlGraphicsRow::doPositioning() +{ + int hoffset = 0; + + for (int ii = 0; ii < positionedItems.count(); ++ii) { + const PositionedItem &child = positionedItems.at(ii); + if (!child.item || isInvisible(child.item)) + continue; + + if(child.item->x() != hoffset) + positionX(hoffset, child); + + hoffset += child.item->width(); + hoffset += spacing(); + } +} + +QML_DEFINE_TYPE(Qt,4,6,Grid,QmlGraphicsGrid) + +/*! + \qmlclass Grid QmlGraphicsGrid + \brief The Grid item positions its children in a grid. + \inherits Item + + The Grid item positions its child items so that they are + aligned in a grid and are not overlapping. Spacing can be added + between the items. It also provides for transitions to be set when items are + added, moved, or removed in the positioner. Adding and removing apply + both to items which are deleted or have their position in the + document changed so as to no longer be children of the positioner, as + well as to items which have their opacity set to or from zero so + as to appear or disappear. + + The Grid defaults to using four columns, and as many rows as + are necessary to fit all the child items. The number of rows + and/or the number of columns can be constrained by setting the rows + or columns properties. The grid positioner calculates a grid with + rectangular cells of sufficient size to hold all items, and then + places the items in the cells, going across then down, and + positioning each item at the (0,0) corner of the cell. The below + example demonstrates this. + + \table + \row + \o \image gridLayout_example.png + \o + \qml +Grid { + columns: 3 + spacing: 2 + Rectangle { color: "red"; width: 50; height: 50 } + Rectangle { color: "green"; width: 20; height: 50 } + Rectangle { color: "blue"; width: 50; height: 20 } + Rectangle { color: "cyan"; width: 50; height: 50 } + Rectangle { color: "magenta"; width: 10; height: 10 } +} + \endqml + \endtable + + Note that the positioner assumes that the x and y positions of its children + will not change. If you manually change the x or y properties in script, bind + the x or y properties, or use anchors on a child of a positioner, then the + positioner may exhibit strange behaviour. +*/ +/*! + \qmlproperty Transition Grid::add + This property holds the transition to apply when adding an item to the positioner. + The transition is only applied to the added item(s). + Positioner transitions will only affect the position (x,y) of items. + + Added can mean that either the object has been created or + reparented, and thus is now a child or the positioner, or that the + object has had its opacity increased from zero, and thus is now + visible. + + +*/ +/*! + \qmlproperty Transition Grid::move + This property holds the transition to apply when moving an item within the positioner. + Positioner transitions will only affect the position (x,y) of items. + + This can happen when other items are added or removed from the positioner, or + when items resize themselves. + + \qml +Grid { + move: Transition { + NumberAnimation { + matchProperties: "x,y" + ease: "easeOutBounce" + } + } +} + \endqml + +*/ +/*! + \qmlproperty int Grid::spacing + + spacing is the amount in pixels left empty between each adjacent + item, and defaults to 0. + + The below example places a Grid containing a red, a blue and a + green rectangle on a gray background. The area the grid positioner + occupies is colored white. The top positioner has the default of no spacing, + and the bottom positioner has its spacing set to 2. + + \image spacing_a.png + \image spacing_b.png + +*/ +/*! + \internal + \class QmlGraphicsGrid + \brief The QmlGraphicsGrid class lays out items in a grid. + \ingroup group_layouts + +*/ +QmlGraphicsGrid::QmlGraphicsGrid(QmlGraphicsItem *parent) : + QmlGraphicsBasePositioner(Both, parent) +{ + _columns=-1; + _rows=-1; +} + +/*! + \qmlproperty int Grid::columns + This property holds the number of columns in the grid. + + When the columns property is set the Grid will always have + that many columns. Note that if you do not have enough items to + fill this many columns some columns will be of zero width. +*/ + +/*! + \qmlproperty int Grid::rows + This property holds the number of rows in the grid. + + When the rows property is set the Grid will always have that + many rows. Note that if you do not have enough items to fill this + many rows some rows will be of zero width. +*/ + +void QmlGraphicsGrid::doPositioning() +{ + int c=_columns,r=_rows;//Actual number of rows/columns + int numVisible = positionedItems.count(); + if (_columns==-1 && _rows==-1){ + c = 4; + r = (numVisible+3)/4; + }else if (_rows==-1){ + r = (numVisible+(_columns-1))/_columns; + }else if (_columns==-1){ + c = (numVisible+(_rows-1))/_rows; + } + + QList<int> maxColWidth; + QList<int> maxRowHeight; + int childIndex =0; + 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 == positionedItems.count()) + continue; + const PositionedItem &child = positionedItems.at(childIndex++); + if (!child.item || isInvisible(child.item)) + continue; + if (child.item->width() > maxColWidth[j]) + maxColWidth[j] = child.item->width(); + if (child.item->height() > maxRowHeight[i]) + maxRowHeight[i] = child.item->height(); + } + } + + int xoffset=0; + int yoffset=0; + int curRow =0; + int curCol =0; + for (int i = 0; i < positionedItems.count(); ++i) { + const PositionedItem &child = positionedItems.at(i); + if (!child.item || isInvisible(child.item)) + continue; + if((child.item->x()!=xoffset)||(child.item->y()!=yoffset)){ + positionX(xoffset, child); + positionY(yoffset, child); + } + xoffset+=maxColWidth[curCol]+spacing(); + curCol++; + curCol%=c; + if (!curCol){ + yoffset+=maxRowHeight[curRow]+spacing(); + xoffset=0; + curRow++; + if (curRow>=r) + break; + } + } +} + + +QML_DEFINE_TYPE(Qt,4,6,Flow,QmlGraphicsFlow) +/*! + \qmlclass Flow QmlGraphicsFlow + \brief The Flow item lines up its children side by side, wrapping as necessary. + \inherits Item + + +*/ +/*! + \qmlproperty Transition Flow::add + This property holds the transition to apply when adding an item to the positioner. + The transition will only be applied to the added item(s). + Positioner transitions will only affect the position (x,y) of items. + + Added can mean that either the object has been created or reparented, and thus is now a child or the positioner, or that the object has had its opacity increased from zero, and thus is now visible. + + +*/ +/*! + \qmlproperty Transition Flow::move + This property holds the transition to apply when moving an item within the positioner. + Positioner transitions will only affect the position (x,y) of items. + + This can happen when other items are added or removed from the positioner, or when items resize themselves. + + \qml +Flow { + id: positioner + move: Transition { + NumberAnimation { + matchProperties: "x,y" + ease: "easeOutBounce" + } + } +} + \endqml + +*/ +/*! + \qmlproperty int Flow::spacing + + spacing is the amount in pixels left empty between each adjacent + item, and defaults to 0. + +*/ + +class QmlGraphicsFlowPrivate : public QmlGraphicsBasePositionerPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsFlow) + +public: + QmlGraphicsFlowPrivate() + : QmlGraphicsBasePositionerPrivate(), flow(QmlGraphicsFlow::LeftToRight) + {} + + QmlGraphicsFlow::Flow flow; +}; + +QmlGraphicsFlow::QmlGraphicsFlow(QmlGraphicsItem *parent) +: QmlGraphicsBasePositioner(*(new QmlGraphicsFlowPrivate), Both, parent) +{ +} + +/*! + \qmlproperty enumeration Flow::flow + This property holds the flow of the layout. + + Possible values are \c LeftToRight (default) and \c TopToBottom. + + If \a flow is \c LeftToRight, the items are positioned next to + to each other from left to right until the width of the Flow + is exceeded, then wrapped to the next line. + If \a flow is \c TopToBottom, the items are positioned next to each + other from top to bottom until the height of the Flow is exceeded, + then wrapped to the next column. +*/ +QmlGraphicsFlow::Flow QmlGraphicsFlow::flow() const +{ + Q_D(const QmlGraphicsFlow); + return d->flow; +} + +void QmlGraphicsFlow::setFlow(Flow flow) +{ + Q_D(QmlGraphicsFlow); + if (d->flow != flow) { + d->flow = flow; + prePositioning(); + emit flowChanged(); + } +} + +void QmlGraphicsFlow::doPositioning() +{ + Q_D(QmlGraphicsFlow); + + int hoffset = 0; + int voffset = 0; + int linemax = 0; + + for (int i = 0; i < positionedItems.count(); ++i) { + const PositionedItem &child = positionedItems.at(i); + if (!child.item || isInvisible(child.item)) + continue; + + if (d->flow == LeftToRight) { + if (hoffset && hoffset + child.item->width() > width()) { + hoffset = 0; + voffset += linemax + spacing(); + linemax = 0; + } + } else { + if (voffset && voffset + child.item->height() > height()) { + voffset = 0; + hoffset += linemax + spacing(); + linemax = 0; + } + } + + if(child.item->x() != hoffset || child.item->y() != voffset){ + positionX(hoffset, child); + positionY(voffset, child); + } + + 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())); + } + } +} + + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicspositioners_p.h b/src/declarative/graphicsitems/qmlgraphicspositioners_p.h new file mode 100644 index 0000000..d6711f6 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspositioners_p.h @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSLAYOUTS_H +#define QMLGRAPHICSLAYOUTS_H + +#include "qmlgraphicsitem.h" + +#include "../util/qmlstate_p.h" +#include <private/qpodvector_p.h> + +#include <QtCore/QObject> +#include <QtCore/QString> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class QmlGraphicsBasePositionerPrivate; + +class Q_DECLARATIVE_EXPORT QmlGraphicsBasePositioner : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(int spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY(QmlTransition *move READ move WRITE setMove) + Q_PROPERTY(QmlTransition *add READ add WRITE setAdd) +public: + enum PositionerType { None = 0x0, Horizontal = 0x1, Vertical = 0x2, Both = 0x3 }; + QmlGraphicsBasePositioner(PositionerType, QmlGraphicsItem *parent); + ~QmlGraphicsBasePositioner(); + + int spacing() const; + void setSpacing(int); + + QmlTransition *move() const; + void setMove(QmlTransition *); + + QmlTransition *add() const; + void setAdd(QmlTransition *); + +protected: + QmlGraphicsBasePositioner(QmlGraphicsBasePositionerPrivate &dd, PositionerType at, QmlGraphicsItem *parent); + virtual void componentComplete(); + virtual QVariant itemChange(GraphicsItemChange, const QVariant &); + void finishApplyTransitions(); + +Q_SIGNALS: + void spacingChanged(); + +protected Q_SLOTS: + virtual void doPositioning()=0; + void prePositioning(); + +protected: + struct PositionedItem { + PositionedItem(QmlGraphicsItem *i) : item(i), isNew(false), isVisible(true) {} + bool operator==(const PositionedItem &other) const { return other.item == item; } + QmlGraphicsItem *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(QmlGraphicsBasePositioner) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsBasePositioner) +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsColumn : public QmlGraphicsBasePositioner +{ + Q_OBJECT +public: + QmlGraphicsColumn(QmlGraphicsItem *parent=0); +protected Q_SLOTS: + virtual void doPositioning(); +private: + Q_DISABLE_COPY(QmlGraphicsColumn) +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsRow: public QmlGraphicsBasePositioner +{ + Q_OBJECT +public: + QmlGraphicsRow(QmlGraphicsItem *parent=0); +protected Q_SLOTS: + virtual void doPositioning(); +private: + Q_DISABLE_COPY(QmlGraphicsRow) +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsGrid : public QmlGraphicsBasePositioner +{ + Q_OBJECT + Q_PROPERTY(int rows READ rows WRITE setRows) + Q_PROPERTY(int columns READ columns WRITE setcolumns) +public: + QmlGraphicsGrid(QmlGraphicsItem *parent=0); + + int rows() const {return _rows;} + void setRows(const int rows){_rows = rows;} + + int columns() const {return _columns;} + void setcolumns(const int columns){_columns = columns;} +protected Q_SLOTS: + virtual void doPositioning(); + +private: + int _rows; + int _columns; + Q_DISABLE_COPY(QmlGraphicsGrid) +}; + +class QmlGraphicsFlowPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsFlow: public QmlGraphicsBasePositioner +{ + Q_OBJECT + Q_PROPERTY(Flow flow READ flow WRITE setFlow NOTIFY flowChanged) +public: + QmlGraphicsFlow(QmlGraphicsItem *parent=0); + + Q_ENUMS(Flow) + enum Flow { LeftToRight, TopToBottom }; + Flow flow() const; + void setFlow(Flow); + +Q_SIGNALS: + void flowChanged(); + +protected Q_SLOTS: + virtual void doPositioning(); + +protected: + QmlGraphicsFlow(QmlGraphicsFlowPrivate &dd, QmlGraphicsItem *parent); +private: + Q_DISABLE_COPY(QmlGraphicsFlow) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsFlow) +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsColumn) +QML_DECLARE_TYPE(QmlGraphicsRow) +QML_DECLARE_TYPE(QmlGraphicsGrid) +QML_DECLARE_TYPE(QmlGraphicsFlow) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicspositioners_p_p.h b/src/declarative/graphicsitems/qmlgraphicspositioners_p_p.h new file mode 100644 index 0000000..55a31c7 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicspositioners_p_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSLAYOUTS_P_H +#define QMLGRAPHICSLAYOUTS_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 "qmlgraphicspositioners_p.h" + +#include "qmlgraphicsitem_p.h" + +#include <qmlstate_p.h> +#include <qmltransitionmanager_p_p.h> +#include <qmlstateoperations_p.h> + +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtCore/QTimer> +#include <QDebug> + +QT_BEGIN_NAMESPACE +class QmlGraphicsBasePositionerPrivate : public QmlGraphicsItemPrivate, public QmlGraphicsItemChangeListener +{ + Q_DECLARE_PUBLIC(QmlGraphicsBasePositioner) + +public: + QmlGraphicsBasePositionerPrivate() + : spacing(0), type(QmlGraphicsBasePositioner::None), moveTransition(0), addTransition(0), + queuedPositioning(false) + { + } + + void init(QmlGraphicsBasePositioner::PositionerType at) + { + type = at; + } + + int spacing; + QmlGraphicsBasePositioner::PositionerType type; + QmlTransition *moveTransition; + QmlTransition *addTransition; + QmlStateOperation::ActionList addActions; + QmlStateOperation::ActionList moveActions; + QmlTransitionManager addTransitionManager; + QmlTransitionManager moveTransitionManager; + + void watchChanges(QmlGraphicsItem *other); + void unwatchChanges(QmlGraphicsItem* other); + bool queuedPositioning; + + virtual void itemSiblingOrderChanged(QmlGraphicsItem* other) + { + Q_Q(QmlGraphicsBasePositioner); + Q_UNUSED(other); + if(!queuedPositioning){ + //Delay is due to many children often being reordered at once + //And we only want to reposition them all once + QTimer::singleShot(0,q,SLOT(prePositioning())); + queuedPositioning = true; + } + } + + void itemGeometryChanged(QmlGraphicsItem *, const QRectF &newGeometry, const QRectF &oldGeometry) + { + Q_Q(QmlGraphicsBasePositioner); + if (newGeometry.size() != oldGeometry.size()) + q->prePositioning(); + } + virtual void itemVisibilityChanged(QmlGraphicsItem *) + { + Q_Q(QmlGraphicsBasePositioner); + q->prePositioning(); + } + virtual void itemOpacityChanged(QmlGraphicsItem *) + { + Q_Q(QmlGraphicsBasePositioner); + q->prePositioning(); + } + + void itemDestroyed(QmlGraphicsItem *item) + { + Q_Q(QmlGraphicsBasePositioner); + q->positionedItems.removeOne(QmlGraphicsBasePositioner::PositionedItem(item)); + } +}; + +QT_END_NAMESPACE +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicsrectangle.cpp b/src/declarative/graphicsitems/qmlgraphicsrectangle.cpp new file mode 100644 index 0000000..cc09436 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsrectangle.cpp @@ -0,0 +1,467 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsrectangle_p.h" +#include "qmlgraphicsrectangle_p_p.h" + +#include <QPainter> +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE +QML_DEFINE_TYPE(Qt,4,6,Pen,QmlGraphicsPen) +QML_DEFINE_TYPE(Qt,4,6,GradientStop,QmlGraphicsGradientStop) +QML_DEFINE_TYPE(Qt,4,6,Gradient,QmlGraphicsGradient) + +/*! + \internal + \class QmlGraphicsPen + \brief The QmlGraphicsPen class provides a pen used for drawing rectangle borders on a QmlView. + + By default, the pen is invalid and nothing is drawn. You must either set a color (then the default + width is 1) or a width (then the default color is black). + + A width of 1 indicates is a single-pixel line on the border of the item being painted. + + Example: + \qml + Rectangle { border.width: 2; border.color: "red" ... } + \endqml +*/ + +void QmlGraphicsPen::setColor(const QColor &c) +{ + _color = c; + _valid = _color.alpha() ? true : false; + emit penChanged(); +} + +void QmlGraphicsPen::setWidth(int w) +{ + if (_width == w && _valid) + return; + + _width = w; + _valid = (_width < 1) ? false : true; + emit penChanged(); +} + + +/*! + \qmlclass GradientStop QmlGraphicsGradientStop + \brief The GradientStop item defines the color at a position in a Gradient + + \sa Gradient +*/ + +/*! + \qmlproperty real GradientStop::position + \qmlproperty color GradientStop::color + + Sets a \e color at a \e position in a gradient. +*/ + +void QmlGraphicsGradientStop::updateGradient() +{ + if (QmlGraphicsGradient *grad = qobject_cast<QmlGraphicsGradient*>(parent())) + grad->doUpdate(); +} + +/*! + \qmlclass Gradient QmlGraphicsGradient + \brief The Gradient item defines a gradient fill. + + A gradient is defined by two or more colors, which will be blended seemlessly. The + colors are specified at their position in the range 0.0 - 1.0 via + the GradientStop item. For example, the following code paints a + rectangle with a gradient starting with red, blending to yellow at 1/3 of the + size of the rectangle, and ending with Green: + + \table + \row + \o \image gradient.png + \o \quotefile doc/src/snippets/declarative/gradient.qml + \endtable + + \sa GradientStop +*/ + +/*! + \qmlproperty list<GradientStop> Gradient::stops + This property holds the gradient stops describing the gradient. +*/ + +const QGradient *QmlGraphicsGradient::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 QmlGraphicsGradientStop *stop = m_stops.at(i); + m_gradient->setCoordinateMode(QGradient::ObjectBoundingMode); + m_gradient->setColorAt(stop->position(), stop->color()); + } + } + + return m_gradient; +} + +void QmlGraphicsGradient::doUpdate() +{ + delete m_gradient; + m_gradient = 0; + emit updated(); +} + +QML_DEFINE_TYPE(Qt,4,6,Rectangle,QmlGraphicsRectangle) + +/*! + \qmlclass Rectangle QmlGraphicsRectangle + \brief The Rectangle item allows you to add rectangles to a scene. + \inherits Item + + A Rectangle is painted having a solid fill (color) and an optional border. + You can also create rounded rectangles using the radius property. + + \qml + Rectangle { + width: 100 + height: 100 + color: "red" + border.color: "black" + border.width: 5 + radius: 10 + } + \endqml + + \image declarative-rect.png +*/ + +/*! + \internal + \class QmlGraphicsRectangle + \brief The QmlGraphicsRectangle class provides a rectangle item that you can add to a QmlView. +*/ +QmlGraphicsRectangle::QmlGraphicsRectangle(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsRectanglePrivate), parent) +{ + Q_D(QmlGraphicsRectangle); + d->init(); + setFlag(QGraphicsItem::ItemHasNoContents, false); +} + +void QmlGraphicsRectangle::doUpdate() +{ + Q_D(QmlGraphicsRectangle); + d->rectImage = QPixmap(); + const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0; + d->setPaintMargin((pw+1)/2); + update(); +} + +/*! + \qmlproperty int Rectangle::border.width + \qmlproperty color Rectangle::border.color + + The width and color used to draw the border of the rectangle. + + A width of 1 creates a thin line. For no line, use a width of 0 or a transparent color. + + To keep the border smooth (rather than blurry), odd widths cause the rectangle to be painted at + a half-pixel offset; +*/ +QmlGraphicsPen *QmlGraphicsRectangle::border() +{ + Q_D(QmlGraphicsRectangle); + return d->getPen(); +} + +/*! + \qmlproperty Gradient Rectangle::gradient + + The gradient to use to fill the rectangle. + + This property allows for the construction of simple vertical gradients. + Other gradients may by formed by adding rotation to the rectangle. + + \table + \row + \o \image declarative-rect_gradient.png + \o + \qml + Rectangle { y: 0; width: 80; height: 80; color: "lightsteelblue" } + Rectangle { y: 100; width: 80; height: 80 + gradient: Gradient { + GradientStop { position: 0.0; color: "lightsteelblue" } + GradientStop { position: 1.0; color: "blue" } + } + } + Rectangle { rotation: 90; x: 80; y: 200; width: 80; height: 80 + gradient: Gradient { + GradientStop { position: 0.0; color: "lightsteelblue" } + GradientStop { position: 1.0; color: "blue" } + } + } + // The x offset is needed because the rotation is from the top left corner + \endqml + \endtable + + If both a gradient and a color are specified, the gradient will be used. + + \sa Gradient, color +*/ +QmlGraphicsGradient *QmlGraphicsRectangle::gradient() const +{ + Q_D(const QmlGraphicsRectangle); + return d->gradient; +} + +void QmlGraphicsRectangle::setGradient(QmlGraphicsGradient *gradient) +{ + Q_D(QmlGraphicsRectangle); + if (d->gradient == gradient) + return; + if (d->gradient) + disconnect(d->gradient, SIGNAL(updated()), this, SLOT(doUpdate())); + d->gradient = gradient; + if (d->gradient) + connect(d->gradient, SIGNAL(updated()), this, SLOT(doUpdate())); + update(); +} + + +/*! + \qmlproperty real Rectangle::radius + This property holds the corner radius used to draw a rounded rectangle. + + If radius is non-zero, the rectangle will be painted as a rounded rectangle, otherwise it will be + painted as a normal rectangle. The same radius is used by all 4 corners; there is currently + no way to specify different radii for different corners. +*/ +qreal QmlGraphicsRectangle::radius() const +{ + Q_D(const QmlGraphicsRectangle); + return d->radius; +} + +void QmlGraphicsRectangle::setRadius(qreal radius) +{ + Q_D(QmlGraphicsRectangle); + if (d->radius == radius) + return; + + d->radius = radius; + d->rectImage = QPixmap(); + update(); + emit radiusChanged(); +} + +/*! + \qmlproperty color Rectangle::color + This property holds the color used to fill the rectangle. + + \qml + // green rectangle using hexidecimal notation + Rectangle { color: "#00FF00" } + + // steelblue rectangle using SVG color name + Rectangle { color: "steelblue" } + \endqml + + The default color is white. + + If both a gradient and a color are specified, the gradient will be used. +*/ +QColor QmlGraphicsRectangle::color() const +{ + Q_D(const QmlGraphicsRectangle); + return d->color; +} + +void QmlGraphicsRectangle::setColor(const QColor &c) +{ + Q_D(QmlGraphicsRectangle); + if (d->color == c) + return; + + d->color = c; + d->rectImage = QPixmap(); + update(); + emit colorChanged(); +} + +void QmlGraphicsRectangle::generateRoundedRect() +{ + Q_D(QmlGraphicsRectangle); + if (d->rectImage.isNull()) { + const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0; + const int radius = qCeil(d->radius); //ensure odd numbered width/height so we get 1-pixel center + d->rectImage = QPixmap(radius*2 + 3 + pw*2, radius*2 + 3 + pw*2); + d->rectImage.fill(Qt::transparent); + QPainter p(&(d->rectImage)); + p.setRenderHint(QPainter::Antialiasing); + if (d->pen && d->pen->isValid()) { + QPen pn(QColor(d->pen->color()), d->pen->width()); + p.setPen(pn); + } else { + p.setPen(Qt::NoPen); + } + p.setBrush(d->color); + if (pw%2) + p.drawRoundedRect(QRectF(qreal(pw)/2+1, qreal(pw)/2+1, d->rectImage.width()-(pw+1), d->rectImage.height()-(pw+1)), d->radius, d->radius); + else + p.drawRoundedRect(QRectF(qreal(pw)/2, qreal(pw)/2, d->rectImage.width()-pw, d->rectImage.height()-pw), d->radius, d->radius); + } +} + +void QmlGraphicsRectangle::generateBorderedRect() +{ + Q_D(QmlGraphicsRectangle); + if (d->rectImage.isNull()) { + const int pw = d->pen && d->pen->isValid() ? d->pen->width() : 0; + d->rectImage = QPixmap(pw*2 + 3, pw*2 + 3); + d->rectImage.fill(Qt::transparent); + QPainter p(&(d->rectImage)); + p.setRenderHint(QPainter::Antialiasing); + if (d->pen && d->pen->isValid()) { + QPen pn(QColor(d->pen->color()), d->pen->width()); + pn.setJoinStyle(Qt::MiterJoin); + p.setPen(pn); + } else { + p.setPen(Qt::NoPen); + } + p.setBrush(d->color); + if (pw%2) + p.drawRect(QRectF(qreal(pw)/2+1, qreal(pw)/2+1, d->rectImage.width()-(pw+1), d->rectImage.height()-(pw+1))); + else + p.drawRect(QRectF(qreal(pw)/2, qreal(pw)/2, d->rectImage.width()-pw, d->rectImage.height()-pw)); + } +} + +void QmlGraphicsRectangle::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + Q_D(QmlGraphicsRectangle); + if (d->radius > 0 || (d->pen && d->pen->isValid()) + || (d->gradient && d->gradient->gradient()) ) { + drawRect(*p); + } + else { + bool oldAA = p->testRenderHint(QPainter::Antialiasing); + if (d->smooth) + p->setRenderHints(QPainter::Antialiasing, true); + p->fillRect(QRectF(0, 0, width(), height()), d->color); + if (d->smooth) + p->setRenderHint(QPainter::Antialiasing, oldAA); + } +} + +void QmlGraphicsRectangle::drawRect(QPainter &p) +{ + Q_D(QmlGraphicsRectangle); + if (d->gradient && d->gradient->gradient()) { + // XXX This path is still slower than the image path + // Image path won't work for gradients though + bool oldAA = p.testRenderHint(QPainter::Antialiasing); + if (d->smooth) + p.setRenderHint(QPainter::Antialiasing); + if (d->pen && d->pen->isValid()) { + QPen pn(QColor(d->pen->color()), d->pen->width()); + p.setPen(pn); + } else { + p.setPen(Qt::NoPen); + } + p.setBrush(*d->gradient->gradient()); + if (d->radius > 0.) + p.drawRoundedRect(0, 0, width(), height(), d->radius, d->radius); + else + p.drawRect(0, 0, width(), height()); + if (d->smooth) + p.setRenderHint(QPainter::Antialiasing, oldAA); + } else { + bool oldAA = p.testRenderHint(QPainter::Antialiasing); + bool oldSmooth = p.testRenderHint(QPainter::SmoothPixmapTransform); + if (d->smooth) + p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth); + + const int pw = d->pen && d->pen->isValid() ? (d->pen->width()+1)/2*2 : 0; + + if (d->radius > 0) + generateRoundedRect(); + else + generateBorderedRect(); + + int xOffset = (d->rectImage.width()-1)/2; + int yOffset = (d->rectImage.height()-1)/2; + Q_ASSERT(d->rectImage.width() == 2*xOffset + 1); + Q_ASSERT(d->rectImage.height() == 2*yOffset + 1); + + QMargins margins(xOffset, yOffset, xOffset, yOffset); + QTileRules rules(Qt::StretchTile, Qt::StretchTile); + //NOTE: even though our item may have qreal-based width and height, qDrawBorderPixmap only supports QRects + qDrawBorderPixmap(&p, QRect(-pw/2, -pw/2, width()+pw, height()+pw), margins, d->rectImage, d->rectImage.rect(), margins, rules); + + if (d->smooth) { + p.setRenderHint(QPainter::Antialiasing, oldAA); + p.setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth); + } + } +} + +/*! + \qmlproperty bool Rectangle::smooth + + Set this property if you want the item to be smoothly scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. + + \image rect-smooth.png +*/ + +QRectF QmlGraphicsRectangle::boundingRect() const +{ + Q_D(const QmlGraphicsRectangle); + return QRectF(-d->paintmargin, -d->paintmargin, d->width+d->paintmargin*2, d->height+d->paintmargin*2); +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsrectangle_p.h b/src/declarative/graphicsitems/qmlgraphicsrectangle_p.h new file mode 100644 index 0000000..4f4c1cf --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsrectangle_p.h @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSRECT_H +#define QMLGRAPHICSRECT_H + +#include "qmlgraphicsitem.h" + +#include <QtGui/qbrush.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class Q_DECLARATIVE_EXPORT QmlGraphicsPen : 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: + QmlGraphicsPen(QObject *parent=0) + : QObject(parent), _width(1), _color("#000000"), _valid(false) + {} + + int width() const { return _width; } + void setWidth(int w); + + QColor color() const { return _color; } + void setColor(const QColor &c); + + bool isValid() { return _valid; }; + +Q_SIGNALS: + void penChanged(); + +private: + int _width; + QColor _color; + bool _valid; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsGradientStop : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal position READ position WRITE setPosition) + Q_PROPERTY(QColor color READ color WRITE setColor) + +public: + QmlGraphicsGradientStop(QObject *parent=0) : QObject(parent) {} + + qreal position() const { return m_position; } + void setPosition(qreal position) { m_position = position; updateGradient(); } + + QColor color() const { return m_color; } + void setColor(const QColor &color) { m_color = color; updateGradient(); } + +private: + void updateGradient(); + +private: + qreal m_position; + QColor m_color; +}; + +class Q_DECLARATIVE_EXPORT QmlGraphicsGradient : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QList<QmlGraphicsGradientStop *> *stops READ stops) + Q_CLASSINFO("DefaultProperty", "stops") + +public: + QmlGraphicsGradient(QObject *parent=0) : QObject(parent), m_gradient(0) {} + ~QmlGraphicsGradient() { delete m_gradient; } + + QList<QmlGraphicsGradientStop *> *stops() { return &m_stops; } + + const QGradient *gradient() const; + +Q_SIGNALS: + void updated(); + +private: + void doUpdate(); + +private: + QList<QmlGraphicsGradientStop *> m_stops; + mutable QGradient *m_gradient; + friend class QmlGraphicsGradientStop; +}; + +class QmlGraphicsRectanglePrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsRectangle : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QmlGraphicsGradient *gradient READ gradient WRITE setGradient) + Q_PROPERTY(QmlGraphicsPen * border READ border CONSTANT) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) +public: + QmlGraphicsRectangle(QmlGraphicsItem *parent=0); + + QColor color() const; + void setColor(const QColor &); + + QmlGraphicsPen *border(); + + QmlGraphicsGradient *gradient() const; + void setGradient(QmlGraphicsGradient *gradient); + + qreal radius() const; + void setRadius(qreal radius); + + QRectF boundingRect() const; + + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + +Q_SIGNALS: + void colorChanged(); + void radiusChanged(); + +private Q_SLOTS: + void doUpdate(); + +private: + void generateRoundedRect(); + void generateBorderedRect(); + void drawRect(QPainter &painter); + +private: + Q_DISABLE_COPY(QmlGraphicsRectangle) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsRectangle) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsPen) +QML_DECLARE_TYPE(QmlGraphicsGradientStop) +QML_DECLARE_TYPE(QmlGraphicsGradient) +QML_DECLARE_TYPE(QmlGraphicsRectangle) + +QT_END_HEADER + +#endif // QMLGRAPHICSRECT_H diff --git a/src/declarative/graphicsitems/qmlgraphicsrectangle_p_p.h b/src/declarative/graphicsitems/qmlgraphicsrectangle_p_p.h new file mode 100644 index 0000000..c4bbbe4 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsrectangle_p_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSRECT_P_H +#define QMLGRAPHICSRECT_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 "qmlgraphicsitem_p.h" + +QT_BEGIN_NAMESPACE + +class QmlGraphicsGradient; +class QmlGraphicsRectangle; +class QmlGraphicsRectanglePrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsRectangle) + +public: + QmlGraphicsRectanglePrivate() : + color(Qt::white), gradient(0), pen(0), radius(0), paintmargin(0) + { + } + + ~QmlGraphicsRectanglePrivate() + { + delete pen; + } + + void init() + { + } + + QColor getColor(); + QColor color; + QmlGraphicsGradient *gradient; + QmlGraphicsPen *getPen() { + if (!pen) { + Q_Q(QmlGraphicsRectangle); + pen = new QmlGraphicsPen; + QObject::connect(pen, SIGNAL(penChanged()), q, SLOT(doUpdate())); + } + return pen; + } + QmlGraphicsPen *pen; + qreal radius; + qreal paintmargin; + QPixmap rectImage; + + void setPaintMargin(qreal margin) + { + Q_Q(QmlGraphicsRectangle); + if (margin == paintmargin) + return; + q->prepareGeometryChange(); + paintmargin = margin; + } +}; + +QT_END_NAMESPACE + +#endif // QMLGRAPHICSRECT_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsrepeater.cpp b/src/declarative/graphicsitems/qmlgraphicsrepeater.cpp new file mode 100644 index 0000000..99f0faa --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsrepeater.cpp @@ -0,0 +1,320 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsrepeater_p.h" +#include "qmlgraphicsrepeater_p_p.h" + +#include "qmlgraphicsvisualitemmodel_p.h" + +#include <qmllistaccessor_p.h> + +#include <qlistmodelinterface_p.h> + +QT_BEGIN_NAMESPACE +QmlGraphicsRepeaterPrivate::QmlGraphicsRepeaterPrivate() +: model(0), ownModel(false) +{ +} + +QmlGraphicsRepeaterPrivate::~QmlGraphicsRepeaterPrivate() +{ + if (ownModel) + delete model; +} + +QML_DEFINE_TYPE(Qt,4,6,Repeater,QmlGraphicsRepeater) + +/*! + \qmlclass Repeater QmlGraphicsRepeater + \inherits Item + + \brief The Repeater item allows you to repeat a component based on a model. + + The Repeater item is used when you want to create a large number of + similar items. For each entry in the model, an item is instantiated + in a context seeded with data from the model. If the repeater will + be instantiating a large number of instances, it may be more efficient to + use one of Qt Declarative's \l {xmlViews}{view items}. + + The model may be either an object list, a string list, a number or a Qt model. + In each case, the data element and the index is exposed to each instantiated + component. + + The index is always exposed as an accessible \c index property. + In the case of an object or string list, the data element (of type string + or object) is available as the \c modelData property. In the case of a Qt model, + all roles are available as named properties just like in the view classes. The + following example shows how to use the index property inside the instantiated + items. + + \snippet doc/src/snippets/declarative/repeater-index.qml 0 + + \image repeater-index.png + + Items instantiated by the Repeater are inserted, in order, as + children of the Repeater's parent. The insertion starts immediately after + the repeater's position in its parent stacking list. This is to allow + you to use a Repeater inside a layout. The following QML example shows how + the instantiated items would visually appear stacked between the red and + blue rectangles. + + \snippet doc/src/snippets/declarative/repeater.qml 0 + + \image repeater.png + + The repeater instance continues to own all items it instantiates, even + if they are otherwise manipulated. It is illegal to manually remove an item + created by the Repeater. + */ + +/*! + \internal + \class QmlGraphicsRepeater + \qmlclass Repeater + + XXX Repeater is very conservative in how it instatiates/deletes items. Also + new model entries will not be created and old ones will not be removed. + */ + +/*! + Create a new QmlGraphicsRepeater instance. + */ +QmlGraphicsRepeater::QmlGraphicsRepeater(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsRepeaterPrivate), parent) +{ +} + +/*! + Destroy the repeater instance. All items it instantiated are also + destroyed. + */ +QmlGraphicsRepeater::~QmlGraphicsRepeater() +{ +} + +/*! + \qmlproperty any Repeater::model + + The model providing data for the repeater. + + The model may be either an object list, a string list, a number or a Qt model. + In each case, the data element and the index is exposed to each instantiated + component. The index is always exposed as an accessible \c index property. + In the case of an object or string list, the data element (of type string + or object) is available as the \c modelData property. In the case of a Qt model, + all roles are available as named properties just like in the view classes. + + As a special case the model can also be merely a number. In this case it will + create that many instances of the component. They will also be assigned an index + based on the order they are created. + + Models can also be created directly in QML, using a \l{ListModel} or \l{XmlListModel}. + + \sa {qmlmodels}{Data Models} +*/ +QVariant QmlGraphicsRepeater::model() const +{ + Q_D(const QmlGraphicsRepeater); + return d->dataSource; +} + +void QmlGraphicsRepeater::setModel(const QVariant &model) +{ + Q_D(QmlGraphicsRepeater); + 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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + disconnect(d->model, SIGNAL(destroyingItem(QmlGraphicsItem*)), this, SLOT(destroyingItem(QmlGraphicsItem*))); + */ + } + d->dataSource = model; + QObject *object = qvariant_cast<QObject*>(model); + QmlGraphicsVisualModel *vim = 0; + if (object && (vim = qobject_cast<QmlGraphicsVisualModel *>(object))) { + if (d->ownModel) { + delete d->model; + d->ownModel = false; + } + d->model = vim; + } else { + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(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(createdItem(int, QmlGraphicsItem*)), this, SLOT(createdItem(int,QmlGraphicsItem*))); + connect(d->model, SIGNAL(destroyingItem(QmlGraphicsItem*)), this, SLOT(destroyingItem(QmlGraphicsItem*))); + */ + regenerate(); + emit countChanged(); + } +} + +/*! + \qmlproperty Component Repeater::delegate + \default + + The delegate provides a template defining each item instantiated by the repeater. + The index is exposed as an accessible \c index property. Properties of the + model are also available depending upon the type of \l {qmlmodels}{Data Model}. + */ +QmlComponent *QmlGraphicsRepeater::delegate() const +{ + Q_D(const QmlGraphicsRepeater); + if (d->model) { + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) + return dataModel->delegate(); + } + + return 0; +} + +void QmlGraphicsRepeater::setDelegate(QmlComponent *delegate) +{ + Q_D(QmlGraphicsRepeater); + if (!d->ownModel) { + d->model = new QmlGraphicsVisualDataModel(qmlContext(this)); + d->ownModel = true; + } + if (QmlGraphicsVisualDataModel *dataModel = qobject_cast<QmlGraphicsVisualDataModel*>(d->model)) { + dataModel->setDelegate(delegate); + regenerate(); + } +} + +/*! + \qmlproperty int Repeater::count + + This property holds the number of items in the repeater. +*/ +int QmlGraphicsRepeater::count() const +{ + Q_D(const QmlGraphicsRepeater); + if (d->model) + return d->model->count(); + return 0; +} + + +/*! + \internal + */ +void QmlGraphicsRepeater::componentComplete() +{ + QmlGraphicsItem::componentComplete(); + regenerate(); +} + +/*! + \internal + */ +QVariant QmlGraphicsRepeater::itemChange(GraphicsItemChange change, + const QVariant &value) +{ + QVariant rv = QmlGraphicsItem::itemChange(change, value); + if (change == ItemParentHasChanged) { + regenerate(); + } + + return rv; +} + +void QmlGraphicsRepeater::clear() +{ + Q_D(QmlGraphicsRepeater); + if (d->model) { + foreach (QmlGraphicsItem *item, d->deletables) { + item->setParentItem(this); + d->model->release(item); + } + } + d->deletables.clear(); +} + +/*! + \internal + */ +void QmlGraphicsRepeater::regenerate() +{ + Q_D(QmlGraphicsRepeater); + + clear(); + + if (!d->model || !d->model->count() || !d->model->isValid() || !parentItem() || !isComponentComplete()) + return; + + for (int ii = 0; ii < count(); ++ii) { + QmlGraphicsItem *item = d->model->item(ii); + if (item) { + item->setParent(parentItem()); + item->stackBefore(this); + d->deletables << item; + } + } +} + +void QmlGraphicsRepeater::itemsInserted(int, int) +{ + regenerate(); +} + +void QmlGraphicsRepeater::itemsRemoved(int, int) +{ + regenerate(); +} + +void QmlGraphicsRepeater::itemsMoved(int,int,int) +{ + regenerate(); +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsrepeater_p.h b/src/declarative/graphicsitems/qmlgraphicsrepeater_p.h new file mode 100644 index 0000000..61f82d7 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsrepeater_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSREPEATER_H +#define QMLGRAPHICSREPEATER_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsRepeaterPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsRepeater : public QmlGraphicsItem +{ + Q_OBJECT + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_CLASSINFO("DefaultProperty", "delegate") + +public: + QmlGraphicsRepeater(QmlGraphicsItem *parent=0); + virtual ~QmlGraphicsRepeater(); + + QVariant model() const; + void setModel(const QVariant &); + + QmlComponent *delegate() const; + void setDelegate(QmlComponent *); + + int count() const; + +Q_SIGNALS: + void countChanged(); + +private: + void clear(); + void regenerate(); + +protected: + virtual void componentComplete(); + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + +private Q_SLOTS: + void itemsInserted(int,int); + void itemsRemoved(int,int); + void itemsMoved(int,int,int); + +private: + Q_DISABLE_COPY(QmlGraphicsRepeater) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsRepeater) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsRepeater) + +QT_END_HEADER + +#endif // QMLGRAPHICSREPEATER_H diff --git a/src/declarative/graphicsitems/qmlgraphicsrepeater_p_p.h b/src/declarative/graphicsitems/qmlgraphicsrepeater_p_p.h new file mode 100644 index 0000000..e6d7bfd --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsrepeater_p_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSREPEATER_P_H +#define QMLGRAPHICSREPEATER_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 "qmlgraphicsrepeater_p.h" + +#include "qmlgraphicsitem_p.h" + +#include <QPointer> + +QT_BEGIN_NAMESPACE + +class QmlContext; +class QmlGraphicsVisualModel; +class QmlGraphicsRepeaterPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsRepeater) + +public: + QmlGraphicsRepeaterPrivate(); + ~QmlGraphicsRepeaterPrivate(); + + QmlGraphicsVisualModel *model; + QVariant dataSource; + bool ownModel; + + QList<QPointer<QmlGraphicsItem> > deletables; +}; + +QT_END_NAMESPACE +#endif // QMLGRAPHICSREPEATER_P_H diff --git a/src/declarative/graphicsitems/qmlgraphicsscalegrid.cpp b/src/declarative/graphicsitems/qmlgraphicsscalegrid.cpp new file mode 100644 index 0000000..f50b79b --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsscalegrid.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsscalegrid_p_p.h" + +#include <qml.h> + +#include <QBuffer> +#include <QDebug> + +QT_BEGIN_NAMESPACE +/*! + \internal + \class QmlGraphicsScaleGrid + \brief The QmlGraphicsScaleGrid class allows you to specify a 3x3 grid to use in scaling an image. +*/ +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsScaleGrid) + +QmlGraphicsScaleGrid::QmlGraphicsScaleGrid(QObject *parent) : QObject(parent), _left(0), _top(0), _right(0), _bottom(0) +{ +} + +QmlGraphicsScaleGrid::~QmlGraphicsScaleGrid() +{ +} + +bool QmlGraphicsScaleGrid::isNull() const +{ + return !_left && !_top && !_right && !_bottom; +} + +void QmlGraphicsScaleGrid::setLeft(int pos) +{ + if (_left != pos) { + _left = pos; + emit borderChanged(); + } +} + +void QmlGraphicsScaleGrid::setTop(int pos) +{ + if (_top != pos) { + _top = pos; + emit borderChanged(); + } +} + +void QmlGraphicsScaleGrid::setRight(int pos) +{ + if (_right != pos) { + _right = pos; + emit borderChanged(); + } +} + +void QmlGraphicsScaleGrid::setBottom(int pos) +{ + if (_bottom != pos) { + _bottom = pos; + emit borderChanged(); + } +} + +QmlGraphicsGridScaledImage::QmlGraphicsGridScaledImage() +: _l(-1), _r(-1), _t(-1), _b(-1), + _h(QmlGraphicsBorderImage::Stretch), _v(QmlGraphicsBorderImage::Stretch) +{ +} + +QmlGraphicsGridScaledImage::QmlGraphicsGridScaledImage(const QmlGraphicsGridScaledImage &o) +: _l(o._l), _r(o._r), _t(o._t), _b(o._b), _h(o._h), _v(o._v), _pix(o._pix) +{ +} + +QmlGraphicsGridScaledImage &QmlGraphicsGridScaledImage::operator=(const QmlGraphicsGridScaledImage &o) +{ + _l = o._l; + _r = o._r; + _t = o._t; + _b = o._b; + _h = o._h; + _v = o._v; + _pix = o._pix; + return *this; +} + +QmlGraphicsGridScaledImage::QmlGraphicsGridScaledImage(QIODevice *data) +: _l(-1), _r(-1), _t(-1), _b(-1), _h(QmlGraphicsBorderImage::Stretch), _v(QmlGraphicsBorderImage::Stretch) +{ + int l = -1; + int r = -1; + int t = -1; + int b = -1; + QString imgFile; + + while(!data->atEnd()) { + QString line = QString::fromUtf8(data->readLine().trimmed()); + if (line.isEmpty() || line.startsWith(QLatin1Char('#'))) + continue; + + QStringList list = line.split(QLatin1Char(':')); + if (list.count() != 2) + return; + + list[0] = list[0].trimmed(); + list[1] = list[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; +} + +QmlGraphicsBorderImage::TileMode QmlGraphicsGridScaledImage::stringToRule(const QString &s) +{ + if (s == QLatin1String("Stretch")) + return QmlGraphicsBorderImage::Stretch; + if (s == QLatin1String("Repeat")) + return QmlGraphicsBorderImage::Repeat; + if (s == QLatin1String("Round")) + return QmlGraphicsBorderImage::Round; + + qWarning() << "Unknown tile rule specified. Using Stretch"; + return QmlGraphicsBorderImage::Stretch; +} + +bool QmlGraphicsGridScaledImage::isValid() const +{ + return _l >= 0; +} + +int QmlGraphicsGridScaledImage::gridLeft() const +{ + return _l; +} + +int QmlGraphicsGridScaledImage::gridRight() const +{ + return _r; +} + +int QmlGraphicsGridScaledImage::gridTop() const +{ + return _t; +} + +int QmlGraphicsGridScaledImage::gridBottom() const +{ + return _b; +} + +QString QmlGraphicsGridScaledImage::pixmapUrl() const +{ + return _pix; +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicsscalegrid_p_p.h b/src/declarative/graphicsitems/qmlgraphicsscalegrid_p_p.h new file mode 100644 index 0000000..88938a7 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsscalegrid_p_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSSCALEGRID_H +#define QMLGRAPHICSSCALEGRID_H + +#include "qmlgraphicsborderimage_p.h" + +#include "../util/qmlpixmapcache_p.h" +#include <qml.h> + +#include <QtCore/QString> +#include <QtCore/QObject> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class Q_DECLARATIVE_EXPORT QmlGraphicsScaleGrid : 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: + QmlGraphicsScaleGrid(QObject *parent=0); + ~QmlGraphicsScaleGrid(); + + 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_EXPORT QmlGraphicsGridScaledImage +{ +public: + QmlGraphicsGridScaledImage(); + QmlGraphicsGridScaledImage(const QmlGraphicsGridScaledImage &); + QmlGraphicsGridScaledImage(QIODevice*); + QmlGraphicsGridScaledImage &operator=(const QmlGraphicsGridScaledImage &); + bool isValid() const; + int gridLeft() const; + int gridRight() const; + int gridTop() const; + int gridBottom() const; + QmlGraphicsBorderImage::TileMode horizontalTileRule() const { return _h; } + QmlGraphicsBorderImage::TileMode verticalTileRule() const { return _v; } + + QString pixmapUrl() const; + +private: + static QmlGraphicsBorderImage::TileMode stringToRule(const QString &); + +private: + int _l; + int _r; + int _t; + int _b; + QmlGraphicsBorderImage::TileMode _h; + QmlGraphicsBorderImage::TileMode _v; + QString _pix; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsScaleGrid) + +QT_END_HEADER + +#endif // QMLGRAPHICSSCALEGRID_H diff --git a/src/declarative/graphicsitems/qmlgraphicstext.cpp b/src/declarative/graphicsitems/qmlgraphicstext.cpp new file mode 100644 index 0000000..b13fb7c --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstext.cpp @@ -0,0 +1,927 @@ +/**************************************************************************** +** +** 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 "qmlgraphicstext_p.h" +#include "qmlgraphicstext_p_p.h" +#include <qmlstyledtext_p.h> + +#include <qfxperf_p_p.h> + +#include <QTextLayout> +#include <QTextLine> +#include <QTextDocument> +#include <QTextCursor> +#include <QGraphicsSceneMouseEvent> +#include <QPainter> +#include <QAbstractTextDocumentLayout> +#include <qmath.h> + +QT_BEGIN_NAMESPACE +QML_DEFINE_TYPE(Qt,4,6,Text,QmlGraphicsText) + +/*! + \qmlclass Text QmlGraphicsText + \brief The Text item allows you to add formatted text to a scene. + \inherits Item + + It can display both plain and rich text. For example: + + \qml + Text { text: "Hello World!"; font.family: "Helvetica"; font.pointSize: 24; color: "red" } + Text { text: "<b>Hello</b> <i>World!</i>" } + \endqml + + \image declarative-text.png + + If height and width are not explicitly set, Text will attempt to determine how + much room is needed and set it accordingly. Unless \c wrap is set, it will always + prefer width to height (all text will be placed on a single line). + + The \c elide property can alternatively be used to fit a single line of + plain text to a set width. + + Text provides read-only text. For editable text, see \l TextEdit. +*/ + +/*! + \internal + \class QmlGraphicsText + \qmlclass Text + \ingroup group_coreitems + + \brief The QmlGraphicsText class provides a formatted text item that you can add to a QmlView. + + Text was designed for read-only text; it does not allow for any text editing. + It can display both plain and rich text. For example: + + \qml + Text { text: "Hello World!"; font.family: "Helvetica"; font.pointSize: 24; color: "red" } + Text { text: "<b>Hello</b> <i>World!</i>" } + \endqml + + \image text.png + + If height and width are not explicitly set, Text will attempt to determine how + much room is needed and set it accordingly. Unless \c wrap is set, it will always + prefer width to height (all text will be placed on a single line). + + The \c elide property can alternatively be used to fit a line of plain text to a set width. + + A QmlGraphicsText object can be instantiated in Qml using the tag \c Text. +*/ +QmlGraphicsText::QmlGraphicsText(QmlGraphicsItem *parent) + : QmlGraphicsItem(*(new QmlGraphicsTextPrivate), parent) +{ + setAcceptedMouseButtons(Qt::LeftButton); + setFlag(QGraphicsItem::ItemHasNoContents, false); +} + +QmlGraphicsText::~QmlGraphicsText() +{ +} + + +QmlGraphicsTextPrivate::~QmlGraphicsTextPrivate() +{ +} + +/*! + \qmlproperty string Text::font.family + \qmlproperty bool Text::font.bold + \qmlproperty bool Text::font.italic + \qmlproperty bool Text::font.underline + \qmlproperty real Text::font.pointSize + \qmlproperty int Text::font.pixelSize + + Set the Text's font attributes. +*/ +QFont QmlGraphicsText::font() const +{ + Q_D(const QmlGraphicsText); + return d->font; +} + +void QmlGraphicsText::setFont(const QFont &font) +{ + Q_D(QmlGraphicsText); + if (d->font == font) + return; + + d->font = font; + + d->updateLayout(); + d->markImgDirty(); + emit fontChanged(d->font); +} + +void QmlGraphicsText::setText(const QString &n) +{ +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::QmlGraphicsText_setText> st; +#endif + Q_D(QmlGraphicsText); + if (d->text == n) + return; + + d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(n)); + if (d->richText) { + if (!d->doc) { + d->doc = new QTextDocument(this); + d->doc->setDocumentMargin(0); + } + d->doc->setHtml(n); + } + + d->text = n; + d->updateLayout(); + d->markImgDirty(); + emit textChanged(d->text); +} + +/*! + \qmlproperty string Text::text + + The text to display. Text supports both plain and rich text strings. + + The item will try to automatically determine whether the text should + be treated as rich text. This determination is made using Qt::mightBeRichText(). +*/ +QString QmlGraphicsText::text() const +{ + Q_D(const QmlGraphicsText); + return d->text; +} + +void QmlGraphicsText::setColor(const QColor &color) +{ + Q_D(QmlGraphicsText); + if (d->color == color) + return; + + d->color = color; + d->markImgDirty(); + emit colorChanged(d->color); +} + +/*! + \qmlproperty color Text::color + + The text color. + + \qml + //green text using hexadecimal notation + Text { color: "#00FF00"; ... } + + //steelblue text using SVG color name + Text { color: "steelblue"; ... } + \endqml +*/ + +QColor QmlGraphicsText::color() const +{ + Q_D(const QmlGraphicsText); + return d->color; +} + +/*! + \qmlproperty enumeration Text::style + + Set an additional text style. + + Supported text styles are \c Normal, \c Outline, \c Raised and \c Sunken. + + \qml + Row { + Text { font.pointSize: 24; text: "Normal" } + Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" } + Text { font.pointSize: 24; text: "Outline"; style: Text.Outline; styleColor: "red" } + Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" } + } + \endqml + + \image declarative-textstyle.png +*/ +QmlGraphicsText::TextStyle QmlGraphicsText::style() const +{ + Q_D(const QmlGraphicsText); + return d->style; +} + +void QmlGraphicsText::setStyle(QmlGraphicsText::TextStyle style) +{ + Q_D(QmlGraphicsText); + if (d->style == style) + return; + + d->style = style; + d->markImgDirty(); + emit styleChanged(d->style); +} + +void QmlGraphicsText::setStyleColor(const QColor &color) +{ + Q_D(QmlGraphicsText); + if (d->styleColor == color) + return; + + d->styleColor = color; + d->markImgDirty(); + emit styleColorChanged(d->styleColor); +} + +/*! + \qmlproperty color Text::styleColor + + Defines the secondary color used by text styles. + + \c styleColor is used as the outline color for outlined text, and as the + shadow color for raised or sunken text. If no style has been set, it is not + used at all. + */ +QColor QmlGraphicsText::styleColor() const +{ + Q_D(const QmlGraphicsText); + return d->styleColor; +} + +/*! + \qmlproperty enumeration Text::horizontalAlignment + \qmlproperty enumeration Text::verticalAlignment + + Sets the horizontal and vertical alignment of the text within the Text items + width and height. By default, the text is top-left aligned. + + The valid values for \c horizontalAlignment are \c AlignLeft, \c AlignRight and + \c AlignHCenter. The valid values for \c verticalAlignment are \c AlignTop, \c AlignBottom + and \c AlignVCenter. +*/ +QmlGraphicsText::HAlignment QmlGraphicsText::hAlign() const +{ + Q_D(const QmlGraphicsText); + return d->hAlign; +} + +void QmlGraphicsText::setHAlign(HAlignment align) +{ + Q_D(QmlGraphicsText); + if (d->hAlign == align) + return; + + d->hAlign = align; + emit horizontalAlignmentChanged(align); +} + +QmlGraphicsText::VAlignment QmlGraphicsText::vAlign() const +{ + Q_D(const QmlGraphicsText); + return d->vAlign; +} + +void QmlGraphicsText::setVAlign(VAlignment align) +{ + Q_D(QmlGraphicsText); + if (d->vAlign == align) + return; + + d->vAlign = align; + emit verticalAlignmentChanged(align); +} + +/*! + \qmlproperty bool Text::wrap + + Set this property to wrap the text to the Text item's width. The text will only + wrap if an explicit width has been set. + + Wrapping is done on word boundaries (i.e. it is a "word-wrap"). If the text cannot be + word-wrapped to the specified width it will be partially drawn outside of the item's bounds. + If this is undesirable then enable clipping on the item (Item::clip). + + Wrapping is off by default. +*/ +//### Future may provide choice of wrap modes, such as QTextOption::WrapAtWordBoundaryOrAnywhere +bool QmlGraphicsText::wrap() const +{ + Q_D(const QmlGraphicsText); + return d->wrap; +} + +void QmlGraphicsText::setWrap(bool w) +{ + Q_D(QmlGraphicsText); + if (w == d->wrap) + return; + + d->wrap = w; + + d->updateLayout(); + d->markImgDirty(); + emit wrapChanged(d->wrap); +} + +/*! + \qmlproperty enumeration Text::textFormat + + The way the text property should be displayed. + + Supported text formats are \c AutoText, \c PlainText, \c RichText and \c StyledText + + The default is AutoText. If the text format is AutoText the text element + will automatically determine whether the text should be treated as + rich text. This determination is made using Qt::mightBeRichText(). + + StyledText is an optimized format supporting some basic text + styling markup, in the style of html 3.2: + + \code + <font size="4" color="#ff0000">font size and color</font> + <b>bold</b> + <i>italic</i> + <br> + > < & + \endcode + + \c StyledText parser is strict, requiring tags to be correctly nested. + + \table + \row + \o + \qml +Column { + TextEdit { + font.pointSize: 24 + text: "<b>Hello</b> <i>World!</i>" + } + TextEdit { + font.pointSize: 24 + textFormat: "RichText" + text: "<b>Hello</b> <i>World!</i>" + } + TextEdit { + font.pointSize: 24 + textFormat: "PlainText" + text: "<b>Hello</b> <i>World!</i>" + } +} + \endqml + \o \image declarative-textformat.png + \endtable +*/ + +QmlGraphicsText::TextFormat QmlGraphicsText::textFormat() const +{ + Q_D(const QmlGraphicsText); + return d->format; +} + +void QmlGraphicsText::setTextFormat(TextFormat format) +{ + Q_D(QmlGraphicsText); + 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) { + //### delete control? (and vice-versa below) + d->updateLayout(); + d->markImgDirty(); + } else if (!wasRich && d->richText) { + if (!d->doc) { + d->doc = new QTextDocument(this); + d->doc->setDocumentMargin(0); + } + d->doc->setHtml(d->text); + d->updateLayout(); + d->markImgDirty(); + } + + emit textFormatChanged(d->format); +} + +/*! + \qmlproperty enumeration Text::elide + + Set this property to elide parts of the text fit to the Text item's width. + The text will only elide if an explicit width has been set. + + This property cannot be used with wrap enabled or with rich text. + + Eliding can be \c ElideNone (the default), \c ElideLeft, \c ElideMiddle, or \c ElideRight. + + If the text is a multi-length string, and the mode is not \c ElideNone, + the first string that fits will be used, otherwise the last will be elided. + + Multi-length strings are ordered from longest to shortest, separated by the + Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}). +*/ +QmlGraphicsText::TextElideMode QmlGraphicsText::elideMode() const +{ + Q_D(const QmlGraphicsText); + return d->elideMode; +} + +void QmlGraphicsText::setElideMode(QmlGraphicsText::TextElideMode mode) +{ + Q_D(QmlGraphicsText); + if (mode == d->elideMode) + return; + + d->elideMode = mode; + + d->updateLayout(); + d->markImgDirty(); + emit elideModeChanged(d->elideMode); +} + +void QmlGraphicsText::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + Q_D(QmlGraphicsText); + if (newGeometry.width() != oldGeometry.width()) { + if (d->wrap || d->elideMode != QmlGraphicsText::ElideNone) { + //re-elide if needed + if (d->singleline && d->elideMode != QmlGraphicsText::ElideNone && + isComponentComplete() && widthValid()) { + + QFontMetrics fm(d->font); + QString tmp = fm.elidedText(d->text,(Qt::TextElideMode)d->elideMode,width()); // XXX still worth layout...? + d->layout.setText(tmp); + } + + d->imgDirty = true; + d->updateSize(); + } + } + QmlGraphicsItem::geometryChanged(newGeometry, oldGeometry); +} + +void QmlGraphicsTextPrivate::updateLayout() +{ + Q_Q(QmlGraphicsText); + if (q->isComponentComplete()) { + //setup instance of QTextLayout for all cases other than richtext + if (!richText) { + layout.clearLayout(); + layout.setFont(font); + if (format != QmlGraphicsText::StyledText) { + QString tmp = text; + tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); + singleline = !tmp.contains(QChar::LineSeparator); + if (singleline && elideMode != QmlGraphicsText::ElideNone && q->widthValid()) { + QFontMetrics fm(font); + tmp = fm.elidedText(tmp,(Qt::TextElideMode)elideMode,q->width()); // XXX still worth layout...? + } + layout.setText(tmp); + } else { + singleline = false; + QmlStyledText::parse(text, layout); + } + } + updateSize(); + } else { + dirty = true; + } +} + +void QmlGraphicsTextPrivate::updateSize() +{ + Q_Q(QmlGraphicsText); + if (q->isComponentComplete()) { + QFontMetrics fm(font); + if (text.isEmpty()) { + q->setImplicitHeight(fm.height()); + return; + } + + int dy = q->height(); + QSize size(0, 0); + + //setup instance of QTextLayout for all cases other than richtext + if (!richText) { + size = setupTextLayout(&layout); + cachedLayoutSize = size; + dy -= size.height(); + } else { + singleline = false; // richtext can't elide or be optimized for single-line case + doc->setDefaultFont(font); + QTextOption option((Qt::Alignment)int(hAlign | vAlign)); + if (wrap) + option.setWrapMode(QTextOption::WordWrap); + else + option.setWrapMode(QTextOption::NoWrap); + doc->setDefaultTextOption(option); + if (wrap && !q->heightValid() && 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(); + } + int yoff = 0; + + if (q->heightValid()) { + if (vAlign == QmlGraphicsText::AlignBottom) + yoff = dy; + else if (vAlign == QmlGraphicsText::AlignVCenter) + yoff = dy/2; + } + q->setBaselineOffset(fm.ascent() + yoff); + + //### need to comfirm cost of always setting these for richText + q->setImplicitWidth(richText ? (int)doc->idealWidth() : size.width()); + q->setImplicitHeight(richText ? (int)doc->size().height() : size.height()); + } else { + dirty = true; + } +} + +// ### text layout handling should be profiled and optimized as needed +// what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine); + +void QmlGraphicsTextPrivate::drawOutline() +{ + QPixmap img = QPixmap(imgStyleCache.width()+2,imgStyleCache.height()+2); + img.fill(Qt::transparent); + + QPainter ppm(&img); + + QPoint pos(imgCache.rect().topLeft()); + pos += QPoint(-1, 0); + ppm.drawPixmap(pos, imgStyleCache); + pos += QPoint(2, 0); + ppm.drawPixmap(pos, imgStyleCache); + pos += QPoint(-1, -1); + ppm.drawPixmap(pos, imgStyleCache); + pos += QPoint(0, 2); + ppm.drawPixmap(pos, imgStyleCache); + + pos += QPoint(0, -1); + ppm.drawPixmap(pos, imgCache); + ppm.end(); + + imgCache = img; +} + +void QmlGraphicsTextPrivate::drawOutline(int yOffset) +{ + QPixmap img = QPixmap(imgStyleCache.width()+2,imgStyleCache.height()+2); + img.fill(Qt::transparent); + + QPainter ppm(&img); + + QPoint pos(imgCache.rect().topLeft()); + pos += QPoint(0, yOffset); + ppm.drawPixmap(pos, imgStyleCache); + + pos += QPoint(0, -yOffset); + ppm.drawPixmap(pos, imgCache); + ppm.end(); + + imgCache = img; +} + +QSize QmlGraphicsTextPrivate::setupTextLayout(QTextLayout *layout) +{ + Q_Q(QmlGraphicsText); + layout->setCacheEnabled(true); + + int height = 0; + qreal widthUsed = 0; + qreal lineWidth = 0; + + //set manual width + if ((wrap || elideMode != QmlGraphicsText::ElideNone) && q->widthValid()) + lineWidth = q->width(); + + layout->beginLayout(); + + while (1) { + QTextLine line = layout->createLine(); + if (!line.isValid()) + break; + + if ((wrap || elideMode != QmlGraphicsText::ElideNone) && q->widthValid()) + line.setLineWidth(lineWidth); + } + layout->endLayout(); + + int x = 0; + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + line.setPosition(QPointF(0, height)); + height += int(line.height()); + + if (!cache) { + if (hAlign == QmlGraphicsText::AlignLeft) { + x = 0; + } else if (hAlign == QmlGraphicsText::AlignRight) { + x = q->width() - (int)line.naturalTextWidth(); + } else if (hAlign == QmlGraphicsText::AlignHCenter) { + x = (q->width() - (int)line.naturalTextWidth()) / 2; + } + line.setPosition(QPoint(x, (int)line.y())); + } + } + + return QSize(qCeil(widthUsed), height); +} + +QPixmap QmlGraphicsTextPrivate::wrappedTextImage(bool drawStyle) +{ + //do layout + QSize size = cachedLayoutSize; + + int x = 0; + for (int i = 0; i < layout.lineCount(); ++i) { + QTextLine line = layout.lineAt(i); + if (hAlign == QmlGraphicsText::AlignLeft) { + x = 0; + } else if (hAlign == QmlGraphicsText::AlignRight) { + x = size.width() - (int)line.naturalTextWidth(); + } else if (hAlign == QmlGraphicsText::AlignHCenter) { + x = (size.width() - (int)line.naturalTextWidth()) / 2; + } + line.setPosition(QPoint(x, (int)line.y())); + } + + //paint text + QPixmap img(size); + if (!size.isEmpty()) { + img.fill(Qt::transparent); + QPainter p(&img); + drawWrappedText(&p, QPointF(0,0), drawStyle); + } + return img; +} + +void QmlGraphicsTextPrivate::drawWrappedText(QPainter *p, const QPointF &pos, bool drawStyle) +{ + if (drawStyle) + p->setPen(styleColor); + else + p->setPen(color); + p->setFont(font); + layout.draw(p, pos); +} + +QPixmap QmlGraphicsTextPrivate::richTextImage(bool drawStyle) +{ + QSize size = doc->size().toSize(); + + //paint text + QPixmap img(size); + img.fill(Qt::transparent); + QPainter p(&img); + + QAbstractTextDocumentLayout::PaintContext context; + + if (drawStyle) { + context.palette.setColor(QPalette::Text, styleColor); + // ### Do we really want this? + QTextOption colorOption; + colorOption.setFlags(QTextOption::SuppressColors); + doc->setDefaultTextOption(colorOption); + } else { + context.palette.setColor(QPalette::Text, color); + } + doc->documentLayout()->draw(&p, context); + if (drawStyle) + doc->setDefaultTextOption(QTextOption()); + return img; +} + +void QmlGraphicsTextPrivate::checkImgCache() +{ + if (!imgDirty) + return; + + bool empty = text.isEmpty(); + if (empty) { + imgCache = QPixmap(); + imgStyleCache = QPixmap(); + } else if (richText) { + imgCache = richTextImage(false); + if (style != QmlGraphicsText::Normal) + imgStyleCache = richTextImage(true); //### should use styleColor + } else { + imgCache = wrappedTextImage(false); + if (style != QmlGraphicsText::Normal) + imgStyleCache = wrappedTextImage(true); //### should use styleColor + } + if (!empty) + switch (style) { + case QmlGraphicsText::Outline: + drawOutline(); + break; + case QmlGraphicsText::Sunken: + drawOutline(-1); + break; + case QmlGraphicsText::Raised: + drawOutline(1); + break; + default: + break; + } + + imgDirty = false; +} + +void QmlGraphicsText::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) +{ + Q_D(QmlGraphicsText); + + if (d->cache || d->richText || d->style != Normal) { + d->checkImgCache(); + if (d->imgCache.isNull()) + return; + + bool oldAA = p->testRenderHint(QPainter::Antialiasing); + bool oldSmooth = p->testRenderHint(QPainter::SmoothPixmapTransform); + if (d->smooth) + p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth); + + int w = width(); + int h = height(); + + int x = 0; + int y = 0; + + switch (d->hAlign) { + case AlignLeft: + x = 0; + break; + case AlignRight: + x = w - d->imgCache.width(); + break; + case AlignHCenter: + x = (w - d->imgCache.width()) / 2; + break; + } + + switch (d->vAlign) { + case AlignTop: + y = 0; + break; + case AlignBottom: + y = h - d->imgCache.height(); + break; + case AlignVCenter: + y = (h - d->imgCache.height()) / 2; + break; + } + + bool needClip = !clip() && (d->imgCache.width() > width() || + d->imgCache.height() > height()); + + if (needClip) { + p->save(); + p->setClipRect(boundingRect(), Qt::IntersectClip); + } + p->drawPixmap(x, y, d->imgCache); + if (needClip) + p->restore(); + + if (d->smooth) { + p->setRenderHint(QPainter::Antialiasing, oldAA); + p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth); + } + } else { + int h = height(); + int y = 0; + + switch (d->vAlign) { + case AlignTop: + y = 0; + break; + case AlignBottom: + y = h - d->cachedLayoutSize.height(); + break; + case AlignVCenter: + y = (h - d->cachedLayoutSize.height()) / 2; + break; + } + bool needClip = !clip() && (d->cachedLayoutSize.width() > width() || + d->cachedLayoutSize.height() > height()); + + if (needClip) { + p->save(); + p->setClipRect(boundingRect(), Qt::IntersectClip); + } + d->drawWrappedText(p, QPointF(0,y), false); + if (needClip) + p->restore(); + } +} + +/*! + \qmlproperty bool Text::smooth + + Set this property if you want the text to be smoothly scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +void QmlGraphicsText::componentComplete() +{ + Q_D(QmlGraphicsText); +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::TextComponentComplete> cc; +#endif + QmlGraphicsItem::componentComplete(); + if (d->dirty) { + d->updateLayout(); + d->dirty = false; + } +} + +/*! + \overload + Handles the given mouse \a event. + */ +void QmlGraphicsText::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsText); + + if (!d->richText || !d->doc || d->doc->documentLayout()->anchorAt(event->pos()).isEmpty()) { + event->setAccepted(false); + d->activeLink = QString(); + } 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()) + QmlGraphicsItem::mousePressEvent(event); + +} + +/*! + \qmlsignal Text::linkActivated(link) + + This handler is called when the user clicks on a link embedded in the text. +*/ + +/*! + \overload + Handles the given mouse \a event. + */ +void QmlGraphicsText::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsText); + + // ### 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()) + QmlGraphicsItem::mouseReleaseEvent(event); +} +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicstext_p.h b/src/declarative/graphicsitems/qmlgraphicstext_p.h new file mode 100644 index 0000000..8fa2e65 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstext_p.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSTEXT_H +#define QMLGRAPHICSTEXT_H + +#include "qmlgraphicsitem.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class QmlGraphicsTextPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsText : public QmlGraphicsItem +{ + Q_OBJECT + Q_ENUMS(HAlignment) + Q_ENUMS(VAlignment) + Q_ENUMS(TextStyle) + Q_ENUMS(TextFormat) + Q_ENUMS(TextElideMode) + + 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 NOTIFY horizontalAlignmentChanged) + Q_PROPERTY(VAlignment verticalAlignment READ vAlign WRITE setVAlign NOTIFY verticalAlignmentChanged) + Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged) //### there are several wrap modes in Qt + Q_PROPERTY(TextFormat textFormat READ textFormat WRITE setTextFormat NOTIFY textFormatChanged) + Q_PROPERTY(TextElideMode elide READ elideMode WRITE setElideMode NOTIFY elideModeChanged) //### elideMode? + +public: + QmlGraphicsText(QmlGraphicsItem *parent=0); + ~QmlGraphicsText(); + + enum HAlignment { AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter }; + 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 }; + + 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); + + VAlignment vAlign() const; + void setVAlign(VAlignment align); + + bool wrap() const; + void setWrap(bool w); + + TextFormat textFormat() const; + void setTextFormat(TextFormat format); + + TextElideMode elideMode() const; + void setElideMode(TextElideMode); + + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + virtual void componentComplete(); + +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 wrapChanged(bool wrap); + void textFormatChanged(TextFormat textFormat); + void elideModeChanged(TextElideMode mode); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + +private: + Q_DISABLE_COPY(QmlGraphicsText) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsText) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsText) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicstext_p_p.h b/src/declarative/graphicsitems/qmlgraphicstext_p_p.h new file mode 100644 index 0000000..1e29e58 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstext_p_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSTEXT_P_H +#define QMLGRAPHICSTEXT_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 "qmlgraphicsitem.h" +#include "qmlgraphicsitem_p.h" + +#include <qml.h> + +#include <QtGui/qtextlayout.h> + +QT_BEGIN_NAMESPACE + +class QTextLayout; +class QTextDocument; + +class QmlGraphicsTextPrivate : public QmlGraphicsItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsText) +public: + QmlGraphicsTextPrivate() + : color((QRgb)0), style(QmlGraphicsText::Normal), imgDirty(true), + hAlign(QmlGraphicsText::AlignLeft), vAlign(QmlGraphicsText::AlignTop), elideMode(QmlGraphicsText::ElideNone), + dirty(true), wrap(false), richText(false), singleline(false), cache(true), doc(0), + format(QmlGraphicsText::AutoText) + { +#ifdef QML_NO_TEXT_CACHE + cache = false; +#endif + } + + ~QmlGraphicsTextPrivate(); + + void updateSize(); + void updateLayout(); + void markImgDirty() { + Q_Q(QmlGraphicsText); + imgDirty = true; + if (q->isComponentComplete()) + q->update(); + } + void checkImgCache(); + + void drawOutline(); + void drawOutline(int yOffset); + + QPixmap wrappedTextImage(bool drawStyle); + void drawWrappedText(QPainter *p, const QPointF &pos, bool drawStyle); + QPixmap richTextImage(bool drawStyle); + QSize setupTextLayout(QTextLayout *layout); + + QString text; + QFont font; + QColor color; + QmlGraphicsText::TextStyle style; + QColor styleColor; + QString activeLink; + bool imgDirty; + QPixmap imgCache; + QPixmap imgStyleCache; + QmlGraphicsText::HAlignment hAlign; + QmlGraphicsText::VAlignment vAlign; + QmlGraphicsText::TextElideMode elideMode; + bool dirty:1; + bool wrap:1; + bool richText:1; + bool singleline:1; + bool cache:1; + QTextDocument *doc; + QTextLayout layout; + QSize cachedLayoutSize; + QmlGraphicsText::TextFormat format; +}; + +QT_END_NAMESPACE +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicstextedit.cpp b/src/declarative/graphicsitems/qmlgraphicstextedit.cpp new file mode 100644 index 0000000..fc80258 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstextedit.cpp @@ -0,0 +1,1035 @@ +/**************************************************************************** +** +** 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 "qmlgraphicstextedit_p.h" +#include "qmlgraphicstextedit_p_p.h" + +#include "qmlgraphicsevents_p_p.h" + +#include <qfxperf_p_p.h> + +#include <QTextLayout> +#include <QTextLine> +#include <QTextDocument> +#include <QGraphicsSceneMouseEvent> +#include <QDebug> +#include <QPainter> + +#include <private/qtextcontrol_p.h> + +QT_BEGIN_NAMESPACE +QML_DEFINE_TYPE(Qt,4,6,TextEdit,QmlGraphicsTextEdit) + +/*! + \qmlclass TextEdit QmlGraphicsTextEdit + \brief The TextEdit item allows you to add editable formatted text to a scene. + + It can display both plain and rich text. For example: + + \qml +TextEdit { + id: edit + text: "<b>Hello</b> <i>World!</i>" + focus: true + font.family: "Helvetica" + font.pointSize: 20 + color: "blue" + width: 240 +} + \endqml + + \image declarative-textedit.gif + + \sa Text +*/ + +/*! + \internal + \class QmlGraphicsTextEdit + \qmlclass TextEdit + \ingroup group_coreitems + + \brief The QmlGraphicsTextEdit class provides an editable formatted text item that you can add to a QmlView. + + It can display both plain and rich text. + + \image declarative-textedit.png + + A QmlGraphicsTextEdit object can be instantiated in Qml using the tag \c <TextEdit>. +*/ + +/*! + Constructs a new QmlGraphicsTextEdit. +*/ +QmlGraphicsTextEdit::QmlGraphicsTextEdit(QmlGraphicsItem *parent) +: QmlGraphicsPaintedItem(*(new QmlGraphicsTextEditPrivate), parent) +{ + Q_D(QmlGraphicsTextEdit); + d->init(); +} + +QString QmlGraphicsTextEdit::text() const +{ + Q_D(const QmlGraphicsTextEdit); + + if (d->richText) + return d->document->toHtml(); + else + return d->document->toPlainText(); +} + +/*! + \qmlproperty string TextEdit::font.family + \qmlproperty bool TextEdit::font.bold + \qmlproperty bool TextEdit::font.italic + \qmlproperty bool TextEdit::font.underline + \qmlproperty real TextEdit::font.pointSize + \qmlproperty int TextEdit::font.pixelSize + + Set the TextEdit's font attributes. +*/ + +/*! + \qmlproperty string TextEdit::text + + The text to display. If the text format is AutoText the text edit will + automatically determine whether the text should be treated as + rich text. This determination is made using Qt::mightBeRichText(). +*/ +void QmlGraphicsTextEdit::setText(const QString &text) +{ + Q_D(QmlGraphicsTextEdit); + if (QmlGraphicsTextEdit::text() == text) + return; + d->text = text; + d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(text)); + if (d->richText) { + d->control->setHtml(text); + } else { + d->control->setPlainText(text); + } + q_textChanged(); +} + +/*! + \qmlproperty enumeration TextEdit::textFormat + + The way the text property should be displayed. + + Supported text formats are \c AutoText, \c PlainText and \c RichText. + + The default is AutoText. If the text format is AutoText the text edit + will automatically determine whether the text should be treated as + rich text. This determination is made using Qt::mightBeRichText(). + + \table + \row + \o + \qml +Column { + TextEdit { + font.pointSize: 24 + text: "<b>Hello</b> <i>World!</i>" + } + TextEdit { + font.pointSize: 24 + textFormat: "RichText" + text: "<b>Hello</b> <i>World!</i>" + } + TextEdit { + font.pointSize: 24 + textFormat: "PlainText" + text: "<b>Hello</b> <i>World!</i>" + } +} + \endqml + \o \image declarative-textformat.png + \endtable +*/ +QmlGraphicsTextEdit::TextFormat QmlGraphicsTextEdit::textFormat() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->format; +} + +void QmlGraphicsTextEdit::setTextFormat(TextFormat format) +{ + Q_D(QmlGraphicsTextEdit); + 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) { + d->control->setHtml(d->text); + updateSize(); + } + d->format = format; + emit textFormatChanged(d->format); +} + +QFont QmlGraphicsTextEdit::font() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->font; +} + +void QmlGraphicsTextEdit::setFont(const QFont &font) +{ + Q_D(QmlGraphicsTextEdit); + d->font = font; + + clearCache(); + d->document->setDefaultFont(d->font); + if(d->cursor){ + d->cursor->setHeight(QFontMetrics(d->font).height()); + moveCursorDelegate(); + } + updateSize(); + update(); +} + +/*! + \qmlproperty color TextEdit::color + + The text color. + + \qml +// green text using hexadecimal notation +TextEdit { color: "#00FF00"; ... } + +// steelblue text using SVG color name +TextEdit { color: "steelblue"; ... } + \endqml +*/ +QColor QmlGraphicsTextEdit::color() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->color; +} + +void QmlGraphicsTextEdit::setColor(const QColor &color) +{ + Q_D(QmlGraphicsTextEdit); + if (d->color == color) + return; + + clearCache(); + d->color = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::Text, color); + d->control->setPalette(pal); + update(); + emit colorChanged(d->color); +} + +/*! + \qmlproperty color TextEdit::selectionColor + + The text highlight color, used behind selections. +*/ +QColor QmlGraphicsTextEdit::selectionColor() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->selectionColor; +} + +void QmlGraphicsTextEdit::setSelectionColor(const QColor &color) +{ + Q_D(QmlGraphicsTextEdit); + if (d->selectionColor == color) + return; + + clearCache(); + d->selectionColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::Highlight, color); + d->control->setPalette(pal); + update(); + emit selectionColorChanged(d->selectionColor); +} + +/*! + \qmlproperty color TextEdit::selectedTextColor + + The selected text color, used in selections. +*/ +QColor QmlGraphicsTextEdit::selectedTextColor() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->selectedTextColor; +} + +void QmlGraphicsTextEdit::setSelectedTextColor(const QColor &color) +{ + Q_D(QmlGraphicsTextEdit); + if (d->selectedTextColor == color) + return; + + clearCache(); + d->selectedTextColor = color; + QPalette pal = d->control->palette(); + pal.setColor(QPalette::HighlightedText, color); + d->control->setPalette(pal); + update(); + emit selectedTextColorChanged(d->selectedTextColor); +} + +/*! + \qmlproperty enumeration TextEdit::horizontalAlignment + \qmlproperty enumeration TextEdit::verticalAlignment + + Sets the horizontal and vertical alignment of the text within the TextEdit items + width and height. By default, the text is top-left aligned. + + The valid values for \c horizontalAlignment are \c AlignLeft, \c AlignRight and + \c AlignHCenter. The valid values for \c verticalAlignment are \c AlignTop, \c AlignBottom + and \c AlignVCenter. +*/ +QmlGraphicsTextEdit::HAlignment QmlGraphicsTextEdit::hAlign() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->hAlign; +} + +void QmlGraphicsTextEdit::setHAlign(QmlGraphicsTextEdit::HAlignment alignment) +{ + Q_D(QmlGraphicsTextEdit); + if (alignment == d->hAlign) + return; + d->hAlign = alignment; + d->updateDefaultTextOption(); + updateSize(); + emit horizontalAlignmentChanged(d->hAlign); +} + +QmlGraphicsTextEdit::VAlignment QmlGraphicsTextEdit::vAlign() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->vAlign; +} + +void QmlGraphicsTextEdit::setVAlign(QmlGraphicsTextEdit::VAlignment alignment) +{ + Q_D(QmlGraphicsTextEdit); + if (alignment == d->vAlign) + return; + d->vAlign = alignment; + d->updateDefaultTextOption(); + updateSize(); + emit verticalAlignmentChanged(d->vAlign); +} + +bool QmlGraphicsTextEdit::wrap() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->wrap; +} + +/*! + \qmlproperty bool TextEdit::wrap + + Set this property to wrap the text to the TextEdit item's width. + The text will only wrap if an explicit width has been set. + + Wrapping is done on word boundaries (i.e. it is a "word-wrap"). Wrapping is off by default. +*/ +void QmlGraphicsTextEdit::setWrap(bool w) +{ + Q_D(QmlGraphicsTextEdit); + if (w == d->wrap) + return; + d->wrap = w; + d->updateDefaultTextOption(); + updateSize(); + emit wrapChanged(d->wrap); +} + +/*! + \qmlproperty bool TextEdit::cursorVisible + If true the text edit shows a cursor. + + This property is set and unset when the text edit gets focus, but it can also + be set directly (useful, for example, if a KeyProxy might forward keys to it). +*/ +bool QmlGraphicsTextEdit::isCursorVisible() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->cursorVisible; +} + +void QmlGraphicsTextEdit::setCursorVisible(bool on) +{ + Q_D(QmlGraphicsTextEdit); + 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, 0)); + emit cursorVisibleChanged(d->cursorVisible); +} + +/*! + \qmlproperty int TextEdit::cursorPosition + The position of the cursor in the TextEdit. +*/ +int QmlGraphicsTextEdit::cursorPosition() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->textCursor().position(); +} + +void QmlGraphicsTextEdit::setCursorPosition(int pos) +{ + Q_D(QmlGraphicsTextEdit); + QTextCursor cursor = d->control->textCursor(); + if (cursor.position() == pos) + return; + cursor.setPosition(pos); + d->control->setTextCursor(cursor); +} + +/*! + \qmlproperty Component TextEdit::cursorDelegate + The delegate for the cursor in the TextEdit. + + If you set a cursorDelegate for a TextEdit, this delegate will be used for + drawing the cursor instead of the standard cursor. An instance of the + delegate will be created and managed by the text edit when a cursor is + needed, and the x and y properties of delegate instance will be set so as + to be one pixel before the top left of the current character. + + Note that the root item of the delegate component must be a QmlGraphicsItem or + QmlGraphicsItem derived item. +*/ +QmlComponent* QmlGraphicsTextEdit::cursorDelegate() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->cursorComponent; +} + +void QmlGraphicsTextEdit::setCursorDelegate(QmlComponent* c) +{ + Q_D(QmlGraphicsTextEdit); + if(d->cursorComponent){ + if(d->cursor){ + disconnect(d->control, SIGNAL(cursorPositionChanged()), + this, SLOT(moveCursorDelegate())); + d->control->setCursorWidth(-1); + dirtyCache(cursorRect()); + delete d->cursor; + d->cursor = 0; + } + } + d->cursorComponent = c; + if(c && c->isReady()){ + loadCursorDelegate(); + }else{ + if(c) + connect(c, SIGNAL(statusChanged()), + this, SLOT(loadCursorDelegate())); + } + + emit cursorDelegateChanged(); +} + +void QmlGraphicsTextEdit::loadCursorDelegate() +{ + Q_D(QmlGraphicsTextEdit); + if(d->cursorComponent->isLoading()) + return; + d->cursor = qobject_cast<QmlGraphicsItem*>(d->cursorComponent->create(qmlContext(this))); + if(d->cursor){ + connect(d->control, SIGNAL(cursorPositionChanged()), + this, SLOT(moveCursorDelegate())); + d->control->setCursorWidth(0); + dirtyCache(cursorRect()); + d->cursor->setParentItem(this); + d->cursor->setHeight(QFontMetrics(d->font).height()); + moveCursorDelegate(); + }else{ + qWarning() << QLatin1String("Error loading cursor delegate for TextEdit:") + objectName(); + } +} + +/*! + \qmlproperty int TextEdit::selectionStart + + The cursor position before the first character in the current selection. + Setting this and selectionEnd allows you to specify a selection in the + text edit. + + Note that if selectionStart == selectionEnd then there is no current + selection. If you attempt to set selectionStart to a value outside of + the current text, selectionStart will not be changed. + + \sa selectionEnd, cursorPosition, selectedText +*/ +int QmlGraphicsTextEdit::selectionStart() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->textCursor().selectionStart(); +} + +void QmlGraphicsTextEdit::setSelectionStart(int s) +{ + Q_D(QmlGraphicsTextEdit); + if(d->lastSelectionStart == s || s < 0 || s > text().length()) + return; + d->lastSelectionStart = s; + d->updateSelection();// Will emit the relevant signals +} + +/*! + \qmlproperty int TextEdit::selectionEnd + + The cursor position after the last character in the current selection. + Setting this and selectionStart allows you to specify a selection in the + text edit. + + Note that if selectionStart == selectionEnd then there is no current + selection. If you attempt to set selectionEnd to a value outside of + the current text, selectionEnd will not be changed. + + \sa selectionStart, cursorPosition, selectedText +*/ +int QmlGraphicsTextEdit::selectionEnd() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->textCursor().selectionEnd(); +} + +void QmlGraphicsTextEdit::setSelectionEnd(int s) +{ + Q_D(QmlGraphicsTextEdit); + if(d->lastSelectionEnd == s || s < 0 || s > text().length()) + return; + d->lastSelectionEnd = s; + d->updateSelection();// Will emit the relevant signals +} + +/*! + \qmlproperty string TextEdit::selectedText + + This read-only property provides the text currently selected in the + text edit. + + It is equivalent to the following snippet, but is faster and easier + to use. + \code + //myTextEdit is the id of the TextEdit + myTextEdit.text.toString().substring(myTextEdit.selectionStart, + myTextEdit.selectionEnd); + \endcode +*/ +QString QmlGraphicsTextEdit::selectedText() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->textCursor().selectedText(); +} + +/*! + \qmlproperty bool TextEdit::focusOnPress + + Whether the TextEdit should gain focus on a mouse press. By default this is + set to true. +*/ +bool QmlGraphicsTextEdit::focusOnPress() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->focusOnPress; +} + +void QmlGraphicsTextEdit::setFocusOnPress(bool on) +{ + Q_D(QmlGraphicsTextEdit); + if (d->focusOnPress == on) + return; + d->focusOnPress = on; + emit focusOnPressChanged(d->focusOnPress); +} + +/*! + \qmlproperty bool TextEdit::persistentSelection + + Whether the TextEdit should keep the selection visible when it loses focus to another + item in the scene. By default this is set to true; +*/ +bool QmlGraphicsTextEdit::persistentSelection() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->persistentSelection; +} + +void QmlGraphicsTextEdit::setPersistentSelection(bool on) +{ + Q_D(QmlGraphicsTextEdit); + if (d->persistentSelection == on) + return; + d->persistentSelection = on; + emit persistentSelectionChanged(d->persistentSelection); +} + +qreal QmlGraphicsTextEdit::textMargin() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->textMargin; +} + +void QmlGraphicsTextEdit::setTextMargin(qreal margin) +{ + Q_D(QmlGraphicsTextEdit); + if (d->textMargin == margin) + return; + d->textMargin = margin; + d->document->setDocumentMargin(d->textMargin); + emit textMarginChanged(d->textMargin); +} + +void QmlGraphicsTextEdit::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.width() != oldGeometry.width()) + updateSize(); + QmlGraphicsPaintedItem::geometryChanged(newGeometry, oldGeometry); +} + +/*! + Ensures any delayed caching or data loading the class + needs to performed is complete. +*/ +void QmlGraphicsTextEdit::componentComplete() +{ + Q_D(QmlGraphicsTextEdit); + QmlGraphicsPaintedItem::componentComplete(); + if (d->dirty) { + updateSize(); + d->dirty = false; + } +} + +/*! + \qmlproperty bool TextEdit::readOnly + + Whether the user an interact with the TextEdit item. If this + property is set to true the text cannot be edited by user interaction. + + By default this property is false. +*/ +void QmlGraphicsTextEdit::setReadOnly(bool r) +{ + Q_D(QmlGraphicsTextEdit); + if (r == isReadOnly()) + return; + + + Qt::TextInteractionFlags flags = Qt::NoTextInteraction; + if (r) { + flags = Qt::TextSelectableByMouse; + } else { + flags = Qt::TextEditorInteraction; + } + d->control->setTextInteractionFlags(flags); + if (!r) + d->control->moveCursor(QTextCursor::End); + + emit readOnlyChanged(r); +} + +bool QmlGraphicsTextEdit::isReadOnly() const +{ + Q_D(const QmlGraphicsTextEdit); + return !(d->control->textInteractionFlags() & Qt::TextEditable); +} + +/*! + Sets how the text edit should interact with user input to the given + \a flags. +*/ +void QmlGraphicsTextEdit::setTextInteractionFlags(Qt::TextInteractionFlags flags) +{ + Q_D(QmlGraphicsTextEdit); + d->control->setTextInteractionFlags(flags); +} + +/*! + Returns the flags specifying how the text edit should interact + with user input. +*/ +Qt::TextInteractionFlags QmlGraphicsTextEdit::textInteractionFlags() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->textInteractionFlags(); +} + +/*! + Returns the rectangle where the text cursor is rendered + within the text edit. +*/ +QRect QmlGraphicsTextEdit::cursorRect() const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->cursorRect().toRect(); +} + + +/*! +\overload +Handles the given \a event. +*/ +bool QmlGraphicsTextEdit::event(QEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + if (event->type() == QEvent::ShortcutOverride) { + d->control->processEvent(event, QPointF(0, 0)); + return event->isAccepted(); + } + return QmlGraphicsPaintedItem::event(event); +} + +/*! +\overload +Handles the given key \a event. +*/ +void QmlGraphicsTextEdit::keyPressEvent(QKeyEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + d->control->processEvent(event, QPointF(0, 0)); + + if (!event->isAccepted()) + QmlGraphicsPaintedItem::keyPressEvent(event); +} + +/*! +\overload +Handles the given key \a event. +*/ +void QmlGraphicsTextEdit::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + d->control->processEvent(event, QPointF(0, 0)); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::keyReleaseEvent(event); +} + +/*! + \overload + Handles changing of the focus property. Focus is applied to the control + even if the edit does not have active focus. This is because things + like KeyProxy can give the behavior of focus even when hasFocus() isn't + true. +*/ +void QmlGraphicsTextEdit::focusChanged(bool hasFocus) +{ + setCursorVisible(hasFocus); + QmlGraphicsItem::focusChanged(hasFocus); +} + +/*! + Causes all text to be selected. +*/ +void QmlGraphicsTextEdit::selectAll() +{ + Q_D(QmlGraphicsTextEdit); + d->control->selectAll(); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QmlGraphicsTextEdit::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + if (d->focusOnPress){ + QGraphicsItem *p = parentItem();//###Is there a better way to find my focus scope? + while(p) { + if(p->flags() & QGraphicsItem::ItemIsFocusScope){ + p->setFocus(); + break; + } + p = p->parentItem(); + } + setFocus(true); + } + d->control->processEvent(event, QPointF(0, 0)); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::mousePressEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QmlGraphicsTextEdit::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + QWidget *widget = event->widget(); + if (widget && (d->control->textInteractionFlags() & Qt::TextEditable) && boundingRect().contains(event->pos())) + qt_widget_private(widget)->handleSoftwareInputPanel(event->button(), d->focusOnPress); + + d->control->processEvent(event, QPointF(0, 0)); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::mousePressEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QmlGraphicsTextEdit::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + d->control->processEvent(event, QPointF(0, 0)); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::mouseDoubleClickEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QmlGraphicsTextEdit::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + d->control->processEvent(event, QPointF(0, 0)); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::mousePressEvent(event); +} + +/*! +\overload +Handles the given input method \a event. +*/ +void QmlGraphicsTextEdit::inputMethodEvent(QInputMethodEvent *event) +{ + Q_D(QmlGraphicsTextEdit); + d->control->processEvent(event, QPointF(0, 0)); +} + +/*! +\overload +Returns the value of the given \a property. +*/ +QVariant QmlGraphicsTextEdit::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QmlGraphicsTextEdit); + return d->control->inputMethodQuery(property); +} + +/*! +Draws the contents of the text edit using the given \a painter within +the given \a bounds. +*/ +void QmlGraphicsTextEdit::drawContents(QPainter *painter, const QRect &bounds) +{ + Q_D(QmlGraphicsTextEdit); + + painter->setRenderHint(QPainter::TextAntialiasing, true); + + d->control->drawContents(painter, bounds); +} + +void QmlGraphicsTextEdit::updateImgCache(const QRectF &r) +{ + dirtyCache(r.toRect()); + emit update(); +} + +/*! + \qmlproperty bool TextEdit::smooth + + Set this property if you want the text to be smoothly scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +void QmlGraphicsTextEditPrivate::init() +{ + Q_Q(QmlGraphicsTextEdit); + + q->setSmooth(smooth); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QGraphicsItem::ItemHasNoContents, false); + q->setFlag(QGraphicsItem::ItemAcceptsInputMethod); + + control = new QTextControl(q); + control->setIgnoreUnusedNavigationEvents(true); + + QObject::connect(control, SIGNAL(updateRequest(QRectF)), q, SLOT(updateImgCache(QRectF))); + + QObject::connect(control, SIGNAL(textChanged()), q, SLOT(q_textChanged())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SIGNAL(selectionChanged())); + QObject::connect(control, SIGNAL(selectionChanged()), q, SLOT(updateSelectionMarkers())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SLOT(updateSelectionMarkers())); + QObject::connect(control, SIGNAL(cursorPositionChanged()), q, SIGNAL(cursorPositionChanged())); + + document = control->document(); + document->setDefaultFont(font); + document->setDocumentMargin(textMargin); + document->setUndoRedoEnabled(false); // flush undo buffer. + document->setUndoRedoEnabled(true); + updateDefaultTextOption(); +} + +void QmlGraphicsTextEdit::q_textChanged() +{ + updateSize(); + emit textChanged(text()); +} + +void QmlGraphicsTextEdit::moveCursorDelegate() +{ + Q_D(QmlGraphicsTextEdit); + if(!d->cursor) + return; + QRectF cursorRect = d->control->cursorRect(); + d->cursor->setX(cursorRect.x()); + d->cursor->setY(cursorRect.y()); +} + +void QmlGraphicsTextEditPrivate::updateSelection() +{ + Q_Q(QmlGraphicsTextEdit); + QTextCursor cursor = control->textCursor(); + bool startChange = (lastSelectionStart != cursor.selectionStart()); + bool endChange = (lastSelectionEnd != cursor.selectionEnd()); + //### Is it worth calculating a more minimal set of movements? + cursor.beginEditBlock(); + cursor.setPosition(lastSelectionStart, QTextCursor::MoveAnchor); + cursor.setPosition(lastSelectionEnd, QTextCursor::KeepAnchor); + cursor.endEditBlock(); + control->setTextCursor(cursor); + if(startChange) + q->selectionStartChanged(); + if(endChange) + q->selectionEndChanged(); + startChange = (lastSelectionStart != control->textCursor().selectionStart()); + endChange = (lastSelectionEnd != control->textCursor().selectionEnd()); + if(startChange || endChange) + qWarning() << "QmlGraphicsTextEditPrivate::updateSelection() has failed you."; +} + +void QmlGraphicsTextEdit::updateSelectionMarkers() +{ + Q_D(QmlGraphicsTextEdit); + if(d->lastSelectionStart != d->control->textCursor().selectionStart()){ + d->lastSelectionStart = d->control->textCursor().selectionStart(); + emit selectionStartChanged(); + } + if(d->lastSelectionEnd != d->control->textCursor().selectionEnd()){ + d->lastSelectionEnd = d->control->textCursor().selectionEnd(); + emit selectionEndChanged(); + } +} + +//### we should perhaps be a bit smarter here -- depending on what has changed, we shouldn't +// need to do all the calculations each time +void QmlGraphicsTextEdit::updateSize() +{ + Q_D(QmlGraphicsTextEdit); + if (isComponentComplete()) { + QFontMetrics fm = QFontMetrics(d->font); + int dy = height(); + // ### assumes that if the width is set, the text will fill to edges + // ### (unless wrap is false, then clipping will occur) + if (widthValid()) + d->document->setTextWidth(width()); + dy -= (int)d->document->size().height(); + + int yoff = 0; + if (heightValid()) { + if (d->vAlign == AlignBottom) + yoff = dy; + else if (d->vAlign == AlignVCenter) + yoff = dy/2; + } + setBaselineOffset(fm.ascent() + yoff + d->textMargin); + + //### need to comfirm cost of always setting these + int newWidth = (int)d->document->idealWidth(); + d->document->setTextWidth(newWidth); // ### QTextDoc> Alignment will not work unless textWidth is set. Does Text need this line as well? + int cursorWidth = 1; + if(d->cursor) + cursorWidth = d->cursor->width(); + newWidth += cursorWidth; + if(!d->document->isEmpty()) + newWidth += 3;// ### Need a better way of accounting for space between char and cursor + // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed. + setImplicitWidth(newWidth); + setImplicitHeight(d->text.isEmpty() ? fm.height() : (int)d->document->size().height()); + + setContentsSize(QSize(width(), height())); + } else { + d->dirty = true; + } + emit update(); +} + +void QmlGraphicsTextEditPrivate::updateDefaultTextOption() +{ + QTextOption opt = document->defaultTextOption(); + int oldAlignment = opt.alignment(); + opt.setAlignment((Qt::Alignment)(int)(hAlign | vAlign)); + + QTextOption::WrapMode oldWrapMode = opt.wrapMode(); + + if (wrap) + opt.setWrapMode(QTextOption::WordWrap); + else + opt.setWrapMode(QTextOption::NoWrap); + + if (oldWrapMode == opt.wrapMode() && oldAlignment == opt.alignment()) + return; + document->setDefaultTextOption(opt); +} + +QT_END_NAMESPACE diff --git a/src/declarative/graphicsitems/qmlgraphicstextedit_p.h b/src/declarative/graphicsitems/qmlgraphicstextedit_p.h new file mode 100644 index 0000000..e95b077 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstextedit_p.h @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSTEXTEDIT_H +#define QMLGRAPHICSTEXTEDIT_H + +#include "qmlgraphicstext_p.h" +#include "qmlgraphicspainteditem_p.h" + +#include <QtGui/qtextdocument.h> +#include <QtGui/qtextoption.h> +#include <QtGui/qtextcursor.h> +#include <QtGui/qtextformat.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + + +class QmlGraphicsTextEditPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsTextEdit : public QmlGraphicsPaintedItem +{ + Q_OBJECT + Q_ENUMS(VAlignment) + Q_ENUMS(HAlignment) + Q_ENUMS(TextFormat) + + 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 NOTIFY horizontalAlignmentChanged) + Q_PROPERTY(VAlignment verticalAlignment READ vAlign WRITE setVAlign NOTIFY verticalAlignmentChanged) + Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged) //### other wrap modes + 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(QmlComponent* cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged) + Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectionChanged) + Q_PROPERTY(bool focusOnPress READ focusOnPress WRITE setFocusOnPress NOTIFY focusOnPressChanged) + 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) + +public: + QmlGraphicsTextEdit(QmlGraphicsItem *parent=0); + + enum HAlignment { + AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter + }; + + enum VAlignment { + AlignTop = Qt::AlignTop, + AlignBottom = Qt::AlignBottom, + AlignVCenter = Qt::AlignVCenter + }; + + enum TextFormat { + PlainText = Qt::PlainText, + RichText = Qt::RichText, + AutoText = Qt::AutoText + }; + + 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); + + VAlignment vAlign() const; + void setVAlign(VAlignment align); + + bool wrap() const; + void setWrap(bool w); + + bool isCursorVisible() const; + void setCursorVisible(bool on); + + int cursorPosition() const; + void setCursorPosition(int pos); + + QmlComponent* cursorDelegate() const; + void setCursorDelegate(QmlComponent*); + + int selectionStart() const; + void setSelectionStart(int); + + int selectionEnd() const; + void setSelectionEnd(int); + + QString selectedText() const; + + bool focusOnPress() const; + void setFocusOnPress(bool on); + + bool persistentSelection() const; + void setPersistentSelection(bool on); + + qreal textMargin() const; + void setTextMargin(qreal margin); + + virtual void componentComplete(); + + /* FROM EDIT */ + void setReadOnly(bool); + bool isReadOnly() const; + + void setTextInteractionFlags(Qt::TextInteractionFlags flags); + Qt::TextInteractionFlags textInteractionFlags() const; + + QRect cursorRect() const; + + QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + +Q_SIGNALS: + void textChanged(const QString &); + void cursorPositionChanged(); + 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 wrapChanged(bool isWrapped); + void textFormatChanged(TextFormat textFormat); + void readOnlyChanged(bool isReadOnly); + void cursorVisibleChanged(bool isCursorVisible); + void cursorDelegateChanged(); + void focusOnPressChanged(bool focusIsPressed); + void persistentSelectionChanged(bool isPersistentSelection); + void textMarginChanged(qreal textMargin); + +public Q_SLOTS: + void selectAll(); + +private Q_SLOTS: + void updateImgCache(const QRectF &rect); + void q_textChanged(); + void updateSelectionMarkers(); + void moveCursorDelegate(); + void loadCursorDelegate(); + +private: + void updateSize(); + +protected: + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + + bool event(QEvent *); + void keyPressEvent(QKeyEvent *); + void keyReleaseEvent(QKeyEvent *); + + void focusChanged(bool); + + // mouse filter? + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + + void inputMethodEvent(QInputMethodEvent *e); + + void drawContents(QPainter *, const QRect &); +private: + Q_DISABLE_COPY(QmlGraphicsTextEdit) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsTextEdit) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsTextEdit) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicstextedit_p_p.h b/src/declarative/graphicsitems/qmlgraphicstextedit_p_p.h new file mode 100644 index 0000000..8914bfd --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstextedit_p_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSTEXTEDIT_P_H +#define QMLGRAPHICSTEXTEDIT_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 "qmlgraphicsitem.h" +#include "qmlgraphicspainteditem_p_p.h" + +#include <qml.h> + +QT_BEGIN_NAMESPACE +class QTextLayout; +class QTextDocument; +class QTextControl; +class QmlGraphicsTextEditPrivate : public QmlGraphicsPaintedItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsTextEdit) + +public: + QmlGraphicsTextEditPrivate() + : color("black"), imgDirty(true), hAlign(QmlGraphicsTextEdit::AlignLeft), vAlign(QmlGraphicsTextEdit::AlignTop), + dirty(false), wrap(false), richText(false), cursorVisible(false), focusOnPress(true), + persistentSelection(true), textMargin(0.0), lastSelectionStart(0), lastSelectionEnd(0), + cursorComponent(0), cursor(0), format(QmlGraphicsTextEdit::AutoText), document(0) + { + } + + void init(); + + void updateDefaultTextOption(); + void relayoutDocument(); + void updateSelection(); + + QString text; + QFont font; + QColor color; + QColor selectionColor; + QColor selectedTextColor; + QString style; + QColor styleColor; + bool imgDirty; + QPixmap imgCache; + QPixmap imgStyleCache; + QmlGraphicsTextEdit::HAlignment hAlign; + QmlGraphicsTextEdit::VAlignment vAlign; + bool dirty; + bool wrap; + bool richText; + bool cursorVisible; + bool focusOnPress; + bool persistentSelection; + qreal textMargin; + int lastSelectionStart; + int lastSelectionEnd; + QmlComponent* cursorComponent; + QmlGraphicsItem* cursor; + QmlGraphicsTextEdit::TextFormat format; + QTextDocument *document; + QTextControl *control; +}; + +QT_END_NAMESPACE +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicstextinput.cpp b/src/declarative/graphicsitems/qmlgraphicstextinput.cpp new file mode 100644 index 0000000..6d79c7a --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstextinput.cpp @@ -0,0 +1,916 @@ +/**************************************************************************** +** +** 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 "qmlgraphicstextinput_p.h" +#include "qmlgraphicstextinput_p_p.h" + +#include <qmlinfo.h> + +#include <QValidator> +#include <QApplication> +#include <QFontMetrics> +#include <QPainter> + +QT_BEGIN_NAMESPACE + +QML_DEFINE_TYPE(Qt,4,6,TextInput,QmlGraphicsTextInput); +QML_DEFINE_NOCREATE_TYPE(QValidator); +QML_DEFINE_TYPE(Qt,4,6,QIntValidator,QIntValidator); +QML_DEFINE_TYPE(Qt,4,6,QDoubleValidator,QDoubleValidator); +QML_DEFINE_TYPE(Qt,4,6,QRegExpValidator,QRegExpValidator); + +/*! + \qmlclass TextInput QmlGraphicsTextInput + The TextInput item allows you to add an editable line of text to a scene. + + TextInput can only display a single line of text, and can only display + plain text. However it can provide addition input constraints on the text. + + Input constraints include setting a QValidator, an input mask, or a + maximum input length. +*/ +QmlGraphicsTextInput::QmlGraphicsTextInput(QmlGraphicsItem* parent) + : QmlGraphicsPaintedItem(*(new QmlGraphicsTextInputPrivate), parent) +{ + Q_D(QmlGraphicsTextInput); + d->init(); +} + +QmlGraphicsTextInput::~QmlGraphicsTextInput() +{ +} + +/*! + \qmlproperty string TextInput::text + + The text in the TextInput. +*/ + +QString QmlGraphicsTextInput::text() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->text(); +} + +void QmlGraphicsTextInput::setText(const QString &s) +{ + Q_D(QmlGraphicsTextInput); + if(s == text()) + return; + d->control->setText(s); + //emit textChanged(); +} + +/*! + \qmlproperty string TextInput::font.family + \qmlproperty bool TextInput::font.bold + \qmlproperty bool TextInput::font.italic + \qmlproperty bool TextInput::font.underline + \qmlproperty real TextInput::font.pointSize + \qmlproperty int TextInput::font.pixelSize + + Set the TextInput's font attributes. +*/ +QFont QmlGraphicsTextInput::font() const +{ + Q_D(const QmlGraphicsTextInput); + return d->font; +} + +void QmlGraphicsTextInput::setFont(const QFont &font) +{ + Q_D(QmlGraphicsTextInput); + if (d->font == font) + return; + + d->font = font; + + d->control->setFont(d->font); + if(d->cursorItem){ + d->cursorItem->setHeight(QFontMetrics(d->font).height()); + moveCursor(); + } + updateSize(); + emit fontChanged(d->font); +} + +/*! + \qmlproperty color TextInput::color + + The text color. +*/ +QColor QmlGraphicsTextInput::color() const +{ + Q_D(const QmlGraphicsTextInput); + return d->color; +} + +void QmlGraphicsTextInput::setColor(const QColor &c) +{ + Q_D(QmlGraphicsTextInput); + d->color = c; +} + + +/*! + \qmlproperty color TextInput::selectionColor + + The text highlight color, used behind selections. +*/ +QColor QmlGraphicsTextInput::selectionColor() const +{ + Q_D(const QmlGraphicsTextInput); + return d->selectionColor; +} + +void QmlGraphicsTextInput::setSelectionColor(const QColor &color) +{ + Q_D(QmlGraphicsTextInput); + if (d->selectionColor == color) + return; + + d->selectionColor = color; + QPalette p = d->control->palette(); + p.setColor(QPalette::Highlight, d->selectionColor); + d->control->setPalette(p); + emit selectionColorChanged(color); +} + +/*! + \qmlproperty color TextInput::selectedTextColor + + The highlighted text color, used in selections. +*/ +QColor QmlGraphicsTextInput::selectedTextColor() const +{ + Q_D(const QmlGraphicsTextInput); + return d->selectedTextColor; +} + +void QmlGraphicsTextInput::setSelectedTextColor(const QColor &color) +{ + Q_D(QmlGraphicsTextInput); + if (d->selectedTextColor == color) + return; + + d->selectedTextColor = color; + QPalette p = d->control->palette(); + p.setColor(QPalette::HighlightedText, d->selectedTextColor); + d->control->setPalette(p); + emit selectedTextColorChanged(color); +} + +/*! + \qmlproperty enumeration TextInput::horizontalAlignment + + Sets the horizontal alignment of the text within the TextInput item's + width and height. By default, the text is left aligned. + + TextInput does not have vertical alignment, as the natural height is + exactly the height of the single line of text. If you set the height + manually to something larger, TextInput will always be top aligned + vertically. You can use anchors to align it however you want within + another item. + + The valid values for \c horizontalAlignment are \c AlignLeft, \c AlignRight and + \c AlignHCenter. +*/ +QmlGraphicsTextInput::HAlignment QmlGraphicsTextInput::hAlign() const +{ + Q_D(const QmlGraphicsTextInput); + return d->hAlign; +} + +void QmlGraphicsTextInput::setHAlign(HAlignment align) +{ + Q_D(QmlGraphicsTextInput); + if(align == d->hAlign) + return; + d->hAlign = align; + emit horizontalAlignmentChanged(d->hAlign); +} + +bool QmlGraphicsTextInput::isReadOnly() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->isReadOnly(); +} + +void QmlGraphicsTextInput::setReadOnly(bool ro) +{ + Q_D(QmlGraphicsTextInput); + if (d->control->isReadOnly() == ro) + return; + + d->control->setReadOnly(ro); + + emit readOnlyChanged(ro); +} + +int QmlGraphicsTextInput::maxLength() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->maxLength(); +} + +void QmlGraphicsTextInput::setMaxLength(int ml) +{ + Q_D(QmlGraphicsTextInput); + if (d->control->maxLength() == ml) + return; + + d->control->setMaxLength(ml); + + emit maximumLengthChanged(ml); +} + +/*! + \qmlproperty bool TextInput::cursorVisible + Set to true when the TextInput shows a cursor. + + This property is set and unset when the TextInput gets focus, so that other + properties can be bound to whether the cursor is currently showing. As it + gets set and unset automatically, when you set the value yourself you must + keep in mind that your value may be overwritten. + + It can be set directly in script, for example if a KeyProxy might + forward keys to it and you desire it to look active when this happens + (but without actually giving it the focus). + + It should not be set directly on the element, like in the below QML, + as the specified value will be overridden an lost on focus changes. + + \code + TextInput { + text: "Text" + cursorVisible: false + } + \endcode + + In the above snippet the cursor will still become visible when the + TextInput gains focus. +*/ +bool QmlGraphicsTextInput::isCursorVisible() const +{ + Q_D(const QmlGraphicsTextInput); + return d->cursorVisible; +} + +void QmlGraphicsTextInput::setCursorVisible(bool on) +{ + Q_D(QmlGraphicsTextInput); + if (d->cursorVisible == on) + return; + d->cursorVisible = on; + d->control->setCursorBlinkPeriod(on?QApplication::cursorFlashTime():0); + //d->control should emit the cursor update regions + emit cursorVisibleChanged(d->cursorVisible); +} + +/*! + \qmlproperty int TextInput::cursorPosition + The position of the cursor in the TextInput. +*/ +int QmlGraphicsTextInput::cursorPosition() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->cursor(); +} +void QmlGraphicsTextInput::setCursorPosition(int cp) +{ + Q_D(QmlGraphicsTextInput); + d->control->moveCursor(cp); +} + +/*! + \internal + + Returns a Rect which encompasses the cursor, but which may be larger than is + required. Ignores custom cursor delegates. +*/ +QRect QmlGraphicsTextInput::cursorRect() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->cursorRect(); +} + +/*! + \qmlproperty int TextInput::selectionStart + + The cursor position before the first character in the current selection. + Setting this and selectionEnd allows you to specify a selection in the + text edit. + + Note that if selectionStart == selectionEnd then there is no current + selection. If you attempt to set selectionStart to a value outside of + the current text, selectionStart will not be changed. + + \sa selectionEnd, cursorPosition, selectedText +*/ +int QmlGraphicsTextInput::selectionStart() const +{ + Q_D(const QmlGraphicsTextInput); + return d->lastSelectionStart; +} + +void QmlGraphicsTextInput::setSelectionStart(int s) +{ + Q_D(QmlGraphicsTextInput); + if(d->lastSelectionStart == s || s < 0 || s > text().length()) + return; + d->lastSelectionStart = s; + d->control->setSelection(s, d->lastSelectionEnd - s); +} + +/*! + \qmlproperty int TextInput::selectionEnd + + The cursor position after the last character in the current selection. + Setting this and selectionStart allows you to specify a selection in the + text edit. + + Note that if selectionStart == selectionEnd then there is no current + selection. If you attempt to set selectionEnd to a value outside of + the current text, selectionEnd will not be changed. + + \sa selectionStart, cursorPosition, selectedText +*/ +int QmlGraphicsTextInput::selectionEnd() const +{ + Q_D(const QmlGraphicsTextInput); + return d->lastSelectionEnd; +} + +void QmlGraphicsTextInput::setSelectionEnd(int s) +{ + Q_D(QmlGraphicsTextInput); + if(d->lastSelectionEnd == s || s < 0 || s > text().length()) + return; + d->lastSelectionEnd = s; + d->control->setSelection(d->lastSelectionStart, s - d->lastSelectionStart); +} + +/*! + \qmlproperty string TextInput::selectedText + + This read-only property provides the text currently selected in the + text input. + + It is equivalent to the following snippet, but is faster and easier + to use. + + \qml + myTextInput.text.toString().substring(myTextInput.selectionStart, + myTextInput.selectionEnd); + \endqml +*/ +QString QmlGraphicsTextInput::selectedText() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->selectedText(); +} + +/*! + \qmlproperty bool TextInput::focusOnPress + + Whether the TextInput should gain focus on a mouse press. By default this is + set to true. +*/ +bool QmlGraphicsTextInput::focusOnPress() const +{ + Q_D(const QmlGraphicsTextInput); + return d->focusOnPress; +} + +void QmlGraphicsTextInput::setFocusOnPress(bool b) +{ + Q_D(QmlGraphicsTextInput); + if (d->focusOnPress == b) + return; + + d->focusOnPress = b; + + emit focusOnPressChanged(d->focusOnPress); +} + +/*! + \qmlproperty QValidator* TextInput::validator + + Allows you to set a QValidator on the TextInput. When a validator is set + the TextInput will only accept input which leaves the text property in + an acceptable or intermediate state. The accepted signal will only be sent + if the text is in an acceptable state when enter is pressed. + + Currently supported validators are QIntValidator, QDoubleValidator and + QRegExpValidator. For details, refer to their C++ documentation and remember + that all Q_PROPERTIES are accessible from Qml. A brief usage guide follows: + + QIntValidator and QDoubleValidator both are controllable through two properties, + top and bottom. The difference is that for QIntValidator the top and bottom properties + should be integers, and for QDoubleValidator they should be doubles. QRegExpValidator + has a single string property, regExp, which should be set to the regular expression to + be used for validation. An example of using validators is shown below, which allows + input of integers between 11 and 31 into the text input: + + \code + import Qt 4.6 + TextInput{ + validator: QIntValidator{bottom: 11; top: 31;} + focus: true + } + \endcode + + \sa acceptableInput, inputMask +*/ +QValidator* QmlGraphicsTextInput::validator() const +{ + Q_D(const QmlGraphicsTextInput); + //###const cast isn't good, but needed for property system? + return const_cast<QValidator*>(d->control->validator()); +} + +void QmlGraphicsTextInput::setValidator(QValidator* v) +{ + Q_D(QmlGraphicsTextInput); + if (d->control->validator() == v) + return; + + d->control->setValidator(v); + if(!d->control->hasAcceptableInput()){ + d->oldValidity = false; + emit acceptableInputChanged(); + } + + emit validatorChanged(); +} + +/*! + \qmlproperty string TextInput::inputMask + + Allows you to set an input mask on the TextInput, restricting the allowable + text inputs. See QLineEdit::inputMask for further details, as the exact + same mask strings are used by TextInput. + + \sa acceptableInput, validator +*/ +QString QmlGraphicsTextInput::inputMask() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->inputMask(); +} + +void QmlGraphicsTextInput::setInputMask(const QString &im) +{ + Q_D(QmlGraphicsTextInput); + if (d->control->inputMask() == im) + return; + + d->control->setInputMask(im); + emit inputMaskChanged(d->control->inputMask()); +} + +/*! + \qmlproperty bool TextInput::acceptableInput + + This property is always true unless a validator or input mask has been set. + If a validator or input mask has been set, this property will only be true + if the current text is acceptable to the validator or input mask as a final + string (not as an intermediate string). +*/ +bool QmlGraphicsTextInput::hasAcceptableInput() const +{ + Q_D(const QmlGraphicsTextInput); + return d->control->hasAcceptableInput(); +} + +/*! + \qmlproperty TextInput.EchoMode TextInput::echoMode + + Specifies how the text should be displayed in the TextInput. + The default is Normal, which displays the text as it is. Other values + are Password, which displays asterixes instead of characters, NoEcho, + which displays nothing, and PasswordEchoOnEdit, which displays all but the + current character as asterixes. + +*/ +QmlGraphicsTextInput::EchoMode QmlGraphicsTextInput::echoMode() const +{ + Q_D(const QmlGraphicsTextInput); + return (QmlGraphicsTextInput::EchoMode)d->control->echoMode(); +} + +void QmlGraphicsTextInput::setEchoMode(QmlGraphicsTextInput::EchoMode echo) +{ + Q_D(QmlGraphicsTextInput); + if (echoMode() == echo) + return; + + d->control->setEchoMode((uint)echo); + emit echoModeChanged(echoMode()); +} + +/*! + \qmlproperty Component TextInput::cursorDelegate + The delegate for the cursor in the TextInput. + + If you set a cursorDelegate for a TextInput, this delegate will be used for + drawing the cursor instead of the standard cursor. An instance of the + delegate will be created and managed by the TextInput when a cursor is + needed, and the x property of delegate instance will be set so as + to be one pixel before the top left of the current character. + + Note that the root item of the delegate component must be a QmlGraphicsItem or + QmlGraphicsItem derived item. +*/ +QmlComponent* QmlGraphicsTextInput::cursorDelegate() const +{ + Q_D(const QmlGraphicsTextInput); + return d->cursorComponent; +} + +void QmlGraphicsTextInput::setCursorDelegate(QmlComponent* c) +{ + Q_D(QmlGraphicsTextInput); + 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())); + delete d->cursorItem; + }else{ + d->startCreatingCursor(); + } + + emit cursorDelegateChanged(); +} + +void QmlGraphicsTextInputPrivate::startCreatingCursor() +{ + Q_Q(QmlGraphicsTextInput); + q->connect(control, SIGNAL(cursorPositionChanged(int, int)), + q, SLOT(moveCursor())); + if(cursorComponent->isReady()){ + q->createCursor(); + }else if(cursorComponent->isLoading()){ + q->connect(cursorComponent, SIGNAL(statusChanged(int)), + q, SLOT(createCursor())); + }else{//isError + qmlInfo(q) << QmlGraphicsTextInput::tr("Could not load cursor delegate"); + qWarning() << cursorComponent->errors(); + } +} + +void QmlGraphicsTextInput::createCursor() +{ + Q_D(QmlGraphicsTextInput); + if(d->cursorComponent->isError()){ + qmlInfo(this) << tr("Could not load cursor delegate"); + qWarning() << d->cursorComponent->errors(); + return; + } + + if(!d->cursorComponent->isReady()) + return; + + if(d->cursorItem) + delete d->cursorItem; + d->cursorItem = qobject_cast<QmlGraphicsItem*>(d->cursorComponent->create()); + if(!d->cursorItem){ + qmlInfo(this) << tr("Could not instantiate cursor delegate"); + //The failed instantiation should print its own error messages + return; + } + + d->cursorItem->setParentItem(this); + d->cursorItem->setX(d->control->cursorToX()); + d->cursorItem->setHeight(d->control->height()); +} + +void QmlGraphicsTextInput::moveCursor() +{ + Q_D(QmlGraphicsTextInput); + if(!d->cursorItem) + return; + d->cursorItem->setX(d->control->cursorToX() - d->hscroll); +} + +int QmlGraphicsTextInput::xToPos(int x) +{ + Q_D(const QmlGraphicsTextInput); + return d->control->xToPos(x - d->hscroll); +} + +void QmlGraphicsTextInput::focusChanged(bool hasFocus) +{ + Q_D(QmlGraphicsTextInput); + d->focused = hasFocus; + setCursorVisible(hasFocus); + QmlGraphicsItem::focusChanged(hasFocus); +} + +void QmlGraphicsTextInput::keyPressEvent(QKeyEvent* ev) +{ + Q_D(QmlGraphicsTextInput); + if((d->control->cursor() == 0 && ev->key() == Qt::Key_Left) + || (d->control->cursor() == d->control->text().length() + && ev->key() == Qt::Key_Right)){ + //ignore when moving off the end + ev->ignore(); + }else{ + d->control->processKeyEvent(ev); + } + if (!ev->isAccepted()) + QmlGraphicsPaintedItem::keyPressEvent(ev); +} + +void QmlGraphicsTextInput::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsTextInput); + if(d->focusOnPress){ + QGraphicsItem *p = parentItem();//###Is there a better way to find my focus scope? + while(p) { + if(p->flags() & QGraphicsItem::ItemIsFocusScope){ + p->setFocus(); + break; + } + p = p->parentItem(); + } + setFocus(true); + } + d->control->processEvent(event); +} + +/*! +\overload +Handles the given mouse \a event. +*/ +void QmlGraphicsTextInput::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsTextInput); + QWidget *widget = event->widget(); + if (widget && !d->control->isReadOnly() && boundingRect().contains(event->pos())) + qt_widget_private(widget)->handleSoftwareInputPanel(event->button(), d->focusOnPress); + d->control->processEvent(event); +} + +bool QmlGraphicsTextInput::event(QEvent* ev) +{ + Q_D(QmlGraphicsTextInput); + //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::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + break; + default: + handled = d->control->processEvent(ev); + } + if(!handled) + return QmlGraphicsPaintedItem::event(ev); + return true; +} + +void QmlGraphicsTextInput::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.width() != oldGeometry.width()) + updateSize(); + QmlGraphicsPaintedItem::geometryChanged(newGeometry, oldGeometry); +} + +void QmlGraphicsTextInput::drawContents(QPainter *p, const QRect &r) +{ + Q_D(QmlGraphicsTextInput); + 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); + if(d->hAlign != AlignLeft){ + QFontMetrics fm = QFontMetrics(d->font); + //###Is this using bearing appropriately? + int minLB = qMax(0, -fm.minLeftBearing()); + int minRB = qMax(0, -fm.minRightBearing()); + int widthUsed = qRound(d->control->naturalTextWidth()) + 1 + minRB; + int hOffset = 0; + if(d->hAlign == AlignRight){ + hOffset = width() - widthUsed; + }else if(d->hAlign == AlignHCenter){ + hOffset = (width() - widthUsed) / 2; + } + hOffset -= minLB; + offset = QPoint(hOffset, 0); + } + QRect clipRect = r; + d->control->draw(p, offset, clipRect, flags); + + p->restore(); +} + +/*! +\overload +Returns the value of the given \a property. +*/ +QVariant QmlGraphicsTextInput::inputMethodQuery(Qt::InputMethodQuery property) const +{ + Q_D(const QmlGraphicsTextInput); + switch(property) { + case Qt::ImFont: + return font(); + case Qt::ImCursorPosition: + return QVariant(d->control->cursor()); + case Qt::ImSurroundingText: + 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 QmlGraphicsTextInput::selectAll() +{ + Q_D(QmlGraphicsTextInput); + d->control->setSelection(0, d->control->text().length()); +} + + +/*! + \qmlproperty bool TextInput::smooth + + Set this property if you want the text to be smoothly scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the item is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the item is stationary on + the screen. A common pattern when animating an item is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +void QmlGraphicsTextInputPrivate::init() +{ + Q_Q(QmlGraphicsTextInput); + control->setCursorWidth(1); + control->setPasswordCharacter(QLatin1Char('*')); + control->setLayoutDirection(Qt::LeftToRight); + q->setSmooth(smooth); + q->setAcceptedMouseButtons(Qt::LeftButton); + q->setFlag(QGraphicsItem::ItemHasNoContents, false); + q->setFlag(QGraphicsItem::ItemAcceptsInputMethod); + q->connect(control, SIGNAL(cursorPositionChanged(int,int)), + q, SLOT(cursorPosChanged())); + q->connect(control, SIGNAL(selectionChanged()), + q, SLOT(selectionChanged())); + q->connect(control, SIGNAL(textChanged(const QString &)), + q, SLOT(q_textChanged())); + q->connect(control, SIGNAL(accepted()), + q, SIGNAL(accepted())); + q->connect(control, SIGNAL(updateNeeded(QRect)), + q, SLOT(updateRect(QRect))); + q->connect(control, SIGNAL(cursorPositionChanged(int,int)), + q, SLOT(updateRect()));//TODO: Only update rect between pos's + q->connect(control, SIGNAL(selectionChanged()), + q, SLOT(updateRect()));//TODO: Only update rect in selection + //Note that above TODOs probably aren't that big a savings + q->updateSize(); + oldValidity = control->hasAcceptableInput(); + lastSelectionStart = 0; + lastSelectionEnd = 0; +} + +void QmlGraphicsTextInput::cursorPosChanged() +{ + Q_D(QmlGraphicsTextInput); + emit cursorPositionChanged(); + + 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 QmlGraphicsTextInput::selectionChanged() +{ + Q_D(QmlGraphicsTextInput); + 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 QmlGraphicsTextInput::q_textChanged() +{ + Q_D(QmlGraphicsTextInput); + updateSize(); + emit textChanged(); + if(hasAcceptableInput() != d->oldValidity){ + d->oldValidity = hasAcceptableInput(); + emit acceptableInputChanged(); + } +} + +void QmlGraphicsTextInput::updateRect(const QRect &r) +{ + if(r == QRect()) + clearCache(); + else + dirtyCache(r); + update(); +} + +void QmlGraphicsTextInput::updateSize(bool needsRedraw) +{ + Q_D(QmlGraphicsTextInput); + int w = width(); + int h = height(); + setImplicitHeight(d->control->height()); + int cursorWidth = d->control->cursorWidth(); + if(d->cursorItem) + cursorWidth = d->cursorItem->width(); + //### Is QFontMetrics too slow? + QFontMetricsF fm(d->font); + setImplicitWidth(fm.width(d->control->displayText())+cursorWidth); + setContentsSize(QSize(width(), height()));//Repaints if changed + if(w==width() && h==height() && needsRedraw){ + clearCache(); + update(); + } +} + +QT_END_NAMESPACE + diff --git a/src/declarative/graphicsitems/qmlgraphicstextinput_p.h b/src/declarative/graphicsitems/qmlgraphicstextinput_p.h new file mode 100644 index 0000000..4708381 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstextinput_p.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSTEXTINPUT_H +#define QMLGRAPHICSTEXTINPUT_H + +#include "qmlgraphicstext_p.h" +#include "qmlgraphicspainteditem_p.h" + +#include <QGraphicsSceneMouseEvent> +#include <QIntValidator> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsTextInputPrivate; +class QValidator; +class Q_DECLARATIVE_EXPORT QmlGraphicsTextInput : public QmlGraphicsPaintedItem +{ + Q_OBJECT + Q_ENUMS(HAlignment) + Q_ENUMS(EchoMode) + + 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 NOTIFY horizontalAlignmentChanged) + + 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 cursorRect READ cursorRect NOTIFY cursorPositionChanged) + Q_PROPERTY(QmlComponent *cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged) + Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) + Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectedTextChanged) + + Q_PROPERTY(int maximumLength READ maxLength WRITE setMaxLength NOTIFY maximumLengthChanged) + Q_PROPERTY(QValidator* validator READ validator WRITE setValidator NOTIFY validatorChanged) + Q_PROPERTY(QString inputMask READ inputMask WRITE setInputMask NOTIFY inputMaskChanged) + Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints) + + Q_PROPERTY(bool acceptableInput READ hasAcceptableInput NOTIFY acceptableInputChanged) + Q_PROPERTY(EchoMode echoMode READ echoMode WRITE setEchoMode NOTIFY echoModeChanged) + Q_PROPERTY(bool focusOnPress READ focusOnPress WRITE setFocusOnPress NOTIFY focusOnPressChanged) + +public: + QmlGraphicsTextInput(QmlGraphicsItem* parent=0); + ~QmlGraphicsTextInput(); + + enum EchoMode {//To match QLineEdit::EchoMode + Normal, + NoEcho, + Password, + PasswordEchoOnEdit + }; + + enum HAlignment { + AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter + }; + + //### Should we have this function, x based properties, + //### or copy TextEdit with x instead of QTextCursor? + Q_INVOKABLE int xToPos(int x); + + 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); + + bool isReadOnly() const; + void setReadOnly(bool); + + bool isCursorVisible() const; + void setCursorVisible(bool on); + + int cursorPosition() const; + void setCursorPosition(int cp); + + QRect cursorRect() const; + + int selectionStart() const; + void setSelectionStart(int); + + int selectionEnd() const; + void setSelectionEnd(int); + + QString selectedText() const; + + int maxLength() const; + void setMaxLength(int ml); + + QValidator * validator() const; + void setValidator(QValidator* v); + + QString inputMask() const; + void setInputMask(const QString &im); + + EchoMode echoMode() const; + void setEchoMode(EchoMode echo); + + QmlComponent* cursorDelegate() const; + void setCursorDelegate(QmlComponent*); + + bool focusOnPress() const; + void setFocusOnPress(bool); + + bool hasAcceptableInput() const; + + void drawContents(QPainter *p,const QRect &r); + QVariant inputMethodQuery(Qt::InputMethodQuery property) const; + +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 focusOnPressChanged(bool focusOnPress); + +protected: + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void keyPressEvent(QKeyEvent* ev); + bool event(QEvent *e); + + void focusChanged(bool hasFocus); + +public Q_SLOTS: + void selectAll(); + +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()); + +private: + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsTextInput) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsTextInput) +QML_DECLARE_TYPE(QValidator) +QML_DECLARE_TYPE(QIntValidator) +QML_DECLARE_TYPE(QDoubleValidator) +QML_DECLARE_TYPE(QRegExpValidator) + +QT_END_HEADER + +#endif // QMLGRAPHICSTEXTINPUT_H diff --git a/src/declarative/graphicsitems/qmlgraphicstextinput_p_p.h b/src/declarative/graphicsitems/qmlgraphicstextinput_p_p.h new file mode 100644 index 0000000..9eb6e07 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicstextinput_p_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSTEXTINPUT_P_H +#define QMLGRAPHICSTEXTINPUT_P_H + +#include "qmlgraphicstextinput_p.h" + +#include "qmlgraphicspainteditem_p_p.h" + +#include <qml.h> + +#include <QPointer> + +#include <private/qlinecontrol_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. + +QT_BEGIN_NAMESPACE + +class QmlGraphicsTextInputPrivate : public QmlGraphicsPaintedItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsTextInput) +public: + QmlGraphicsTextInputPrivate() : control(new QLineControl(QString())), + color((QRgb)0), style(QmlGraphicsText::Normal), + styleColor((QRgb)0), hAlign(QmlGraphicsTextInput::AlignLeft), + hscroll(0), oldScroll(0), focused(false), focusOnPress(true), + cursorVisible(false) + { + } + + ~QmlGraphicsTextInputPrivate() + { + delete control; + } + + void init(); + void startCreatingCursor(); + + QLineControl* control; + + QFont font; + QColor color; + QColor selectionColor; + QColor selectedTextColor; + QmlGraphicsText::TextStyle style; + QColor styleColor; + QmlGraphicsTextInput::HAlignment hAlign; + QPointer<QmlComponent> cursorComponent; + QPointer<QmlGraphicsItem> cursorItem; + + int lastSelectionStart; + int lastSelectionEnd; + int oldHeight; + int oldWidth; + bool oldValidity; + int hscroll; + int oldScroll; + bool focused; + bool focusOnPress; + bool cursorVisible; +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/declarative/graphicsitems/qmlgraphicsvisualitemmodel.cpp b/src/declarative/graphicsitems/qmlgraphicsvisualitemmodel.cpp new file mode 100644 index 0000000..2fc143d --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsvisualitemmodel.cpp @@ -0,0 +1,1155 @@ +/**************************************************************************** +** +** 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 "qmlgraphicsvisualitemmodel_p.h" + +#include "qmlgraphicsitem.h" + +#include <qmlcontext.h> +#include <qmlengine.h> +#include <qmlexpression.h> +#include <qmlpackage_p.h> +#include <qmlopenmetaobject_p.h> +#include <qmllistaccessor_p.h> +#include <qmlinfo.h> +#include <qmldeclarativedata_p.h> +#include <qmlpropertycache_p.h> +#include <qmlguard_p.h> + +#include <qlistmodelinterface_p.h> +#include <qhash.h> +#include <qlist.h> +#include <qmetaobjectbuilder_p.h> +#include <QtCore/qdebug.h> + +#include <private/qobject_p.h> + +QML_DECLARE_TYPE(QListModelInterface) + +QT_BEGIN_NAMESPACE + +class QmlGraphicsVisualItemModelAttached : public QObject +{ + Q_OBJECT + +public: + QmlGraphicsVisualItemModelAttached(QObject *parent) + : QObject(parent), m_index(0) {} + ~QmlGraphicsVisualItemModelAttached() { + 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 QmlGraphicsVisualItemModelAttached *properties(QObject *obj) { + QmlGraphicsVisualItemModelAttached *rv = attachedProperties.value(obj); + if (!rv) { + rv = new QmlGraphicsVisualItemModelAttached(obj); + attachedProperties.insert(obj, rv); + } + return rv; + } + +Q_SIGNALS: + void indexChanged(); + +public: + int m_index; + + static QHash<QObject*, QmlGraphicsVisualItemModelAttached*> attachedProperties; +}; + +QHash<QObject*, QmlGraphicsVisualItemModelAttached*> QmlGraphicsVisualItemModelAttached::attachedProperties; + + +class QmlGraphicsVisualItemModelPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsVisualItemModel) +public: + QmlGraphicsVisualItemModelPrivate() : QObjectPrivate(), children(this) {} + + struct ItemList : public QmlConcreteList<QmlGraphicsItem *> + { + ItemList(QmlGraphicsVisualItemModelPrivate *m) : QmlConcreteList<QmlGraphicsItem *>(), model(m) {} + + void append(QmlGraphicsItem *item); + + QmlGraphicsVisualItemModelPrivate *model; + }; + + void itemAppended() { + Q_Q(QmlGraphicsVisualItemModel); + QmlGraphicsVisualItemModelAttached *attached = QmlGraphicsVisualItemModelAttached::properties(children.last()); + attached->setIndex(children.count()-1); + emit q->itemsInserted(children.count()-1, 1); + emit q->countChanged(); + } + + void emitChildrenChanged() { + Q_Q(QmlGraphicsVisualItemModel); + emit q->childrenChanged(); + } + ItemList children; +}; + + +/*! + \qmlclass VisualItemModel QmlGraphicsVisualItemModel + \brief The VisualItemModel allows items to be provided to a view. + + The children of the VisualItemModel are provided in a model which + can be used in a view. Note that no delegate should be + provided to a view since the VisualItemModel contains the + visual delegate (items). + + An item can determine its index within the + model via the VisualItemModel.index attached property. + + The example below places three colored rectangles in a ListView. + \code + Item { + VisualItemModel { + id: itemModel + Rectangle { height: 30; width: 80; color: "red" } + Rectangle { height: 30; width: 80; color: "green" } + Rectangle { height: 30; width: 80; color: "blue" } + } + + ListView { + anchors.fill: parent + model: itemModel + } + } + \endcode +*/ +QmlGraphicsVisualItemModel::QmlGraphicsVisualItemModel() + : QmlGraphicsVisualModel(*(new QmlGraphicsVisualItemModelPrivate)) +{ +} + +QmlList<QmlGraphicsItem *> *QmlGraphicsVisualItemModel::children() +{ + Q_D(QmlGraphicsVisualItemModel); + return &(d->children); +} + +/*! + \qmlproperty int VisualItemModel::count + + The number of items in the model. This property is readonly. +*/ +int QmlGraphicsVisualItemModel::count() const +{ + Q_D(const QmlGraphicsVisualItemModel); + return d->children.count(); +} + +bool QmlGraphicsVisualItemModel::isValid() const +{ + return true; +} + +QmlGraphicsItem *QmlGraphicsVisualItemModel::item(int index, bool) +{ + Q_D(QmlGraphicsVisualItemModel); + return d->children.at(index); +} + +QmlGraphicsVisualModel::ReleaseFlags QmlGraphicsVisualItemModel::release(QmlGraphicsItem *) +{ + // Nothing to do + return 0; +} + +void QmlGraphicsVisualItemModel::completeItem() +{ + // Nothing to do +} + +QString QmlGraphicsVisualItemModel::stringValue(int index, const QString &name) +{ + Q_D(QmlGraphicsVisualItemModel); + if (index < 0 || index >= d->children.count()) + return QString(); + return QmlEngine::contextForObject(d->children.at(index))->contextProperty(name).toString(); +} + +QVariant QmlGraphicsVisualItemModel::evaluate(int index, const QString &expression, QObject *objectContext) +{ + Q_D(QmlGraphicsVisualItemModel); + if (index < 0 || index >= d->children.count()) + return QVariant(); + QmlContext *ccontext = qmlContext(this); + QmlContext *ctxt = new QmlContext(ccontext); + ctxt->addDefaultObject(d->children.at(index)); + QmlExpression e(ctxt, expression, objectContext); + e.setTrackChange(false); + QVariant value = e.value(); + delete ctxt; + return value; +} + +int QmlGraphicsVisualItemModel::indexOf(QmlGraphicsItem *item, QObject *) const +{ + Q_D(const QmlGraphicsVisualItemModel); + return d->children.indexOf(item); +} + +void QmlGraphicsVisualItemModelPrivate::ItemList::append(QmlGraphicsItem *item) +{ + QmlConcreteList<QmlGraphicsItem*>::append(item); + item->QObject::setParent(model->q_ptr); + model->itemAppended(); + + model->emitChildrenChanged(); +} + +QmlGraphicsVisualItemModelAttached *QmlGraphicsVisualItemModel::qmlAttachedProperties(QObject *obj) +{ + return QmlGraphicsVisualItemModelAttached::properties(obj); +} + +//============================================================================ + +class VDMDelegateDataType : public QmlOpenMetaObjectType +{ +public: + VDMDelegateDataType(const QMetaObject *base, QmlEngine *engine) : QmlOpenMetaObjectType(base, engine) {} + + void propertyCreated(int, QMetaPropertyBuilder &prop) { + prop.setWritable(false); + } +}; + +class QmlGraphicsVisualDataModelParts; +class QmlGraphicsVisualDataModelData; +class QmlGraphicsVisualDataModelPrivate : public QObjectPrivate +{ +public: + QmlGraphicsVisualDataModelPrivate(QmlContext *); + + static QmlGraphicsVisualDataModelPrivate *get(QmlGraphicsVisualDataModel *m) { + return static_cast<QmlGraphicsVisualDataModelPrivate *>(QObjectPrivate::get(m)); + } + + QmlGuard<QListModelInterface> m_listModelInterface; + QmlGuard<QAbstractItemModel> m_abstractItemModel; + QmlGuard<QmlGraphicsVisualDataModel> m_visualItemModel; + QString m_part; + + QmlComponent *m_delegate; + QmlContext *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)); + if (m_roles.count() == 1) + m_roleNames.insert("modelData", m_roles.at(0)); + } 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() == 1) + m_roleNames.insert("modelData", m_roles.at(0)); + } else if (m_listAccessor) { + m_roleNames.insert("modelData", 0); + if (m_listAccessor->type() == QmlListAccessor::Instance) { + if (QObject *object = m_listAccessor->at(0).value<QObject*>()) { + int count = object->metaObject()->propertyCount(); + for (int ii = 1; ii < count; ++ii) { + const QMetaProperty &prop = object->metaObject()->property(ii); + m_roleNames.insert(prop.name(), 0); + } + } + } + } + } + } + + void createMetaData() { + if (!m_metaDataCreated) { + ensureRoles(); + QHash<QByteArray, int>::const_iterator it = m_roleNames.begin(); + while (it != m_roleNames.end()) { + m_delegateDataType->createProperty(it.key()); + ++it; + } + 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(); + if (m_listAccessor) + return m_listAccessor->count(); + return 0; + } + + Cache m_cache; + QHash<QObject *, QmlPackage*> m_packaged; + + QmlGraphicsVisualDataModelParts *m_parts; + friend class QmlGraphicsVisualItemParts; + + VDMDelegateDataType *m_delegateDataType; + friend class QmlGraphicsVisualDataModelData; + bool m_metaDataCreated; + bool m_metaDataCacheable; + + QmlGraphicsVisualDataModelData *data(QObject *item); + + QVariant m_modelVariant; + QmlListAccessor *m_listAccessor; +}; + +class QmlGraphicsVisualDataModelDataMetaObject : public QmlOpenMetaObject +{ +public: + QmlGraphicsVisualDataModelDataMetaObject(QObject *parent, QmlOpenMetaObjectType *type) + : QmlOpenMetaObject(parent, type) {} + + virtual QVariant initialValue(int); + virtual int createProperty(const char *, const char *); + +private: + friend class QmlGraphicsVisualDataModelData; + QHash<int,int> roleToProp; +}; + +class QmlGraphicsVisualDataModelData : public QObject +{ +Q_OBJECT +public: + QmlGraphicsVisualDataModelData(int index, QmlGraphicsVisualDataModel *model); + ~QmlGraphicsVisualDataModelData(); + + Q_PROPERTY(int index READ index NOTIFY indexChanged) + int index() const; + void setIndex(int index); + + int propForRole(int) const; + void setValue(int, const QVariant &); + +Q_SIGNALS: + void indexChanged(); + +private: + friend class QmlGraphicsVisualDataModelDataMetaObject; + int m_index; + QmlGuard<QmlGraphicsVisualDataModel> m_model; + QmlGraphicsVisualDataModelDataMetaObject *m_meta; +}; + +int QmlGraphicsVisualDataModelData::propForRole(int id) const +{ + QHash<int,int>::const_iterator it = m_meta->roleToProp.find(id); + if (it != m_meta->roleToProp.end()) + return m_meta->roleToProp[id]; + return -1; +} + +void QmlGraphicsVisualDataModelData::setValue(int id, const QVariant &val) +{ + m_meta->setValue(id, val); +} + +int QmlGraphicsVisualDataModelDataMetaObject::createProperty(const char *name, const char *type) +{ + QmlGraphicsVisualDataModelData *data = + static_cast<QmlGraphicsVisualDataModelData *>(object()); + + if (!data->m_model) + return -1; + + QmlGraphicsVisualDataModelPrivate *model = QmlGraphicsVisualDataModelPrivate::get(data->m_model); + + if ((!model->m_listModelInterface || !model->m_abstractItemModel) && model->m_listAccessor) { + if (model->m_listAccessor->type() == QmlListAccessor::QmlList + || model->m_listAccessor->type() == QmlListAccessor::QListPtr) { + model->ensureRoles(); + QObject *object = model->m_listAccessor->at(data->m_index).value<QObject*>(); + if (object && object->property(name).isValid()) + return QmlOpenMetaObject::createProperty(name, type); + } + } + return -1; +} + +QVariant QmlGraphicsVisualDataModelDataMetaObject::initialValue(int propId) +{ + QmlGraphicsVisualDataModelData *data = + static_cast<QmlGraphicsVisualDataModelData *>(object()); + + Q_ASSERT(data->m_model); + QmlGraphicsVisualDataModelPrivate *model = QmlGraphicsVisualDataModelPrivate::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() == QmlListAccessor::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()) { + roleToProp.insert(*it, propId); + QVariant value = model->m_listModelInterface->data(data->m_index, *it); + return value; + } else if (model->m_roles.count() == 1 && propName == "modelData") { + //for compatability with other lists, assign modelData if there is only a single role + roleToProp.insert(model->m_roles.first(), propId); + QVariant value = model->m_listModelInterface->data(data->m_index, model->m_roles.first()); + return value; + } + } else if (model->m_abstractItemModel) { + model->ensureRoles(); + QHash<QByteArray,int>::const_iterator it = model->m_roleNames.find(propName); + if (it != model->m_roleNames.end()) { + roleToProp.insert(*it, propId); + QModelIndex index = model->m_abstractItemModel->index(data->m_index, 0); + return model->m_abstractItemModel->data(index, *it); + } + } + Q_ASSERT(!"Can never be reached"); + return QVariant(); +} + +QmlGraphicsVisualDataModelData::QmlGraphicsVisualDataModelData(int index, + QmlGraphicsVisualDataModel *model) +: m_index(index), m_model(model), +m_meta(new QmlGraphicsVisualDataModelDataMetaObject(this, QmlGraphicsVisualDataModelPrivate::get(model)->m_delegateDataType)) +{ + QmlGraphicsVisualDataModelPrivate *modelPriv = QmlGraphicsVisualDataModelPrivate::get(model); + if (modelPriv->m_metaDataCacheable) { + if (!modelPriv->m_metaDataCreated) + modelPriv->createMetaData(); + m_meta->setCached(true); + } +} + +QmlGraphicsVisualDataModelData::~QmlGraphicsVisualDataModelData() +{ +} + +int QmlGraphicsVisualDataModelData::index() const +{ + return m_index; +} + +// This is internal only - it should not be set from qml +void QmlGraphicsVisualDataModelData::setIndex(int index) +{ + m_index = index; + emit indexChanged(); +} + +//--------------------------------------------------------------------------- + +class QmlGraphicsVisualDataModelPartsMetaObject : public QmlOpenMetaObject +{ +public: + QmlGraphicsVisualDataModelPartsMetaObject(QObject *parent) + : QmlOpenMetaObject(parent) {} + + virtual void propertyCreated(int, QMetaPropertyBuilder &); + virtual QVariant initialValue(int); +}; + +class QmlGraphicsVisualDataModelParts : public QObject +{ +Q_OBJECT +public: + QmlGraphicsVisualDataModelParts(QmlGraphicsVisualDataModel *parent); + +private: + friend class QmlGraphicsVisualDataModelPartsMetaObject; + QmlGraphicsVisualDataModel *model; +}; + +void QmlGraphicsVisualDataModelPartsMetaObject::propertyCreated(int, QMetaPropertyBuilder &prop) +{ + prop.setWritable(false); +} + +QVariant QmlGraphicsVisualDataModelPartsMetaObject::initialValue(int id) +{ + QmlGraphicsVisualDataModel *m = new QmlGraphicsVisualDataModel; + m->setParent(object()); + m->setPart(QString::fromUtf8(name(id))); + m->setModel(QVariant::fromValue(static_cast<QmlGraphicsVisualDataModelParts *>(object())->model)); + + QVariant var = QVariant::fromValue((QObject *)m); + return var; +} + +QmlGraphicsVisualDataModelParts::QmlGraphicsVisualDataModelParts(QmlGraphicsVisualDataModel *parent) +: QObject(parent), model(parent) +{ + new QmlGraphicsVisualDataModelPartsMetaObject(this); +} + +QmlGraphicsVisualDataModelPrivate::QmlGraphicsVisualDataModelPrivate(QmlContext *ctxt) +: m_listModelInterface(0), m_abstractItemModel(0), m_visualItemModel(0), m_delegate(0) +, m_context(ctxt), m_parts(0), m_delegateDataType(0), m_metaDataCreated(false) +, m_metaDataCacheable(false), m_listAccessor(0) +{ +} + +QmlGraphicsVisualDataModelData *QmlGraphicsVisualDataModelPrivate::data(QObject *item) +{ + QmlGraphicsVisualDataModelData *dataItem = + item->findChild<QmlGraphicsVisualDataModelData *>(); + Q_ASSERT(dataItem); + return dataItem; +} + +//--------------------------------------------------------------------------- + +QmlGraphicsVisualDataModel::QmlGraphicsVisualDataModel() +: QmlGraphicsVisualModel(*(new QmlGraphicsVisualDataModelPrivate(0))) +{ +} + +QmlGraphicsVisualDataModel::QmlGraphicsVisualDataModel(QmlContext *ctxt) +: QmlGraphicsVisualModel(*(new QmlGraphicsVisualDataModelPrivate(ctxt))) +{ +} + +QmlGraphicsVisualDataModel::~QmlGraphicsVisualDataModel() +{ + Q_D(QmlGraphicsVisualDataModel); + if (d->m_listAccessor) + delete d->m_listAccessor; + if (d->m_delegateDataType) + d->m_delegateDataType->release(); +} + +QVariant QmlGraphicsVisualDataModel::model() const +{ + Q_D(const QmlGraphicsVisualDataModel); + return d->m_modelVariant; +} + +void QmlGraphicsVisualDataModel::setModel(const QVariant &model) +{ + Q_D(QmlGraphicsVisualDataModel); + 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(const QModelIndex &,int,int)), + this, SLOT(_q_rowsInserted(const QModelIndex &,int,int))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(rowsRemoved(const QModelIndex &,int,int)), + this, SLOT(_q_rowsRemoved(const QModelIndex &,int,int))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), + this, SLOT(_q_dataChanged(const QModelIndex&,const QModelIndex&))); + QObject::disconnect(d->m_abstractItemModel, SIGNAL(rowsMoved(const QModelIndex&,int,int,const QModelIndex&,int)), + this, SLOT(_q_rowsMoved(const QModelIndex&,int,int,const QModelIndex&,int))); + } 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,QmlPackage*)), + this, SLOT(_q_createdPackage(int,QmlPackage*))); + QObject::disconnect(d->m_visualItemModel, SIGNAL(destroyingPackage(QmlPackage*)), + this, SLOT(_q_destroyingPackage(QmlPackage*))); + 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(&QmlGraphicsVisualDataModelData::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(const QModelIndex &,int,int)), + this, SLOT(_q_rowsInserted(const QModelIndex &,int,int))); + QObject::connect(d->m_abstractItemModel, SIGNAL(rowsRemoved(const QModelIndex &,int,int)), + this, SLOT(_q_rowsRemoved(const QModelIndex &,int,int))); + QObject::connect(d->m_abstractItemModel, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), + this, SLOT(_q_dataChanged(const QModelIndex&,const QModelIndex&))); + QObject::connect(d->m_abstractItemModel, SIGNAL(rowsMoved(const QModelIndex&,int,int,const QModelIndex&,int)), + this, SLOT(_q_rowsMoved(const QModelIndex&,int,int,const QModelIndex&,int))); + d->m_metaDataCacheable = true; + return; + } + if ((d->m_visualItemModel = qvariant_cast<QmlGraphicsVisualDataModel *>(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,QmlPackage*)), + this, SLOT(_q_createdPackage(int,QmlPackage*))); + QObject::connect(d->m_visualItemModel, SIGNAL(destroyingPackage(QmlPackage*)), + this, SLOT(_q_destroyingPackage(QmlPackage*))); + return; + } + d->m_listAccessor = new QmlListAccessor; + d->m_listAccessor->setList(model, d->m_context?d->m_context->engine():qmlEngine(this)); + if (d->m_listAccessor->type() != QmlListAccessor::QmlList && d->m_listAccessor->type() != QmlListAccessor::QListPtr) + d->m_metaDataCacheable = true; + if (d->m_delegate && d->modelCount()) { + emit itemsInserted(0, d->modelCount()); + emit countChanged(); + } +} + +QmlComponent *QmlGraphicsVisualDataModel::delegate() const +{ + Q_D(const QmlGraphicsVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->delegate(); + return d->m_delegate; +} + +void QmlGraphicsVisualDataModel::setDelegate(QmlComponent *delegate) +{ + Q_D(QmlGraphicsVisualDataModel); + bool wasValid = d->m_delegate != 0; + d->m_delegate = delegate; + 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(); + } +} + +QString QmlGraphicsVisualDataModel::part() const +{ + Q_D(const QmlGraphicsVisualDataModel); + return d->m_part; +} + +void QmlGraphicsVisualDataModel::setPart(const QString &part) +{ + Q_D(QmlGraphicsVisualDataModel); + d->m_part = part; +} + +int QmlGraphicsVisualDataModel::count() const +{ + Q_D(const QmlGraphicsVisualDataModel); + return d->modelCount(); +} + +QmlGraphicsItem *QmlGraphicsVisualDataModel::item(int index, bool complete) +{ + Q_D(QmlGraphicsVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->item(index, d->m_part.toUtf8(), complete); + return item(index, QByteArray(), complete); +} + +/* + Returns ReleaseStatus flags. +*/ +QmlGraphicsVisualDataModel::ReleaseFlags QmlGraphicsVisualDataModel::release(QmlGraphicsItem *item) +{ + Q_D(QmlGraphicsVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->release(item); + + ReleaseFlags stat = 0; + QObject *obj = item; + bool inPackage = false; + + QHash<QObject*,QmlPackage*>::iterator it = d->m_packaged.find(item); + if (it != d->m_packaged.end()) { + QmlPackage *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)) { + if (inPackage) { + emit destroyingPackage(qobject_cast<QmlPackage*>(obj)); + } else { + item->setVisible(false); + static_cast<QGraphicsItem*>(item)->setParentItem(0); + } + stat |= Destroyed; + obj->deleteLater(); + } else if (!inPackage) { + stat |= Referenced; + } + + return stat; +} + +QObject *QmlGraphicsVisualDataModel::parts() +{ + Q_D(QmlGraphicsVisualDataModel); + if (!d->m_parts) + d->m_parts = new QmlGraphicsVisualDataModelParts(this); + return d->m_parts; +} + +QmlGraphicsItem *QmlGraphicsVisualDataModel::item(int index, const QByteArray &viewId, bool complete) +{ + Q_D(QmlGraphicsVisualDataModel); + 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); + if (!nobj) { + QmlContext *ccontext = d->m_context; + if (!ccontext) ccontext = qmlContext(this); + QmlContext *ctxt = new QmlContext(ccontext); + QmlGraphicsVisualDataModelData *data = new QmlGraphicsVisualDataModelData(index, this); + ctxt->setContextProperty(QLatin1String("model"), data); + ctxt->addDefaultObject(data); + nobj = d->m_delegate->beginCreate(ctxt); + if (complete) + d->m_delegate->completeCreate(); + if (nobj) { + ctxt->setParent(nobj); + data->setParent(nobj); + d->m_cache.insertItem(index, nobj); + if (QmlPackage *package = qobject_cast<QmlPackage *>(nobj)) + emit createdPackage(index, package); + } else { + delete data; + delete ctxt; + qWarning() << d->m_delegate->errors(); + } + } + QmlGraphicsItem *item = qobject_cast<QmlGraphicsItem *>(nobj); + if (!item) { + QmlPackage *package = qobject_cast<QmlPackage *>(nobj); + if (package) { + QObject *o = package->part(QString::fromUtf8(viewId)); + item = qobject_cast<QmlGraphicsItem *>(o); + if (item) + d->m_packaged.insertMulti(item, package); + } + } + if (!item) { + d->m_cache.releaseItem(nobj); + qmlInfo(d->m_delegate) << QmlGraphicsVisualDataModel::tr("Delegate component must be Item type."); + } + + return item; +} + +void QmlGraphicsVisualDataModel::completeItem() +{ + Q_D(QmlGraphicsVisualDataModel); + if (d->m_visualItemModel) { + d->m_visualItemModel->completeItem(); + return; + } + + d->m_delegate->completeCreate(); +} + +QString QmlGraphicsVisualDataModel::stringValue(int index, const QString &name) +{ + Q_D(QmlGraphicsVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->stringValue(index, name); + + 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 QmlGraphicsVisualDataModelData(index, this); + tempData = true; + } + + QmlDeclarativeData *ddata = QmlDeclarativeData::get(data); + if (ddata && ddata->propertyCache) { + QmlPropertyCache::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; +} + +QVariant QmlGraphicsVisualDataModel::evaluate(int index, const QString &expression, QObject *objectContext) +{ + Q_D(QmlGraphicsVisualDataModel); + if (d->m_visualItemModel) + return d->m_visualItemModel->evaluate(index, expression, objectContext); + + if ((!d->m_listModelInterface && !d->m_abstractItemModel) || !d->m_delegate) + return QVariant(); + + QVariant value; + QObject *nobj = d->m_cache.item(index); + if (nobj) { + QmlGraphicsItem *item = qobject_cast<QmlGraphicsItem *>(nobj); + if (item) { + QmlExpression e(qmlContext(item), expression, objectContext); + e.setTrackChange(false); + value = e.value(); + } + } else { + QmlContext *ccontext = d->m_context; + if (!ccontext) ccontext = qmlContext(this); + QmlContext *ctxt = new QmlContext(ccontext); + QmlGraphicsVisualDataModelData *data = new QmlGraphicsVisualDataModelData(index, this); + ctxt->addDefaultObject(data); + QmlExpression e(ctxt, expression, objectContext); + e.setTrackChange(false); + value = e.value(); + delete data; + delete ctxt; + } + + return value; +} + +int QmlGraphicsVisualDataModel::indexOf(QmlGraphicsItem *item, QObject *) const +{ + QVariant val = QmlEngine::contextForObject(item)->contextProperty(QLatin1String("index")); + return val.toInt(); + return -1; +} + +void QmlGraphicsVisualDataModel::_q_itemsChanged(int index, int count, + const QList<int> &roles) +{ + Q_D(QmlGraphicsVisualDataModel); + // XXX - highly inefficient + for (int ii = index; ii < index + count; ++ii) { + + if (QObject *item = d->m_cache.item(ii)) { + QmlGraphicsVisualDataModelData *data = d->data(item); + + for (int roleIdx = 0; roleIdx < roles.count(); ++roleIdx) { + int role = roles.at(roleIdx); + int propId = data->propForRole(role); + if (propId != -1) { + if (d->m_listModelInterface) { + data->setValue(propId, d->m_listModelInterface->data(ii, QList<int>() << role).value(role)); + } else if (d->m_abstractItemModel) { + QModelIndex index = d->m_abstractItemModel->index(ii, 0); + data->setValue(propId, d->m_abstractItemModel->data(index, role)); + } + } + } + } + } +} + +void QmlGraphicsVisualDataModel::_q_itemsInserted(int index, int count) +{ + Q_D(QmlGraphicsVisualDataModel); + // XXX - highly inefficient + QHash<int,QmlGraphicsVisualDataModelPrivate::ObjectRef> items; + for (QHash<int,QmlGraphicsVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + + if (iter.key() >= index) { + QmlGraphicsVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() + count; + iter = d->m_cache.erase(iter); + + items.insert(index, objRef); + + QmlGraphicsVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + d->m_cache.unite(items); + + emit itemsInserted(index, count); + emit countChanged(); +} + +void QmlGraphicsVisualDataModel::_q_itemsRemoved(int index, int count) +{ + Q_D(QmlGraphicsVisualDataModel); + // XXX - highly inefficient + QHash<int, QmlGraphicsVisualDataModelPrivate::ObjectRef> items; + for (QHash<int, QmlGraphicsVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + if (iter.key() >= index && iter.key() < index + count) { + QmlGraphicsVisualDataModelPrivate::ObjectRef objRef = *iter; + iter = d->m_cache.erase(iter); + items.insertMulti(-1, objRef); //XXX perhaps better to maintain separately + QmlGraphicsVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(-1); + } else if (iter.key() >= index + count) { + QmlGraphicsVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() - count; + iter = d->m_cache.erase(iter); + items.insert(index, objRef); + QmlGraphicsVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + + d->m_cache.unite(items); + emit itemsRemoved(index, count); + emit countChanged(); +} + +void QmlGraphicsVisualDataModel::_q_itemsMoved(int from, int to, int count) +{ + Q_D(QmlGraphicsVisualDataModel); + // XXX - highly inefficient + QHash<int,QmlGraphicsVisualDataModelPrivate::ObjectRef> items; + for (QHash<int,QmlGraphicsVisualDataModelPrivate::ObjectRef>::Iterator iter = d->m_cache.begin(); + iter != d->m_cache.end(); ) { + + if (iter.key() >= from && iter.key() < from + count) { + QmlGraphicsVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() - from + to; + iter = d->m_cache.erase(iter); + + items.insert(index, objRef); + + QmlGraphicsVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + for (QHash<int,QmlGraphicsVisualDataModelPrivate::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)) { + QmlGraphicsVisualDataModelPrivate::ObjectRef objRef = *iter; + int index = iter.key() + diff; + iter = d->m_cache.erase(iter); + + items.insert(index, objRef); + + QmlGraphicsVisualDataModelData *data = d->data(objRef.obj); + data->setIndex(index); + } else { + ++iter; + } + } + d->m_cache.unite(items); + + emit itemsMoved(from, to, count); +} + +void QmlGraphicsVisualDataModel::_q_rowsInserted(const QModelIndex &parent, int begin, int end) +{ + if (!parent.isValid()) + _q_itemsInserted(begin, end - begin + 1); +} + +void QmlGraphicsVisualDataModel::_q_rowsRemoved(const QModelIndex &parent, int begin, int end) +{ + if (!parent.isValid()) + _q_itemsRemoved(begin, end - begin + 1); +} + +void QmlGraphicsVisualDataModel::_q_rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) +{ + const int count = sourceEnd - sourceStart + 1; + if (!destinationParent.isValid() && !sourceParent.isValid()) { + _q_itemsMoved(sourceStart, destinationRow, count); + } else if (!sourceParent.isValid()) { + _q_itemsRemoved(sourceStart, count); + } else if (!destinationParent.isValid()) { + _q_itemsInserted(destinationRow, count); + } +} + +void QmlGraphicsVisualDataModel::_q_dataChanged(const QModelIndex &begin, const QModelIndex &end) +{ + Q_D(QmlGraphicsVisualDataModel); + if (!begin.parent().isValid()) + _q_itemsChanged(begin.row(), end.row() - begin.row() + 1, d->m_roles); +} + +void QmlGraphicsVisualDataModel::_q_createdPackage(int index, QmlPackage *package) +{ + Q_D(QmlGraphicsVisualDataModel); + emit createdItem(index, qobject_cast<QmlGraphicsItem*>(package->part(d->m_part))); +} + +void QmlGraphicsVisualDataModel::_q_destroyingPackage(QmlPackage *package) +{ + Q_D(QmlGraphicsVisualDataModel); + emit destroyingItem(qobject_cast<QmlGraphicsItem*>(package->part(d->m_part))); +} + +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsVisualModel); +QML_DEFINE_TYPE(Qt,4,6,VisualItemModel,QmlGraphicsVisualItemModel) +QML_DEFINE_TYPE(Qt,4,6,VisualDataModel,QmlGraphicsVisualDataModel) + +QT_END_NAMESPACE + +#include <qmlgraphicsvisualitemmodel.moc> diff --git a/src/declarative/graphicsitems/qmlgraphicsvisualitemmodel_p.h b/src/declarative/graphicsitems/qmlgraphicsvisualitemmodel_p.h new file mode 100644 index 0000000..ef849b0 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicsvisualitemmodel_p.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSVISUALDATAMODEL_H +#define QMLGRAPHICSVISUALDATAMODEL_H + +#include <qml.h> + +#include <QtCore/qobject.h> +#include <QtCore/qabstractitemmodel.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +/***************************************************************************** + ***************************************************************************** + XXX Experimental + ***************************************************************************** +*****************************************************************************/ + +class QmlGraphicsItem; +class QmlComponent; +class QmlPackage; +class QmlGraphicsVisualDataModelPrivate; + +class Q_DECLARATIVE_EXPORT QmlGraphicsVisualModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int count READ count NOTIFY countChanged) + +public: + QmlGraphicsVisualModel() {} + virtual ~QmlGraphicsVisualModel() {} + + enum ReleaseFlag { Referenced = 0x01, Destroyed = 0x02 }; + Q_DECLARE_FLAGS(ReleaseFlags, ReleaseFlag) + + virtual int count() const = 0; + virtual bool isValid() const = 0; + virtual QmlGraphicsItem *item(int index, bool complete=true) = 0; + virtual ReleaseFlags release(QmlGraphicsItem *item) = 0; + virtual void completeItem() = 0; + virtual QVariant evaluate(int index, const QString &expression, QObject *objectContext) = 0; + virtual QString stringValue(int, const QString &) { return QString(); } + + virtual int indexOf(QmlGraphicsItem *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 createdItem(int index, QmlGraphicsItem *item); + void destroyingItem(QmlGraphicsItem *item); + +protected: + QmlGraphicsVisualModel(QObjectPrivate &dd, QObject *parent = 0) + : QObject(dd, parent) {} + +private: + Q_DISABLE_COPY(QmlGraphicsVisualModel) +}; + +class QmlGraphicsVisualItemModelAttached; +class QmlGraphicsVisualItemModelPrivate; +class Q_DECLARATIVE_EXPORT QmlGraphicsVisualItemModel : public QmlGraphicsVisualModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QmlGraphicsVisualItemModel) + + Q_PROPERTY(QmlList<QmlGraphicsItem *>* children READ children NOTIFY childrenChanged DESIGNABLE false) + Q_CLASSINFO("DefaultProperty", "children") + +public: + QmlGraphicsVisualItemModel(); + virtual ~QmlGraphicsVisualItemModel() {} + + virtual int count() const; + virtual bool isValid() const; + virtual QmlGraphicsItem *item(int index, bool complete=true); + virtual ReleaseFlags release(QmlGraphicsItem *item); + virtual void completeItem(); + virtual QString stringValue(int index, const QString &role); + virtual QVariant evaluate(int index, const QString &expression, QObject *objectContext); + + virtual int indexOf(QmlGraphicsItem *item, QObject *objectContext) const; + + QmlList<QmlGraphicsItem *> *children(); + + static QmlGraphicsVisualItemModelAttached *qmlAttachedProperties(QObject *obj); + +Q_SIGNALS: + void childrenChanged(); + +private: + Q_DISABLE_COPY(QmlGraphicsVisualItemModel) +}; + + +class Q_DECLARATIVE_EXPORT QmlGraphicsVisualDataModel : public QmlGraphicsVisualModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QmlGraphicsVisualDataModel) + + Q_PROPERTY(QVariant model READ model WRITE setModel) + Q_PROPERTY(QmlComponent *delegate READ delegate WRITE setDelegate) + Q_PROPERTY(QString part READ part WRITE setPart) + Q_PROPERTY(QObject *parts READ parts CONSTANT) + Q_CLASSINFO("DefaultProperty", "delegate") +public: + QmlGraphicsVisualDataModel(); + QmlGraphicsVisualDataModel(QmlContext *); + virtual ~QmlGraphicsVisualDataModel(); + + QVariant model() const; + void setModel(const QVariant &); + + QmlComponent *delegate() const; + void setDelegate(QmlComponent *); + + QString part() const; + void setPart(const QString &); + + int count() const; + bool isValid() const { return delegate() != 0; } + QmlGraphicsItem *item(int index, bool complete=true); + QmlGraphicsItem *item(int index, const QByteArray &, bool complete=true); + ReleaseFlags release(QmlGraphicsItem *item); + void completeItem(); + virtual QString stringValue(int index, const QString &role); + QVariant evaluate(int index, const QString &expression, QObject *objectContext); + + int indexOf(QmlGraphicsItem *item, QObject *objectContext) const; + + QObject *parts(); + +Q_SIGNALS: + void createdPackage(int index, QmlPackage *package); + void destroyingPackage(QmlPackage *package); + +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_createdPackage(int index, QmlPackage *package); + void _q_destroyingPackage(QmlPackage *package); + +private: + Q_DISABLE_COPY(QmlGraphicsVisualDataModel) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsVisualModel) +QML_DECLARE_TYPE(QmlGraphicsVisualItemModel) +QML_DECLARE_TYPEINFO(QmlGraphicsVisualItemModel, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QmlGraphicsVisualDataModel) + +QT_END_HEADER + +#endif // QMLGRAPHICSVISUALDATAMODEL_H diff --git a/src/declarative/graphicsitems/qmlgraphicswebview.cpp b/src/declarative/graphicsitems/qmlgraphicswebview.cpp new file mode 100644 index 0000000..85fd0d7 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicswebview.cpp @@ -0,0 +1,1351 @@ +/**************************************************************************** +** +** 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 "qmlgraphicswebview_p.h" +#include "qmlgraphicswebview_p_p.h" + +#include "qmlgraphicspainteditem_p_p.h" + +#include <qml.h> +#include <qmlengine.h> +#include <qmlstate_p.h> + +#include <QDebug> +#include <QPen> +#include <QFile> +#include <QEvent> +#include <QMouseEvent> +#include <QKeyEvent> +#include <QBasicTimer> +#include <QApplication> +#include <QGraphicsSceneMouseEvent> +#include <QtWebKit/QWebPage> +#include <QtWebKit/QWebFrame> +#include <QtWebKit/QWebElement> +#include <QtWebKit/QWebSettings> +#include <qlistmodelinterface_p.h> + +QT_BEGIN_NAMESPACE +QML_DEFINE_TYPE(Qt,4,6,WebView,QmlGraphicsWebView) +QML_DEFINE_NOCREATE_TYPE(QAction) + +static const int MAX_DOUBLECLICK_TIME=500; // XXX need better gesture system + +QML_DEFINE_NOCREATE_TYPE(QmlGraphicsWebSettings) + +class QmlGraphicsWebViewPrivate : public QmlGraphicsPaintedItemPrivate +{ + Q_DECLARE_PUBLIC(QmlGraphicsWebView) + +public: + QmlGraphicsWebViewPrivate() + : QmlGraphicsPaintedItemPrivate(), page(0), preferredwidth(0), preferredheight(0), + progress(1.0), status(QmlGraphicsWebView::Null), pending(PendingNone), + newWindowComponent(0), newWindowParent(0), + pressTime(400), + windowObjects(this), + rendering(true) + { + } + + QUrl url; // page url might be different if it has not loaded yet + QWebPage *page; + + int preferredwidth, preferredheight; + qreal progress; + QmlGraphicsWebView::Status status; + QString statusText; + enum { PendingNone, PendingUrl, PendingHtml, PendingContent } pending; + QUrl pending_url; + QString pending_string; + QByteArray pending_data; + mutable QmlGraphicsWebSettings settings; + QmlComponent *newWindowComponent; + QmlGraphicsItem *newWindowParent; + + QBasicTimer pressTimer; + QPoint pressPoint; + int pressTime; // milliseconds before it's a "hold" + + void updateWindowObjects(); + class WindowObjectList : public QmlConcreteList<QObject *> + { + public: + WindowObjectList(QmlGraphicsWebViewPrivate *p) + : priv(p) {} + virtual void append(QObject *v) { + QmlConcreteList<QObject *>::append(v); + priv->updateWindowObjects(); + } + private: + QmlGraphicsWebViewPrivate *priv; + } windowObjects; + + bool rendering; +}; + +/*! + \qmlclass WebView QmlGraphicsWebView + \brief The WebView item allows you to add web content to a canvas. + \inherits Item + + A WebView renders web content based on a URL. + + If the width and height of the item is not set, they will + dynamically adjust to a size appropriate for the content. + This width may be large for typical online web pages. + + If the preferredWidth is set, the width will be this amount or larger, + usually laying out the web content to fit the preferredWidth. + + \qml + WebView { + url: "http://www.nokia.com" + width: 490 + height: 400 + scale: 0.5 + smooth: false + smoothCache: true + } + \endqml + + \image webview.png + + The item includes no scrolling, scaling, + toolbars, etc., those must be implemented around WebView. See the WebBrowser example + for a demonstration of this. +*/ + +/*! + \internal + \class QmlGraphicsWebView + \brief The QmlGraphicsWebView class allows you to add web content to a QmlView. + + A WebView renders web content base on a URL. + + \image webview.png + + The item includes no scrolling, scaling, + toolbars, etc., those must be implemented around WebView. See the WebBrowser example + for a demonstration of this. + + A QmlGraphicsWebView object can be instantiated in Qml using the tag \l WebView. +*/ + +QmlGraphicsWebView::QmlGraphicsWebView(QmlGraphicsItem *parent) + : QmlGraphicsPaintedItem(*(new QmlGraphicsWebViewPrivate), parent) +{ + init(); +} + +QmlGraphicsWebView::~QmlGraphicsWebView() +{ + Q_D(QmlGraphicsWebView); + delete d->page; +} + +void QmlGraphicsWebView::init() +{ + Q_D(QmlGraphicsWebView); + + setAcceptHoverEvents(true); + setAcceptedMouseButtons(Qt::LeftButton); + setFlag(QGraphicsItem::ItemHasNoContents, false); + + d->page = 0; +} + +void QmlGraphicsWebView::componentComplete() +{ + QmlGraphicsPaintedItem::componentComplete(); + Q_D(QmlGraphicsWebView); + switch (d->pending) { + case QmlGraphicsWebViewPrivate::PendingUrl: + setUrl(d->pending_url); + break; + case QmlGraphicsWebViewPrivate::PendingHtml: + setHtml(d->pending_string, d->pending_url); + break; + case QmlGraphicsWebViewPrivate::PendingContent: + setContent(d->pending_data, d->pending_string, d->pending_url); + break; + default: + break; + } + d->pending = QmlGraphicsWebViewPrivate::PendingNone; + d->updateWindowObjects(); +} + +QmlGraphicsWebView::Status QmlGraphicsWebView::status() const +{ + Q_D(const QmlGraphicsWebView); + return d->status; +} + + +/*! + \qmlproperty real WebView::progress + This property holds the progress of loading the current URL, from 0 to 1. + + If you just want to know when progress gets to 1, use + WebView::onLoadFinished() or WebView::onLoadFailed() instead. +*/ +qreal QmlGraphicsWebView::progress() const +{ + Q_D(const QmlGraphicsWebView); + return d->progress; +} + +void QmlGraphicsWebView::doLoadStarted() +{ + Q_D(QmlGraphicsWebView); + + if (!d->url.isEmpty()) { + d->status = Loading; + emit statusChanged(d->status); + } + emit loadStarted(); +} + +void QmlGraphicsWebView::doLoadProgress(int p) +{ + Q_D(QmlGraphicsWebView); + if (d->progress == p/100.0) + return; + d->progress = p/100.0; + emit progressChanged(); +} + +void QmlGraphicsWebView::pageUrlChanged() +{ + Q_D(QmlGraphicsWebView); + + page()->setViewportSize(QSize( + d->preferredwidth>0 ? d->preferredwidth : width(), + d->preferredheight>0 ? d->preferredheight : height())); + expandToWebPage(); + + if ((d->url.isEmpty() && page()->mainFrame()->url() != QUrl(QLatin1String("about:blank"))) + || (d->url != page()->mainFrame()->url() && !page()->mainFrame()->url().isEmpty())) + { + d->url = page()->mainFrame()->url(); + if (d->url == QUrl(QLatin1String("about:blank"))) + d->url = QUrl(); + emit urlChanged(); + } +} + +void QmlGraphicsWebView::doLoadFinished(bool ok) +{ + Q_D(QmlGraphicsWebView); + + if (title().isEmpty()) + pageUrlChanged(); // XXX bug 232556 - pages with no title never get urlChanged() + + if (ok) { + d->status = d->url.isEmpty() ? Null : Ready; + emit loadFinished(); + } else { + d->status = Error; + emit loadFailed(); + } + emit statusChanged(d->status); +} + +/*! + \qmlproperty url WebView::url + This property holds the URL to the page displayed in this item. It can be set, + but also can change spontaneously (eg. because of network redirection). + + If the url is empty, the page is blank. + + The url is always absolute (QML will resolve relative URL strings in the context + of the containing QML document). +*/ +QUrl QmlGraphicsWebView::url() const +{ + Q_D(const QmlGraphicsWebView); + return d->url; +} + +void QmlGraphicsWebView::setUrl(const QUrl &url) +{ + Q_D(QmlGraphicsWebView); + if (url == d->url) + return; + + if (isComponentComplete()) { + d->url = url; + page()->setViewportSize(QSize( + d->preferredwidth>0 ? d->preferredwidth : width(), + d->preferredheight>0 ? d->preferredheight : height())); + QUrl seturl = url; + if (seturl.isEmpty()) + seturl = QUrl(QLatin1String("about:blank")); + + Q_ASSERT(!seturl.isRelative()); + + page()->mainFrame()->load(seturl); + + emit urlChanged(); + } else { + d->pending = d->PendingUrl; + d->pending_url = url; + } +} + +/*! + \qmlproperty int WebView::preferredWidth + This property holds the ideal width for displaying the current URL. +*/ +int QmlGraphicsWebView::preferredWidth() const +{ + Q_D(const QmlGraphicsWebView); + return d->preferredwidth; +} + +void QmlGraphicsWebView::setPreferredWidth(int iw) +{ + Q_D(QmlGraphicsWebView); + if (d->preferredwidth == iw) return; + d->preferredwidth = iw; + //expandToWebPage(); + emit preferredWidthChanged(); +} + +/*! + \qmlproperty int WebView::preferredHeight + This property holds the ideal height for displaying the current URL. + This only affects the area zoomed by heuristicZoom(). +*/ +int QmlGraphicsWebView::preferredHeight() const +{ + Q_D(const QmlGraphicsWebView); + return d->preferredheight; +} +void QmlGraphicsWebView::setPreferredHeight(int ih) +{ + Q_D(QmlGraphicsWebView); + if (d->preferredheight == ih) return; + d->preferredheight = ih; + emit preferredHeightChanged(); +} + +/*! + \qmlmethod bool WebView::evaluateJavaScript(string) + + Evaluates the \a scriptSource JavaScript inside the context of the + main web frame, and returns the result of the last executed statement. + + Note that this JavaScript does \e not have any access to QML objects + except as made available as windowObjects. +*/ +QVariant QmlGraphicsWebView::evaluateJavaScript(const QString &scriptSource) +{ + return this->page()->mainFrame()->evaluateJavaScript(scriptSource); +} + +void QmlGraphicsWebView::focusChanged(bool hasFocus) +{ + QFocusEvent e(hasFocus ? QEvent::FocusIn : QEvent::FocusOut); + page()->event(&e); + QmlGraphicsItem::focusChanged(hasFocus); +} + +void QmlGraphicsWebView::initialLayout() +{ + // nothing useful to do at this point +} + +void QmlGraphicsWebView::noteContentsSizeChanged(const QSize&) +{ + expandToWebPage(); +} + +void QmlGraphicsWebView::expandToWebPage() +{ + Q_D(QmlGraphicsWebView); + QSize cs = page()->mainFrame()->contentsSize(); + if (cs.width() < d->preferredwidth) + cs.setWidth(d->preferredwidth); + if (cs.height() < d->preferredheight) + cs.setHeight(d->preferredheight); + if (widthValid()) + cs.setWidth(width()); + if (heightValid()) + cs.setHeight(height()); + if (cs != page()->viewportSize()) { + page()->setViewportSize(cs); + } + if (cs != contentsSize()) + setContentsSize(cs); +} + +void QmlGraphicsWebView::geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry) +{ + if (newGeometry.size() != oldGeometry.size()) + expandToWebPage(); + QmlGraphicsPaintedItem::geometryChanged(newGeometry, oldGeometry); +} + +void QmlGraphicsWebView::paintPage(const QRect& r) +{ + dirtyCache(r); + update(); +} + +/*! + \qmlproperty list<object> WebView::javaScriptWindowObjects + + This property is a list of object that are available from within + the webview's JavaScript context. + + The \a object will be inserted as a child of the frame's window + object, under the name given by the attached property \c WebView.windowObjectName. + + \qml + WebView { + javaScriptWindowObjects: Object { + WebView.windowObjectName: "coordinates" + } + } + \endqml + + Properties of the object will be exposed as JavaScript properties and slots as + JavaScript methods. + + If Javascript is not enabled for this page, then this property does nothing. +*/ +QmlList<QObject *> *QmlGraphicsWebView::javaScriptWindowObjects() +{ + Q_D(QmlGraphicsWebView); + return &d->windowObjects; +} + +class QmlGraphicsWebViewAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString windowObjectName READ windowObjectName WRITE setWindowObjectName) +public: + QmlGraphicsWebViewAttached(QObject *parent) + : QObject(parent) + { + } + + QString windowObjectName() const + { + return m_windowObjectName; + } + + void setWindowObjectName(const QString &n) + { + m_windowObjectName = n; + } + +private: + QString m_windowObjectName; +}; + +QmlGraphicsWebViewAttached *QmlGraphicsWebView::qmlAttachedProperties(QObject *o) +{ + return new QmlGraphicsWebViewAttached(o); +} + +void QmlGraphicsWebViewPrivate::updateWindowObjects() +{ + Q_Q(QmlGraphicsWebView); + if (!q->isComponentComplete() || !page) + return; + + for (int ii = 0; ii < windowObjects.count(); ++ii) { + QObject *object = windowObjects.at(ii); + QmlGraphicsWebViewAttached *attached = static_cast<QmlGraphicsWebViewAttached *>(qmlAttachedPropertiesObject<QmlGraphicsWebView>(object)); + if (attached && !attached->windowObjectName().isEmpty()) { + page->mainFrame()->addToJavaScriptWindowObject(attached->windowObjectName(), object); + } + } +} + +bool QmlGraphicsWebView::renderingEnabled() const +{ + Q_D(const QmlGraphicsWebView); + return d->rendering; +} + +void QmlGraphicsWebView::setRenderingEnabled(bool enabled) +{ + Q_D(QmlGraphicsWebView); + if (d->rendering == enabled) + return; + d->rendering = enabled; + setCacheFrozen(!enabled); + if (enabled) + clearCache(); +} + + +void QmlGraphicsWebView::drawContents(QPainter *p, const QRect &r) +{ + Q_D(QmlGraphicsWebView); + if (d->rendering) + page()->mainFrame()->render(p,r); +} + +QMouseEvent *QmlGraphicsWebView::sceneMouseEventToMouseEvent(QGraphicsSceneMouseEvent *e) +{ + QEvent::Type t; + switch(e->type()) { + default: + case QEvent::GraphicsSceneMousePress: + t = QEvent::MouseButtonPress; + break; + case QEvent::GraphicsSceneMouseRelease: + t = QEvent::MouseButtonRelease; + break; + case QEvent::GraphicsSceneMouseMove: + t = QEvent::MouseMove; + break; + case QGraphicsSceneEvent::GraphicsSceneMouseDoubleClick: + t = QEvent::MouseButtonDblClick; + break; + } + + QMouseEvent *me = new QMouseEvent(t, (e->pos()/contentsScale()).toPoint(), e->button(), e->buttons(), 0); + return me; +} + +QMouseEvent *QmlGraphicsWebView::sceneHoverMoveEventToMouseEvent(QGraphicsSceneHoverEvent *e) +{ + QEvent::Type t = QEvent::MouseMove; + + QMouseEvent *me = new QMouseEvent(t, (e->pos()/contentsScale()).toPoint(), Qt::NoButton, Qt::NoButton, 0); + + return me; +} + + +/*! + \qmlsignal WebView::onDoubleClick(clickx,clicky) + + The WebView does not pass double-click events to the web engine, but rather + emits this signals. +*/ + +void QmlGraphicsWebView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + QMouseEvent *me = sceneMouseEventToMouseEvent(event); + emit doubleClick(me->x(),me->y()); + delete me; +} + +/*! + \qmlmethod bool WebView::heuristicZoom(clickX,clickY,maxzoom) + + Finds a zoom that: + \list + \i shows a whole item + \i includes (\a clickX, \a clickY) + \i fits into the preferredWidth and preferredHeight + \i zooms by no more than \a maxzoom + \i is more than 10% above the current zoom + \endlist + + If such a zoom exists, emits zoomTo(zoom,centerX,centerY) and returns true; otherwise, + no signal is emitted and returns false. +*/ +bool QmlGraphicsWebView::heuristicZoom(int clickX, int clickY, qreal maxzoom) +{ + Q_D(QmlGraphicsWebView); + if (contentsScale() >= maxzoom/zoomFactor()) + return false; + qreal ozf = contentsScale(); + QRect showarea = elementAreaAt(clickX, clickY, d->preferredwidth/maxzoom, d->preferredheight/maxzoom); + qreal z = qMin(qreal(d->preferredwidth)/showarea.width(),qreal(d->preferredheight)/showarea.height()); + if (z > maxzoom/zoomFactor()) + z = maxzoom/zoomFactor(); + if (z/ozf > 1.2) { + QRectF r(showarea.left()*z, showarea.top()*z, showarea.width()*z, showarea.height()*z); + emit zoomTo(z,r.x()+r.width()/2, r.y()+r.height()/2); + return true; + } else { + return false; + } +} + +/*! + \qmlproperty int WebView::pressGrabTime + + The number of milliseconds the user must press before the WebView + starts passing move events through to the web engine (rather than + letting other QML elements such as a Flickable take them). + + Defaults to 400ms. Set to 0 to always grab and pass move events to + the web engine. +*/ +int QmlGraphicsWebView::pressGrabTime() const +{ + Q_D(const QmlGraphicsWebView); + return d->pressTime; +} + +void QmlGraphicsWebView::setPressGrabTime(int ms) +{ + Q_D(QmlGraphicsWebView); + d->pressTime = ms; +} + +void QmlGraphicsWebView::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsWebView); + + setFocus (true); + QMouseEvent *me = sceneMouseEventToMouseEvent(event); + + d->pressPoint = me->pos(); + if (d->pressTime) { + d->pressTimer.start(d->pressTime,this); + setKeepMouseGrab(false); + } else { + grabMouse(); + setKeepMouseGrab(true); + } + + page()->event(me); + event->setAccepted( +/* + It is not correct to send the press event upwards, if it is not accepted by WebKit + e.g. push button does not work, if done so as QGraphicsScene will not send the release event at all to WebKit + Might be a bug in WebKit, though + */ +#if 1 //QT_VERSION <= 0x040500 // XXX see bug 230835 + true +#else + me->isAccepted() +#endif + ); + delete me; + if (!event->isAccepted()) { + QmlGraphicsPaintedItem::mousePressEvent(event); + } +} + +void QmlGraphicsWebView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsWebView); + + QMouseEvent *me = sceneMouseEventToMouseEvent(event); + page()->event(me); + d->pressTimer.stop(); + event->setAccepted( +/* + It is not correct to send the press event upwards, if it is not accepted by WebKit + e.g. push button does not work, if done so as QGraphicsScene will not send all the events to WebKit + */ +#if 1 //QT_VERSION <= 0x040500 // XXX see bug 230835 + true +#else + me->isAccepted() +#endif + ); + delete me; + if (!event->isAccepted()) { + QmlGraphicsPaintedItem::mouseReleaseEvent(event); + } + setKeepMouseGrab(false); + ungrabMouse(); +} + +void QmlGraphicsWebView::timerEvent(QTimerEvent *event) +{ + Q_D(QmlGraphicsWebView); + if (event->timerId() == d->pressTimer.timerId()) { + d->pressTimer.stop(); + grabMouse(); + setKeepMouseGrab(true); + } +} + +void QmlGraphicsWebView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(QmlGraphicsWebView); + + QMouseEvent *me = sceneMouseEventToMouseEvent(event); + if (d->pressTimer.isActive()) { + if ((me->pos() - d->pressPoint).manhattanLength() > QApplication::startDragDistance()) { + d->pressTimer.stop(); + } + } + if (keepMouseGrab()) { + page()->event(me); + event->setAccepted( +/* + It is not correct to send the press event upwards, if it is not accepted by WebKit + e.g. push button does not work, if done so as QGraphicsScene will not send the release event at all to WebKit + Might be a bug in WebKit, though + */ +#if 1 // QT_VERSION <= 0x040500 // XXX see bug 230835 + true +#else + me->isAccepted() +#endif + ); + } + delete me; + if (!event->isAccepted()) + QmlGraphicsPaintedItem::mouseMoveEvent(event); + +} +void QmlGraphicsWebView::hoverMoveEvent (QGraphicsSceneHoverEvent * event) +{ + QMouseEvent *me = sceneHoverMoveEventToMouseEvent(event); + page()->event(me); + event->setAccepted( +#if QT_VERSION <= 0x040500 // XXX see bug 230835 + true +#else + me->isAccepted() +#endif + ); + delete me; + if (!event->isAccepted()) + QmlGraphicsPaintedItem::hoverMoveEvent(event); +} + +void QmlGraphicsWebView::keyPressEvent(QKeyEvent* event) +{ + page()->event(event); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::keyPressEvent(event); +} + +void QmlGraphicsWebView::keyReleaseEvent(QKeyEvent* event) +{ + page()->event(event); + if (!event->isAccepted()) + QmlGraphicsPaintedItem::keyReleaseEvent(event); +} + +bool QmlGraphicsWebView::sceneEvent(QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *k = static_cast<QKeyEvent *>(event); + if (k->key() == Qt::Key_Tab || k->key() == Qt::Key_Backtab) { + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + page()->event(event); + if (event->isAccepted()) + return true; + } + } + } + return QmlGraphicsPaintedItem::sceneEvent(event); +} + + +/*! + \qmlproperty action WebView::back + This property holds the action for causing the previous URL in the history to be displayed. +*/ +QAction *QmlGraphicsWebView::backAction() const +{ + return page()->action(QWebPage::Back); +} + +/*! + \qmlproperty action WebView::forward + This property holds the action for causing the next URL in the history to be displayed. +*/ +QAction *QmlGraphicsWebView::forwardAction() const +{ + return page()->action(QWebPage::Forward); +} + +/*! + \qmlproperty action WebView::reload + This property holds the action for reloading with the current URL +*/ +QAction *QmlGraphicsWebView::reloadAction() const +{ + return page()->action(QWebPage::Reload); +} + +/*! + \qmlproperty action WebView::stop + This property holds the action for stopping loading with the current URL +*/ +QAction *QmlGraphicsWebView::stopAction() const +{ + return page()->action(QWebPage::Stop); +} + +/*! + \qmlproperty real WebView::title + This property holds the title of the web page currently viewed + + By default, this property contains an empty string. +*/ +QString QmlGraphicsWebView::title() const +{ + return page()->mainFrame()->title(); +} + + + +/*! + \qmlproperty pixmap WebView::icon + This property holds the icon associated with the web page currently viewed +*/ +QPixmap QmlGraphicsWebView::icon() const +{ + return page()->mainFrame()->icon().pixmap(QSize(256,256)); +} + + +/*! + \qmlproperty real WebView::zoomFactor + This property holds the multiplier used to scale the contents of a Web page. +*/ +void QmlGraphicsWebView::setZoomFactor(qreal factor) +{ + Q_D(QmlGraphicsWebView); + if (factor == page()->mainFrame()->zoomFactor()) + return; + + page()->mainFrame()->setZoomFactor(factor); + page()->setViewportSize(QSize( + d->preferredwidth>0 ? d->preferredwidth*factor : width()*factor, + d->preferredheight>0 ? d->preferredheight*factor : height()*factor)); + expandToWebPage(); + + emit zoomFactorChanged(); +} + +qreal QmlGraphicsWebView::zoomFactor() const +{ + return page()->mainFrame()->zoomFactor(); +} + +/*! + \qmlproperty string WebView::statusText + + This property is the current status suggested by the current web page. In a web browser, + such status is often shown in some kind of status bar. +*/ +void QmlGraphicsWebView::setStatusText(const QString& s) +{ + Q_D(QmlGraphicsWebView); + d->statusText = s; + emit statusTextChanged(); +} + +void QmlGraphicsWebView::windowObjectCleared() +{ + Q_D(QmlGraphicsWebView); + d->updateWindowObjects(); +} + +QString QmlGraphicsWebView::statusText() const +{ + Q_D(const QmlGraphicsWebView); + return d->statusText; +} + +QWebPage *QmlGraphicsWebView::page() const +{ + Q_D(const QmlGraphicsWebView); + + if (!d->page) { + QmlGraphicsWebView *self = const_cast<QmlGraphicsWebView*>(this); + QWebPage *wp = new QmlGraphicsWebPage(self); + + // QML items don't default to having a background, + // even though most we pages will set one anyway. + QPalette pal = QApplication::palette(); + pal.setBrush(QPalette::Base, QColor::fromRgbF(0, 0, 0, 0)); + wp->setPalette(pal); + + wp->setNetworkAccessManager(qmlEngine(this)->networkAccessManager()); + + self->setPage(wp); + + return wp; + } + + return d->page; +} + + +// The QObject interface to settings(). +/*! + \qmlproperty string WebView::settings.standardFontFamily + \qmlproperty string WebView::settings.fixedFontFamily + \qmlproperty string WebView::settings.serifFontFamily + \qmlproperty string WebView::settings.sansSerifFontFamily + \qmlproperty string WebView::settings.cursiveFontFamily + \qmlproperty string WebView::settings.fantasyFontFamily + + \qmlproperty int WebView::settings.minimumFontSize + \qmlproperty int WebView::settings.minimumLogicalFontSize + \qmlproperty int WebView::settings.defaultFontSize + \qmlproperty int WebView::settings.defaultFixedFontSize + + \qmlproperty bool WebView::settings.autoLoadImages + \qmlproperty bool WebView::settings.javascriptEnabled + \qmlproperty bool WebView::settings.javaEnabled + \qmlproperty bool WebView::settings.pluginsEnabled + \qmlproperty bool WebView::settings.privateBrowsingEnabled + \qmlproperty bool WebView::settings.javascriptCanOpenWindows + \qmlproperty bool WebView::settings.javascriptCanAccessClipboard + \qmlproperty bool WebView::settings.developerExtrasEnabled + \qmlproperty bool WebView::settings.linksIncludedInFocusChain + \qmlproperty bool WebView::settings.zoomTextOnly + \qmlproperty bool WebView::settings.printElementBackgrounds + \qmlproperty bool WebView::settings.offlineStorageDatabaseEnabled + \qmlproperty bool WebView::settings.offlineWebApplicationCacheEnabled + \qmlproperty bool WebView::settings.localStorageDatabaseEnabled + \qmlproperty bool WebView::settings.localContentCanAccessRemoteUrls + + These properties give access to the settings controlling the web view. + + See QWebSettings for details of these properties. + + \qml + WebView { + settings.pluginsEnabled: true + settings.standardFontFamily: "Arial" + ... + } + \endqml +*/ +QmlGraphicsWebSettings *QmlGraphicsWebView::settingsObject() const +{ + Q_D(const QmlGraphicsWebView); + d->settings.s = page()->settings(); + return &d->settings; +} + +void QmlGraphicsWebView::setPage(QWebPage *page) +{ + Q_D(QmlGraphicsWebView); + if (d->page == page) + return; + if (d->page) { + if (d->page->parent() == this) { + delete d->page; + } else { + d->page->disconnect(this); + } + } + d->page = page; + d->page->setViewportSize(QSize( + d->preferredwidth>0 ? d->preferredwidth : width(), + d->preferredheight>0 ? d->preferredheight : height())); + d->page->mainFrame()->setScrollBarPolicy(Qt::Horizontal,Qt::ScrollBarAlwaysOff); + d->page->mainFrame()->setScrollBarPolicy(Qt::Vertical,Qt::ScrollBarAlwaysOff); + connect(d->page,SIGNAL(repaintRequested(QRect)),this,SLOT(paintPage(QRect))); + connect(d->page->mainFrame(),SIGNAL(urlChanged(QUrl)),this,SLOT(pageUrlChanged())); + connect(d->page->mainFrame(), SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged(QString))); + connect(d->page->mainFrame(), SIGNAL(iconChanged()), this, SIGNAL(iconChanged())); + connect(d->page->mainFrame(), SIGNAL(contentsSizeChanged(QSize)), this, SLOT(noteContentsSizeChanged(QSize))); + connect(d->page->mainFrame(), SIGNAL(initialLayoutCompleted()), this, SLOT(initialLayout())); + + connect(d->page,SIGNAL(loadStarted()),this,SLOT(doLoadStarted())); + connect(d->page,SIGNAL(loadProgress(int)),this,SLOT(doLoadProgress(int))); + connect(d->page,SIGNAL(loadFinished(bool)),this,SLOT(doLoadFinished(bool))); + connect(d->page,SIGNAL(statusBarMessage(QString)),this,SLOT(setStatusText(QString))); + + connect(d->page->mainFrame(),SIGNAL(javaScriptWindowObjectCleared()),this,SLOT(windowObjectCleared())); +} + +/*! + \qmlsignal WebView::onLoadStarted() + + This handler is called when the web engine begins loading + a page. Later, WebView::onLoadFinished() or WebView::onLoadFailed() + will be emitted. +*/ + +/*! + \qmlsignal WebView::onLoadFinished() + + This handler is called when the web engine \e successfully + finishes loading a page, including any component content + (WebView::onLoadFailed() will be emitted otherwise). + + \sa progress +*/ + +/*! + \qmlsignal WebView::onLoadFailed() + + This handler is called when the web engine fails loading + a page or any component content + (WebView::onLoadFinished() will be emitted on success). +*/ + +void QmlGraphicsWebView::load(const QNetworkRequest &request, + QNetworkAccessManager::Operation operation, + const QByteArray &body) +{ + page()->mainFrame()->load(request, operation, body); +} + +QString QmlGraphicsWebView::html() const +{ + return page()->mainFrame()->toHtml(); +} + +/*! + \qmlproperty string WebView::html + This property holds HTML text set directly + + The html property can be set as a string. + + \qml + WebView { + html: "<p>This is <b>HTML</b>." + } + \endqml +*/ +void QmlGraphicsWebView::setHtml(const QString &html, const QUrl &baseUrl) +{ + Q_D(QmlGraphicsWebView); + page()->setViewportSize(QSize( + d->preferredwidth>0 ? d->preferredwidth : width(), + d->preferredheight>0 ? d->preferredheight : height())); + if (isComponentComplete()) + page()->mainFrame()->setHtml(html, baseUrl); + else { + d->pending = d->PendingHtml; + d->pending_url = baseUrl; + d->pending_string = html; + } +} + +void QmlGraphicsWebView::setContent(const QByteArray &data, const QString &mimeType, const QUrl &baseUrl) +{ + Q_D(QmlGraphicsWebView); + page()->setViewportSize(QSize( + d->preferredwidth>0 ? d->preferredwidth : width(), + d->preferredheight>0 ? d->preferredheight : height())); + + if (isComponentComplete()) + page()->mainFrame()->setContent(data,mimeType,qmlContext(this)->resolvedUrl(baseUrl)); + else { + d->pending = d->PendingContent; + d->pending_url = baseUrl; + d->pending_string = mimeType; + d->pending_data = data; + } +} + +QWebHistory *QmlGraphicsWebView::history() const +{ + return page()->history(); +} + +QWebSettings *QmlGraphicsWebView::settings() const +{ + return page()->settings(); +} + +QmlGraphicsWebView *QmlGraphicsWebView::createWindow(QWebPage::WebWindowType type) +{ + Q_D(QmlGraphicsWebView); + switch (type) { + case QWebPage::WebBrowserWindow: { + if (!d->newWindowComponent && d->newWindowParent) + qWarning("WebView::newWindowComponent not set - WebView::newWindowParent ignored"); + else if (d->newWindowComponent && !d->newWindowParent) + qWarning("WebView::newWindowParent not set - WebView::newWindowComponent ignored"); + else if (d->newWindowComponent && d->newWindowParent) { + QmlGraphicsWebView *webview = 0; + QmlContext *windowContext = new QmlContext(qmlContext(this)); + + QObject *nobj = d->newWindowComponent->create(windowContext); + if (nobj) { + windowContext->setParent(nobj); + QmlGraphicsItem *item = qobject_cast<QmlGraphicsItem *>(nobj); + if (!item) { + delete nobj; + } else { + webview = item->findChild<QmlGraphicsWebView*>(); + if (!webview) { + delete item; + } else { + item->setParent(d->newWindowParent); + } + } + } else { + delete windowContext; + } + + return webview; + } + } + break; + case QWebPage::WebModalDialog: { + // Not supported + } + } + return 0; +} + +/*! + \qmlproperty component WebView::newWindowComponent + + This property holds the component to use for new windows. + The component must have a WebView somewhere in its structure. + + When the web engine requests a new window, it will be an instance of + this component. + + The parent of the new window is set by newWindowParent. It must be set. +*/ +QmlComponent *QmlGraphicsWebView::newWindowComponent() const +{ + Q_D(const QmlGraphicsWebView); + return d->newWindowComponent; +} + +void QmlGraphicsWebView::setNewWindowComponent(QmlComponent *newWindow) +{ + Q_D(QmlGraphicsWebView); + delete d->newWindowComponent; + d->newWindowComponent = newWindow; +} + + +/*! + \qmlproperty item WebView::newWindowParent + + The parent item for new windows. + + \sa newWindowComponent +*/ +QmlGraphicsItem *QmlGraphicsWebView::newWindowParent() const +{ + Q_D(const QmlGraphicsWebView); + return d->newWindowParent; +} + +void QmlGraphicsWebView::setNewWindowParent(QmlGraphicsItem *parent) +{ + Q_D(QmlGraphicsWebView); + delete d->newWindowParent; + d->newWindowParent = parent; +} + +/*! + Returns the area of the largest element at position (\a x,\a y) that is no larger + than \a maxwidth by \a maxheight pixels. + + May return an area larger in the case when no smaller element is at the position. +*/ +QRect QmlGraphicsWebView::elementAreaAt(int x, int y, int maxwidth, int maxheight) const +{ + QWebHitTestResult hit = page()->mainFrame()->hitTestContent(QPoint(x,y)); + QRect rv = hit.boundingRect(); + QWebElement element = hit.enclosingBlockElement(); + if (maxwidth<=0) maxwidth = INT_MAX; + if (maxheight<=0) maxheight = INT_MAX; + while (!element.parent().isNull() && element.geometry().width() <= maxwidth && element.geometry().height() <= maxheight) { + rv = element.geometry(); + element = element.parent(); + } + return rv; +} + +/*! + \internal + \class QmlGraphicsWebPage + \brief The QmlGraphicsWebPage class is a QWebPage that can create QML plugins. + + \sa QmlGraphicsWebView +*/ +QmlGraphicsWebPage::QmlGraphicsWebPage(QmlGraphicsWebView *parent) : + QWebPage(parent) +{ +} + +QmlGraphicsWebPage::~QmlGraphicsWebPage() +{ +} + +void QmlGraphicsWebPage::javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID) +{ + qWarning() << sourceID << ':' << lineNumber << ':' << message; +} + +QString QmlGraphicsWebPage::chooseFile(QWebFrame *originatingFrame, const QString& oldFile) +{ + // Not supported (it's modal) + Q_UNUSED(originatingFrame) + Q_UNUSED(oldFile) + return oldFile; +} + +void QmlGraphicsWebPage::javaScriptAlert(QWebFrame *originatingFrame, const QString& msg) +{ + Q_UNUSED(originatingFrame) + emit viewItem()->alert(msg); +} + +bool QmlGraphicsWebPage::javaScriptConfirm(QWebFrame *originatingFrame, const QString& msg) +{ + // Not supported (it's modal) + Q_UNUSED(originatingFrame) + Q_UNUSED(msg) + return false; +} + +bool QmlGraphicsWebPage::javaScriptPrompt(QWebFrame *originatingFrame, const QString& msg, const QString& defaultValue, QString* result) +{ + // Not supported (it's modal) + Q_UNUSED(originatingFrame) + Q_UNUSED(msg) + Q_UNUSED(defaultValue) + Q_UNUSED(result) + return false; +} + + +/* + Qt WebKit does not understand non-QWidget plugins, so dummy widgets + are created, parented to a single dummy tool window. + + The requirements for QML object plugins are input to the Qt WebKit + non-QWidget plugin support, which will obsolete this kludge. +*/ +class QWidget_Dummy_Plugin : public QWidget +{ + Q_OBJECT +public: + static QWidget *dummy_shared_parent() + { + static QWidget *dsp = 0; + if (!dsp) { + dsp = new QWidget(0,Qt::Tool); + dsp->setGeometry(-10000,-10000,0,0); + dsp->show(); + } + return dsp; + } + QWidget_Dummy_Plugin(const QUrl& url, QmlGraphicsWebView *view, const QStringList ¶mNames, const QStringList ¶mValues) : + QWidget(dummy_shared_parent()), + propertyNames(paramNames), + propertyValues(paramValues), + webview(view) + { + QmlEngine *engine = qmlEngine(webview); + component = new QmlComponent(engine, url, this); + item = 0; + if (component->isLoading()) + connect(component, SIGNAL(statusChanged(QmlComponent::Status)), this, SLOT(qmlLoaded())); + else + qmlLoaded(); + } + +public Q_SLOTS: + void qmlLoaded() + { + if (component->isError()) { + // ### Could instead give these errors to the WebView to handle. + qWarning() << component->errors(); + return; + } + item = qobject_cast<QmlGraphicsItem*>(component->create(qmlContext(webview))); + item->setParent(webview); + QString jsObjName; + for (int i=0; i<propertyNames.count(); ++i) { + if (propertyNames[i] != QLatin1String("type") && propertyNames[i] != QLatin1String("data")) { + item->setProperty(propertyNames[i].toUtf8(),propertyValues[i]); + if (propertyNames[i] == QLatin1String("objectname")) + jsObjName = propertyValues[i]; + } + } + if (!jsObjName.isNull()) { + QWebFrame *f = webview->page()->mainFrame(); + f->addToJavaScriptWindowObject(jsObjName, item); + } + resizeEvent(0); + delete component; + component = 0; + } + void resizeEvent(QResizeEvent*) + { + if (item) { + item->setX(x()); + item->setY(y()); + item->setWidth(width()); + item->setHeight(height()); + } + } + +private: + QmlComponent *component; + QmlGraphicsItem *item; + QStringList propertyNames, propertyValues; + QmlGraphicsWebView *webview; +}; + +QmlGraphicsWebView *QmlGraphicsWebPage::viewItem() +{ + return static_cast<QmlGraphicsWebView*>(parent()); +} + +QObject *QmlGraphicsWebPage::createPlugin(const QString &, const QUrl &url, const QStringList ¶mNames, const QStringList ¶mValues) +{ + QUrl comp = qmlContext(viewItem())->resolvedUrl(url); + return new QWidget_Dummy_Plugin(comp,viewItem(),paramNames,paramValues); +} + +QWebPage *QmlGraphicsWebPage::createWindow(WebWindowType type) +{ + QmlGraphicsWebView *newView = viewItem()->createWindow(type); + if (newView) + return newView->page(); + return 0; +} + +QT_END_NAMESPACE + +#include <qmlgraphicswebview.moc> diff --git a/src/declarative/graphicsitems/qmlgraphicswebview_p.h b/src/declarative/graphicsitems/qmlgraphicswebview_p.h new file mode 100644 index 0000000..f5edb7a --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicswebview_p.h @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSWEBVIEW_H +#define QMLGRAPHICSWEBVIEW_H + +#include "qmlgraphicspainteditem_p.h" + +#include <QtGui/QAction> +#include <QtCore/QUrl> +#include <QtNetwork/qnetworkaccessmanager.h> +#include <QtWebKit/QWebPage> + +QT_BEGIN_HEADER + +class QWebHistory; +class QWebSettings; + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) +class QmlGraphicsWebViewPrivate; +class QNetworkRequest; +class QmlGraphicsWebView; + +class Q_DECLARATIVE_EXPORT QmlGraphicsWebPage : public QWebPage +{ + Q_OBJECT +public: + explicit QmlGraphicsWebPage(QmlGraphicsWebView *parent); + ~QmlGraphicsWebPage(); +protected: + QObject *createPlugin(const QString &classid, const QUrl &url, const QStringList ¶mNames, const QStringList ¶mValues); + QWebPage *createWindow(WebWindowType type); + void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID); + QString chooseFile(QWebFrame *originatingFrame, const QString& oldFile); + void javaScriptAlert(QWebFrame *originatingFrame, const QString& msg); + bool javaScriptConfirm(QWebFrame *originatingFrame, const QString& msg); + bool javaScriptPrompt(QWebFrame *originatingFrame, const QString& msg, const QString& defaultValue, QString* result); + +private: + QmlGraphicsWebView *viewItem(); +}; + + +class QmlGraphicsWebViewAttached; +class QmlGraphicsWebSettings; + +//### TODO: browser plugins + +class Q_DECLARATIVE_EXPORT QmlGraphicsWebView : public QmlGraphicsPaintedItem +{ + Q_OBJECT + + Q_ENUMS(Status SelectionMode) + + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(QPixmap icon READ icon NOTIFY iconChanged) + Q_PROPERTY(qreal zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) + Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged) + + Q_PROPERTY(QString html READ html WRITE setHtml) + + Q_PROPERTY(int pressGrabTime READ pressGrabTime WRITE setPressGrabTime) + + Q_PROPERTY(int preferredWidth READ preferredWidth WRITE setPreferredWidth NOTIFY preferredWidthChanged) + Q_PROPERTY(int preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged) + Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) + Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + + Q_PROPERTY(QAction* reload READ reloadAction CONSTANT) + Q_PROPERTY(QAction* back READ backAction CONSTANT) + Q_PROPERTY(QAction* forward READ forwardAction CONSTANT) + Q_PROPERTY(QAction* stop READ stopAction CONSTANT) + + Q_PROPERTY(QmlGraphicsWebSettings* settings READ settingsObject CONSTANT) + + Q_PROPERTY(QmlList<QObject *>* javaScriptWindowObjects READ javaScriptWindowObjects CONSTANT) + + Q_PROPERTY(QmlComponent* newWindowComponent READ newWindowComponent WRITE setNewWindowComponent) + Q_PROPERTY(QmlGraphicsItem* newWindowParent READ newWindowParent WRITE setNewWindowParent) + + Q_PROPERTY(bool renderingEnabled READ renderingEnabled WRITE setRenderingEnabled) + +public: + QmlGraphicsWebView(QmlGraphicsItem *parent=0); + ~QmlGraphicsWebView(); + + QUrl url() const; + void setUrl(const QUrl &); + + QString title() const; + + QPixmap icon() const; + + qreal zoomFactor() const; + void setZoomFactor(qreal); + Q_INVOKABLE bool heuristicZoom(int clickX, int clickY, qreal maxzoom); + QRect elementAreaAt(int x, int y, int minwidth, int minheight) const; + + int pressGrabTime() const; + void setPressGrabTime(int); + + int preferredWidth() const; + void setPreferredWidth(int); + int preferredHeight() const; + void setPreferredHeight(int); + + enum Status { Null, Ready, Loading, Error }; + Status status() const; + qreal progress() const; + QString statusText() const; + + QAction *reloadAction() const; + QAction *backAction() const; + QAction *forwardAction() const; + QAction *stopAction() const; + + QWebPage *page() const; + void setPage(QWebPage *page); + + void load(const QNetworkRequest &request, + QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, + const QByteArray &body = QByteArray()); + + QString html() const; + + void setHtml(const QString &html, const QUrl &baseUrl = QUrl()); + void setContent(const QByteArray &data, const QString &mimeType = QString(), const QUrl &baseUrl = QUrl()); + + QWebHistory *history() const; + QWebSettings *settings() const; + QmlGraphicsWebSettings *settingsObject() const; + + bool renderingEnabled() const; + void setRenderingEnabled(bool); + + QmlList<QObject *> *javaScriptWindowObjects(); + + static QmlGraphicsWebViewAttached *qmlAttachedProperties(QObject *); + + QmlComponent *newWindowComponent() const; + void setNewWindowComponent(QmlComponent *newWindow); + QmlGraphicsItem *newWindowParent() const; + void setNewWindowParent(QmlGraphicsItem *newWindow); + +Q_SIGNALS: + void preferredWidthChanged(); + void preferredHeightChanged(); + void urlChanged(); + void progressChanged(); + void statusChanged(Status); + void titleChanged(const QString&); + void iconChanged(); + void statusTextChanged(); + void zoomFactorChanged(); + + void loadStarted(); + void loadFinished(); + void loadFailed(); + + void doubleClick(int clickX, int clickY); + + void zoomTo(qreal zoom, int centerX, int centerY); + + void alert(const QString& message); + +public Q_SLOTS: + QVariant evaluateJavaScript(const QString&); + +private Q_SLOTS: + void expandToWebPage(); + void paintPage(const QRect&); + void doLoadStarted(); + void doLoadProgress(int p); + void doLoadFinished(bool ok); + void setStatusText(const QString&); + void windowObjectCleared(); + void pageUrlChanged(); + void noteContentsSizeChanged(const QSize&); + void initialLayout(); + +protected: + void drawContents(QPainter *, const QRect &); + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void timerEvent(QTimerEvent *event); + void hoverMoveEvent (QGraphicsSceneHoverEvent * event); + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + virtual void geometryChanged(const QRectF &newGeometry, + const QRectF &oldGeometry); + virtual void focusChanged(bool); + virtual bool sceneEvent(QEvent *event); + QmlGraphicsWebView *createWindow(QWebPage::WebWindowType type); + +private: + void init(); + virtual void componentComplete(); + Q_DISABLE_COPY(QmlGraphicsWebView) + Q_DECLARE_PRIVATE_D(QGraphicsItem::d_ptr.data(), QmlGraphicsWebView) + QMouseEvent *sceneMouseEventToMouseEvent(QGraphicsSceneMouseEvent *); + QMouseEvent *sceneHoverMoveEventToMouseEvent(QGraphicsSceneHoverEvent *); + friend class QmlGraphicsWebPage; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsWebView) +QML_DECLARE_TYPEINFO(QmlGraphicsWebView, QML_HAS_ATTACHED_PROPERTIES) +QML_DECLARE_TYPE(QAction) + +QT_END_HEADER + +#endif diff --git a/src/declarative/graphicsitems/qmlgraphicswebview_p_p.h b/src/declarative/graphicsitems/qmlgraphicswebview_p_p.h new file mode 100644 index 0000000..5659059 --- /dev/null +++ b/src/declarative/graphicsitems/qmlgraphicswebview_p_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QMLGRAPHICSWEBVIEW_P_H +#define QMLGRAPHICSWEBVIEW_P_H + +#include <qml.h> + +#include <QtWebKit/QWebPage> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QmlGraphicsWebSettings : public QObject { + Q_OBJECT + + Q_PROPERTY(QString standardFontFamily READ standardFontFamily WRITE setStandardFontFamily) + Q_PROPERTY(QString fixedFontFamily READ fixedFontFamily WRITE setFixedFontFamily) + Q_PROPERTY(QString serifFontFamily READ serifFontFamily WRITE setSerifFontFamily) + Q_PROPERTY(QString sansSerifFontFamily READ sansSerifFontFamily WRITE setSansSerifFontFamily) + Q_PROPERTY(QString cursiveFontFamily READ cursiveFontFamily WRITE setCursiveFontFamily) + Q_PROPERTY(QString fantasyFontFamily READ fantasyFontFamily WRITE setFantasyFontFamily) + + Q_PROPERTY(int minimumFontSize READ minimumFontSize WRITE setMinimumFontSize) + Q_PROPERTY(int minimumLogicalFontSize READ minimumLogicalFontSize WRITE setMinimumLogicalFontSize) + Q_PROPERTY(int defaultFontSize READ defaultFontSize WRITE setDefaultFontSize) + Q_PROPERTY(int defaultFixedFontSize READ defaultFixedFontSize WRITE setDefaultFixedFontSize) + + Q_PROPERTY(bool autoLoadImages READ autoLoadImages WRITE setAutoLoadImages) + Q_PROPERTY(bool javascriptEnabled READ javascriptEnabled WRITE setJavascriptEnabled) + Q_PROPERTY(bool javaEnabled READ javaEnabled WRITE setJavaEnabled) + Q_PROPERTY(bool pluginsEnabled READ pluginsEnabled WRITE setPluginsEnabled) + Q_PROPERTY(bool privateBrowsingEnabled READ privateBrowsingEnabled WRITE setPrivateBrowsingEnabled) + Q_PROPERTY(bool javascriptCanOpenWindows READ javascriptCanOpenWindows WRITE setJavascriptCanOpenWindows) + Q_PROPERTY(bool javascriptCanAccessClipboard READ javascriptCanAccessClipboard WRITE setJavascriptCanAccessClipboard) + Q_PROPERTY(bool developerExtrasEnabled READ developerExtrasEnabled WRITE setDeveloperExtrasEnabled) + Q_PROPERTY(bool linksIncludedInFocusChain READ linksIncludedInFocusChain WRITE setLinksIncludedInFocusChain) + Q_PROPERTY(bool zoomTextOnly READ zoomTextOnly WRITE setZoomTextOnly) + Q_PROPERTY(bool printElementBackgrounds READ printElementBackgrounds WRITE setPrintElementBackgrounds) + Q_PROPERTY(bool offlineStorageDatabaseEnabled READ offlineStorageDatabaseEnabled WRITE setOfflineStorageDatabaseEnabled) + Q_PROPERTY(bool offlineWebApplicationCacheEnabled READ offlineWebApplicationCacheEnabled WRITE setOfflineWebApplicationCacheEnabled) + Q_PROPERTY(bool localStorageDatabaseEnabled READ localStorageDatabaseEnabled WRITE setLocalStorageDatabaseEnabled) + Q_PROPERTY(bool localContentCanAccessRemoteUrls READ localContentCanAccessRemoteUrls WRITE setLocalContentCanAccessRemoteUrls) + +public: + QmlGraphicsWebSettings() {} + + QString standardFontFamily() const { return s->fontFamily(QWebSettings::StandardFont); } + void setStandardFontFamily(const QString& f) { s->setFontFamily(QWebSettings::StandardFont,f); } + QString fixedFontFamily() const { return s->fontFamily(QWebSettings::FixedFont); } + void setFixedFontFamily(const QString& f) { s->setFontFamily(QWebSettings::FixedFont,f); } + QString serifFontFamily() const { return s->fontFamily(QWebSettings::SerifFont); } + void setSerifFontFamily(const QString& f) { s->setFontFamily(QWebSettings::SerifFont,f); } + QString sansSerifFontFamily() const { return s->fontFamily(QWebSettings::SansSerifFont); } + void setSansSerifFontFamily(const QString& f) { s->setFontFamily(QWebSettings::SansSerifFont,f); } + QString cursiveFontFamily() const { return s->fontFamily(QWebSettings::CursiveFont); } + void setCursiveFontFamily(const QString& f) { s->setFontFamily(QWebSettings::CursiveFont,f); } + QString fantasyFontFamily() const { return s->fontFamily(QWebSettings::FantasyFont); } + void setFantasyFontFamily(const QString& f) { s->setFontFamily(QWebSettings::FantasyFont,f); } + + int minimumFontSize() const { return s->fontSize(QWebSettings::MinimumFontSize); } + void setMinimumFontSize(int size) { s->setFontSize(QWebSettings::MinimumFontSize,size); } + int minimumLogicalFontSize() const { return s->fontSize(QWebSettings::MinimumLogicalFontSize); } + void setMinimumLogicalFontSize(int size) { s->setFontSize(QWebSettings::MinimumLogicalFontSize,size); } + int defaultFontSize() const { return s->fontSize(QWebSettings::DefaultFontSize); } + void setDefaultFontSize(int size) { s->setFontSize(QWebSettings::DefaultFontSize,size); } + int defaultFixedFontSize() const { return s->fontSize(QWebSettings::DefaultFixedFontSize); } + void setDefaultFixedFontSize(int size) { s->setFontSize(QWebSettings::DefaultFixedFontSize,size); } + + bool autoLoadImages() const { return s->testAttribute(QWebSettings::AutoLoadImages); } + void setAutoLoadImages(bool on) { s->setAttribute(QWebSettings::AutoLoadImages, on); } + bool javascriptEnabled() const { return s->testAttribute(QWebSettings::JavascriptEnabled); } + void setJavascriptEnabled(bool on) { s->setAttribute(QWebSettings::JavascriptEnabled, on); } + bool javaEnabled() const { return s->testAttribute(QWebSettings::JavaEnabled); } + void setJavaEnabled(bool on) { s->setAttribute(QWebSettings::JavaEnabled, on); } + bool pluginsEnabled() const { return s->testAttribute(QWebSettings::PluginsEnabled); } + void setPluginsEnabled(bool on) { s->setAttribute(QWebSettings::PluginsEnabled, on); } + bool privateBrowsingEnabled() const { return s->testAttribute(QWebSettings::PrivateBrowsingEnabled); } + void setPrivateBrowsingEnabled(bool on) { s->setAttribute(QWebSettings::PrivateBrowsingEnabled, on); } + bool javascriptCanOpenWindows() const { return s->testAttribute(QWebSettings::JavascriptCanOpenWindows); } + void setJavascriptCanOpenWindows(bool on) { s->setAttribute(QWebSettings::JavascriptCanOpenWindows, on); } + bool javascriptCanAccessClipboard() const { return s->testAttribute(QWebSettings::JavascriptCanAccessClipboard); } + void setJavascriptCanAccessClipboard(bool on) { s->setAttribute(QWebSettings::JavascriptCanAccessClipboard, on); } + bool developerExtrasEnabled() const { return s->testAttribute(QWebSettings::DeveloperExtrasEnabled); } + void setDeveloperExtrasEnabled(bool on) { s->setAttribute(QWebSettings::DeveloperExtrasEnabled, on); } + bool linksIncludedInFocusChain() const { return s->testAttribute(QWebSettings::LinksIncludedInFocusChain); } + void setLinksIncludedInFocusChain(bool on) { s->setAttribute(QWebSettings::LinksIncludedInFocusChain, on); } + bool zoomTextOnly() const { return s->testAttribute(QWebSettings::ZoomTextOnly); } + void setZoomTextOnly(bool on) { s->setAttribute(QWebSettings::ZoomTextOnly, on); } + bool printElementBackgrounds() const { return s->testAttribute(QWebSettings::PrintElementBackgrounds); } + void setPrintElementBackgrounds(bool on) { s->setAttribute(QWebSettings::PrintElementBackgrounds, on); } + bool offlineStorageDatabaseEnabled() const { return s->testAttribute(QWebSettings::OfflineStorageDatabaseEnabled); } + void setOfflineStorageDatabaseEnabled(bool on) { s->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, on); } + bool offlineWebApplicationCacheEnabled() const { return s->testAttribute(QWebSettings::OfflineWebApplicationCacheEnabled); } + void setOfflineWebApplicationCacheEnabled(bool on) { s->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, on); } + bool localStorageDatabaseEnabled() const { return s->testAttribute(QWebSettings::LocalStorageDatabaseEnabled); } + void setLocalStorageDatabaseEnabled(bool on) { s->setAttribute(QWebSettings::LocalStorageDatabaseEnabled, on); } + bool localContentCanAccessRemoteUrls() const { return s->testAttribute(QWebSettings::LocalContentCanAccessRemoteUrls); } + void setLocalContentCanAccessRemoteUrls(bool on) { s->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, on); } + + QWebSettings *s; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QmlGraphicsWebSettings) + +QT_END_HEADER + +#endif |