diff options
author | Warwick Allison <warwick.allison@nokia.com> | 2009-11-09 07:43:42 (GMT) |
---|---|---|
committer | Warwick Allison <warwick.allison@nokia.com> | 2009-11-09 07:43:42 (GMT) |
commit | d2bcb0dfbc79c382dad87d3d2154ca127ae8fc16 (patch) | |
tree | c0848d1fc8fd2215fe32d75dcbe86354b0344282 /src/gui/graphicsview | |
parent | dbbe24f0f04e5a0aaf9223b73bee873e208a1fa7 (diff) | |
parent | b8b7f98b01d9ad03fecac7a0f593ac5734d7af1d (diff) | |
download | Qt-d2bcb0dfbc79c382dad87d3d2154ca127ae8fc16.zip Qt-d2bcb0dfbc79c382dad87d3d2154ca127ae8fc16.tar.gz Qt-d2bcb0dfbc79c382dad87d3d2154ca127ae8fc16.tar.bz2 |
Merge branch '4.6' of git://scm.dev.nokia.troll.no/qt/qt into kinetic-declarativeui
Conflicts:
configure.exe
src/corelib/animation/qabstractanimation.cpp
src/gui/graphicsview/qgraphicsview.cpp
src/s60installs/s60installs.pro
tools/configure/configureapp.cpp
tools/qdoc3/node.h
Diffstat (limited to 'src/gui/graphicsview')
-rw-r--r-- | src/gui/graphicsview/qgraph_p.h | 16 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout.cpp | 21 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 937 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsanchorlayout_p.h | 198 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsitem.cpp | 121 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsitem.h | 11 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsitem_p.h | 20 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicslinearlayout.cpp | 3 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsscene.cpp | 187 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsscene.h | 1 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsscene_p.h | 12 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsview.cpp | 33 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicswidget.cpp | 27 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicswidget.h | 4 | ||||
-rw-r--r-- | src/gui/graphicsview/qsimplex_p.cpp | 2 | ||||
-rw-r--r-- | src/gui/graphicsview/qsimplex_p.h | 2 |
16 files changed, 1115 insertions, 480 deletions
diff --git a/src/gui/graphicsview/qgraph_p.h b/src/gui/graphicsview/qgraph_p.h index f1fa185..0a2bf27 100644 --- a/src/gui/graphicsview/qgraph_p.h +++ b/src/gui/graphicsview/qgraph_p.h @@ -201,11 +201,6 @@ public: return l; } - void setRootVertex(Vertex *vertex) - { - userVertex = vertex; - } - QSet<Vertex*> vertices() const { QSet<Vertex *> setOfVertices; for (const_iterator it = constBegin(); it != constEnd(); ++it) { @@ -241,7 +236,7 @@ public: EdgeData *data = edgeData(v, v1); bool forward = data->from == v; if (forward) { - edges += QString::fromAscii("%1->%2 [label=\"[%3,%4,%5]\" dir=both color=\"#000000:#a0a0a0\"] \n") + edges += QString::fromAscii("\"%1\"->\"%2\" [label=\"[%3,%4,%5]\" dir=both color=\"#000000:#a0a0a0\"] \n") .arg(v->toString()) .arg(v1->toString()) .arg(data->minSize) @@ -250,17 +245,12 @@ public: ; } } - strVertices += QString::fromAscii("%1 [label=\"%2\"]\n").arg(v->toString()).arg(v->toString()); + strVertices += QString::fromAscii("\"%1\" [label=\"%2\"]\n").arg(v->toString()).arg(v->toString()); } return QString::fromAscii("%1\n%2\n").arg(strVertices).arg(edges); } #endif - Vertex *rootVertex() const - { - return userVertex; - } - protected: void createDirectedEdge(Vertex *from, Vertex *to, EdgeData *data) { @@ -286,8 +276,6 @@ protected: } private: - Vertex *userVertex; - QHash<Vertex *, QHash<Vertex *, EdgeData *> *> m_graph; }; diff --git a/src/gui/graphicsview/qgraphicsanchorlayout.cpp b/src/gui/graphicsview/qgraphicsanchorlayout.cpp index 56d70e1..872ec3c 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout.cpp @@ -83,10 +83,8 @@ \clearfloat \section1 Size Hints and Size Policies in an Anchor Layout - QGraphicsAnchorLayout respects each item's size hints and size policies. However it does - not currently respect their stretch factors. This might change in the future, so avoid - using stretch factors in anchor layouts if you want to avoid any future regressions in - behavior. + QGraphicsAnchorLayout respects each item's size hints and size policies. + Note that there are some properties of QSizePolicy that are \l{Known issues}{not respected}. \section1 Spacing within an Anchor Layout @@ -101,6 +99,21 @@ If the spacing is negative, the items will overlap to some extent. + + \section1 Known issues + There are some features that QGraphicsAnchorLayout currently does not support. + This might change in the future, so avoid using these features if you want to + avoid any future regressions in behaviour: + \list + + \o Stretch factors are not respected. + + \o QSizePolicy::ExpandFlag is not respected. + + \o Height for width is not respected. + + \endlist + \sa QGraphicsLinearLayout, QGraphicsGridLayout, QGraphicsLayout */ diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 41aa8aa..182594e 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -61,6 +61,8 @@ QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) QGraphicsAnchorPrivate::~QGraphicsAnchorPrivate() { + // ### + layoutPrivate->restoreSimplifiedGraph(QGraphicsAnchorLayoutPrivate::Orientation(data->orientation)); layoutPrivate->removeAnchor(data->from, data->to); } @@ -105,7 +107,7 @@ qreal QGraphicsAnchorPrivate::spacing() const static void internalSizeHints(QSizePolicy::Policy policy, qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint, qreal *minSize, qreal *prefSize, - qreal *expSize, qreal *maxSize) + qreal *maxSize) { // minSize, prefSize and maxSize are initialized // with item's preferred Size: this is QSizePolicy::Fixed. @@ -135,11 +137,6 @@ static void internalSizeHints(QSizePolicy::Policy policy, *prefSize = *minSize; else *prefSize = prefSizeHint; - - if (policy & QSizePolicy::ExpandFlag) - *expSize = *maxSize; - else - *expSize = *prefSize; } bool AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) @@ -154,7 +151,6 @@ bool AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) if (isLayoutAnchor) { minSize = 0; prefSize = 0; - expSize = 0; maxSize = QWIDGETSIZE_MAX; if (isCenterAnchor) maxSize /= 2; @@ -205,8 +201,8 @@ bool AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) } maxSizeHint = QWIDGETSIZE_MAX; } - internalSizeHints(policy, minSizeHint, prefSizeHint, maxSizeHint, - &minSize, &prefSize, &expSize, &maxSize); + internalSizeHints(policy, minSizeHint, prefSizeHint, maxSizeHint, + &minSize, &prefSize, &maxSize); // Set the anchor effective sizes to preferred. // @@ -217,7 +213,6 @@ bool AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) // recalculate and override the values we set here. sizeAtMinimum = prefSize; sizeAtPreferred = prefSize; - sizeAtExpanding = prefSize; sizeAtMaximum = prefSize; return true; @@ -225,10 +220,20 @@ bool AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) void ParallelAnchorData::updateChildrenSizes() { - firstEdge->sizeAtMinimum = secondEdge->sizeAtMinimum = sizeAtMinimum; - firstEdge->sizeAtPreferred = secondEdge->sizeAtPreferred = sizeAtPreferred; - firstEdge->sizeAtExpanding = secondEdge->sizeAtExpanding = sizeAtExpanding; - firstEdge->sizeAtMaximum = secondEdge->sizeAtMaximum = sizeAtMaximum; + firstEdge->sizeAtMinimum = sizeAtMinimum; + firstEdge->sizeAtPreferred = sizeAtPreferred; + firstEdge->sizeAtMaximum = sizeAtMaximum; + + const bool secondFwd = (secondEdge->from == from); + if (secondFwd) { + secondEdge->sizeAtMinimum = sizeAtMinimum; + secondEdge->sizeAtPreferred = sizeAtPreferred; + secondEdge->sizeAtMaximum = sizeAtMaximum; + } else { + secondEdge->sizeAtMinimum = -sizeAtMinimum; + secondEdge->sizeAtPreferred = -sizeAtPreferred; + secondEdge->sizeAtMaximum = -sizeAtMaximum; + } firstEdge->updateChildrenSizes(); secondEdge->updateChildrenSizes(); @@ -247,8 +252,16 @@ bool ParallelAnchorData::refreshSizeHints_helper(const QLayoutStyleInfo *styleIn return false; } - minSize = qMax(firstEdge->minSize, secondEdge->minSize); - maxSize = qMin(firstEdge->maxSize, secondEdge->maxSize); + // Account for parallel anchors where the second edge is backwards. + // We rely on the fact that a forward anchor of sizes min, pref, max is equivalent + // to a backwards anchor of size (-max, -pref, -min) + const bool secondFwd = (secondEdge->from == from); + const qreal secondMin = secondFwd ? secondEdge->minSize : -secondEdge->maxSize; + const qreal secondPref = secondFwd ? secondEdge->prefSize : -secondEdge->prefSize; + const qreal secondMax = secondFwd ? secondEdge->maxSize : -secondEdge->minSize; + + minSize = qMax(firstEdge->minSize, secondMin); + maxSize = qMin(firstEdge->maxSize, secondMax); // This condition means that the maximum size of one anchor being simplified is smaller than // the minimum size of the other anchor. The consequence is that there won't be a valid size @@ -257,16 +270,27 @@ bool ParallelAnchorData::refreshSizeHints_helper(const QLayoutStyleInfo *styleIn return false; } - expSize = qMax(firstEdge->expSize, secondEdge->expSize); - expSize = qMin(expSize, maxSize); + // The equivalent preferred Size of a parallel anchor is calculated as to + // reduce the deviation from the original preferred sizes _and_ to avoid shrinking + // items below their preferred sizes, unless strictly needed. - prefSize = qMax(firstEdge->prefSize, secondEdge->prefSize); - prefSize = qMin(prefSize, expSize); + // ### This logic only holds if all anchors in the layout are "well-behaved" in the + // following terms: + // + // - There are no negative-sized anchors + // - All sequential anchors are composed of children in the same direction as the + // sequential anchor itself + // + // With these assumptions we can grow a child knowing that no hidden items will + // have to shrink as the result of that. + // If any of these does not hold, we have a situation where the ParallelAnchor + // does not have enough information to calculate its equivalent prefSize. + prefSize = qMax(firstEdge->prefSize, secondPref); + prefSize = qMin(prefSize, maxSize); // See comment in AnchorData::refreshSizeHints() about sizeAt* values sizeAtMinimum = prefSize; sizeAtPreferred = prefSize; - sizeAtExpanding = prefSize; sizeAtMaximum = prefSize; return true; @@ -280,8 +304,7 @@ bool ParallelAnchorData::refreshSizeHints_helper(const QLayoutStyleInfo *styleIn 1 is at Maximum */ static QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> getFactor(qreal value, qreal min, - qreal pref, qreal exp, - qreal max) + qreal pref, qreal max) { QGraphicsAnchorLayoutPrivate::Interval interval; qreal lower; @@ -291,13 +314,9 @@ static QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> getFactor(qreal valu interval = QGraphicsAnchorLayoutPrivate::MinToPreferred; lower = min; upper = pref; - } else if (value < exp) { - interval = QGraphicsAnchorLayoutPrivate::PreferredToExpanding; - lower = pref; - upper = exp; } else { - interval = QGraphicsAnchorLayoutPrivate::ExpandingToMax; - lower = exp; + interval = QGraphicsAnchorLayoutPrivate::PreferredToMax; + lower = pref; upper = max; } @@ -313,7 +332,7 @@ static QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> getFactor(qreal valu static qreal interpolate(const QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> &factor, qreal min, qreal pref, - qreal exp, qreal max) + qreal max) { qreal lower; qreal upper; @@ -323,12 +342,8 @@ static qreal interpolate(const QPair<QGraphicsAnchorLayoutPrivate::Interval, qre lower = min; upper = pref; break; - case QGraphicsAnchorLayoutPrivate::PreferredToExpanding: + case QGraphicsAnchorLayoutPrivate::PreferredToMax: lower = pref; - upper = exp; - break; - case QGraphicsAnchorLayoutPrivate::ExpandingToMax: - lower = exp; upper = max; break; } @@ -341,34 +356,31 @@ void SequentialAnchorData::updateChildrenSizes() // ### REMOVE ME // ### check whether we are guarantee to get those or we need to warn stuff at this // point. - Q_ASSERT(sizeAtMinimum > minSize || qFuzzyCompare(sizeAtMinimum, minSize)); - Q_ASSERT(sizeAtMinimum < maxSize || qFuzzyCompare(sizeAtMinimum, maxSize)); - Q_ASSERT(sizeAtPreferred > minSize || qFuzzyCompare(sizeAtPreferred, minSize)); - Q_ASSERT(sizeAtPreferred < maxSize || qFuzzyCompare(sizeAtPreferred, maxSize)); - Q_ASSERT(sizeAtExpanding > minSize || qFuzzyCompare(sizeAtExpanding, minSize)); - Q_ASSERT(sizeAtExpanding < maxSize || qFuzzyCompare(sizeAtExpanding, maxSize)); - Q_ASSERT(sizeAtMaximum > minSize || qFuzzyCompare(sizeAtMaximum, minSize)); - Q_ASSERT(sizeAtMaximum < maxSize || qFuzzyCompare(sizeAtMaximum, maxSize)); + Q_ASSERT(sizeAtMinimum > minSize || qAbs(sizeAtMinimum - minSize) < 0.00000001); + Q_ASSERT(sizeAtPreferred > minSize || qAbs(sizeAtPreferred - minSize) < 0.00000001); + Q_ASSERT(sizeAtMaximum > minSize || qAbs(sizeAtMaximum - minSize) < 0.00000001); + + // These may be false if this anchor was in parallel with the layout stucture + // Q_ASSERT(sizeAtMinimum < maxSize || qAbs(sizeAtMinimum - maxSize) < 0.00000001); + // Q_ASSERT(sizeAtPreferred < maxSize || qAbs(sizeAtPreferred - maxSize) < 0.00000001); + // Q_ASSERT(sizeAtMaximum < maxSize || qAbs(sizeAtMaximum - maxSize) < 0.00000001); // Band here refers if the value is in the Minimum To Preferred // band (the lower band) or the Preferred To Maximum (the upper band). const QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> minFactor = - getFactor(sizeAtMinimum, minSize, prefSize, expSize, maxSize); + getFactor(sizeAtMinimum, minSize, prefSize, maxSize); const QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> prefFactor = - getFactor(sizeAtPreferred, minSize, prefSize, expSize, maxSize); - const QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> expFactor = - getFactor(sizeAtExpanding, minSize, prefSize, expSize, maxSize); + getFactor(sizeAtPreferred, minSize, prefSize, maxSize); const QPair<QGraphicsAnchorLayoutPrivate::Interval, qreal> maxFactor = - getFactor(sizeAtMaximum, minSize, prefSize, expSize, maxSize); + getFactor(sizeAtMaximum, minSize, prefSize, maxSize); for (int i = 0; i < m_edges.count(); ++i) { AnchorData *e = m_edges.at(i); - e->sizeAtMinimum = interpolate(minFactor, e->minSize, e->prefSize, e->expSize, e->maxSize); - e->sizeAtPreferred = interpolate(prefFactor, e->minSize, e->prefSize, e->expSize, e->maxSize); - e->sizeAtExpanding = interpolate(expFactor, e->minSize, e->prefSize, e->expSize, e->maxSize); - e->sizeAtMaximum = interpolate(maxFactor, e->minSize, e->prefSize, e->expSize, e->maxSize); + e->sizeAtMinimum = interpolate(minFactor, e->minSize, e->prefSize, e->maxSize); + e->sizeAtPreferred = interpolate(prefFactor, e->minSize, e->prefSize, e->maxSize); + e->sizeAtMaximum = interpolate(maxFactor, e->minSize, e->prefSize, e->maxSize); e->updateChildrenSizes(); } @@ -384,7 +396,6 @@ bool SequentialAnchorData::refreshSizeHints_helper(const QLayoutStyleInfo *style { minSize = 0; prefSize = 0; - expSize = 0; maxSize = 0; for (int i = 0; i < m_edges.count(); ++i) { @@ -396,14 +407,12 @@ bool SequentialAnchorData::refreshSizeHints_helper(const QLayoutStyleInfo *style minSize += edge->minSize; prefSize += edge->prefSize; - expSize += edge->expSize; maxSize += edge->maxSize; } // See comment in AnchorData::refreshSizeHints() about sizeAt* values sizeAtMinimum = prefSize; sizeAtPreferred = prefSize; - sizeAtExpanding = prefSize; sizeAtMaximum = prefSize; return true; @@ -478,12 +487,15 @@ QGraphicsAnchorLayoutPrivate::QGraphicsAnchorLayoutPrivate() for (int j = 0; j < 3; ++j) { sizeHints[i][j] = -1; } - sizeAtExpanding[i] = -1; interpolationProgress[i] = -1; spacings[i] = -1; graphSimplified[i] = false; graphHasConflicts[i] = false; + + layoutFirstVertex[i] = 0; + layoutCentralVertex[i] = 0; + layoutLastVertex[i] = 0; } } @@ -526,33 +538,67 @@ inline static qreal checkAdd(qreal a, qreal b) /*! \internal - Adds \a newAnchor to the graph \a g. + Adds \a newAnchor to the graph. Returns the newAnchor itself if it could be added without further changes to the graph. If a - new parallel anchor had to be created, then returns the new parallel anchor. In case the - addition is unfeasible -- because a parallel setup is not possible, returns 0. + new parallel anchor had to be created, then returns the new parallel anchor. If a parallel anchor + had to be created and it results in an unfeasible setup, \a feasible is set to false, otherwise + true. + + Note that in the case a new parallel anchor is created, it might also take over some constraints + from its children anchors. */ -static AnchorData *addAnchorMaybeParallel(Graph<AnchorVertex, AnchorData> *g, - AnchorData *newAnchor) +AnchorData *QGraphicsAnchorLayoutPrivate::addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible) { - bool feasible = true; + Orientation orientation = Orientation(newAnchor->orientation); + Graph<AnchorVertex, AnchorData> &g = graph[orientation]; + *feasible = true; // If already exists one anchor where newAnchor is supposed to be, we create a parallel // anchor. - if (AnchorData *oldAnchor = g->takeEdge(newAnchor->from, newAnchor->to)) { + if (AnchorData *oldAnchor = g.takeEdge(newAnchor->from, newAnchor->to)) { ParallelAnchorData *parallel = new ParallelAnchorData(oldAnchor, newAnchor); + // The parallel anchor will "replace" its children anchors in + // every center constraint that they appear. + + // ### If the dependent (center) anchors had reference(s) to their constraints, we + // could avoid traversing all the itemCenterConstraints. + QList<QSimplexConstraint *> &constraints = itemCenterConstraints[orientation]; + + AnchorData *children[2] = { oldAnchor, newAnchor }; + QList<QSimplexConstraint *> *childrenConstraints[2] = { ¶llel->m_firstConstraints, + ¶llel->m_secondConstraints }; + + for (int i = 0; i < 2; ++i) { + AnchorData *child = children[i]; + QList<QSimplexConstraint *> *childConstraints = childrenConstraints[i]; + + if (!child->isCenterAnchor) + continue; + + parallel->isCenterAnchor = true; + + for (int i = 0; i < constraints.count(); ++i) { + QSimplexConstraint *c = constraints[i]; + if (c->variables.contains(child)) { + childConstraints->append(c); + qreal v = c->variables.take(child); + c->variables.insert(parallel, v); + } + } + } + // At this point we can identify that the parallel anchor is not feasible, e.g. one // anchor minimum size is bigger than the other anchor maximum size. - feasible = parallel->refreshSizeHints_helper(0, false); + *feasible = parallel->refreshSizeHints_helper(0, false); newAnchor = parallel; } - g->createEdge(newAnchor->from, newAnchor->to, newAnchor); - return feasible ? newAnchor : 0; + g.createEdge(newAnchor->from, newAnchor->to, newAnchor); + return newAnchor; } - /*! \internal @@ -656,30 +702,185 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraph(Orientation orientation) if (graphSimplified[orientation]) return true; - graphSimplified[orientation] = true; #if 0 qDebug("Simplifying Graph for %s", orientation == Horizontal ? "Horizontal" : "Vertical"); #endif - if (!graph[orientation].rootVertex()) - return true; + // Vertex simplification + if (!simplifyVertices(orientation)) { + restoreVertices(orientation); + return false; + } + // Anchor simplification bool dirty; bool feasible = true; do { dirty = simplifyGraphIteration(orientation, &feasible); } while (dirty && feasible); - if (!feasible) - graphSimplified[orientation] = false; + // Note that if we are not feasible, we fallback and make sure that the graph is fully restored + if (!feasible) { + graphSimplified[orientation] = true; + restoreSimplifiedGraph(orientation); + restoreVertices(orientation); + return false; + } + + graphSimplified[orientation] = true; + return true; +} + +static AnchorVertex *replaceVertex_helper(AnchorData *data, AnchorVertex *oldV, AnchorVertex *newV) +{ + AnchorVertex *other; + if (data->from == oldV) { + data->from = newV; + other = data->to; + } else { + data->to = newV; + other = data->from; + } + return other; +} + +bool QGraphicsAnchorLayoutPrivate::replaceVertex(Orientation orientation, AnchorVertex *oldV, + AnchorVertex *newV, const QList<AnchorData *> &edges) +{ + Graph<AnchorVertex, AnchorData> &g = graph[orientation]; + bool feasible = true; + + for (int i = 0; i < edges.count(); ++i) { + AnchorData *ad = edges[i]; + AnchorVertex *otherV = replaceVertex_helper(ad, oldV, newV); + +#if defined(QT_DEBUG) + ad->name = QString::fromAscii("%1 --to--> %2").arg(ad->from->toString()).arg(ad->to->toString()); +#endif + + bool newFeasible; + AnchorData *newAnchor = addAnchorMaybeParallel(ad, &newFeasible); + feasible &= newFeasible; + + if (newAnchor != ad) { + // A parallel was created, we mark that in the list of anchors created by vertex + // simplification. This is needed because we want to restore them in a separate step + // from the restoration of anchor simplification. + anchorsFromSimplifiedVertices[orientation].append(newAnchor); + } + + g.takeEdge(oldV, otherV); + } return feasible; } /*! \internal +*/ +bool QGraphicsAnchorLayoutPrivate::simplifyVertices(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + Graph<AnchorVertex, AnchorData> &g = graph[orientation]; + + // We'll walk through vertices + QStack<AnchorVertex *> stack; + stack.push(layoutFirstVertex[orientation]); + QSet<AnchorVertex *> visited; + + while (!stack.isEmpty()) { + AnchorVertex *v = stack.pop(); + visited.insert(v); + + // Each adjacent of 'v' is a possible vertex to be merged. So we traverse all of + // them. Since once a merge is made, we might add new adjacents, and we don't want to + // pass two times through one adjacent. The 'index' is used to track our position. + QList<AnchorVertex *> adjacents = g.adjacentVertices(v); + int index = 0; + + while (index < adjacents.count()) { + AnchorVertex *next = adjacents.at(index); + index++; + + AnchorData *data = g.edgeData(v, next); + const bool bothLayoutVertices = v->m_item == q && next->m_item == q; + const bool zeroSized = !data->minSize && !data->maxSize; + + if (!bothLayoutVertices && zeroSized) { + + // Create a new vertex pair, note that we keep a list of those vertices so we can + // easily process them when restoring the graph. + AnchorVertexPair *newV = new AnchorVertexPair(v, next, data); + simplifiedVertices[orientation].append(newV); + + // Collect the anchors of both vertices, the new vertex pair will take their place + // in those anchors + const QList<AnchorVertex *> &vAdjacents = g.adjacentVertices(v); + const QList<AnchorVertex *> &nextAdjacents = g.adjacentVertices(next); + + for (int i = 0; i < vAdjacents.count(); ++i) { + AnchorVertex *adjacent = vAdjacents.at(i); + if (adjacent != next) { + AnchorData *ad = g.edgeData(v, adjacent); + newV->m_firstAnchors.append(ad); + } + } + + for (int i = 0; i < nextAdjacents.count(); ++i) { + AnchorVertex *adjacent = nextAdjacents.at(i); + if (adjacent != v) { + AnchorData *ad = g.edgeData(next, adjacent); + newV->m_secondAnchors.append(ad); + + // We'll also add new vertices to the adjacent list of the new 'v', to be + // created as a vertex pair and replace the current one. + if (!adjacents.contains(adjacent)) + adjacents.append(adjacent); + } + } + + // ### merge this loop into the ones that calculated m_firstAnchors/m_secondAnchors? + // Make newV take the place of v and next + bool feasible = replaceVertex(orientation, v, newV, newV->m_firstAnchors); + feasible &= replaceVertex(orientation, next, newV, newV->m_secondAnchors); + + // Update the layout vertex information if one of the vertices is a layout vertex. + AnchorVertex *layoutVertex = 0; + if (v->m_item == q) + layoutVertex = v; + else if (next->m_item == q) + layoutVertex = next; + + if (layoutVertex) { + // Layout vertices always have m_item == q... + newV->m_item = q; + changeLayoutVertex(orientation, layoutVertex, newV); + } + + g.takeEdge(v, next); + + // If a non-feasibility is found, we leave early and cancel the simplification + if (!feasible) + return false; + + v = newV; + visited.insert(newV); + + } else if (!visited.contains(next) && !stack.contains(next)) { + // If the adjacent is not fit for merge and it wasn't visited by the outermost + // loop, we add it to the stack. + stack.push(next); + } + } + } + + return true; +} + +/*! + \internal One iteration of the simplification algorithm. Returns true if another iteration is needed. @@ -700,7 +901,7 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP QSet<AnchorVertex *> visited; QStack<QPair<AnchorVertex *, AnchorVertex *> > stack; - stack.push(qMakePair(static_cast<AnchorVertex *>(0), g.rootVertex())); + stack.push(qMakePair(static_cast<AnchorVertex *>(0), layoutFirstVertex[orientation])); QVector<AnchorVertex*> candidates; bool candidatesForward; @@ -719,7 +920,8 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP // (a) it is a layout vertex, we don't simplify away the layout vertices; // (b) it does not have exactly 2 adjacents; // (c) it will change the direction of the sequence; - // (d) its next adjacent is already visited (a cycle in the graph). + // (d) its next adjacent is already visited (a cycle in the graph); + // (e) the next anchor is a center anchor. const QList<AnchorVertex *> &adjacents = g.adjacentVertices(v); const bool isLayoutVertex = v->m_item == q; @@ -742,13 +944,14 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP candidatesForward = (beforeSequence == data->from); } - // This is a tricky part. We peek at the next vertex to find out + // This is a tricky part. We peek at the next vertex to find out whether // - // - whether the edge from this vertex to the next vertex has the same direction; - // - whether we already visited the next vertex. + // - the edge from this vertex to the next vertex has the same direction; + // - we already visited the next vertex; + // - the next anchor is a center. // - // Those are needed to identify (c) and (d). Note that unlike (a) and (b), we preempt - // the end of sequence by looking into the next vertex. + // Those are needed to identify the remaining end of sequence cases. Note that unlike + // (a) and (b), we preempt the end of sequence by looking into the next vertex. // Peek at the next vertex AnchorVertex *after; @@ -766,8 +969,8 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP const bool willChangeDirection = (candidatesForward != (v == data->from)); const bool cycleFound = visited.contains(after); - // Now cases (c) and (d)... - endOfSequence = willChangeDirection || cycleFound; + // Now cases (c), (d) and (e)... + endOfSequence = willChangeDirection || cycleFound || data->isCenterAnchor; if (endOfSequence) { if (!willChangeDirection) { @@ -839,9 +1042,10 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP // If 'beforeSequence' and 'afterSequence' already had an anchor between them, we'll // create a parallel anchor between the new sequence and the old anchor. - AnchorData *newAnchor = addAnchorMaybeParallel(&g, sequence); + bool newFeasible; + AnchorData *newAnchor = addAnchorMaybeParallel(sequence, &newFeasible); - if (!newAnchor) { + if (!newFeasible) { *feasible = false; return false; } @@ -861,48 +1065,70 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP return false; } -static void restoreSimplifiedAnchor(Graph<AnchorVertex, AnchorData> &g, - AnchorData *edge, - AnchorVertex *before, - AnchorVertex *after) +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedAnchor(AnchorData *edge) { - Q_ASSERT(edge->type != AnchorData::Normal); #if 0 static const char *anchortypes[] = {"Normal", "Sequential", "Parallel"}; qDebug("Restoring %s edge.", anchortypes[int(edge->type)]); #endif - if (edge->type == AnchorData::Sequential) { - SequentialAnchorData* seqEdge = static_cast<SequentialAnchorData*>(edge); - // restore the sequential anchor - AnchorVertex *prev = before; - AnchorVertex *last = after; - if (edge->from != prev) - qSwap(last, prev); - - for (int i = 0; i < seqEdge->m_edges.count(); ++i) { - AnchorVertex *v1 = (i < seqEdge->m_children.count()) ? seqEdge->m_children.at(i) : last; - AnchorData *data = seqEdge->m_edges.at(i); - if (data->type != AnchorData::Normal) { - restoreSimplifiedAnchor(g, data, prev, v1); - } else { - g.createEdge(prev, v1, data); - } - prev = v1; + + Graph<AnchorVertex, AnchorData> &g = graph[edge->orientation]; + + if (edge->type == AnchorData::Normal) { + g.createEdge(edge->from, edge->to, edge); + + } else if (edge->type == AnchorData::Sequential) { + SequentialAnchorData *sequence = static_cast<SequentialAnchorData *>(edge); + + for (int i = 0; i < sequence->m_edges.count(); ++i) { + AnchorData *data = sequence->m_edges.at(i); + restoreSimplifiedAnchor(data); } + + delete sequence; + } else if (edge->type == AnchorData::Parallel) { - ParallelAnchorData* parallelEdge = static_cast<ParallelAnchorData*>(edge); - AnchorData *parallelEdges[2] = {parallelEdge->firstEdge, - parallelEdge->secondEdge}; - for (int i = 0; i < 2; ++i) { - AnchorData *data = parallelEdges[i]; - if (data->type == AnchorData::Normal) { - g.createEdge(before, after, data); - } else { - restoreSimplifiedAnchor(g, data, before, after); - } - } + + // Skip parallel anchors that were created by vertex simplification, they will be processed + // later, when restoring vertex simplification. + // ### we could improve this check bit having a bit inside 'edge' + if (anchorsFromSimplifiedVertices[edge->orientation].contains(edge)) + return; + + ParallelAnchorData* parallel = static_cast<ParallelAnchorData*>(edge); + restoreSimplifiedConstraints(parallel); + + // ### Because of the way parallel anchors are created in the anchor simplification + // algorithm, we know that one of these will be a sequence, so it'll be safe if the other + // anchor create an edge between the same vertices as the parallel. + Q_ASSERT(parallel->firstEdge->type == AnchorData::Sequential + || parallel->secondEdge->type == AnchorData::Sequential); + restoreSimplifiedAnchor(parallel->firstEdge); + restoreSimplifiedAnchor(parallel->secondEdge); + + delete parallel; + } +} + +void QGraphicsAnchorLayoutPrivate::restoreSimplifiedConstraints(ParallelAnchorData *parallel) +{ + if (!parallel->isCenterAnchor) + return; + + for (int i = 0; i < parallel->m_firstConstraints.count(); ++i) { + QSimplexConstraint *c = parallel->m_firstConstraints.at(i); + qreal v = c->variables[parallel]; + c->variables.remove(parallel); + c->variables.insert(parallel->firstEdge, v); + } + + for (int i = 0; i < parallel->m_secondConstraints.count(); ++i) { + QSimplexConstraint *c = parallel->m_secondConstraints.at(i); + qreal v = c->variables[parallel]; + c->variables.remove(parallel); + c->variables.insert(parallel->secondEdge, v); } } @@ -917,19 +1143,93 @@ void QGraphicsAnchorLayoutPrivate::restoreSimplifiedGraph(Orientation orientatio orientation == Horizontal ? "Horizontal" : "Vertical"); #endif + // Restore anchor simplification Graph<AnchorVertex, AnchorData> &g = graph[orientation]; - QList<QPair<AnchorVertex*, AnchorVertex*> > connections = g.connections(); for (int i = 0; i < connections.count(); ++i) { AnchorVertex *v1 = connections.at(i).first; AnchorVertex *v2 = connections.at(i).second; AnchorData *edge = g.edgeData(v1, v2); - if (edge->type != AnchorData::Normal) { - AnchorData *oldEdge = g.takeEdge(v1, v2); - restoreSimplifiedAnchor(g, edge, v1, v2); - delete oldEdge; + + // We restore only sequential anchors and parallels that were not created by + // vertex simplification. + if (edge->type == AnchorData::Sequential + || (edge->type == AnchorData::Parallel && + !anchorsFromSimplifiedVertices[orientation].contains(edge))) { + + g.takeEdge(v1, v2); + restoreSimplifiedAnchor(edge); } } + + restoreVertices(orientation); +} + +void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) +{ + Q_Q(QGraphicsAnchorLayout); + + Graph<AnchorVertex, AnchorData> &g = graph[orientation]; + QList<AnchorVertexPair *> &toRestore = simplifiedVertices[orientation]; + + // We will restore the vertices in the inverse order of creation, this way we ensure that + // the vertex being restored was not wrapped by another simplification. + for (int i = toRestore.count() - 1; i >= 0; --i) { + AnchorVertexPair *pair = toRestore.at(i); + QList<AnchorVertex *> adjacents = g.adjacentVertices(pair); + + // Restore the removed edge, this will also restore both vertices 'first' and 'second' to + // the graph structure. + AnchorVertex *first = pair->m_first; + AnchorVertex *second = pair->m_second; + g.createEdge(first, second, pair->m_removedAnchor); + + // Restore the anchors for the first child vertex + for (int j = 0; j < pair->m_firstAnchors.count(); ++j) { + AnchorData *ad = pair->m_firstAnchors.at(j); + Q_ASSERT(ad->from == pair || ad->to == pair); + + replaceVertex_helper(ad, pair, first); + g.createEdge(ad->from, ad->to, ad); + } + + // Restore the anchors for the second child vertex + for (int j = 0; j < pair->m_secondAnchors.count(); ++j) { + AnchorData *ad = pair->m_secondAnchors.at(j); + Q_ASSERT(ad->from == pair || ad->to == pair); + + replaceVertex_helper(ad, pair, second); + g.createEdge(ad->from, ad->to, ad); + } + + for (int j = 0; j < adjacents.count(); ++j) { + g.takeEdge(pair, adjacents.at(j)); + } + + // The pair simplified a layout vertex, so place back the correct vertex in the variable + // that track layout vertices + if (pair->m_item == q) { + AnchorVertex *layoutVertex = first->m_item == q ? first : second; + Q_ASSERT(layoutVertex->m_item == q); + changeLayoutVertex(orientation, pair, layoutVertex); + } + + delete pair; + } + toRestore.clear(); + + // The restoration process for vertex simplification also restored the effect of the + // parallel anchors created during vertex simplification, so we just need to restore + // the constraints in case of parallels that contain center anchors. For the same + // reason as above, order matters here. + QList<AnchorData *> ¶llelAnchors = anchorsFromSimplifiedVertices[orientation]; + + for (int i = parallelAnchors.count() - 1; i >= 0; --i) { + ParallelAnchorData *parallel = static_cast<ParallelAnchorData *>(parallelAnchors.at(i)); + restoreSimplifiedConstraints(parallel); + delete parallel; + } + parallelAnchors.clear(); } QGraphicsAnchorLayoutPrivate::Orientation @@ -959,9 +1259,10 @@ void QGraphicsAnchorLayoutPrivate::createLayoutEdges() data->maxSize = QWIDGETSIZE_MAX; data->skipInPreferred = 1; - // Set the Layout Left edge as the root of the horizontal graph. - AnchorVertex *v = internalVertex(layout, Qt::AnchorLeft); - graph[Horizontal].setRootVertex(v); + // Save a reference to layout vertices + layoutFirstVertex[Horizontal] = internalVertex(layout, Qt::AnchorLeft); + layoutCentralVertex[Horizontal] = 0; + layoutLastVertex[Horizontal] = internalVertex(layout, Qt::AnchorRight); // Vertical data = new AnchorData; @@ -970,17 +1271,18 @@ void QGraphicsAnchorLayoutPrivate::createLayoutEdges() data->maxSize = QWIDGETSIZE_MAX; data->skipInPreferred = 1; - // Set the Layout Top edge as the root of the vertical graph. - v = internalVertex(layout, Qt::AnchorTop); - graph[Vertical].setRootVertex(v); + // Save a reference to layout vertices + layoutFirstVertex[Vertical] = internalVertex(layout, Qt::AnchorTop); + layoutCentralVertex[Vertical] = 0; + layoutLastVertex[Vertical] = internalVertex(layout, Qt::AnchorBottom); } void QGraphicsAnchorLayoutPrivate::deleteLayoutEdges() { Q_Q(QGraphicsAnchorLayout); - Q_ASSERT(internalVertex(q, Qt::AnchorHorizontalCenter) == NULL); - Q_ASSERT(internalVertex(q, Qt::AnchorVerticalCenter) == NULL); + Q_ASSERT(!internalVertex(q, Qt::AnchorHorizontalCenter)); + Q_ASSERT(!internalVertex(q, Qt::AnchorVerticalCenter)); removeAnchor_helper(internalVertex(q, Qt::AnchorLeft), internalVertex(q, Qt::AnchorRight)); @@ -1019,6 +1321,8 @@ void QGraphicsAnchorLayoutPrivate::createItemEdges(QGraphicsLayoutItem *item) void QGraphicsAnchorLayoutPrivate::createCenterAnchors( QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge) { + Q_Q(QGraphicsAnchorLayout); + Orientation orientation; switch (centerEdge) { case Qt::AnchorHorizontalCenter: @@ -1061,24 +1365,32 @@ void QGraphicsAnchorLayoutPrivate::createCenterAnchors( c->variables.insert(data, 1.0); addAnchor_helper(item, firstEdge, item, centerEdge, data); data->isCenterAnchor = true; + data->dependency = AnchorData::Master; data->refreshSizeHints(0); data = new AnchorData; c->variables.insert(data, -1.0); addAnchor_helper(item, centerEdge, item, lastEdge, data); data->isCenterAnchor = true; + data->dependency = AnchorData::Slave; data->refreshSizeHints(0); itemCenterConstraints[orientation].append(c); // Remove old one removeAnchor_helper(first, last); + + if (item == q) { + layoutCentralVertex[orientation] = internalVertex(q, centerEdge); + } } void QGraphicsAnchorLayoutPrivate::removeCenterAnchors( QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge, bool substitute) { + Q_Q(QGraphicsAnchorLayout); + Orientation orientation; switch (centerEdge) { case Qt::AnchorHorizontalCenter: @@ -1120,7 +1432,7 @@ void QGraphicsAnchorLayoutPrivate::removeCenterAnchors( AnchorData *oldData = g.edgeData(first, center); // Remove center constraint for (int i = itemCenterConstraints[orientation].count() - 1; i >= 0; --i) { - if (itemCenterConstraints[orientation][i]->variables.contains(oldData)) { + if (itemCenterConstraints[orientation].at(i)->variables.contains(oldData)) { delete itemCenterConstraints[orientation].takeAt(i); break; } @@ -1151,6 +1463,10 @@ void QGraphicsAnchorLayoutPrivate::removeCenterAnchors( // by this time, the center vertex is deleted and merged into a non-centered internal anchor removeAnchor_helper(first, internalVertex(item, lastEdge)); } + + if (item == q) { + layoutCentralVertex[orientation] = 0; + } } @@ -1180,7 +1496,7 @@ void QGraphicsAnchorLayoutPrivate::removeCenterConstraints(QGraphicsLayoutItem * // Look for our anchor in all item center constraints, then remove it for (int i = 0; i < itemCenterConstraints[orientation].size(); ++i) { - if (itemCenterConstraints[orientation][i]->variables.contains(internalAnchor)) { + if (itemCenterConstraints[orientation].at(i)->variables.contains(internalAnchor)) { delete itemCenterConstraints[orientation].takeAt(i); break; } @@ -1690,7 +2006,7 @@ QList<AnchorData *> getVariables(QList<QSimplexConstraint *> constraints) { QSet<AnchorData *> variableSet; for (int i = 0; i < constraints.count(); ++i) { - const QSimplexConstraint *c = constraints[i]; + const QSimplexConstraint *c = constraints.at(i); foreach (QSimplexVariable *var, c->variables.keys()) { variableSet += static_cast<AnchorData *>(var); } @@ -1724,12 +2040,19 @@ QList<AnchorData *> getVariables(QList<QSimplexConstraint *> constraints) void QGraphicsAnchorLayoutPrivate::calculateGraphs( QGraphicsAnchorLayoutPrivate::Orientation orientation) { - Q_Q(QGraphicsAnchorLayout); - #if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) lastCalculationUsedSimplex[orientation] = false; #endif + // ### This is necessary because now we do vertex simplification, we still don't know + // differentiate between invalidate()s that doesn't need resimplification and those which + // need. For example, when size hint of an item changes, this may cause an anchor to reach 0 or to + // leave 0 and get a size. In both cases we need resimplify. + // + // ### one possible solution would be tracking all the 0-sized anchors, if this set change, we need + // resimplify. + restoreSimplifiedGraph(orientation); + // Reset the nominal sizes of each anchor based on the current item sizes. This function // works with both simplified and non-simplified graphs, so it'll work when the // simplification is going to be reused. @@ -1768,12 +2091,12 @@ void QGraphicsAnchorLayoutPrivate::calculateGraphs( // 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]; + QList<QSimplexConstraint *> trunkConstraints = parts.at(0); QList<AnchorData *> trunkVariables = getVariables(trunkConstraints); // For minimum and maximum, use the path between the two layout sides as the // objective function. - AnchorVertex *v = internalVertex(q, pickEdge(Qt::AnchorRight, orientation)); + AnchorVertex *v = layoutLastVertex[orientation]; GraphPath trunkPath = graphPaths[orientation].value(v); bool feasible = calculateTrunk(orientation, trunkPath, trunkConstraints, trunkVariables); @@ -1787,7 +2110,7 @@ void QGraphicsAnchorLayoutPrivate::calculateGraphs( if (!feasible) break; - QList<QSimplexConstraint *> partConstraints = parts[i]; + QList<QSimplexConstraint *> partConstraints = parts.at(i); QList<AnchorData *> partVariables = getVariables(partConstraints); Q_ASSERT(!partVariables.isEmpty()); feasible &= calculateNonTrunk(partConstraints, partVariables); @@ -1836,27 +2159,19 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const if (feasible) { solvePreferred(allConstraints, variables); - // Note that we don't include the sizeHintConstraints, since they - // have a different logic for solveExpanding(). - solveExpanding(constraints, variables); - - // Calculate and set the preferred and expanding sizes for the layout, + // Calculate and set the preferred size for the layout, // from the edge sizes that were calculated above. qreal pref(0.0); - qreal expanding(0.0); foreach (const AnchorData *ad, path.positives) { pref += ad->sizeAtPreferred; - expanding += ad->sizeAtExpanding; } foreach (const AnchorData *ad, path.negatives) { pref -= ad->sizeAtPreferred; - expanding -= ad->sizeAtExpanding; } sizeHints[orientation][Qt::MinimumSize] = min; sizeHints[orientation][Qt::PreferredSize] = pref; sizeHints[orientation][Qt::MaximumSize] = max; - sizeAtExpanding[orientation] = expanding; } qDeleteAll(sizeHintConstraints); @@ -1870,13 +2185,11 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const AnchorData *ad = path.positives.toList()[0]; ad->sizeAtMinimum = ad->minSize; ad->sizeAtPreferred = ad->prefSize; - ad->sizeAtExpanding = ad->expSize; ad->sizeAtMaximum = ad->maxSize; sizeHints[orientation][Qt::MinimumSize] = ad->sizeAtMinimum; sizeHints[orientation][Qt::PreferredSize] = ad->sizeAtPreferred; sizeHints[orientation][Qt::MaximumSize] = ad->sizeAtMaximum; - sizeAtExpanding[orientation] = ad->sizeAtExpanding; } #if defined(QT_DEBUG) || defined(Q_AUTOTEST_EXPORT) @@ -1899,10 +2212,9 @@ bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList<QSimplexConstra // Propagate size at preferred to other sizes. Semi-floats always will be // in their sizeAtPreferred. for (int j = 0; j < variables.count(); ++j) { - AnchorData *ad = variables[j]; + AnchorData *ad = variables.at(j); Q_ASSERT(ad); ad->sizeAtMinimum = ad->sizeAtPreferred; - ad->sizeAtExpanding = ad->sizeAtPreferred; ad->sizeAtMaximum = ad->sizeAtPreferred; } } @@ -1955,7 +2267,7 @@ void QGraphicsAnchorLayoutPrivate::findPaths(Orientation orientation) QSet<AnchorData *> visited; - AnchorVertex *root = graph[orientation].rootVertex(); + AnchorVertex *root = layoutFirstVertex[orientation]; graphPaths[orientation].insert(root, GraphPath()); @@ -2013,7 +2325,7 @@ void QGraphicsAnchorLayoutPrivate::constraintsFromPaths(Orientation orientation) QList<GraphPath> pathsToVertex = graphPaths[orientation].values(vertex); for (int i = 1; i < valueCount; ++i) { constraints[orientation] += \ - pathsToVertex[0].constraint(pathsToVertex[i]); + pathsToVertex[0].constraint(pathsToVertex.at(i)); } } } @@ -2041,9 +2353,37 @@ void QGraphicsAnchorLayoutPrivate::updateAnchorSizes(Orientation orientation) QList<QSimplexConstraint *> QGraphicsAnchorLayoutPrivate::constraintsFromSizeHints( const QList<AnchorData *> &anchors) { + if (anchors.isEmpty()) + return QList<QSimplexConstraint *>(); + + // Look for the layout edge. That can be either the first half in case the + // layout is split in two, or the whole layout anchor. + Orientation orient = Orientation(anchors.first()->orientation); + AnchorData *layoutEdge = 0; + if (layoutCentralVertex[orient]) { + layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutCentralVertex[orient]); + } else { + layoutEdge = graph[orient].edgeData(layoutFirstVertex[orient], layoutLastVertex[orient]); + + // If maxSize is less then "infinite", that means there are other anchors + // grouped together with this one. We can't ignore its maximum value so we + // set back the variable to NULL to prevent the continue condition from being + // satisfied in the loop below. + if (layoutEdge->maxSize < QWIDGETSIZE_MAX) + layoutEdge = 0; + } + + // For each variable, create constraints based on size hints QList<QSimplexConstraint *> anchorConstraints; + bool unboundedProblem = true; for (int i = 0; i < anchors.size(); ++i) { - AnchorData *ad = anchors[i]; + AnchorData *ad = anchors.at(i); + + // Anchors that have their size directly linked to another one don't need constraints + // For exammple, the second half of an item has exactly the same size as the first half + // thus constraining the latter is enough. + if (ad->dependency == AnchorData::Slave) + continue; if ((ad->minSize == ad->maxSize) || qFuzzyCompare(ad->minSize, ad->maxSize)) { QSimplexConstraint *c = new QSimplexConstraint; @@ -2051,6 +2391,7 @@ QList<QSimplexConstraint *> QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin c->constant = ad->minSize; c->ratio = QSimplexConstraint::Equal; anchorConstraints += c; + unboundedProblem = false; } else { QSimplexConstraint *c = new QSimplexConstraint; c->variables.insert(ad, 1.0); @@ -2058,14 +2399,30 @@ QList<QSimplexConstraint *> QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin c->ratio = QSimplexConstraint::MoreOrEqual; anchorConstraints += c; + // We avoid adding restrictions to the layout internal anchors. That's + // to prevent unnecessary fair distribution from happening due to this + // artificial restriction. + if (ad == layoutEdge) + continue; + c = new QSimplexConstraint; c->variables.insert(ad, 1.0); c->constant = ad->maxSize; c->ratio = QSimplexConstraint::LessOrEqual; anchorConstraints += c; + unboundedProblem = false; } } + // If no upper boundary restriction was added, add one to avoid unbounded problem + if (unboundedProblem) { + QSimplexConstraint *c = new QSimplexConstraint; + c->variables.insert(layoutEdge, 1.0); + c->constant = QWIDGETSIZE_MAX; + c->ratio = QSimplexConstraint::LessOrEqual; + anchorConstraints += c; + } + return anchorConstraints; } @@ -2075,38 +2432,26 @@ QList<QSimplexConstraint *> QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin QList< QList<QSimplexConstraint *> > QGraphicsAnchorLayoutPrivate::getGraphParts(Orientation orientation) { - Q_Q(QGraphicsAnchorLayout); + Q_ASSERT(layoutFirstVertex[orientation] && layoutLastVertex[orientation]); - // Find layout vertices and edges for the current orientation. - AnchorVertex *layoutFirstVertex = \ - internalVertex(q, pickEdge(Qt::AnchorLeft, orientation)); - - AnchorVertex *layoutCentralVertex = \ - internalVertex(q, pickEdge(Qt::AnchorHorizontalCenter, orientation)); - - AnchorVertex *layoutLastVertex = \ - internalVertex(q, pickEdge(Qt::AnchorRight, orientation)); - - Q_ASSERT(layoutFirstVertex && layoutLastVertex); - - AnchorData *edgeL1 = NULL; - AnchorData *edgeL2 = NULL; + AnchorData *edgeL1 = 0; + AnchorData *edgeL2 = 0; // The layout may have a single anchor between Left and Right or two half anchors // passing through the center - if (layoutCentralVertex) { - edgeL1 = graph[orientation].edgeData(layoutFirstVertex, layoutCentralVertex); - edgeL2 = graph[orientation].edgeData(layoutCentralVertex, layoutLastVertex); + if (layoutCentralVertex[orientation]) { + edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutCentralVertex[orientation]); + edgeL2 = graph[orientation].edgeData(layoutCentralVertex[orientation], layoutLastVertex[orientation]); } else { - edgeL1 = graph[orientation].edgeData(layoutFirstVertex, layoutLastVertex); + edgeL1 = graph[orientation].edgeData(layoutFirstVertex[orientation], layoutLastVertex[orientation]); } QLinkedList<QSimplexConstraint *> remainingConstraints; for (int i = 0; i < constraints[orientation].count(); ++i) { - remainingConstraints += constraints[orientation][i]; + remainingConstraints += constraints[orientation].at(i); } for (int i = 0; i < itemCenterConstraints[orientation].count(); ++i) { - remainingConstraints += itemCenterConstraints[orientation][i]; + remainingConstraints += itemCenterConstraints[orientation].at(i); } QList<QSimplexConstraint *> trunkConstraints; @@ -2276,6 +2621,21 @@ void QGraphicsAnchorLayoutPrivate::setItemsGeometries(const QRectF &geom) } /*! + \internal + + Fill the distance in the vertex and in the sub-vertices if its a combined vertex. +*/ +static void setVertexDistance(AnchorVertex *v, qreal distance) +{ + v->distance = distance; + if (v->m_type == AnchorVertex::Pair) { + AnchorVertexPair *pair = static_cast<AnchorVertexPair *>(v); + setVertexDistance(pair->m_first, distance); + setVertexDistance(pair->m_second, distance); + } +} + +/*! \internal Calculate the position of each vertex based on the paths to each of @@ -2288,9 +2648,9 @@ void QGraphicsAnchorLayoutPrivate::calculateVertexPositions( QSet<AnchorVertex *> visited; // Get root vertex - AnchorVertex *root = graph[orientation].rootVertex(); + AnchorVertex *root = layoutFirstVertex[orientation]; - root->distance = 0; + setVertexDistance(root, 0); visited.insert(root); // Add initial edges to the queue @@ -2314,7 +2674,7 @@ void QGraphicsAnchorLayoutPrivate::calculateVertexPositions( continue; visited.insert(pair.second); - interpolateEdge(pair.first, edge, orientation); + interpolateEdge(pair.first, edge); QList<AnchorVertex *> adjacents = graph[orientation].adjacentVertices(pair.second); for (int i = 0; i < adjacents.count(); ++i) { @@ -2343,7 +2703,6 @@ void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( result = getFactor(current, sizeHints[orientation][Qt::MinimumSize], sizeHints[orientation][Qt::PreferredSize], - sizeAtExpanding[orientation], sizeHints[orientation][Qt::MaximumSize]); interpolationInterval[orientation] = result.first; @@ -2358,7 +2717,6 @@ void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( - minimum size, - preferred size, - - size when all expanding anchors are expanded, - maximum size. These three key values are calculated in advance using linear @@ -2370,36 +2728,32 @@ void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( vertices to be initalized, so it calls specialized functions that will recurse back to interpolateEdge(). */ -void QGraphicsAnchorLayoutPrivate::interpolateEdge(AnchorVertex *base, - AnchorData *edge, - Orientation orientation) +void QGraphicsAnchorLayoutPrivate::interpolateEdge(AnchorVertex *base, AnchorData *edge) { + const Orientation orientation = Orientation(edge->orientation); const QPair<Interval, qreal> factor(interpolationInterval[orientation], interpolationProgress[orientation]); qreal edgeDistance = interpolate(factor, edge->sizeAtMinimum, edge->sizeAtPreferred, - edge->sizeAtExpanding, edge->sizeAtMaximum); + edge->sizeAtMaximum); Q_ASSERT(edge->from == base || edge->to == base); - if (edge->from == base) - edge->to->distance = base->distance + edgeDistance; - else - edge->from->distance = base->distance - edgeDistance; + // Calculate the distance for the vertex opposite to the base + if (edge->from == base) { + setVertexDistance(edge->to, base->distance + edgeDistance); + } else { + setVertexDistance(edge->from, base->distance - edgeDistance); + } // Process child anchors if (edge->type == AnchorData::Sequential) - interpolateSequentialEdges(edge->from, - static_cast<SequentialAnchorData *>(edge), - orientation); + interpolateSequentialEdges(static_cast<SequentialAnchorData *>(edge)); else if (edge->type == AnchorData::Parallel) - interpolateParallelEdges(edge->from, - static_cast<ParallelAnchorData *>(edge), - orientation); + interpolateParallelEdges(static_cast<ParallelAnchorData *>(edge)); } -void QGraphicsAnchorLayoutPrivate::interpolateParallelEdges( - AnchorVertex *base, ParallelAnchorData *data, Orientation orientation) +void QGraphicsAnchorLayoutPrivate::interpolateParallelEdges(ParallelAnchorData *data) { // In parallels the boundary vertices are already calculate, we // just need to look for sequential groups inside, because only @@ -2407,46 +2761,44 @@ void QGraphicsAnchorLayoutPrivate::interpolateParallelEdges( // First edge if (data->firstEdge->type == AnchorData::Sequential) - interpolateSequentialEdges(base, - static_cast<SequentialAnchorData *>(data->firstEdge), - orientation); + interpolateSequentialEdges(static_cast<SequentialAnchorData *>(data->firstEdge)); else if (data->firstEdge->type == AnchorData::Parallel) - interpolateParallelEdges(base, - static_cast<ParallelAnchorData *>(data->firstEdge), - orientation); + interpolateParallelEdges(static_cast<ParallelAnchorData *>(data->firstEdge)); // Second edge if (data->secondEdge->type == AnchorData::Sequential) - interpolateSequentialEdges(base, - static_cast<SequentialAnchorData *>(data->secondEdge), - orientation); + interpolateSequentialEdges(static_cast<SequentialAnchorData *>(data->secondEdge)); else if (data->secondEdge->type == AnchorData::Parallel) - interpolateParallelEdges(base, - static_cast<ParallelAnchorData *>(data->secondEdge), - orientation); + interpolateParallelEdges(static_cast<ParallelAnchorData *>(data->secondEdge)); } -void QGraphicsAnchorLayoutPrivate::interpolateSequentialEdges( - AnchorVertex *base, SequentialAnchorData *data, Orientation orientation) +void QGraphicsAnchorLayoutPrivate::interpolateSequentialEdges(SequentialAnchorData *data) { - AnchorVertex *prev = base; + // This method is supposed to handle any sequential anchor, even out-of-order + // ones. However, in the current QGAL implementation we should get only the + // well behaved ones. + Q_ASSERT(data->m_edges.first()->from == data->from); + Q_ASSERT(data->m_edges.last()->to == data->to); - // ### I'm not sure whether this assumption is safe. If not, - // consider that m_edges.last() could be used instead (so - // at(0) would be the one to be treated specially). - Q_ASSERT(base == data->m_edges.at(0)->to || base == data->m_edges.at(0)->from); + // At this point, the two outter vertices already have their distance + // calculated. + // We use the first as the base to calculate the internal ones + + AnchorVertex *prev = data->from; - // Skip the last for (int i = 0; i < data->m_edges.count() - 1; ++i) { - AnchorData *child = data->m_edges.at(i); - interpolateEdge(prev, child, orientation); - prev = child->to; + AnchorData *edge = data->m_edges.at(i); + interpolateEdge(prev, edge); + + // Use the recently calculated vertex as the base for the next one + const bool edgeIsForward = (edge->from == prev); + prev = edgeIsForward ? edge->to : edge->from; } // Treat the last specially, since we already calculated it's end // vertex, so it's only interesting if it's a complex one if (data->m_edges.last()->type != AnchorData::Normal) - interpolateEdge(prev, data->m_edges.last(), orientation); + interpolateEdge(prev, data->m_edges.last()); } bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList<QSimplexConstraint *> &constraints, @@ -2472,9 +2824,10 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList<QSimplexConstraint *> // Save sizeAtMinimum results QList<AnchorData *> variables = getVariables(constraints); for (int i = 0; i < variables.size(); ++i) { - AnchorData *ad = static_cast<AnchorData *>(variables[i]); - Q_ASSERT(ad->result >= ad->minSize || qFuzzyCompare(ad->result, ad->minSize)); + AnchorData *ad = static_cast<AnchorData *>(variables.at(i)); ad->sizeAtMinimum = ad->result; + Q_ASSERT(ad->sizeAtMinimum >= ad->minSize || + qAbs(ad->sizeAtMinimum - ad->minSize) < 0.00000001); } // Calculate maximum values @@ -2482,9 +2835,10 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList<QSimplexConstraint *> // Save sizeAtMaximum results for (int i = 0; i < variables.size(); ++i) { - AnchorData *ad = static_cast<AnchorData *>(variables[i]); - Q_ASSERT(ad->result <= ad->maxSize || qFuzzyCompare(ad->result, ad->maxSize)); + AnchorData *ad = static_cast<AnchorData *>(variables.at(i)); ad->sizeAtMaximum = ad->result; + // Q_ASSERT(ad->sizeAtMaximum <= ad->maxSize || + // qAbs(ad->sizeAtMaximum - ad->maxSize) < 0.00000001); } } return feasible; @@ -2515,7 +2869,7 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QList<QSimplexConstraint // A + A_shrinker - A_grower = A_pref // for (int i = 0; i < variables.size(); ++i) { - AnchorData *ad = variables[i]; + AnchorData *ad = variables.at(i); if (ad->skipInPreferred) continue; @@ -2546,7 +2900,7 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QList<QSimplexConstraint // Save sizeAtPreferred results for (int i = 0; i < variables.size(); ++i) { - AnchorData *ad = variables[i]; + AnchorData *ad = variables.at(i); ad->sizeAtPreferred = ad->result; } @@ -2563,139 +2917,6 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QList<QSimplexConstraint /*! \internal - Calculate the "expanding" keyframe - - This new keyframe sits between the already existing sizeAtPreferred and - sizeAtMaximum keyframes. Its goal is to modify the interpolation between - the latter as to respect the "expanding" size policy of some anchors. - - Previously all items would be subject to a linear interpolation between - sizeAtPreferred and sizeAtMaximum values. This will change now, the - expanding anchors will change their size before the others. To calculate - this keyframe we use the following logic: - - 1) Ask each anchor for their desired expanding size (ad->expSize), this - value depends on the anchor expanding property in the following way: - - - Expanding normal anchors want to grow towards their maximum size - - Non-expanding normal anchors want to remain at their preferred size. - - Sequential anchors wants to grow towards a size that is calculated by: - summarizing it's child anchors, where it will use preferred size for non-expanding anchors - and maximum size for expanding anchors. - - Parallel anchors want to grow towards the smallest maximum size of all the expanding anchors. - - 2) Clamp their desired values to the value they assume in the neighbour - keyframes (sizeAtPreferred and sizeAtExpanding) - - 3) Run simplex with a setup that ensures the following: - - a. Anchors will change their value from their sizeAtPreferred towards - their sizeAtMaximum as much as required to ensure that ALL anchors - reach their respective "desired" expanding sizes. - - b. No anchors will change their value beyond what is NEEDED to satisfy - the requirement above. - - The final result is that, at the "expanding" keyframe expanding anchors - will grow and take with them all anchors that are parallel to them. - However, non-expanding anchors will remain at their preferred size unless - they are forced to grow by a parallel expanding anchor. - - Note: For anchors where the sizeAtPreferred is bigger than sizeAtMaximum, - the visual effect when the layout grows from its preferred size is - the following: Expanding anchors will keep their size while non - expanding ones will shrink. Only after non-expanding anchors have - shrinked all the way, the expanding anchors will start to shrink too. -*/ -void QGraphicsAnchorLayoutPrivate::solveExpanding(const QList<QSimplexConstraint *> &constraints, - const QList<AnchorData *> &variables) -{ - QList<QSimplexConstraint *> itemConstraints; - QSimplexConstraint *objective = new QSimplexConstraint; - bool hasExpanding = false; - - // Construct the simplex constraints and objective - for (int i = 0; i < variables.size(); ++i) { - // For each anchor - AnchorData *ad = variables[i]; - - // Clamp the desired expanding size - qreal upperBoundary = qMax(ad->sizeAtPreferred, ad->sizeAtMaximum); - qreal lowerBoundary = qMin(ad->sizeAtPreferred, ad->sizeAtMaximum); - qreal boundedExpSize = qBound(lowerBoundary, ad->expSize, upperBoundary); - - // Expanding anchors are those that want to move from their preferred size - if (boundedExpSize != ad->sizeAtPreferred) - hasExpanding = true; - - // Lock anchor between boundedExpSize and sizeAtMaximum (ensure 3.a) - if (boundedExpSize == ad->sizeAtMaximum || qFuzzyCompare(boundedExpSize, ad->sizeAtMaximum)) { - // The interval has only one possible value, we can use an "Equal" - // constraint and don't need to add this variable to the objective. - QSimplexConstraint *itemC = new QSimplexConstraint; - itemC->ratio = QSimplexConstraint::Equal; - itemC->variables.insert(ad, 1.0); - itemC->constant = boundedExpSize; - itemConstraints << itemC; - } else { - // Add MoreOrEqual and LessOrEqual constraints. - QSimplexConstraint *itemC = new QSimplexConstraint; - itemC->ratio = QSimplexConstraint::MoreOrEqual; - itemC->variables.insert(ad, 1.0); - itemC->constant = qMin(boundedExpSize, ad->sizeAtMaximum); - itemConstraints << itemC; - - itemC = new QSimplexConstraint; - itemC->ratio = QSimplexConstraint::LessOrEqual; - itemC->variables.insert(ad, 1.0); - itemC->constant = qMax(boundedExpSize, ad->sizeAtMaximum); - itemConstraints << itemC; - - // Create objective to avoid the anchors from moving away from - // the preferred size more than the needed amount. (ensure 3.b) - // The objective function is the distance between sizeAtPreferred - // and sizeAtExpanding, it will be minimized. - if (ad->sizeAtExpanding < ad->sizeAtMaximum) { - // Try to shrink this variable towards its sizeAtPreferred value - objective->variables.insert(ad, 1.0); - } else { - // Try to grow this variable towards its sizeAtPreferred value - objective->variables.insert(ad, -1.0); - } - } - } - - // Solve - if (hasExpanding == false) { - // If no anchors are expanding, we don't need to run the simplex - // Set all variables to their preferred size - for (int i = 0; i < variables.size(); ++i) { - variables[i]->sizeAtExpanding = variables[i]->sizeAtPreferred; - } - } else { - // Run simplex - QSimplex simplex; - - // Satisfy expanding (3.a) - bool feasible = simplex.setConstraints(constraints + itemConstraints); - Q_ASSERT(feasible); - - // Reduce damage (3.b) - simplex.setObjective(objective); - simplex.solveMin(); - - // Collect results - for (int i = 0; i < variables.size(); ++i) { - variables[i]->sizeAtExpanding = variables[i]->result; - } - } - - delete objective; - qDeleteAll(itemConstraints); -} - -/*! - \internal Returns true if there are no arrangement that satisfies all constraints. Otherwise returns false. diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h index 7dd0d65..3ef37f9 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.h +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -78,66 +78,30 @@ QT_BEGIN_NAMESPACE Represents a vertex (anchorage point) in the internal graph */ struct AnchorVertex { + enum Type { + Normal = 0, + Pair + }; + AnchorVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge) - : m_item(item), m_edge(edge) {} + : m_item(item), m_edge(edge), m_type(Normal) {} AnchorVertex() - : m_item(0), m_edge(Qt::AnchorPoint(0)) {} + : m_item(0), m_edge(Qt::AnchorPoint(0)), m_type(Normal) {} #ifdef QT_DEBUG inline QString toString() const; #endif + QGraphicsLayoutItem *m_item; Qt::AnchorPoint m_edge; + uint m_type : 1; // Current distance from this vertex to the layout edge (Left or Top) // Value is calculated from the current anchors sizes. qreal distance; }; -#ifdef QT_DEBUG -inline QString AnchorVertex::toString() const -{ - if (!this || !m_item) { - return QLatin1String("NULL"); - } - QString edge; - switch (m_edge) { - case Qt::AnchorLeft: - edge = QLatin1String("Left"); - break; - case Qt::AnchorHorizontalCenter: - edge = QLatin1String("HorizontalCenter"); - break; - case Qt::AnchorRight: - edge = QLatin1String("Right"); - break; - case Qt::AnchorTop: - edge = QLatin1String("Top"); - break; - case Qt::AnchorVerticalCenter: - edge = QLatin1String("VerticalCenter"); - break; - case Qt::AnchorBottom: - edge = QLatin1String("Bottom"); - break; - default: - edge = QLatin1String("None"); - break; - } - QString itemName; - if (m_item->isLayout()) { - itemName = QLatin1String("layout"); - } else { - if (QGraphicsItem *item = m_item->graphicsItem()) { - itemName = item->data(0).toString(); - } - } - edge.insert(0, QLatin1String("%1_")); - return edge.arg(itemName); -} -#endif - /*! \internal @@ -150,14 +114,21 @@ struct AnchorData : public QSimplexVariable { Parallel }; + enum Dependency { + Independent = 0, + Master, + Slave + }; + AnchorData() : QSimplexVariable(), item(0), from(0), to(0), - minSize(0), prefSize(0), expSize(0), maxSize(0), + minSize(0), prefSize(0), maxSize(0), sizeAtMinimum(0), sizeAtPreferred(0), - sizeAtExpanding(0), sizeAtMaximum(0), + sizeAtMaximum(0), graphicsAnchor(0), skipInPreferred(0), type(Normal), hasSize(true), isLayoutAnchor(false), - isCenterAnchor(false), orientation(0) {} + isCenterAnchor(false), orientation(0), + dependency(Independent) {} virtual void updateChildrenSizes() {} virtual bool refreshSizeHints(const QLayoutStyleInfo *styleInfo); @@ -194,7 +165,6 @@ struct AnchorData : public QSimplexVariable { // size. qreal minSize; qreal prefSize; - qreal expSize; qreal maxSize; // These attributes define which sizes should that anchor be in when the @@ -202,7 +172,6 @@ struct AnchorData : public QSimplexVariable { // calculated by the Simplex solver based on the current layout setup. qreal sizeAtMinimum; qreal sizeAtPreferred; - qreal sizeAtExpanding; qreal sizeAtMaximum; QGraphicsAnchor *graphicsAnchor; @@ -212,6 +181,7 @@ struct AnchorData : public QSimplexVariable { uint isLayoutAnchor : 1; // if this anchor is an internal layout anchor uint isCenterAnchor : 1; uint orientation : 1; + uint dependency : 2; // either Independent, Master or Slave }; #ifdef QT_DEBUG @@ -250,10 +220,11 @@ struct ParallelAnchorData : public AnchorData type = AnchorData::Parallel; orientation = first->orientation; - // ### Those asserts force that both child anchors have the same direction, - // but can't we simplify a pair of anchors in opposite directions? - Q_ASSERT(first->from == second->from); - Q_ASSERT(first->to == second->to); + // This assert whether the child anchors share their vertices + Q_ASSERT(((first->from == second->from) && (first->to == second->to)) || + ((first->from == second->to) && (first->to == second->from))); + + // We arbitrarily choose the direction of the first child as "our" direction from = first->from; to = first->to; #ifdef QT_DEBUG @@ -268,8 +239,73 @@ struct ParallelAnchorData : public AnchorData AnchorData* firstEdge; AnchorData* secondEdge; + + QList<QSimplexConstraint *> m_firstConstraints; + QList<QSimplexConstraint *> m_secondConstraints; }; +struct AnchorVertexPair : public AnchorVertex { + AnchorVertexPair(AnchorVertex *v1, AnchorVertex *v2, AnchorData *data) + : AnchorVertex(), m_first(v1), m_second(v2), m_removedAnchor(data) { + m_type = AnchorVertex::Pair; + } + + AnchorVertex *m_first; + AnchorVertex *m_second; + + AnchorData *m_removedAnchor; + QList<AnchorData *> m_firstAnchors; + QList<AnchorData *> m_secondAnchors; +}; + +#ifdef QT_DEBUG +inline QString AnchorVertex::toString() const +{ + if (!this) { + return QLatin1String("NULL"); + } else if (m_type == Pair) { + const AnchorVertexPair *vp = static_cast<const AnchorVertexPair *>(this); + return QString::fromAscii("(%1, %2)").arg(vp->m_first->toString()).arg(vp->m_second->toString()); + } else if (!m_item) { + return QString::fromAscii("NULL_%1").arg(int(this)); + } + QString edge; + switch (m_edge) { + case Qt::AnchorLeft: + edge = QLatin1String("Left"); + break; + case Qt::AnchorHorizontalCenter: + edge = QLatin1String("HorizontalCenter"); + break; + case Qt::AnchorRight: + edge = QLatin1String("Right"); + break; + case Qt::AnchorTop: + edge = QLatin1String("Top"); + break; + case Qt::AnchorVerticalCenter: + edge = QLatin1String("VerticalCenter"); + break; + case Qt::AnchorBottom: + edge = QLatin1String("Bottom"); + break; + default: + edge = QLatin1String("None"); + break; + } + QString itemName; + if (m_item->isLayout()) { + itemName = QLatin1String("layout"); + } else { + if (QGraphicsItem *item = m_item->graphicsItem()) { + itemName = item->data(0).toString(); + } + } + edge.insert(0, QLatin1String("%1_")); + return edge.arg(itemName); +} +#endif + /*! \internal @@ -337,8 +373,7 @@ public: // Interval represents which interpolation interval are we operating in. enum Interval { MinToPreferred = 0, - PreferredToExpanding, - ExpandingToMax + PreferredToMax }; // Several structures internal to the layout are duplicated to handle @@ -427,20 +462,33 @@ public: QLayoutStyleInfo &styleInfo() const; - // Activation methods - bool simplifyGraph(Orientation orientation); - bool simplifyGraphIteration(Orientation orientation, bool *feasible); - void restoreSimplifiedGraph(Orientation orientation); + AnchorData *addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible); + // Activation void calculateGraphs(); void calculateGraphs(Orientation orientation); + // Simplification + bool simplifyGraph(Orientation orientation); + bool simplifyVertices(Orientation orientation); + bool simplifyGraphIteration(Orientation orientation, bool *feasible); + + bool replaceVertex(Orientation orientation, AnchorVertex *oldV, + AnchorVertex *newV, const QList<AnchorData *> &edges); + + + void restoreSimplifiedGraph(Orientation orientation); + void restoreSimplifiedAnchor(AnchorData *edge); + void restoreSimplifiedConstraints(ParallelAnchorData *parallel); + void restoreVertices(Orientation orientation); + bool calculateTrunk(Orientation orientation, const GraphPath &trunkPath, const QList<QSimplexConstraint *> &constraints, const QList<AnchorData *> &variables); bool calculateNonTrunk(const QList<QSimplexConstraint *> &constraints, const QList<AnchorData *> &variables); + // Support functions for calculateGraph() bool refreshAllSizeHints(Orientation orientation); void findPaths(Orientation orientation); void constraintsFromPaths(Orientation orientation); @@ -460,6 +508,17 @@ public: return internalVertex(qMakePair(const_cast<QGraphicsLayoutItem *>(item), edge)); } + inline void changeLayoutVertex(Orientation orientation, AnchorVertex *oldV, AnchorVertex *newV) + { + if (layoutFirstVertex[orientation] == oldV) + layoutFirstVertex[orientation] = newV; + else if (layoutCentralVertex[orientation] == oldV) + layoutCentralVertex[orientation] = newV; + else if (layoutLastVertex[orientation] == oldV) + layoutLastVertex[orientation] = newV; + } + + AnchorVertex *addInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); void removeInternalVertex(QGraphicsLayoutItem *item, Qt::AnchorPoint edge); @@ -468,19 +527,15 @@ public: void calculateVertexPositions(Orientation orientation); void setupEdgesInterpolation(Orientation orientation); - void interpolateEdge(AnchorVertex *base, AnchorData *edge, Orientation orientation); - void interpolateSequentialEdges(AnchorVertex *base, SequentialAnchorData *edge, - Orientation orientation); - void interpolateParallelEdges(AnchorVertex *base, ParallelAnchorData *edge, - Orientation orientation); + void interpolateEdge(AnchorVertex *base, AnchorData *edge); + void interpolateSequentialEdges(SequentialAnchorData *edge); + void interpolateParallelEdges(ParallelAnchorData *edge); // Linear Programming solver methods bool solveMinMax(const QList<QSimplexConstraint *> &constraints, GraphPath path, qreal *min, qreal *max); bool solvePreferred(const QList<QSimplexConstraint *> &constraints, const QList<AnchorData *> &variables); - void solveExpanding(const QList<QSimplexConstraint *> &constraints, - const QList<AnchorData *> &variables); bool hasConflicts() const; #ifdef QT_DEBUG @@ -491,7 +546,6 @@ public: qreal spacings[NOrientations]; // Size hints from simplex engine qreal sizeHints[2][3]; - qreal sizeAtExpanding[2]; // Items QVector<QGraphicsLayoutItem *> items; @@ -504,6 +558,14 @@ public: // Internal graph of anchorage points and anchors, for both orientations Graph<AnchorVertex, AnchorData> graph[2]; + AnchorVertex *layoutFirstVertex[2]; + AnchorVertex *layoutCentralVertex[2]; + AnchorVertex *layoutLastVertex[2]; + + // Combined anchors in order of creation + QList<AnchorVertexPair *> simplifiedVertices[2]; + QList<AnchorData *> anchorsFromSimplifiedVertices[2]; + // Graph paths and constraints, for both orientations QMultiHash<AnchorVertex *, GraphPath> graphPaths[2]; QList<QSimplexConstraint *> constraints[2]; diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp index 52d7020..8474e47 100644 --- a/src/gui/graphicsview/qgraphicsitem.cpp +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -386,6 +386,12 @@ introduced in Qt 4.6. \omitvalue ItemIsFocusScope Internal only (for now). + + \value ItemSendsScenePositionChanges The item enables itemChange() + notifications for ItemScenePositionHasChanged. For performance reasons, + these notifications are disabled by default. You must enable this flag + to receive notifications for scene position changes. This flag was + introduced in Qt 4.6. */ /*! @@ -562,6 +568,14 @@ \value ItemOpacityHasChanged The item's opacity has changed. The value argument is the new opacity (i.e., a double). Do not call setOpacity() as this notification is delivered. The return value is ignored. + + \value ItemScenePositionHasChanged The item's scene position has changed. + This notification is sent if the ItemSendsScenePositionChanges flag is + enabled, and after the item's scene position has changed (i.e., the + position or transformation of the item itself or the position or + transformation of any ancestor has changed). The value argument is the + new scene position (the same as scenePos()), and QGraphicsItem ignores + the return value for this notification (i.e., a read-only notification). */ /*! @@ -990,6 +1004,10 @@ void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent) if (scene) { // Deliver the change to the index scene->d_func()->index->itemChange(q, QGraphicsItem::ItemParentChange, newParentVariant); + + // Disable scene pos notifications for old ancestors + if (scenePosDescendants || (flags & QGraphicsItem::ItemSendsScenePositionChanges)) + scene->d_func()->setScenePosItemEnabled(q, false); } if (subFocusItem && parent) { @@ -1084,10 +1102,15 @@ void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent) parent->d_ptr->addChild(q); parent->itemChange(QGraphicsItem::ItemChildAddedChange, thisPointerVariant); - if (!implicitUpdate && scene) { - scene->d_func()->markDirty(q_ptr, QRect(), - /*invalidateChildren=*/false, - /*maybeDirtyClipPath=*/true); + if (scene) { + if (!implicitUpdate) + scene->d_func()->markDirty(q_ptr, QRect(), + /*invalidateChildren=*/false, + /*maybeDirtyClipPath=*/true); + + // Re-enable scene pos notifications for new ancestors + if (scenePosDescendants || (flags & QGraphicsItem::ItemSendsScenePositionChanges)) + scene->d_func()->setScenePosItemEnabled(q, true); } // Inherit ancestor flags from the new parent. @@ -1336,7 +1359,9 @@ QGraphicsItem::~QGraphicsItem() d_ptr->setParentItemHelper(0); } +#ifndef QT_NO_GRAPHICSEFFECT delete d_ptr->graphicsEffect; +#endif //QT_NO_GRAPHICSEFFECT if (d_ptr->transformData) { for(int i = 0; i < d_ptr->transformData->graphicsTransforms.size(); ++i) { QGraphicsTransform *t = d_ptr->transformData->graphicsTransforms.at(i); @@ -1746,6 +1771,12 @@ void QGraphicsItem::setFlags(GraphicsItemFlags flags) } if (d_ptr->scene) { + if ((flags & ItemSendsScenePositionChanges) != (oldFlags & ItemSendsScenePositionChanges)) { + if (flags & ItemSendsScenePositionChanges) + d_ptr->scene->d_func()->registerScenePosItem(this); + else + d_ptr->scene->d_func()->unregisterScenePosItem(this); + } d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true, /*maybeDirtyClipPath*/true); @@ -2506,7 +2537,9 @@ void QGraphicsItem::setOpacity(qreal opacity) // Update. if (d_ptr->scene) { +#ifndef QT_NO_GRAPHICSEFFECT d_ptr->invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true, /*maybeDirtyClipPath=*/false, @@ -2523,6 +2556,7 @@ void QGraphicsItem::setOpacity(qreal opacity) \since 4.6 */ +#ifndef QT_NO_GRAPHICSEFFECT QGraphicsEffect *QGraphicsItem::graphicsEffect() const { return d_ptr->graphicsEffect; @@ -2569,6 +2603,7 @@ void QGraphicsItem::setGraphicsEffect(QGraphicsEffect *effect) prepareGeometryChange(); } +#endif //QT_NO_GRAPHICSEFFECT /*! \internal @@ -2582,6 +2617,7 @@ void QGraphicsItem::setGraphicsEffect(QGraphicsEffect *effect) */ QRectF QGraphicsItemPrivate::effectiveBoundingRect() const { +#ifndef QT_NO_GRAPHICSEFFECT QGraphicsEffect *effect = graphicsEffect; QRectF brect = effect && effect->isEnabled() ? effect->boundingRect() : q_ptr->boundingRect(); if (ancestorFlags & QGraphicsItemPrivate::AncestorClipsChildren) @@ -2598,6 +2634,10 @@ QRectF QGraphicsItemPrivate::effectiveBoundingRect() const } return brect; +#else //QT_NO_GRAPHICSEFFECT + return q_ptr->boundingRect(); +#endif //QT_NO_GRAPHICSEFFECT + } /*! @@ -2951,7 +2991,7 @@ bool QGraphicsItem::hasFocus() const { if (d_ptr->focusProxy) return d_ptr->focusProxy->hasFocus(); - return (d_ptr->scene && d_ptr->scene->focusItem() == this); + return isActive() && (d_ptr->scene && d_ptr->scene->focusItem() == this); } /*! @@ -3422,6 +3462,7 @@ void QGraphicsItem::setPos(const QPointF &pos) // Send post-notification. itemChange(QGraphicsItem::ItemPositionHasChanged, newPosVariant); + d_ptr->sendScenePosChange(); } /*! @@ -4032,6 +4073,7 @@ void QGraphicsItem::setTransform(const QTransform &matrix, bool combine) // Send post-notification. itemChange(ItemTransformHasChanged, newTransformVariant); + d_ptr->sendScenePosChange(); } /*! @@ -4256,6 +4298,24 @@ void QGraphicsItemPrivate::ensureSequentialSiblingIndex() } /*! + \internal +*/ +inline void QGraphicsItemPrivate::sendScenePosChange() +{ + Q_Q(QGraphicsItem); + if (scene) { + if (flags & QGraphicsItem::ItemSendsScenePositionChanges) + q->itemChange(QGraphicsItem::ItemScenePositionHasChanged, q->scenePos()); + if (scenePosDescendants) { + foreach (QGraphicsItem *item, scene->d_func()->scenePosItems) { + if (q->isAncestorOf(item)) + item->itemChange(QGraphicsItem::ItemScenePositionHasChanged, item->scenePos()); + } + } + } +} + +/*! \since 4.6 Stacks this item before \a sibling, which must be a sibling item (i.e., the @@ -4306,6 +4366,12 @@ void QGraphicsItem::stackBefore(const QGraphicsItem *sibling) ++index; } d_ptr->siblingIndex = siblingIndex; + for (int i = 0; i < siblings->size(); ++i) { + int &index = siblings->at(i)->d_ptr->siblingIndex; + if (i != siblingIndex && index >= siblingIndex && index <= myIndex) + siblings->at(i)->d_ptr->siblingOrderChange(); + } + d_ptr->siblingOrderChange(); } } @@ -4987,6 +5053,7 @@ int QGraphicsItemPrivate::depth() const /*! \internal */ +#ifndef QT_NO_GRAPHICSEFFECT void QGraphicsItemPrivate::invalidateGraphicsEffectsRecursively() { QGraphicsItemPrivate *itemPrivate = this; @@ -4999,6 +5066,7 @@ void QGraphicsItemPrivate::invalidateGraphicsEffectsRecursively() } } while ((itemPrivate = itemPrivate->parent ? itemPrivate->parent->d_ptr.data() : 0)); } +#endif //QT_NO_GRAPHICSEFFECT /*! \internal @@ -5303,6 +5371,16 @@ void QGraphicsItemPrivate::subFocusItemChange() /*! \internal + Subclasses can reimplement this function to be notified when its + siblingIndex order is changed. +*/ +void QGraphicsItemPrivate::siblingOrderChange() +{ +} + +/*! + \internal + Tells us if it is a proxy widget */ bool QGraphicsItemPrivate::isProxyWidget() const @@ -5334,7 +5412,9 @@ void QGraphicsItem::update(const QRectF &rect) return; // Make sure we notify effects about invalidated source. +#ifndef QT_NO_GRAPHICSEFFECT d_ptr->invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT if (CacheMode(d_ptr->cacheMode) != NoCache) { // Invalidate cache. @@ -7353,19 +7433,32 @@ QGraphicsObject::QGraphicsObject(QGraphicsItemPrivate &dd, QGraphicsItem *parent } /*! - Subscribes the graphics object to the given \a gesture for the specified \a context. + Subscribes the graphics object to the given \a gesture with specific \a flags. - \sa QGestureEvent + \sa ungrabGesture(), QGestureEvent */ - -void QGraphicsObject::grabGesture(Qt::GestureType gesture, Qt::GestureContext context) +void QGraphicsObject::grabGesture(Qt::GestureType gesture, Qt::GestureFlags flags) { QGraphicsItemPrivate * const d = QGraphicsItem::d_func(); - d->gestureContext.insert(gesture, context); + d->gestureContext.insert(gesture, flags); (void)QGestureManager::instance(); // create a gesture manager } /*! + Unsubscribes the graphics object from the given \a gesture. + + \sa grabGesture(), QGestureEvent +*/ +void QGraphicsObject::ungrabGesture(Qt::GestureType gesture) +{ + QGraphicsItemPrivate * const d = QGraphicsItem::d_func(); + if (d->gestureContext.remove(gesture)) { + QGestureManager *manager = QGestureManager::instance(); + manager->cleanupCachedGestures(this, gesture); + } +} + +/*! \property QGraphicsObject::parent \brief the parent of the item @@ -10675,6 +10768,7 @@ int QGraphicsItemGroup::type() const return Type; } +#ifndef QT_NO_GRAPHICSEFFECT QRectF QGraphicsItemEffectSourcePrivate::boundingRect(Qt::CoordinateSystem system) const { const bool deviceCoordinates = (system == Qt::DeviceCoordinates); @@ -10814,6 +10908,7 @@ QPixmap QGraphicsItemEffectSourcePrivate::pixmap(Qt::CoordinateSystem system, QP return pixmap; } +#endif //QT_NO_GRAPHICSEFFECT #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug debug, QGraphicsItem *item) @@ -10937,6 +11032,9 @@ QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemChange change) case QGraphicsItem::ItemOpacityHasChanged: str = "ItemOpacityHasChanged"; break; + case QGraphicsItem::ItemScenePositionHasChanged: + str = "ItemScenePositionHasChanged"; + break; } debug << str; return debug; @@ -10994,6 +11092,9 @@ QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag) case QGraphicsItem::ItemIsFocusScope: str = "ItemIsFocusScope"; break; + case QGraphicsItem::ItemSendsScenePositionChanges: + str = "ItemSendsScenePositionChanges"; + break; } debug << str; return debug; diff --git a/src/gui/graphicsview/qgraphicsitem.h b/src/gui/graphicsview/qgraphicsitem.h index f3fe99c..8bbe9f1 100644 --- a/src/gui/graphicsview/qgraphicsitem.h +++ b/src/gui/graphicsview/qgraphicsitem.h @@ -105,7 +105,8 @@ public: ItemAcceptsInputMethod = 0x1000, ItemNegativeZStacksBehindParent = 0x2000, ItemIsPanel = 0x4000, - ItemIsFocusScope = 0x8000 // internal + ItemIsFocusScope = 0x8000, // internal + ItemSendsScenePositionChanges = 0x10000 // NB! Don't forget to increase the d_ptr->flags bit field by 1 when adding a new flag. }; Q_DECLARE_FLAGS(GraphicsItemFlags, GraphicsItemFlag) @@ -137,7 +138,8 @@ public: ItemZValueChange, ItemZValueHasChanged, ItemOpacityChange, - ItemOpacityHasChanged + ItemOpacityHasChanged, + ItemScenePositionHasChanged }; enum CacheMode { @@ -225,9 +227,11 @@ public: qreal effectiveOpacity() const; void setOpacity(qreal opacity); +#ifndef QT_NO_GRAPHICSEFFECT // Effect QGraphicsEffect *graphicsEffect() const; void setGraphicsEffect(QGraphicsEffect *effect); +#endif //QT_NO_GRAPHICSEFFECT Qt::MouseButtons acceptedMouseButtons() const; void setAcceptedMouseButtons(Qt::MouseButtons buttons); @@ -555,7 +559,8 @@ public: using QObject::children; #endif - void grabGesture(Qt::GestureType type, Qt::GestureContext context = Qt::ItemWithChildrenGesture); + void grabGesture(Qt::GestureType type, Qt::GestureFlags flags = Qt::GestureFlags()); + void ungrabGesture(Qt::GestureType type); Q_SIGNALS: void parentChanged(); diff --git a/src/gui/graphicsview/qgraphicsitem_p.h b/src/gui/graphicsview/qgraphicsitem_p.h index 395376a..54f7515 100644 --- a/src/gui/graphicsview/qgraphicsitem_p.h +++ b/src/gui/graphicsview/qgraphicsitem_p.h @@ -179,6 +179,7 @@ public: holesInSiblingIndex(0), sequentialOrdering(1), updateDueToGraphicsEffect(0), + scenePosDescendants(0), globalStackingOrder(-1), q_ptr(0) { @@ -223,7 +224,9 @@ public: bool discardUpdateRequest(bool ignoreClipping = false, bool ignoreVisibleBit = false, bool ignoreDirtyBit = false, bool ignoreOpacity = false) const; int depth() const; +#ifndef QT_NO_GRAPHICSEFFECT void invalidateGraphicsEffectsRecursively(); +#endif //QT_NO_GRAPHICSEFFECT void invalidateDepthRecursively(); void resolveDepth(); void addChild(QGraphicsItem *child); @@ -429,6 +432,8 @@ public: inline void ensureSortedChildren(); static inline bool insertionOrder(QGraphicsItem *a, QGraphicsItem *b); void ensureSequentialSiblingIndex(); + inline void sendScenePosChange(); + virtual void siblingOrderChange(); QPainterPath cachedClipPath; QRectF childrenBoundingRect; @@ -453,7 +458,7 @@ public: QGraphicsItem *focusScopeItem; Qt::InputMethodHints imHints; QGraphicsItem::PanelModality panelModality; - QMap<Qt::GestureType, Qt::GestureContext> gestureContext; + QMap<Qt::GestureType, Qt::GestureFlags> gestureContext; // Packed 32 bits quint32 acceptedMouseButtons : 5; @@ -483,7 +488,7 @@ public: // Packed 32 bits quint32 fullUpdatePending : 1; - quint32 flags : 16; + quint32 flags : 17; quint32 dirtyChildrenBoundingRect : 1; quint32 paintedViewBoundingRectsNeedRepaint : 1; quint32 dirtySceneTransform : 1; @@ -498,14 +503,15 @@ public: quint32 sceneTransformTranslateOnly : 1; quint32 notifyBoundingRectChanged : 1; quint32 notifyInvalidated : 1; - quint32 mouseSetsFocus : 1; // New 32 bits + quint32 mouseSetsFocus : 1; quint32 explicitActivate : 1; quint32 wantsActive : 1; quint32 holesInSiblingIndex : 1; quint32 sequentialOrdering : 1; quint32 updateDueToGraphicsEffect : 1; + quint32 scenePosDescendants : 1; // Optional stacking order int globalStackingOrder; @@ -577,6 +583,7 @@ struct QGraphicsItemPaintInfo quint32 drawItem : 1; }; +#ifndef QT_NO_GRAPHICSEFFECT class QGraphicsItemEffectSourcePrivate : public QGraphicsEffectSourcePrivate { public: @@ -604,8 +611,9 @@ public: inline bool isPixmap() const { - return (item->type() == QGraphicsPixmapItem::Type); - //|| (item->d_ptr->isObject && qobject_cast<QmlGraphicsImage *>(q_func())); + return item->type() == QGraphicsPixmapItem::Type + && !(item->flags() & QGraphicsItem::ItemIsSelectable) + && item->d_ptr->children.size() == 0; } inline const QStyleOption *styleOption() const @@ -630,7 +638,7 @@ public: QGraphicsItemPaintInfo *info; QTransform lastEffectTransform; }; - +#endif //QT_NO_GRAPHICSEFFECT /*! Returns true if \a item1 is on top of \a item2. diff --git a/src/gui/graphicsview/qgraphicslinearlayout.cpp b/src/gui/graphicsview/qgraphicslinearlayout.cpp index 5684f0e..cb68741 100644 --- a/src/gui/graphicsview/qgraphicslinearlayout.cpp +++ b/src/gui/graphicsview/qgraphicslinearlayout.cpp @@ -542,6 +542,9 @@ void QGraphicsLinearLayout::invalidate() QGraphicsLayout::invalidate(); } +/*! + \internal +*/ void QGraphicsLinearLayout::dump(int indent) const { #ifdef QT_DEBUG diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index f982f4b..d1845c2 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -294,6 +294,7 @@ QGraphicsScenePrivate::QGraphicsScenePrivate() needSortTopLevelItems(true), holesInTopLevelSiblingIndex(false), topLevelSequentialOrdering(true), + scenePosDescendantsUpdatePending(false), stickyFocus(false), hasFocus(false), focusItem(0), @@ -488,6 +489,55 @@ void QGraphicsScenePrivate::_q_processDirtyItems() /*! \internal +*/ +void QGraphicsScenePrivate::setScenePosItemEnabled(QGraphicsItem *item, bool enabled) +{ + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->scenePosDescendants = enabled; + p = p->d_ptr->parent; + } + if (!enabled && !scenePosDescendantsUpdatePending) { + scenePosDescendantsUpdatePending = true; + QMetaObject::invokeMethod(q_func(), "_q_updateScenePosDescendants", Qt::QueuedConnection); + } +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::registerScenePosItem(QGraphicsItem *item) +{ + scenePosItems.insert(item); + setScenePosItemEnabled(item, true); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::unregisterScenePosItem(QGraphicsItem *item) +{ + scenePosItems.remove(item); + setScenePosItemEnabled(item, false); +} + +/*! + \internal +*/ +void QGraphicsScenePrivate::_q_updateScenePosDescendants() +{ + foreach (QGraphicsItem *item, scenePosItems) { + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + p->d_ptr->scenePosDescendants = 1; + p = p->d_ptr->parent; + } + } + scenePosDescendantsUpdatePending = false; +} + +/*! + \internal Schedules an item for removal. This function leaves some stale indexes around in the BSP tree if called from the item's destructor; these will @@ -523,6 +573,9 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) widget->d_func()->fixFocusChainBeforeReparenting(0, 0); } + if (item->flags() & QGraphicsItem::ItemSendsScenePositionChanges) + unregisterScenePosItem(item); + item->d_func()->scene = 0; //We need to remove all children first because they might use their parent @@ -1064,9 +1117,8 @@ bool QGraphicsScenePrivate::filterEvent(QGraphicsItem *item, QEvent *event) bool QGraphicsScenePrivate::sendEvent(QGraphicsItem *item, QEvent *event) { if (QGraphicsObject *object = item->toGraphicsObject()) { - QApplicationPrivate *qAppPriv = QApplicationPrivate::instance(); - if (qAppPriv->gestureManager) { - if (qAppPriv->gestureManager->filterEvent(object, event)) + if (qt_gestureManager) { + if (qt_gestureManager->filterEvent(object, event)) return true; } } @@ -2294,8 +2346,9 @@ void QGraphicsScene::clear() // NB! We have to clear the index before deleting items; otherwise the // index might try to access dangling item pointers. d->index->clear(); - const QList<QGraphicsItem *> items = d->topLevelItems; - qDeleteAll(items); + // NB! QGraphicsScenePrivate::unregisterTopLevelItem() removes items + while (!d->topLevelItems.isEmpty()) + delete d->topLevelItems.first(); Q_ASSERT(d->topLevelItems.isEmpty()); d->lastItemCount = 0; d->allItemsIgnoreHoverEvents = true; @@ -2541,6 +2594,9 @@ void QGraphicsScene::addItem(QGraphicsItem *item) } } + if (item->flags() & QGraphicsItem::ItemSendsScenePositionChanges) + d->registerScenePosItem(item); + // Ensure that newly added items that have subfocus set, gain // focus automatically if there isn't a focus item already. if (!d->focusItem && item != d->lastFocusItem && item->focusItem() == item) @@ -2827,18 +2883,20 @@ void QGraphicsScene::removeItem(QGraphicsItem *item) } /*! - Returns the scene's current focus item, or 0 if no item currently has - focus. + When the scene is active, this functions returns the scene's current focus + item, or 0 if no item currently has focus. When the scene is inactive, this + functions returns the item that will gain input focus when the scene becomes + active. The focus item receives keyboard input when the scene receives a key event. - \sa setFocusItem(), QGraphicsItem::hasFocus() + \sa setFocusItem(), QGraphicsItem::hasFocus(), isActive() */ QGraphicsItem *QGraphicsScene::focusItem() const { Q_D(const QGraphicsScene); - return d->focusItem; + return isActive() ? d->focusItem : d->lastFocusItem; } /*! @@ -4578,6 +4636,7 @@ void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter * if (itemHasChildren && itemClipsChildrenToShape) ENSURE_TRANSFORM_PTR; +#ifndef QT_NO_GRAPHICSEFFECT if (item->d_ptr->graphicsEffect && item->d_ptr->graphicsEffect->isEnabled()) { ENSURE_TRANSFORM_PTR; QGraphicsItemPaintInfo info(viewTransform, transformPtr, effectTransform, exposedRegion, widget, &styleOptionTmp, @@ -4600,7 +4659,9 @@ void QGraphicsScenePrivate::drawSubtreeRecursive(QGraphicsItem *item, QPainter * item->d_ptr->graphicsEffect->draw(painter, source); painter->setWorldTransform(restoreTransform); sourced->info = 0; - } else { + } else +#endif //QT_NO_GRAPHICSEFFECT + { draw(item, painter, viewTransform, transformPtr, exposedRegion, widget, opacity, effectTransform, wasDirtyParentSceneTransform, drawItem); } @@ -4769,10 +4830,12 @@ void QGraphicsScenePrivate::markDirty(QGraphicsItem *item, const QRectF &rect, b QGraphicsItem *p = item->d_ptr->parent; while (p) { p->d_ptr->dirtyChildren = 1; +#ifndef QT_NO_GRAPHICSEFFECT if (p->d_ptr->graphicsEffect && p->d_ptr->graphicsEffect->isEnabled()) { p->d_ptr->dirty = 1; p->d_ptr->fullUpdatePending = 1; } +#endif //QT_NO_GRAPHICSEFFECT p = p->d_ptr->parent; } } @@ -5765,7 +5828,7 @@ void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) QWidget *viewport = event->widget(); if (!viewport) return; - QList<QGesture *> allGestures = event->allGestures(); + QList<QGesture *> allGestures = event->gestures(); DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" << "Delivering gestures:" << allGestures; @@ -5904,7 +5967,12 @@ void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) QGraphicsItemPrivate *gid = item->QGraphicsItem::d_func(); foreach(QGesture *g, alreadyIgnoredGestures) { - if (gid->gestureContext.contains(g->gestureType())) + QMap<Qt::GestureType, Qt::GestureFlags>::iterator contextit = + gid->gestureContext.find(g->gestureType()); + bool deliver = contextit != gid->gestureContext.end() && + (g->state() == Qt::GestureStarted || + (contextit.value() & Qt::ReceivePartialGestures)); + if (deliver) gestures += g; } if (gestures.isEmpty()) @@ -5917,8 +5985,12 @@ void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) sendEvent(item, &ev); QSet<QGesture *> ignoredGestures; foreach (QGesture *g, gestures) { - if (!ev.isAccepted() && !ev.isAccepted(g)) + if (!ev.isAccepted() && !ev.isAccepted(g)) { ignoredGestures.insert(g); + } else { + if (g->state() == Qt::GestureStarted) + gestureTargets[g] = item; + } } if (!ignoredGestures.isEmpty()) { // get a list of items under the (current) hotspot of each ignored @@ -5945,6 +6017,13 @@ void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) continue; } } + foreach (QGesture *g, startedGestures) { + if (g->gestureCancelPolicy() == QGesture::CancelAllInContext) { + DEBUG() << "lets try to cancel some"; + // find gestures in context in Qt::GestureStarted or Qt::GestureUpdated state and cancel them + cancelGesturesForChildren(g, event->widget()); + } + } // forget about targets for gestures that have ended foreach (QGesture *g, allGestures) { @@ -5959,6 +6038,88 @@ void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) } } +void QGraphicsScenePrivate::cancelGesturesForChildren(QGesture *original, QWidget *viewport) +{ + Q_ASSERT(original); + QGraphicsItem *originalItem = gestureTargets.value(original); + Q_ASSERT(originalItem); + + // iterate over all active gestures and for each find the owner + // if the owner is part of our sub-hierarchy, cancel it. + + QSet<QGesture *> canceledGestures; + QHash<QGesture *, QGraphicsObject *>::Iterator iter = gestureTargets.begin(); + while (iter != gestureTargets.end()) { + QGraphicsObject *item = iter.value(); + // note that we don't touch the gestures for our originalItem + if (item != originalItem && originalItem->isAncestorOf(item)) { + DEBUG() << " found a gesture to cancel" << iter.key(); + iter.key()->d_func()->state = Qt::GestureCanceled; + canceledGestures << iter.key(); + } + ++iter; + } + + // sort them per target item by cherry picking from almostCanceledGestures and delivering + QSet<QGesture *> almostCanceledGestures = canceledGestures; + QSet<QGesture *>::Iterator setIter; + while (!almostCanceledGestures.isEmpty()) { + QGraphicsObject *target = 0; + QSet<QGesture*> gestures; + setIter = almostCanceledGestures.begin(); + // sort per target item + while (setIter != almostCanceledGestures.end()) { + QGraphicsObject *item = gestureTargets.value(*setIter); + if (target == 0) + target = item; + if (target == item) { + gestures << *setIter; + setIter = almostCanceledGestures.erase(setIter); + } else { + ++setIter; + } + } + Q_ASSERT(target); + + QList<QGesture *> list = gestures.toList(); + QGestureEvent ev(list); + sendEvent(target, &ev); + + foreach (QGesture *g, list) { + if (ev.isAccepted() || ev.isAccepted(g)) + gestures.remove(g); + } + + foreach (QGesture *g, gestures) { + if (!g->hasHotSpot()) + continue; + + QPoint screenPos = g->hotSpot().toPoint(); + QList<QGraphicsItem *> items = itemsAtPosition(screenPos, QPointF(), viewport); + for (int j = 0; j < items.size(); ++j) { + QGraphicsObject *item = items.at(j)->toGraphicsObject(); + if (!item) + continue; + QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); + if (d->gestureContext.contains(g->gestureType())) { + QList<QGesture *> list; + list << g; + QGestureEvent ev(list); + sendEvent(item, &ev); + if (ev.isAccepted() || ev.isAccepted(g)) + break; // successfully delivered + } + } + } + } + + Q_ASSERT(qt_gestureManager); // it would be very odd if we got called without a manager. + for (setIter = canceledGestures.begin(); setIter != canceledGestures.end(); ++setIter) { + qt_gestureManager->recycle(*setIter); + gestureTargets.remove(*setIter); + } +} + QT_END_NAMESPACE #include "moc_qgraphicsscene.cpp" diff --git a/src/gui/graphicsview/qgraphicsscene.h b/src/gui/graphicsview/qgraphicsscene.h index d6d48d7..a47574e 100644 --- a/src/gui/graphicsview/qgraphicsscene.h +++ b/src/gui/graphicsview/qgraphicsscene.h @@ -299,6 +299,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_emitUpdated()) Q_PRIVATE_SLOT(d_func(), void _q_polishItems()) Q_PRIVATE_SLOT(d_func(), void _q_processDirtyItems()) + Q_PRIVATE_SLOT(d_func(), void _q_updateScenePosDescendants()) friend class QGraphicsItem; friend class QGraphicsItemPrivate; friend class QGraphicsView; diff --git a/src/gui/graphicsview/qgraphicsscene_p.h b/src/gui/graphicsview/qgraphicsscene_p.h index cd20fd0..fdec466 100644 --- a/src/gui/graphicsview/qgraphicsscene_p.h +++ b/src/gui/graphicsview/qgraphicsscene_p.h @@ -122,6 +122,13 @@ public: void _q_processDirtyItems(); + QSet<QGraphicsItem *> scenePosItems; + bool scenePosDescendantsUpdatePending; + void setScenePosItemEnabled(QGraphicsItem *item, bool enabled); + void registerScenePosItem(QGraphicsItem *item); + void unregisterScenePosItem(QGraphicsItem *item); + void _q_updateScenePosDescendants(); + void removeItemHelper(QGraphicsItem *item); QBrush backgroundBrush; @@ -234,6 +241,7 @@ public: item->d_ptr->fullUpdatePending = 0; item->d_ptr->ignoreVisible = 0; item->d_ptr->ignoreOpacity = 0; +#ifndef QT_NO_GRAPHICSEFFECT QGraphicsEffect::ChangeFlags flags; if (item->d_ptr->notifyBoundingRectChanged) { flags |= QGraphicsEffect::SourceBoundingRectChanged; @@ -243,12 +251,15 @@ public: flags |= QGraphicsEffect::SourceInvalidated; item->d_ptr->notifyInvalidated = 0; } +#endif //QT_NO_GRAPHICSEFFECT if (recursive) { for (int i = 0; i < item->d_ptr->children.size(); ++i) resetDirtyItem(item->d_ptr->children.at(i), recursive); } +#ifndef QT_NO_GRAPHICSEFFECT if (flags && item->d_ptr->graphicsEffect) item->d_ptr->graphicsEffect->sourceChanged(flags); +#endif //QT_NO_GRAPHICSEFFECT } inline void ensureSortedTopLevelItems() @@ -288,6 +299,7 @@ public: QMap<Qt::GestureType, QGesture *> *conflictedGestures, QList<QList<QGraphicsObject *> > *conflictedItems, QHash<QGesture *, QGraphicsObject *> *normalGestures); + void cancelGesturesForChildren(QGesture *original, QWidget *viewport); void updateInputMethodSensitivityInViews(); diff --git a/src/gui/graphicsview/qgraphicsview.cpp b/src/gui/graphicsview/qgraphicsview.cpp index 9856b1a..87585a2 100644 --- a/src/gui/graphicsview/qgraphicsview.cpp +++ b/src/gui/graphicsview/qgraphicsview.cpp @@ -281,6 +281,7 @@ static const int QGRAPHICSVIEW_PREALLOC_STYLE_OPTIONS = 503; // largest prime < #include <QtGui/qstyleoption.h> #include <QtGui/qinputcontext.h> #ifdef Q_WS_X11 +#include <QtGui/qpaintengine.h> #include <private/qt_x11_p.h> #endif @@ -1511,6 +1512,11 @@ void QGraphicsView::setScene(QGraphicsScene *scene) this, SLOT(updateSceneRect(QRectF))); d->scene->d_func()->removeView(this); d->connectedToScene = false; + + if (isActiveWindow() && isVisible()) { + QEvent windowDeactivate(QEvent::WindowDeactivate); + QApplication::sendEvent(d->scene, &windowDeactivate); + } } // Assign the new scene and update the contents (scrollbars, etc.)). @@ -1532,6 +1538,11 @@ void QGraphicsView::setScene(QGraphicsScene *scene) // enable touch events if any items is interested in them if (!d->scene->d_func()->allItemsIgnoreTouchEvents) d->viewport->setAttribute(Qt::WA_AcceptTouchEvents); + + if (isActiveWindow() && isVisible()) { + QEvent windowActivate(QEvent::WindowActivate); + QApplication::sendEvent(d->scene, &windowActivate); + } } else { d->recalculateContentSize(); } @@ -2637,6 +2648,19 @@ bool QGraphicsView::viewportEvent(QEvent *event) d->scene->d_func()->removePopup(d->scene->d_func()->popupWidgets.first()); QApplication::sendEvent(d->scene, event); break; + case QEvent::Show: + if (d->scene && isActiveWindow()) { + QEvent windowActivate(QEvent::WindowActivate); + QApplication::sendEvent(d->scene, &windowActivate); + } + break; + case QEvent::Hide: + // spontaneous event will generate a WindowDeactivate. + if (!event->spontaneous() && d->scene && isActiveWindow()) { + QEvent windowDeactivate(QEvent::WindowDeactivate); + QApplication::sendEvent(d->scene, &windowDeactivate); + } + break; case QEvent::Leave: // ### This is a temporary fix for until we get proper mouse grab // events. activeMouseGrabberItem should be set to 0 if we lose the @@ -3257,7 +3281,7 @@ void QGraphicsView::paintEvent(QPaintEvent *event) // Set up the painter QPainter painter(viewport()); - painter.setClipRect(exposedSceneRect, Qt::IntersectClip); + painter.setClipRect(event->rect(), Qt::IntersectClip); #ifndef QT_NO_RUBBERBAND if (d->rubberBanding && !d->rubberBandRect.isEmpty()) painter.save(); @@ -3297,7 +3321,12 @@ void QGraphicsView::paintEvent(QPaintEvent *event) backgroundPainter.setClipRegion(d->backgroundPixmapExposed, Qt::ReplaceClip); if (viewTransformed) backgroundPainter.setTransform(viewTransform); - backgroundPainter.setCompositionMode(QPainter::CompositionMode_Source); +#ifdef Q_WS_X11 +#undef X11 + if (backgroundPainter.paintEngine()->type() != QPaintEngine::X11) +#define X11 qt_x11Data +#endif + backgroundPainter.setCompositionMode(QPainter::CompositionMode_Source); drawBackground(&backgroundPainter, exposedSceneRect); d->backgroundPixmapExposed = QRegion(); } diff --git a/src/gui/graphicsview/qgraphicswidget.cpp b/src/gui/graphicsview/qgraphicswidget.cpp index d70a281..d9c65bb 100644 --- a/src/gui/graphicsview/qgraphicswidget.cpp +++ b/src/gui/graphicsview/qgraphicswidget.cpp @@ -318,6 +318,12 @@ void QGraphicsWidget::resize(const QSizeF &size) */ /*! + \property QGraphicsWidget::sizePolicy + \brief the size policy for the widget + \sa sizePolicy(), setSizePolicy(), QWidget::sizePolicy() +*/ + +/*! \property QGraphicsWidget::geometry \brief the geometry of the widget @@ -410,6 +416,27 @@ void QGraphicsWidget::setGeometry(const QRectF &rect) */ /*! + \property QGraphicsWidget::minimumSize + \brief the minimum size of the widget + + \sa setMinimumSize(), minimumSize(), preferredSize, maximumSize +*/ + +/*! + \property QGraphicsWidget::preferredSize + \brief the preferred size of the widget + + \sa setPreferredSize(), preferredSize(), minimumSize, maximumSize +*/ + +/*! + \property QGraphicsWidget::maximumSize + \brief the maximum size of the widget + + \sa setMaximumSize(), maximumSize(), minimumSize, preferredSize +*/ + +/*! Sets the widget's contents margins to \a left, \a top, \a right and \a bottom. diff --git a/src/gui/graphicsview/qgraphicswidget.h b/src/gui/graphicsview/qgraphicswidget.h index 9c71140..05d3a49 100644 --- a/src/gui/graphicsview/qgraphicswidget.h +++ b/src/gui/graphicsview/qgraphicswidget.h @@ -74,6 +74,10 @@ class Q_GUI_EXPORT QGraphicsWidget : public QGraphicsObject, public QGraphicsLay Q_PROPERTY(QFont font READ font WRITE setFont) Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection RESET unsetLayoutDirection) Q_PROPERTY(QSizeF size READ size WRITE resize) + Q_PROPERTY(QSizeF minimumSize READ minimumSize WRITE setMinimumSize) + Q_PROPERTY(QSizeF preferredSize READ preferredSize WRITE setPreferredSize) + Q_PROPERTY(QSizeF maximumSize READ maximumSize WRITE setMaximumSize) + Q_PROPERTY(QSizePolicy sizePolicy READ sizePolicy WRITE setSizePolicy) Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy) Q_PROPERTY(Qt::WindowFlags windowFlags READ windowFlags WRITE setWindowFlags) Q_PROPERTY(QString windowTitle READ windowTitle WRITE setWindowTitle) diff --git a/src/gui/graphicsview/qsimplex_p.cpp b/src/gui/graphicsview/qsimplex_p.cpp index 86b10b4..cd40f9e 100644 --- a/src/gui/graphicsview/qsimplex_p.cpp +++ b/src/gui/graphicsview/qsimplex_p.cpp @@ -288,7 +288,7 @@ bool QSimplex::setConstraints(const QList<QSimplexConstraint *> newConstraints) // original problem. // Otherwise, we clean up our structures and report there is // no feasible solution. - if (valueAt(0, columns - 1) != 0.0) { + if ((valueAt(0, columns - 1) != 0.0) && (qAbs(valueAt(0, columns - 1)) > 0.00001)) { qWarning() << "QSimplex: No feasible solution!"; clearDataStructures(); return false; diff --git a/src/gui/graphicsview/qsimplex_p.h b/src/gui/graphicsview/qsimplex_p.h index 084ad7f..a5816d1 100644 --- a/src/gui/graphicsview/qsimplex_p.h +++ b/src/gui/graphicsview/qsimplex_p.h @@ -107,7 +107,7 @@ struct QSimplexConstraint Q_ASSERT(constant > 0 || qFuzzyCompare(1, 1 + constant)); - if ((leftHandSide == constant) || qFuzzyCompare(1000 + leftHandSide, 1000 + constant)) + if ((leftHandSide == constant) || qAbs(leftHandSide - constant) < 0.00000001) return true; switch (ratio) { |