/**************************************************************************** ** ** 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 #include #include "qml.h" #include "qmlstate.h" #include "qmlstategroup.h" #include "qmlstateoperations.h" #include "private/qfxperf_p.h" #include "qfxpositioners.h" #include "qfxpositioners_p.h" QT_BEGIN_NAMESPACE /*! \internal \class QFxBasePositioner \ingroup group_layouts \brief The QFxBasePositioner class provides a base for QFx layouts. To create a QFx Positioner, simply subclass QFxBasePositioner and implement doLayout(), which is automatically called when the layout might need updating. It is strongly recommended that in your implementation of doLayout() that you use the move, remove and add transitions when those conditions arise. You can use the applyAdd, applyMove and applyRemove functions to do this easily. Note also that the subclass is responsible for adding the spacing in between items. */ QFxBasePositioner::QFxBasePositioner(AutoUpdateType at, QFxItem *parent) : QFxItem(*(new QFxBasePositionerPrivate), parent) { Q_D(QFxBasePositioner); d->init(at); } QFxBasePositioner::QFxBasePositioner(QFxBasePositionerPrivate &dd, AutoUpdateType at, QFxItem *parent) : QFxItem(dd, parent) { Q_D(QFxBasePositioner); d->init(at); } int QFxBasePositioner::spacing() const { Q_D(const QFxBasePositioner); return d->_spacing; } void QFxBasePositioner::setSpacing(int s) { Q_D(QFxBasePositioner); if (s==d->_spacing) return; d->_spacing = s; prePositioning(); emit spacingChanged(); } QmlTransition *QFxBasePositioner::move() const { Q_D(const QFxBasePositioner); return d->moveTransition; } void QFxBasePositioner::setMove(QmlTransition *mt) { Q_D(QFxBasePositioner); d->moveTransition = mt; } QmlTransition *QFxBasePositioner::add() const { Q_D(const QFxBasePositioner); return d->addTransition; } void QFxBasePositioner::setAdd(QmlTransition *add) { Q_D(QFxBasePositioner); d->addTransition = add; } QmlTransition *QFxBasePositioner::remove() const { Q_D(const QFxBasePositioner); return d->removeTransition; } void QFxBasePositioner::setRemove(QmlTransition *remove) { Q_D(QFxBasePositioner); d->removeTransition = remove; } void QFxBasePositioner::componentComplete() { QFxItem::componentComplete(); #ifdef Q_ENABLE_PERFORMANCE_LOG QFxPerfTimer cc; #endif prePositioning(); } QVariant QFxBasePositioner::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemChildAddedChange || change == ItemChildRemovedChange) { prePositioning(); } return QFxItem::itemChange(change, value); } bool QFxBasePositioner::event(QEvent *e) { Q_D(QFxBasePositioner); if (e->type() == QEvent::User) { d->_ep = false; d->_stableItems += d->_newItems; d->_leavingItems.clear(); d->_newItems.clear(); return true; } return QFxItem::event(e); } /*! Items that have just been added to the positioner. This includes invisible items that have turned visible. */ QSet* QFxBasePositioner::newItems() { Q_D(QFxBasePositioner); return &d->_newItems; } /*! Items that are visible in the positioner, not including ones that have just been added. */ QSet* QFxBasePositioner::items() { Q_D(QFxBasePositioner); return &d->_stableItems; } /*! Items that have just left the positioner. This includes visible items that have turned invisible. */ QSet* QFxBasePositioner::leavingItems() { Q_D(QFxBasePositioner); return &d->_leavingItems; } void QFxBasePositioner::prePositioning() { Q_D(QFxBasePositioner); if (!isComponentComplete() || d->_movingItem) return; if (!d->_ep) { d->_ep = true; QCoreApplication::postEvent(this, new QEvent(QEvent::User)); } QSet allItems; QList children = childItems(); for (int ii = 0; ii < children.count(); ++ii) { QFxItem *child = qobject_cast(children.at(ii)); if (!child) continue; if (!d->_items.contains(child)){ QObject::connect(child, SIGNAL(visibleChanged()), this, SLOT(prePositioning())); QObject::connect(child, SIGNAL(opacityChanged()), this, SLOT(prePositioning())); QObject::connect(child, SIGNAL(heightChanged()), this, SLOT(prePositioning())); QObject::connect(child, SIGNAL(widthChanged()), this, SLOT(prePositioning())); d->_items += child; } if (child->opacity() == 0.0){ if (d->_stableItems.contains(child)){ d->_leavingItems += child; d->_stableItems -= child; } }else if (!d->_stableItems.contains(child)){ d->_newItems+=child; } allItems += child; } QSet deletedItems = d->_items - allItems; foreach(QFxItem *child, d->_items){ if (!allItems.contains(child)){ if (!deletedItems.contains(child)) { QObject::disconnect(child, SIGNAL(opacityChanged()), this, SLOT(prePositioning())); QObject::disconnect(child, SIGNAL(heightChanged()), this, SLOT(prePositioning())); QObject::disconnect(child, SIGNAL(widthChanged()), this, SLOT(prePositioning())); } d->_items -= child; } } d->_animated.clear(); doPositioning(); finishApplyTransitions(); //Set implicit size to the size of its children //###To keep this valid, do we need to update on pos change as well? qreal h = 0.0f; qreal w = 0.0f; foreach(QFxItem *child, d->_items){ if(!child->isVisible() || child->opacity() <= 0) continue; h = qMax(h, child->y() + child->height()); w = qMax(w, child->x() + child->width()); } setImplicitHeight(h); setImplicitWidth(w); } void QFxBasePositioner::applyTransition(const QList >& changes, QFxItem* target, QmlStateOperation::ActionList &actions) { Q_D(QFxBasePositioner); if (!target) return; for (int ii=0; ii_animated << target; } void QFxBasePositioner::finishApplyTransitions() { Q_D(QFxBasePositioner); // Note that if a transition is not set the transition manager will // apply the changes directly, in the case someone uses applyAdd/Move/Remove // without testing add()/move()/remove(). d->addTransitionManager.transition(d->addActions, d->addTransition); d->moveTransitionManager.transition(d->moveActions, d->moveTransition); d->removeTransitionManager.transition(d->removeActions, d->removeTransition); d->addActions.clear(); d->moveActions.clear(); d->removeActions.clear(); } void QFxBasePositioner::setMovingItem(QFxItem *i) { Q_D(QFxBasePositioner); d->_movingItem = i; } /*! Applies the positioner's add transition to the \a target item.\a changes is a list of property,value pairs which will be changed on the target using the add transition. */ void QFxBasePositioner::applyAdd(const QList >& changes, QFxItem* target) { Q_D(QFxBasePositioner); applyTransition(changes,target, d->addActions); } /*! Applies the positioner's move transition to the \a target.\a changes is a list of property,value pairs which will be changed on the target using the move transition. */ void QFxBasePositioner::applyMove(const QList >& changes, QFxItem* target) { Q_D(QFxBasePositioner); applyTransition(changes,target, d->moveActions); } /*! Applies the positioner's remove transition to the \a target item.\a changes is a list of property,value pairs which will be changed on the target using the remove transition. */ void QFxBasePositioner::applyRemove(const QList >& changes, QFxItem* target) { Q_D(QFxBasePositioner); applyTransition(changes,target, d->removeActions); } QML_DEFINE_TYPE(Qt,4,6,(QT_VERSION&0x00ff00)>>8,Column,QFxColumn) /*! \qmlclass Column QFxColumn \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::remove This property holds the transition to apply when removing an item from the positioner. The transition is only applied to the removed items. Removed can mean that either the object has been deleted or reparented, and thus is now longer a child of the positioner, or that the object has had its opacity set to zero, and thus is no longer visible. Note that if the item counts as removed because its opacity is zero it will not be visible during the transition unless you set the opacity in the transition, like in the below example. \table \row \o \image positioner-remove.gif \o \qml Column { remove: Transition { NumberAnimation { properties: "opacity" from: 1 to: 0 duration: 500 } } } \endqml \endtable */ /*! \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). 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. \table \row \o \image positioner-add.gif \o \qml Column { add: Transition { NumberAnimation { properties: "opacity" from: 0 to: 1 duration: 500 } } } \endqml \endtable */ /*! \qmlproperty Transition Column::move This property holds the transition to apply when moving an item within the positioner. 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 { properties: "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 QFxColumn \brief The QFxColumn class lines up items vertically. \ingroup group_positioners */ QFxColumn::QFxColumn(QFxItem *parent) : QFxBasePositioner(Vertical, parent) { } void QFxColumn::doPositioning() { int voffset = 0; foreach(QFxItem* item, *leavingItems()){ if (remove()){ QList > changes; applyRemove(changes, item); } } QList children = childItems(); for (int ii = 0; ii < children.count(); ++ii) { QFxItem *child = qobject_cast(children.at(ii)); if (!child || child->opacity() == 0.0) continue; bool needMove = (child->y() != voffset || child->x()); QList > changes; changes << qMakePair(QString(QLatin1String("y")),QVariant(voffset)); changes << qMakePair(QString(QLatin1String("x")),QVariant(0)); if (needMove && items()->contains(child) && move()) { applyMove(changes,child); } else if (!items()->contains(child) && add()) { applyAdd(changes,child); } else if (needMove) { setMovingItem(child); child->setY(voffset); setMovingItem(0); } voffset += child->height(); voffset += spacing(); } } QML_DEFINE_TYPE(Qt,4,6,(QT_VERSION&0x00ff00)>>8,Row,QFxRow) /*! \qmlclass Row QFxRow \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::remove This property holds the transition to apply when removing an item from the positioner. The transition will only be applied to the removed item(s). Removed can mean that either the object has been deleted or reparented, and thus is now longer a child of the positioner, or that the object has had its opacity set to zero, and thus is no longer visible. Note that if the item counts as removed because its opacity is zero it will not be visible during the transition unless you set the opacity in the transition, like in the below example. \qml Row { remove: Transition { NumberAnimation { properties: "opacity" from: 1 to: 0 duration: 500 } } } \endqml */ /*! \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). 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. \qml Row { add: Transition { NumberAnimation { properties: "opacity" from: 0 to: 1 duration: 500 } } } \endqml */ /*! \qmlproperty Transition Row::move This property holds the transition to apply when moving an item within the positioner. 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 { properties: "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 QFxRow \brief The QFxRow class lines up items horizontally. \ingroup group_positioners */ QFxRow::QFxRow(QFxItem *parent) : QFxBasePositioner(Horizontal, parent) { } void QFxRow::doPositioning() { int hoffset = 0; foreach(QFxItem* item, *leavingItems()){ if (remove()){ QList > changes; applyRemove(changes, item); } } QList children = childItems(); for (int ii = 0; ii < children.count(); ++ii) { QFxItem *child = qobject_cast(children.at(ii)); if (!child || child->opacity() == 0.0) continue; bool needMove = (child->x() != hoffset || child->y()); QList > changes; changes << qMakePair(QString(QLatin1String("x")),QVariant(hoffset)); changes << qMakePair(QString(QLatin1String("y")),QVariant(0)); if (needMove && items()->contains(child) && move()) { applyMove(changes,child); } else if (!items()->contains(child) && add()) { applyAdd(changes,child); } else if (needMove) { setMovingItem(child); child->setX(hoffset); setMovingItem(0); } if(child->width() && child->height()){//don't advance for invisible children hoffset += child->width(); hoffset += spacing(); } } } QML_DEFINE_TYPE(Qt,4,6,(QT_VERSION&0x00ff00)>>8,Grid,QFxGrid) /*! \qmlclass Grid QFxGrid \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::remove This property holds the transition to apply when removing an item from the positioner. The transition is only applied to the removed item(s). Removed can mean that either the object has been deleted or reparented, and thus is now longer a child of the positioner, or that the object has had its opacity set to zero, and thus is no longer visible. Note that if the item counts as removed because its opacity is zero it will not be visible during the transition unless you set the opacity in the transition, like in the below example. \qml Grid { remove: Transition { NumberAnimation { properties: "opacity" from: 1 to: 0 duration: 500 } } } \endqml */ /*! \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). 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. \qml Grid { add: Transition { NumberAnimation { properties: "opacity" from: 0 to: 1 duration: 500 } } } \endqml */ /*! \qmlproperty Transition Grid::move This property holds the transition to apply when moving an item within the positioner. This can happen when other items are added or removed from the positioner, or when items resize themselves. \qml Grid { move: Transition { NumberAnimation { properties: "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 QFxGrid \brief The QFxGrid class lays out items in a grid. \ingroup group_layouts */ QFxGrid::QFxGrid(QFxItem *parent) : QFxBasePositioner(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 QFxGrid::doPositioning() { int c=_columns,r=_rows;//Actual number of rows/columns int numVisible = items()->size() + newItems()->size(); 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 maxColWidth; QList maxRowHeight; int childIndex =0; QList children = childItems(); for (int i=0; i(children.at(childIndex++)); if (!child || child->opacity() == 0.0) continue; if (child->width() > maxColWidth[j]) maxColWidth[j] = child->width(); if (child->height() > maxRowHeight[i]) maxRowHeight[i] = child->height(); } } int xoffset=0; int yoffset=0; int curRow =0; int curCol =0; foreach(QFxItem* item, *leavingItems()){ if (remove()){ QList > changes; applyRemove(changes, item); } } foreach(QGraphicsItem* schild, children){ QFxItem *child = qobject_cast(schild); if (!child || child->opacity() == 0.0) continue; bool needMove = (child->x()!=xoffset)||(child->y()!=yoffset); QList > changes; changes << qMakePair(QString(QLatin1String("x")),QVariant(xoffset)); changes << qMakePair(QString(QLatin1String("y")),QVariant(yoffset)); if (newItems()->contains(child) && add()) { applyAdd(changes,child); } else if (needMove) { if (move()){ applyMove(changes,child); }else{ setMovingItem(child); child->setPos(QPointF(xoffset, yoffset)); setMovingItem(0); } } xoffset+=maxColWidth[curCol]+spacing(); curCol++; curCol%=c; if (!curCol){ yoffset+=maxRowHeight[curRow]+spacing(); xoffset=0; curRow++; if (curRow>=r) break; } } } QT_END_NAMESPACE