diff options
author | Jesus Sanchez-Palencia <jesus.palencia@openbossa.org> | 2009-05-28 18:28:23 (GMT) |
---|---|---|
committer | Eduardo M. Fleury <eduardo.fleury@openbossa.org> | 2009-07-22 18:03:57 (GMT) |
commit | b1bd07d163d335ab05b878b907965e43124d8da1 (patch) | |
tree | fe3cc90839501f138657ba6c892379d857288771 /src/gui | |
parent | 26a785e3c65a5f60bcf5bb2e07e58bc6b544dc30 (diff) | |
download | Qt-b1bd07d163d335ab05b878b907965e43124d8da1.zip Qt-b1bd07d163d335ab05b878b907965e43124d8da1.tar.gz Qt-b1bd07d163d335ab05b878b907965e43124d8da1.tar.bz2 |
QGraphicsAnchorLayout: Adding QGraphicsAnchorLayout public and private classes
This is a total re-written implementation of QGraphicsAnchorLayout using
a numerical approach. We use QGraph and QSimplex in order to achieve this.
This first commit gives us a just ready-to-use qgraphicslayout, but the
private class still need to inherit from qgraphicslayoutprivate. Optimizations
and documentation are all work in progress.
Signed-off-by: Anselmo Lacerda S. de Melo <anselmo.melo@openbossa.org>
Signed-off-by: Caio Marcelo de Oliveira Filho <caio.oliveira@openbossa.org>
Signed-off-by: Eduardo M. Fleury <eduardo.fleury@openbossa.org>
Signed-off-by: Jesus Sanchez-Palencia <jesus.palencia@openbossa.org>
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/graphicsview/graphicsview.pri | 8 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout.cpp | 235 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout.h | 109 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 1056 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout_p.h | 301 |
5 files changed, 1707 insertions, 2 deletions
diff --git a/src/gui/graphicsview/graphicsview.pri b/src/gui/graphicsview/graphicsview.pri index 49ba70b..6e32171 100644 --- a/src/gui/graphicsview/graphicsview.pri +++ b/src/gui/graphicsview/graphicsview.pri @@ -22,7 +22,9 @@ HEADERS += graphicsview/qgraphicsgridlayout.h \ graphicsview/qgraphicswidget_p.h \ graphicsview/qgridlayoutengine_p.h \ graphicsview/qgraph_p.h \ - graphicsview/qsimplex_p.h + graphicsview/qsimplex_p.h \ + graphicsview/qgraphicsanchorlayout_p.h \ + graphicsview/qgraphicsanchorlayout.h SOURCES += graphicsview/qgraphicsgridlayout.cpp \ graphicsview/qgraphicsitem.cpp \ @@ -42,4 +44,6 @@ SOURCES += graphicsview/qgraphicsgridlayout.cpp \ graphicsview/qgraphicswidget.cpp \ graphicsview/qgraphicswidget_p.cpp \ graphicsview/qgridlayoutengine.cpp \ - graphicsview/qsimplex_p.cpp + graphicsview/qsimplex_p.cpp \ + graphicsview/qgraphicsanchorlayout_p.cpp \ + graphicsview/qgraphicsanchorlayout.cpp diff --git a/src/gui/graphicsview/qgraphicsanchorlayout.cpp b/src/gui/graphicsview/qgraphicsanchorlayout.cpp new file mode 100644 index 0000000..d737e36 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui 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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgraphicsanchorlayout_p.h" + +QGraphicsAnchorLayout::QGraphicsAnchorLayout(QGraphicsLayoutItem *parent) + : QGraphicsLayout(parent), d_ptr(new QGraphicsAnchorLayoutPrivate()) +{ + // ### REMOVE THAT + d_ptr->q_ptr = this; + + d_ptr->createLayoutEdges(); +} + +QGraphicsAnchorLayout::~QGraphicsAnchorLayout() +{ + Q_D(QGraphicsAnchorLayout); + + for (int i = count() - 1; i >= 0; --i) { + QGraphicsLayoutItem *item = d->items.at(i); + removeAt(i); + if (item) { + if (item->ownedByLayout()) + delete item; + } + } +} + +void QGraphicsAnchorLayout::anchor(QGraphicsLayoutItem *firstItem, + Edge firstEdge, + QGraphicsLayoutItem *secondItem, + Edge secondEdge, qreal spacing) +{ + Q_D(QGraphicsAnchorLayout); + if ((firstItem == 0) || (secondItem == 0)) { + qWarning("QGraphicsAnchorLayout::anchor: " + "Cannot anchor NULL items"); + return; + } + + if (firstItem == secondItem) { + qWarning("QGraphicsAnchorLayout::anchor: " + "Cannot anchor the item to itself"); + return; + } + + if (d->edgeOrientation(secondEdge) != d->edgeOrientation(firstEdge)) { + qWarning("QGraphicsAnchorLayout::anchor: " + "Cannot anchor edges of different orientations"); + return; + } + + // In QGraphicsAnchorLayout, items are represented in its internal + // graph as four anchors that connect: + // - Left -> HCenter + // - HCenter-> Right + // - Top -> VCenter + // - VCenter -> Bottom + + // Ensure that the internal anchors have been created for both items. + if (firstItem != this && !d->items.contains(firstItem)) { + d->createItemEdges(firstItem); + d->addChildItem(firstItem); + } + if (secondItem != this && !d->items.contains(secondItem)) { + d->createItemEdges(secondItem); + d->addChildItem(secondItem); + } + + // Use heuristics to find out what the user meant with this anchor. + d->correctEdgeDirection(firstItem, firstEdge, secondItem, secondEdge); + + // Create actual anchor between firstItem and secondItem. + AnchorData *data; + if (spacing >= 0) { + data = new AnchorData(spacing); + d->addAnchor(firstItem, firstEdge, secondItem, secondEdge, data); + } else { + data = new AnchorData(-spacing); + d->addAnchor(secondItem, secondEdge, firstItem, firstEdge, data); + } + + invalidate(); +} + +void QGraphicsAnchorLayout::removeAnchor(QGraphicsLayoutItem *firstItem, Edge firstEdge, + QGraphicsLayoutItem *secondItem, Edge secondEdge) +{ + Q_D(QGraphicsAnchorLayout); + if ((firstItem == 0) || (secondItem == 0)) { + qWarning("QGraphicsAnchorLayout::removeAnchor: " + "Cannot remove anchor between NULL items"); + return; + } + + if (firstItem == secondItem) { + qWarning("QGraphicsAnchorLayout::removeAnchor: " + "Cannot remove anchor from the item to itself"); + return; + } + + if (d->edgeOrientation(secondEdge) != d->edgeOrientation(firstEdge)) { + qWarning("QGraphicsAnchorLayout::removeAnchor: " + "Cannot remove anchor from edges of different orientations"); + return; + } + + d->removeAnchor(firstItem, firstEdge, secondItem, secondEdge); + + invalidate(); +} + +void QGraphicsAnchorLayout::setGeometry(const QRectF &geom) +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayout::setGeometry(geom); + d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Horizontal); + d->calculateVertexPositions(QGraphicsAnchorLayoutPrivate::Vertical); + d->setItemsGeometries(); +} + +void QGraphicsAnchorLayout::removeAt(int index) +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayoutItem *item = d->items.value(index); + + if (item) { + d->items.remove(index); + d->removeAnchors(item); + item->setParentLayoutItem(0); + } +} + +int QGraphicsAnchorLayout::count() const +{ + Q_D(const QGraphicsAnchorLayout); + return d->items.size(); +} + +QGraphicsLayoutItem *QGraphicsAnchorLayout::itemAt(int index) const +{ + Q_D(const QGraphicsAnchorLayout); + return d->items.value(index); +} + +void QGraphicsAnchorLayout::invalidate() +{ + Q_D(QGraphicsAnchorLayout); + QGraphicsLayout::invalidate(); + d->calculateGraphCacheDirty = 1; +} + +QSizeF QGraphicsAnchorLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const +{ + Q_UNUSED(which); + Q_UNUSED(constraint); + Q_D(const QGraphicsAnchorLayout); + + // Some setup calculations are delayed until the information is + // actually needed, avoiding unnecessary recalculations when + // adding multiple anchors. + + // sizeHint() / effectiveSizeHint() already have a cache + // mechanism, using invalidate() to force recalculation. However + // sizeHint() is called three times after invalidation (for max, + // min and pref), but we just need do our setup once. + + const_cast<QGraphicsAnchorLayoutPrivate *>(d)->calculateGraphs(); + + // ### apply constraint! + QSizeF engineSizeHint( + d->sizeHints[QGraphicsAnchorLayoutPrivate::Horizontal][which], + d->sizeHints[QGraphicsAnchorLayoutPrivate::Vertical][which]); + + qreal left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + + return engineSizeHint + QSizeF(left + right, top + bottom); +} + +//////// DEBUG ///////// +#include <QFile> +void QGraphicsAnchorLayout::dumpGraph() +{ + Q_D(QGraphicsAnchorLayout); + + QFile file(QString::fromAscii("anchorlayout.dot")); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + qWarning("Could not write to %s", file.fileName().toLocal8Bit().constData()); + + QString dotContents = d->graph[0].serializeToDot(); + file.write(dotContents.toLocal8Bit()); + dotContents = d->graph[1].serializeToDot(); + file.write(dotContents.toLocal8Bit()); + + file.close(); +} diff --git a/src/gui/graphicsview/qgraphicsanchorlayout.h b/src/gui/graphicsview/qgraphicsanchorlayout.h new file mode 100644 index 0000000..288aec1 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui 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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGRAPHICSANCHORLAYOUT_H +#define QGRAPHICSANCHORLAYOUT_H + +#include <QtGui/qgraphicsitem.h> +#include <QtGui/qgraphicslayout.h> + +/* +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +#if !defined(QT_NO_GRAPHICSVIEW) || (QT_EDITION & QT_MODULE_GRAPHICSVIEW) != QT_MODULE_GRAPHICSVIEW +*/ + +class QGraphicsAnchorLayoutPrivate; + +class Q_GUI_EXPORT QGraphicsAnchorLayout : public QGraphicsLayout +{ +public: + enum Edge { + Left = 0, + HCenter, + Right, + Top, + VCenter, + Bottom + }; + + QGraphicsAnchorLayout(QGraphicsLayoutItem *parent = 0); + virtual ~QGraphicsAnchorLayout(); + + void anchor(QGraphicsLayoutItem *firstItem, Edge firstEdge, + QGraphicsLayoutItem *secondItem, Edge secondEdge, + qreal spacing = 0); + + void removeAnchor(QGraphicsLayoutItem *firstItem, Edge firstEdge, + QGraphicsLayoutItem *secondItem, Edge secondEdge); + + void removeAt(int index); + void setGeometry(const QRectF &rect); + int count() const; + QGraphicsLayoutItem *itemAt(int index) const; + + void invalidate(); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + + ///////// DEBUG ///////// + void dumpGraph(); + QGraphicsAnchorLayoutPrivate *d_ptr; //### TO REMOVE +protected: + +private: + +// Q_DISABLE_COPY(QGraphicsAnchorLayout) //### TO UNCOMMENT + Q_DECLARE_PRIVATE(QGraphicsAnchorLayout) +}; + +/* +#endif + +QT_END_NAMESPACE + +QT_END_HEADER +*/ + +#endif diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp new file mode 100644 index 0000000..84d1c31 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -0,0 +1,1056 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui 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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QWidget> +#include <QLinkedList> + +#include "qgraphicsanchorlayout_p.h" + +QSimplexConstraint *GraphPath::constraint(const GraphPath &path) const +{ + // Calculate + QSet<AnchorData *> cPositives; + QSet<AnchorData *> cNegatives; + QSet<AnchorData *> intersection; + + cPositives = positives + path.negatives; + cNegatives = negatives + path.positives; + + intersection = cPositives & cNegatives; + + cPositives -= intersection; + cNegatives -= intersection; + + // Fill + QSimplexConstraint *c = new QSimplexConstraint; + QSet<AnchorData *>::iterator i; + for (i = cPositives.begin(); i != cPositives.end(); ++i) + c->variables.insert(*i, 1.0); + + for (i = cNegatives.begin(); i != cNegatives.end(); ++i) + c->variables.insert(*i, -1.0); + + return c; +} + +QString GraphPath::toString() const +{ + QString string("Path: "); + foreach(AnchorData *edge, positives) + string += QString(" (+++) %1").arg(edge->toString()); + + foreach(AnchorData *edge, negatives) + string += QString(" (---) %1").arg(edge->toString()); + + return string; +} + + +QGraphicsAnchorLayoutPrivate::QGraphicsAnchorLayoutPrivate() + : calculateGraphCacheDirty(1) +{ +} + +QGraphicsAnchorLayout::Edge QGraphicsAnchorLayoutPrivate::oppositeEdge( + QGraphicsAnchorLayout::Edge edge) +{ + switch (edge) { + case QGraphicsAnchorLayout::Left: + edge = QGraphicsAnchorLayout::Right; + break; + case QGraphicsAnchorLayout::Right: + edge = QGraphicsAnchorLayout::Left; + break; + case QGraphicsAnchorLayout::Top: + edge = QGraphicsAnchorLayout::Bottom; + break; + case QGraphicsAnchorLayout::Bottom: + edge = QGraphicsAnchorLayout::Top; + break; + default: + break; + } + return edge; +} + +QGraphicsAnchorLayoutPrivate::Orientation +QGraphicsAnchorLayoutPrivate::edgeOrientation(QGraphicsAnchorLayout::Edge edge) +{ + return edge > QGraphicsAnchorLayout::Right ? Vertical : Horizontal; +} + +/*! + \internal + + Create two internal anchors to connect the layout edges and its + central anchorage point. + These anchors doesn't have size restrictions other than the fact they + should always have the same size, which is something we enforce later, + when creating restrictions for the Simplex solver. +*/ +void QGraphicsAnchorLayoutPrivate::createLayoutEdges() +{ + Q_Q(QGraphicsAnchorLayout); + QGraphicsLayoutItem *layout = q; + + // Horizontal + QSimplexConstraint *c = new QSimplexConstraint; + AnchorData *data = new AnchorData(0, 0, QWIDGETSIZE_MAX); + addAnchor(layout, QGraphicsAnchorLayout::Left, layout, + QGraphicsAnchorLayout::HCenter, data); + data->skipInPreferred = 1; + c->variables.insert(data, 1.0); + + data = new AnchorData(0, 0, QWIDGETSIZE_MAX); + addAnchor(layout, QGraphicsAnchorLayout::HCenter, + layout, QGraphicsAnchorLayout::Right, data); + data->skipInPreferred = 1; + c->variables.insert(data, -1.0); + + itemCenterConstraints[Horizontal].append(c); + + // Set the Layout Left edge as the root of the horizontal graph. + AnchorVertex *v; + v = m_vertexList.value(qMakePair(layout, QGraphicsAnchorLayout::Left)); + graph[Horizontal].setRootVertex(v); + + // Vertical + c = new QSimplexConstraint; + data = new AnchorData(0, 0, QWIDGETSIZE_MAX); + addAnchor(layout, QGraphicsAnchorLayout::Top, layout, + QGraphicsAnchorLayout::VCenter, data); + c->variables.insert(data, 1.0); + + data = new AnchorData(0, 0, QWIDGETSIZE_MAX); + addAnchor(layout, QGraphicsAnchorLayout::VCenter, + layout, QGraphicsAnchorLayout::Bottom, data); + c->variables.insert(data, -1.0); + + itemCenterConstraints[Vertical].append(c); + + // Set the Layout Top edge as the root of the vertical graph. + v = m_vertexList.value(qMakePair(layout, QGraphicsAnchorLayout::Top)); + graph[Vertical].setRootVertex(v); +} + +void QGraphicsAnchorLayoutPrivate::createItemEdges(QGraphicsLayoutItem *item) +{ + items.append(item); + + // Horizontal + int minimumSize = item->minimumWidth() / 2; + int preferredSize = item->preferredWidth() / 2; + int maximumSize = item->maximumWidth() / 2; + + QSimplexConstraint *c = new QSimplexConstraint; + + AnchorData *data = new AnchorData(minimumSize, preferredSize, maximumSize); + addAnchor(item, QGraphicsAnchorLayout::Left, item, + QGraphicsAnchorLayout::HCenter, data); + c->variables.insert(data, 1.0); + + data = new AnchorData(minimumSize, preferredSize, maximumSize); + addAnchor(item, QGraphicsAnchorLayout::HCenter, + item, QGraphicsAnchorLayout::Right, data); + c->variables.insert(data, -1.0); + + itemCenterConstraints[Horizontal].append(c); + + // Vertical + minimumSize = item->minimumHeight() / 2; + preferredSize = item->preferredHeight() / 2; + maximumSize = item->maximumHeight() / 2; + + c = new QSimplexConstraint; + + data = new AnchorData(minimumSize, preferredSize, maximumSize); + addAnchor(item, QGraphicsAnchorLayout::Top, item, + QGraphicsAnchorLayout::VCenter, data); + c->variables.insert(data, 1.0); + + data = new AnchorData(minimumSize, preferredSize, maximumSize); + addAnchor(item, QGraphicsAnchorLayout::VCenter, + item, QGraphicsAnchorLayout::Bottom, data); + c->variables.insert(data, -1.0); + + itemCenterConstraints[Vertical].append(c); +} + +void QGraphicsAnchorLayoutPrivate::addAnchor(QGraphicsLayoutItem *firstItem, + QGraphicsAnchorLayout::Edge firstEdge, + QGraphicsLayoutItem *secondItem, + QGraphicsAnchorLayout::Edge secondEdge, + AnchorData *data) +{ + AnchorVertex *v1; + AnchorVertex *v2; + + // Is the Vertex (firstItem, firstEdge) already represented in our + // internal structure? + v1 = m_vertexList.value(qMakePair(firstItem, firstEdge)); + if (!v1) { + v1 = new AnchorVertex(firstItem, firstEdge); + m_vertexList.insert(qMakePair(firstItem, firstEdge), v1); + } + + // The same for the second vertex + v2 = m_vertexList.value(qMakePair(secondItem, secondEdge)); + if (!v2) { + v2 = new AnchorVertex(secondItem, secondEdge); + m_vertexList.insert(qMakePair(secondItem, secondEdge), v2); + } + + // Create a bi-directional edge in the sense it can be transversed both + // from v1 or v2. "data" however is shared between the two references + // so we still know that the anchor direction is from 1 to 2. + data->origin = v1; + data->name = QString("%1 --to--> %2").arg(v1->toString()).arg(v2->toString()); + + graph[edgeOrientation(firstEdge)].createEdge(v1, v2, data); +} + +void QGraphicsAnchorLayoutPrivate::removeAnchor(QGraphicsLayoutItem *firstItem, + QGraphicsAnchorLayout::Edge firstEdge, + QGraphicsLayoutItem *secondItem, + QGraphicsAnchorLayout::Edge secondEdge) +{ + AnchorVertex *v1 = 0; + AnchorVertex *v2 = 0; + + // Is there a representation for the Vertex (firstItem, firstEdge) + // in our internal structure? + if ((v1 = m_vertexList.value(qMakePair(firstItem,firstEdge)))) + m_vertexList.remove(qMakePair(firstItem,firstEdge)); + else + qWarning()<<"This item with this edge is not in the graph"; + + // The same for the second vertex + if ((v2 = m_vertexList.value(qMakePair(secondItem,secondEdge)))) + m_vertexList.remove(qMakePair(secondItem,secondEdge)); + else + qWarning()<<"This item with this edge is not in the graph"; + + if (v1 && v2) { + graph[edgeOrientation(firstEdge)].removeEdge(v1, v2); + } +} + +void QGraphicsAnchorLayoutPrivate::removeAnchors(QGraphicsLayoutItem *item) +{ + AnchorVertex *v1 = 0; + AnchorVertex *v2 = 0; + QList<AnchorVertex *> allVertex; + int edge; + + for (edge = QGraphicsAnchorLayout::Left; edge != QGraphicsAnchorLayout::Bottom; ++edge) { + // Remove all vertex for all edges + QGraphicsAnchorLayout::Edge e = static_cast<QGraphicsAnchorLayout::Edge>(edge); + + if ((v1 = m_vertexList.value(qMakePair(item, e)))) { + m_vertexList.remove(qMakePair(item, e)); + + // Remove all edges + allVertex = graph[edgeOrientation(e)].adjacentVertices(v1); + + foreach (v2, allVertex) + graph[edgeOrientation(e)].removeEdge(v1, v2); + } + } +} + +/*! + \internal + + Use heuristics to determine the correct orientation of a given anchor. + + After API discussions, we decided we would like expressions like + anchor(A, Left, B, Right) to mean the same as anchor(B, Right, A, Left). + The problem with this is that anchors could become ambiguous, for + instance, what does the anchor A, B of size X mean? + + "pos(B) = pos(A) + X" or "pos(A) = pos(B) + X" ? + + To keep the API user friendly and at the same time, keep our algorithm + deterministic, we use an heuristic to determine a direction for each + added anchor and then keep it. The heuristic is based on the fact + that people usually avoid overlapping items, therefore: + + "A, RIGHT to B, LEFT" means that B is to the LEFT of A. + "B, LEFT to A, RIGHT" is corrected to the above anchor. + + Special correction is also applied when one of the items is the + layout. We handle Layout Left as if it was another items's Right + and Layout Right as another item's Left. +*/ +void QGraphicsAnchorLayoutPrivate::correctEdgeDirection(QGraphicsLayoutItem *&firstItem, + QGraphicsAnchorLayout::Edge &firstEdge, + QGraphicsLayoutItem *&secondItem, + QGraphicsAnchorLayout::Edge &secondEdge) +{ + Q_Q(QGraphicsAnchorLayout); + + QGraphicsAnchorLayout::Edge effectiveFirst = firstEdge; + QGraphicsAnchorLayout::Edge effectiveSecond = secondEdge; + + if (firstItem == q) + effectiveFirst = QGraphicsAnchorLayoutPrivate::oppositeEdge(firstEdge); + if (secondItem == q) + effectiveSecond = QGraphicsAnchorLayoutPrivate::oppositeEdge(secondEdge); + + if (effectiveFirst < effectiveSecond) { + + // ### DEBUG + /* printf("Swapping Anchor from %s %d --to--> %s %d\n", + firstItem->isLayout() ? "<layout>" : + qPrintable(static_cast<QGraphicsWidget *>(firstItem)->data(0).toString()), + firstEdge, + secondItem->isLayout() ? "<layout>" : + qPrintable(static_cast<QGraphicsWidget *>(secondItem)->data(0).toString()), + secondEdge); + */ + qSwap(firstItem, secondItem); + qSwap(firstEdge, secondEdge); + } +} + +/*! + \internal + + XXX: REMOVE THIS ONCE WE INHERIT SOMEONE ELSE! +*/ +QGraphicsItem *QGraphicsAnchorLayoutPrivate::parentItem() const +{ + Q_Q(const QGraphicsLayoutItem); + + const QGraphicsLayoutItem *parent = q; + while (parent && parent->isLayout()) { + parent = parent->parentLayoutItem(); + } + return parent ? parent->graphicsItem() : 0; +} + +/*! + \internal +*/ +void QGraphicsAnchorLayoutPrivate::addChildItem(QGraphicsLayoutItem *child) +{ + // XXX: Re-implement this!! + if (child) { + Q_Q(QGraphicsAnchorLayout); + child->setParentLayoutItem(q); + + child->graphicsItem()->setParentItem(parentItem()); + } +} + +/*! + \internal + + Called on activation. Uses Linear Programming to define minimum, preferred + and maximum sizes for the layout. Also calculates the sizes that each item + should assume when the layout is in one of such situations. +*/ +void QGraphicsAnchorLayoutPrivate::calculateGraphs() +{ + if (!calculateGraphCacheDirty) + return; + + calculateGraphs(Horizontal); + calculateGraphs(Vertical); + + calculateGraphCacheDirty = 0; +} + +// ### remove me: +QList<AnchorData *> getVariables(QList<QSimplexConstraint *> constraints) +{ + QSet<AnchorData *> variableSet; + for (int i = 0; i < constraints.count(); ++i) { + const QSimplexConstraint *c = constraints[i]; + foreach (QSimplexVariable *var, c->variables.keys()) { + variableSet += static_cast<AnchorData *>(var); + } + } + return variableSet.toList(); +} + +void QGraphicsAnchorLayoutPrivate::calculateGraphs( + QGraphicsAnchorLayoutPrivate::Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + // Reset the nominal sizes of each anchor based on the current item sizes + setAnchorSizeHintsFromItems(orientation); + + // Traverse all graph edges and store the possible paths to each vertex + findPaths(orientation); + + // From the paths calculated above, extract the constraints that the current + // anchor setup impose, to our Linear Programming problem. + constraintsFromPaths(orientation); + + // Split the constraints and anchors into groups that should be fed to the + // simplex solver independently. Currently we find two groups: + // + // 1) The "trunk", that is, the set of anchors (items) that are connected + // to the two opposite sides of our layout, and thus need to stretch in + // order to fit in the current layout size. + // + // 2) The floating or semi-floating anchors (items) that are those which + // are connected to only one (or none) of the layout sides, thus are not + // influenced by the layout size. + QList<QList<QSimplexConstraint *> > parts; + parts = getGraphParts(orientation); + + // Now run the simplex solver to calculate Minimum, Preferred and Maximum sizes + // of the "trunk" set of constraints and variables. + // ### does trunk always exist? empty = trunk is the layout left->center->right + QList<QSimplexConstraint *> trunkConstraints = parts[0]; + + // For minimum and maximum, use the path between the two layout sides as the + // objective function. + + // Retrieve that path + QGraphicsAnchorLayout::Edge end; + if (orientation == Horizontal) { + end = QGraphicsAnchorLayout::Right; + } else { + end = QGraphicsAnchorLayout::Bottom; + } + AnchorVertex *v = + m_vertexList.value(qMakePair(static_cast<QGraphicsLayoutItem *>(q), end)); + GraphPath trunkPath = graphPaths[orientation].value(v); + + // Solve min and max size hints for trunk + QPair<qreal, qreal> minMax = solveMinMax(trunkConstraints, trunkPath); + sizeHints[orientation][Qt::MinimumSize] = minMax.first; + sizeHints[orientation][Qt::MaximumSize] = minMax.second; + + // Solve for preferred. The objective function is calculated from the constraints + // and variables internally. + solvePreferred(trunkConstraints); + + // Calculate and set the preferred size for the layout from the edge sizes that + // were calculated above. + qreal pref(0.0); + foreach (const AnchorData *ad, trunkPath.positives) { + pref += ad->sizeAtPreferred; + } + foreach (const AnchorData *ad, trunkPath.negatives) { + pref -= ad->sizeAtPreferred; + } + sizeHints[orientation][Qt::PreferredSize] = pref; + + // For the other parts that not the trunk, solve only for the preferred size + // that is the size they will remain at, since they are not stretched by the + // layout. + + // Solve the other only for preferred, skip trunk + for (int i = 1; i < parts.count(); ++i) { + QList<QSimplexConstraint *> partConstraints = parts[i]; + QList<AnchorData *> partVariables = getVariables(partConstraints); + + // ### + if (partVariables.isEmpty()) + continue; + + solvePreferred(partConstraints); + + // Propagate size at preferred to other sizes. Semi-floats + // always will be in their sizeAtPreferred. + for (int j = 0; j < partVariables.count(); ++j) { + AnchorData *ad = static_cast<AnchorData *>(partVariables[j]); + ad->sizeAtMinimum = ad->sizeAtPreferred; + ad->sizeAtMaximum = ad->sizeAtPreferred; + } + } + + // Clean up our data structures. They are not needed anymore since + // distribution uses just interpolation. + qDeleteAll(constraints[orientation]); + constraints[orientation].clear(); + graphPaths[orientation].clear(); // ### +} + +void QGraphicsAnchorLayoutPrivate::setAnchorSizeHintsFromItems(Orientation orientation) +{ + QPair<QGraphicsLayoutItem *, QGraphicsAnchorLayout::Edge> beginningKey; + QPair<QGraphicsLayoutItem *, QGraphicsAnchorLayout::Edge> centerKey; + QPair<QGraphicsLayoutItem *, QGraphicsAnchorLayout::Edge> endKey; + + if (orientation == Horizontal) { + beginningKey.second = QGraphicsAnchorLayout::Left; + centerKey.second = QGraphicsAnchorLayout::HCenter; + endKey.second = QGraphicsAnchorLayout::Right; + } else { + beginningKey.second = QGraphicsAnchorLayout::Top; + centerKey.second = QGraphicsAnchorLayout::VCenter; + endKey.second = QGraphicsAnchorLayout::Bottom; + } + + foreach (QGraphicsLayoutItem *item, items) { + AnchorVertex *beginning, *center, *end; + qreal min, pref, max; + + beginningKey.first = item; + centerKey.first = item; + endKey.first = item; + + beginning = m_vertexList.value(beginningKey); + center = m_vertexList.value(centerKey); + end = m_vertexList.value(endKey); + + if (orientation == Horizontal) { + min = item->minimumWidth(); + pref = item->preferredWidth(); + max = item->maximumWidth(); + } else { + min = item->minimumHeight(); + pref = item->preferredHeight(); + max = item->maximumHeight(); + } + + // To support items that are represented by a single anchor as well as + // those that have been divided into two halfs, we must do this check. + AnchorData *data; + if (center == 0) { + data = graph[orientation].edgeData(beginning, end); + // Set the anchor nominal sizes to those of the corresponding item + data->minSize = min; + data->prefSize = pref; + data->maxSize = max; + + // Set the anchor effective sizes to preferred. + // Note: The idea here is that all items should remain at + // their preferred size unless where that's impossible. + // In cases where the item is subject to restrictions + // (anchored to the layout edges, for instance), the + // simplex solver will be run to recalculate and override + // the values we set here. + data->sizeAtMinimum = pref; + data->sizeAtPreferred = pref; + data->sizeAtMaximum = pref; + } else { + min = min / 2; + pref = pref / 2; + max = max / 2; + + // Same as above, for each half + data = graph[orientation].edgeData(beginning, center); + data->minSize = min; + data->prefSize = pref; + data->maxSize = max; + data->sizeAtMinimum = pref; + data->sizeAtPreferred = pref; + data->sizeAtMaximum = pref; + + data = graph[orientation].edgeData(center, end); + data->minSize = min; + data->prefSize = pref; + data->maxSize = max; + data->sizeAtMinimum = pref; + data->sizeAtPreferred = pref; + data->sizeAtMaximum = pref; + } + } +} + +/*! + \internal + + This method walks the graph using a breadth-first search to find paths + between the root vertex and each vertex on the graph. The edges + directions in each path are considered and they are stored as a + positive edge (left-to-right) or negative edge (right-to-left). + + The list of paths is used later to generate a list of constraints. + */ +void QGraphicsAnchorLayoutPrivate::findPaths(Orientation orientation) +{ + QQueue<QPair<AnchorVertex *, AnchorVertex *> > queue; + + QSet<AnchorData *> visited; + + bool ok; + AnchorVertex *root = graph[orientation].firstVertex(&ok); + + graphPaths[orientation].insert(root, GraphPath()); + + foreach (AnchorVertex *v, graph[orientation].adjacentVertices(root)) { + queue.enqueue(qMakePair(root, v)); + } + + while(!queue.isEmpty()) { + QPair<AnchorVertex *, AnchorVertex *> pair = queue.dequeue(); + AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second); + + if (visited.contains(edge)) + continue; + + visited.insert(edge); + GraphPath current = graphPaths[orientation].value(pair.first); + + if (edge->origin == pair.first) + current.positives.insert(edge); + else + current.negatives.insert(edge); + + graphPaths[orientation].insert(pair.second, current); + + foreach (AnchorVertex *v, + graph[orientation].adjacentVertices(pair.second)) { + queue.enqueue(qMakePair(pair.second, v)); + } + } +} + +/*! + \internal + + Each vertex on the graph that has more than one path to it + represents a contra int to the sizes of the items in these paths. + + This method walks the list of paths to each vertex, generate + the constraints and store them in a list so they can be used later + by the Simplex solver. +*/ +void QGraphicsAnchorLayoutPrivate::constraintsFromPaths(Orientation orientation) +{ + foreach (AnchorVertex *vertex, graphPaths[orientation].uniqueKeys()) + { + int valueCount = graphPaths[orientation].count(vertex); + if (valueCount == 1) + continue; + + QList<GraphPath> pathsToVertex = graphPaths[orientation].values(vertex); + for (int i = 1; i < valueCount; ++i) { + constraints[orientation] += \ + pathsToVertex[0].constraint(pathsToVertex[i]); + } + } +} + +/*! + \Internal +*/ +QList< QList<QSimplexConstraint *> > +QGraphicsAnchorLayoutPrivate::getGraphParts(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + // Find layout vertices and edges for the current orientation. + AnchorVertex *layoutFirstVertex = + m_vertexList.value(qMakePair(static_cast<QGraphicsLayoutItem *>(q), + orientation == Horizontal ? + QGraphicsAnchorLayout::Left :QGraphicsAnchorLayout::Top)); + + AnchorVertex *layoutCentralVertex = + m_vertexList.value(qMakePair(static_cast<QGraphicsLayoutItem *>(q), + orientation == Horizontal ? + QGraphicsAnchorLayout::HCenter : QGraphicsAnchorLayout::VCenter)); + + AnchorVertex *layoutLastVertex = + m_vertexList.value(qMakePair(static_cast<QGraphicsLayoutItem *>(q), + orientation == Horizontal ? + QGraphicsAnchorLayout::Right : QGraphicsAnchorLayout::Bottom)); + + AnchorData *edgeL1 = graph[orientation].edgeData(layoutFirstVertex, layoutCentralVertex); + AnchorData *edgeL2 = graph[orientation].edgeData(layoutCentralVertex, layoutLastVertex); + + QLinkedList<QSimplexConstraint *> remainingConstraints; + for (int i = 0; i < constraints[orientation].count(); ++i) { + remainingConstraints += constraints[orientation][i]; + } + for (int i = 0; i < itemCenterConstraints[orientation].count(); ++i) { + remainingConstraints += itemCenterConstraints[orientation][i]; + } + + QList<QSimplexConstraint *> trunkConstraints; + QSet<QSimplexVariable *> trunkVariables; + + trunkVariables += edgeL1; + trunkVariables += edgeL2; + + bool dirty; + do { + dirty = false; + + QLinkedList<QSimplexConstraint *>::iterator it = remainingConstraints.begin(); + while (it != remainingConstraints.end()) { + QSimplexConstraint *c = *it; + bool match = false; + + // Check if this constraint have some overlap with current + // trunk variables... + foreach (QSimplexVariable *ad, trunkVariables) { + if (c->variables.contains(ad)) { + match = true; + break; + } + } + + // If so, we add it to trunk, and erase it from the + // remaining constraints. + if (match) { + trunkConstraints += c; + trunkVariables += QSet<QSimplexVariable *>::fromList(c->variables.keys()); + it = remainingConstraints.erase(it); + dirty = true; + } else { + ++it; + } + } + } while (dirty); + + QList< QList<QSimplexConstraint *> > result; + result += trunkConstraints; + + if (!remainingConstraints.isEmpty()) { + QList<QSimplexConstraint *> nonTrunkConstraints; + QLinkedList<QSimplexConstraint *>::iterator it = remainingConstraints.begin(); + while (it != remainingConstraints.end()) { + nonTrunkConstraints += *it; + ++it; + } + result += nonTrunkConstraints; + } + + return result; +} + +/*! + \internal + + Use the current vertices distance to calculate and set the geometry of + each item. +*/ +void QGraphicsAnchorLayoutPrivate::setItemsGeometries() +{ + AnchorVertex *firstH, *secondH, *firstV, *secondV; + + foreach (QGraphicsLayoutItem *item, items) { + firstH = + m_vertexList.value(qMakePair(item, QGraphicsAnchorLayout::Left)); + secondH = + m_vertexList.value(qMakePair(item, QGraphicsAnchorLayout::Right)); + firstV = + m_vertexList.value(qMakePair(item, QGraphicsAnchorLayout::Top)); + secondV = + m_vertexList.value(qMakePair(item, QGraphicsAnchorLayout::Bottom)); + + QPointF topLeft(firstH->distance, firstV->distance); + QPointF bottomRight(secondH->distance, secondV->distance); + + item->setGeometry(QRectF(topLeft, bottomRight)); + } +} + +/*! + \internal + + Calculate the position of each vertex based on the paths to each of + them as well as the current edges sizes. +*/ +void QGraphicsAnchorLayoutPrivate::calculateVertexPositions( + QGraphicsAnchorLayoutPrivate::Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + QQueue<QPair<AnchorVertex *, AnchorVertex *> > queue; + QSet<AnchorVertex *> visited; + + // Get root vertex + bool ok; + AnchorVertex *root = graph[orientation].firstVertex(&ok); + + qreal widgetMargin; + qreal layoutMargin; + + // Initialize the first vertex + if (orientation == Horizontal) { + widgetMargin = q->geometry().x(); + q->getContentsMargins(&layoutMargin, 0, 0, 0); + } else { + // Root position is equal to the top margin + widgetMargin = q->geometry().y(); + q->getContentsMargins(0, &layoutMargin, 0, 0); + } + root->distance = widgetMargin + layoutMargin; + visited.insert(root); + + // Add initial edges to the queue + foreach (AnchorVertex *v, graph[orientation].adjacentVertices(root)) { + queue.enqueue(qMakePair(root, v)); + } + + // Do initial calculation required by "interpolateEdge()" + setupEdgesInterpolation(orientation); + + // Traverse the graph and calculate vertex positions. + while (!queue.isEmpty()) { + QPair<AnchorVertex *, AnchorVertex *> pair = queue.dequeue(); + + if (visited.contains(pair.second)) + continue; + visited.insert(pair.second); + + // The distance to the next vertex is equal the distance to the + // previous one plus (or less) the size of the edge between them. + qreal distance; + AnchorData *edge = graph[orientation].edgeData(pair.first, pair.second); + + if (edge->origin == pair.first) { + distance = pair.first->distance + interpolateEdge(edge); + } else { + distance = pair.first->distance - interpolateEdge(edge); + } + pair.second->distance = distance; + + foreach (AnchorVertex *v, + graph[orientation].adjacentVertices(pair.second)) { + queue.enqueue(qMakePair(pair.second, v)); + } + } +} + +/*! + \internal + + Calculate interpolation parameters based on current Layout Size. + Must once before calling "interpolateEdgeSize()" for each edge. +*/ +void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( + Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + qreal lower, upper, current; + + if (orientation == Horizontal) { + current = q->contentsRect().width(); + } else { + current = q->contentsRect().height(); + } + + if (current < sizeHints[orientation][Qt::PreferredSize]) { + interpolationInterval[orientation] = MinToPreferred; + lower = sizeHints[orientation][Qt::MinimumSize]; + upper = sizeHints[orientation][Qt::PreferredSize]; + } else { + interpolationInterval[orientation] = PreferredToMax; + lower = sizeHints[orientation][Qt::PreferredSize]; + upper = sizeHints[orientation][Qt::MaximumSize]; + } + + if (upper == lower) { + interpolationProgress[orientation] = 0; + } else { + interpolationProgress[orientation] = (current - lower) / (upper - lower); + } +} + +/*! + \internal + + Calculate the current Edge size based on the current Layout size and the + size the edge is supposed to have when: + + - the layout is at its minimum size. + - the layout is at its preferred size. + - the layout is at its maximum size. + + These three key values are calculated in advance using linear programming + (more expensive), then subsequential resizes of the parent layout require + a simple interpolation. +*/ +qreal QGraphicsAnchorLayoutPrivate::interpolateEdge(AnchorData *edge) +{ + qreal lower, upper; + + Orientation orientation = edgeOrientation(edge->origin->m_edge); + + if (interpolationInterval[orientation] == MinToPreferred) { + lower = edge->sizeAtMinimum; + upper = edge->sizeAtPreferred; + } else { + lower = edge->sizeAtPreferred; + upper = edge->sizeAtMaximum; + } + + return (interpolationProgress[orientation] * (upper - lower)) + lower; +} + + +QPair<qreal, qreal> +QGraphicsAnchorLayoutPrivate::solveMinMax(QList<QSimplexConstraint *> constraints, + GraphPath path) +{ + QList<AnchorData *> variables = getVariables(constraints); + QList<QSimplexConstraint *> itemConstraints; + + for (int i = 0; i < variables.size(); ++i) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(variables[i], 1.0); + c->constant = variables[i]->minSize; + c->ratio = QSimplexConstraint::MoreOrEqual; + itemConstraints += c; + + c = new QSimplexConstraint; + c->variables.insert(variables[i], 1.0); + c->constant = variables[i]->maxSize; + c->ratio = QSimplexConstraint::LessOrEqual; + itemConstraints += c; + } + + QSimplex simplex; + simplex.setConstraints(constraints + itemConstraints); + + // Obtain the objective constraint + QSimplexConstraint objective; + QSet<AnchorData *>::const_iterator iter; + for (iter = path.positives.constBegin(); iter != path.positives.constEnd(); ++iter) + objective.variables.insert(*iter, 1.0); + + for (iter = path.negatives.constBegin(); iter != path.negatives.constEnd(); ++iter) + objective.variables.insert(*iter, -1.0); + + simplex.setObjective(&objective); + + // Calculate minimum values + qreal min = simplex.solveMin(); + + // Save sizeAtMinimum results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast<AnchorData *>(variables[i]); + ad->sizeAtMinimum = ad->result; + } + + // Calculate maximum values + qreal max = simplex.solveMax(); + + // Save sizeAtMaximum results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast<AnchorData *>(variables[i]); + ad->sizeAtMaximum = ad->result; + } + + qDeleteAll(itemConstraints); + + return qMakePair<qreal, qreal>(min, max); +} + +void QGraphicsAnchorLayoutPrivate::solvePreferred(QList<QSimplexConstraint *> constraints) +{ + QList<AnchorData *> variables = getVariables(constraints); + + // ### + QList<QSimplexConstraint *> itemConstraints; + + for (int i = 0; i < variables.size(); ++i) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(variables[i], 1.0); + c->constant = variables[i]->minSize; + c->ratio = QSimplexConstraint::MoreOrEqual; + itemConstraints += c; + + c = new QSimplexConstraint; + c->variables.insert(variables[i], 1.0); + c->constant = variables[i]->maxSize; + c->ratio = QSimplexConstraint::LessOrEqual; + itemConstraints += c; + } + + QList<QSimplexConstraint *> preferredConstraints; + QList<QSimplexVariable *> preferredVariables; + QSimplexConstraint objective; + + // Fill the objective coefficients for this variable. In the + // end the objective function will be + // + // z = n * (A_shrink + B_shrink + ...) + (A_grower + B_grower + ...) + // + // where n is the number of variables that have + // slacks. Note that here we use the number of variables + // as coefficient, this is to mark the "shrinker slack + // variable" less likely to get value than the "grower + // slack variable". + + // This will fill the values for the structural constraints + // and we now fill the values for the slack constraints (one per variable), + // which have this form (the constant A_pref was set when creating the slacks): + // + // A + A_shrinker - A_grower = A_pref + // + for (int i = 0; i < variables.size(); ++i) { + QSimplexVariable *grower = new QSimplexVariable; + QSimplexVariable *shrinker = new QSimplexVariable; + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(variables[i], 1.0); + c->variables.insert(shrinker, 1.0); + c->variables.insert(grower, -1.0); + c->constant = variables[i]->prefSize; + + preferredConstraints += c; + preferredVariables += grower; + preferredVariables += shrinker; + + objective.variables.insert(grower, 1.0); + objective.variables.insert(shrinker, variables.size()); + } + + + QSimplex simplex; + simplex.setConstraints(constraints + itemConstraints + preferredConstraints); + + simplex.setObjective(&objective); + + // Calculate minimum values + qreal min = simplex.solveMin(); + + // Save sizeAtMinimum results + for (int i = 0; i < variables.size(); ++i) { + AnchorData *ad = static_cast<AnchorData *>(variables[i]); + ad->sizeAtPreferred = ad->result; + } + + qDeleteAll(itemConstraints); + qDeleteAll(preferredConstraints); + qDeleteAll(preferredVariables); + +} diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h new file mode 100644 index 0000000..e8f5783 --- /dev/null +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui 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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGraphicsWidget> + +#include "qgraphicsanchorlayout.h" +#include "qgraph_p.h" +#include "qsimplex_p.h" + +/* + The public QGraphicsAnchorLayout interface represents an anchorage point + as a pair of a <QGraphicsLayoutItem *> and a <QGraphicsAnchorLayout::Edge>. + + Internally though, it has a graph of anchorage points (vertices) and + anchors (edges), represented by the AnchorVertex and AnchorData structs + respectively. +*/ + +/*! + \internal + + Represents a vertex (anchorage point) in the internal graph +*/ +struct AnchorVertex { + AnchorVertex(QGraphicsLayoutItem *item, QGraphicsAnchorLayout::Edge edge) + : m_item(item), m_edge(edge) {} + + AnchorVertex() + : m_item(0), m_edge(QGraphicsAnchorLayout::Edge(0)) {} + + inline QString toString() const; + + QGraphicsLayoutItem *m_item; + QGraphicsAnchorLayout::Edge m_edge; + + // Current distance from this vertex to the layout edge (Left or Top) + // Value is calculated from the current anchors sizes. + qreal distance; +}; + +inline QString AnchorVertex::toString() const +{ + if (!this || !m_item) { + return QLatin1String("NULL"); + } + QString edge; + switch (m_edge) { + case QGraphicsAnchorLayout::Left: + edge = QLatin1String("Left"); + break; + case QGraphicsAnchorLayout::HCenter: + edge = QLatin1String("HorizontalCenter"); + break; + case QGraphicsAnchorLayout::Right: + edge = QLatin1String("Right"); + break; + case QGraphicsAnchorLayout::Top: + edge = QLatin1String("Top"); + break; + case QGraphicsAnchorLayout::VCenter: + edge = QLatin1String("VerticalCenter"); + break; + case QGraphicsAnchorLayout::Bottom: + edge = QLatin1String("Bottom"); + break; + default: + edge = QLatin1String("None"); + break; + } + QString item; + if (m_item->isLayout()) { + item = QLatin1String("layout"); + } else { + QGraphicsWidget *w = static_cast<QGraphicsWidget *>(m_item); + item = w->data(0).toString(); + } + edge.insert(0, QLatin1String("%1_")); + return edge.arg(item); +} + + +/*! + \internal + + Represents an edge (anchor) in the internal graph. +*/ +struct AnchorData : public QSimplexVariable { + AnchorData(qreal minimumSize, qreal preferredSize, qreal maximumSize) + : QSimplexVariable(), minSize(minimumSize), prefSize(preferredSize), + maxSize(maximumSize), sizeAtMinimum(preferredSize), + sizeAtPreferred(preferredSize), sizeAtMaximum(preferredSize), + skipInPreferred(0) {} + + AnchorData(qreal size = 0) + : QSimplexVariable(), minSize(size), prefSize(size), maxSize(size), + sizeAtMinimum(size), sizeAtPreferred(size), sizeAtMaximum(size), + skipInPreferred(0) {} + + inline QString toString() const; + QString name; + + // Anchor is semantically directed + AnchorVertex *origin; + + // Size restrictions of this edge. For anchors internal to items, these + // values are derived from the respective item size hints. For anchors + // that were added by users, these values are equal to the specified anchor + // size. + qreal minSize; + qreal prefSize; + qreal maxSize; + + // These attributes define which sizes should that anchor be in when the + // layout is at its minimum, preferred or maximum sizes. Values are + // calculated by the Simplex solver based on the current layout setup. + qreal sizeAtMinimum; + qreal sizeAtPreferred; + qreal sizeAtMaximum; + + uint skipInPreferred : 1; +}; + +inline QString AnchorData::toString() const +{ + return QString("Anchor(%1)").arg(name); + //return QString().sprintf("Anchor %%1 <Min %.1f Pref %.1f Max %.1f>", + // minSize, prefSize, maxSize).arg(name); +} + + +/*! + \internal + + Representation of a valid path for a given vertex in the graph. + In this struct, "positives" is the set of anchors that have been + traversed in the forward direction, while "negatives" is the set + with the ones walked backwards. + + This paths are compared against each other to produce LP Constraints, + the exact order in which the anchors were traversed is not relevant. +*/ +class GraphPath +{ +public: + GraphPath() {}; + + QSimplexConstraint *constraint(const GraphPath &path) const; + + QString toString() const; + + QSet<AnchorData *> positives; + QSet<AnchorData *> negatives; +}; + + +/*! + \internal + + QGraphicsAnchorLayout private methods and attributes. +*/ +class QGraphicsAnchorLayoutPrivate +{ + Q_DECLARE_PUBLIC(QGraphicsAnchorLayout) + QGraphicsAnchorLayout *q_ptr; + +public: + // When the layout geometry is different from its Minimum, Preferred + // or Maximum values, interpolation is used to calculate the geometries + // of the items. + // + // Interval represents which interpolation interval are we operating in. + enum Interval { + MinToPreferred = 0, + PreferredToMax + }; + + // Several structures internal to the layout are duplicated to handle + // both Horizontal and Vertical restrictions. + // + // Orientation is used to reference the right structure in each context + enum Orientation { + Horizontal = 0, + Vertical + }; + + QGraphicsAnchorLayoutPrivate(); + + static QGraphicsAnchorLayout::Edge oppositeEdge( + QGraphicsAnchorLayout::Edge edge); + + static Orientation edgeOrientation(QGraphicsAnchorLayout::Edge edge); + + // Init methods + void createLayoutEdges(); + void createItemEdges(QGraphicsLayoutItem *item); + + // Anchor Manipulation methods + void addAnchor(QGraphicsLayoutItem *firstItem, + QGraphicsAnchorLayout::Edge firstEdge, + QGraphicsLayoutItem *secondItem, + QGraphicsAnchorLayout::Edge secondEdge, + AnchorData *data); + + void removeAnchor(QGraphicsLayoutItem *firstItem, + QGraphicsAnchorLayout::Edge firstEdge, + QGraphicsLayoutItem *secondItem, + QGraphicsAnchorLayout::Edge secondEdge); + + void removeAnchors(QGraphicsLayoutItem *item); + + void correctEdgeDirection(QGraphicsLayoutItem *&firstItem, + QGraphicsAnchorLayout::Edge &firstEdge, + QGraphicsLayoutItem *&secondItem, + QGraphicsAnchorLayout::Edge &secondEdge); + + // Child manipulation methods + QGraphicsItem *parentItem() const; + void addChildItem(QGraphicsLayoutItem *child); + + // Activation methods + void calculateGraphs(); + void calculateGraphs(Orientation orientation); + void setAnchorSizeHintsFromItems(Orientation orientation); + void findPaths(Orientation orientation); + void constraintsFromPaths(Orientation orientation); + QList<QList<QSimplexConstraint *> > getGraphParts(Orientation orientation); + + // Geometry interpolation methods + void setItemsGeometries(); + void calculateVertexPositions(Orientation orientation); + void setupEdgesInterpolation(Orientation orientation); + qreal interpolateEdge(AnchorData *edge); + + // Linear Programming solver methods + QPair<qreal, qreal> solveMinMax(QList<QSimplexConstraint *> constraints, + GraphPath path); + void solvePreferred(QList<QSimplexConstraint *> constraints); + + // Size hints from simplex engine + qreal sizeHints[2][3]; + + // Items + QVector<QGraphicsLayoutItem *> items; + + // Mapping between high level anchorage points (Item, Edge) to low level + // ones (Graph Vertices) + QHash<QPair<QGraphicsLayoutItem*, QGraphicsAnchorLayout::Edge>, AnchorVertex *> m_vertexList; + + // Internal graph of anchorage points and anchors, for both orientations + Graph<AnchorVertex, AnchorData> graph[2]; + + // Graph paths and constraints, for both orientations + QMultiHash<AnchorVertex *, GraphPath> graphPaths[2]; + QList<QSimplexConstraint *> constraints[2]; + QList<QSimplexConstraint *> itemCenterConstraints[2]; + + // The interpolation interval and progress based on the current size + // as well as the key values (minimum, preferred and maximum) + Interval interpolationInterval[2]; + qreal interpolationProgress[2]; + + uint calculateGraphCacheDirty : 1; +}; |