From 2ef40cee608ddc57a5bfc4ceb91c149b526fe53a Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Wed, 25 Nov 2009 13:27:36 -0300 Subject: QGAL: default spacing can be unset using a negative value After a default spacing is set through QGAL::setSpacing() family of methods, the user must be able to unset it. To avoid adding another public method, we enabled this feature by allowing a negative value to be passed to those methods. Signed-off-by: Eduardo M. Fleury Reviewed-by: Anselmo Lacerda S. de Melo --- src/gui/graphicsview/qgraphicsanchorlayout.cpp | 23 ++---------- .../tst_qgraphicsanchorlayout.cpp | 41 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout.cpp b/src/gui/graphicsview/qgraphicsanchorlayout.cpp index 7e5929e..686096c 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout.cpp @@ -370,12 +370,6 @@ void QGraphicsAnchorLayout::setHorizontalSpacing(qreal spacing) { Q_D(QGraphicsAnchorLayout); - // ### We don't support negative spacing yet - if (spacing < 0) { - spacing = 0; - qWarning() << "QGraphicsAnchorLayout does not support negative spacing."; - } - d->spacings[0] = spacing; invalidate(); } @@ -389,12 +383,6 @@ void QGraphicsAnchorLayout::setVerticalSpacing(qreal spacing) { Q_D(QGraphicsAnchorLayout); - // ### We don't support negative spacing yet - if (spacing < 0) { - spacing = 0; - qWarning() << "QGraphicsAnchorLayout does not support negative spacing."; - } - d->spacings[1] = spacing; invalidate(); } @@ -405,7 +393,8 @@ void QGraphicsAnchorLayout::setVerticalSpacing(qreal spacing) If an item is anchored with no spacing associated with the anchor, it will use the default spacing. - Currently QGraphicsAnchorLayout does not support negative default spacings. + QGraphicsAnchorLayout does not support negative spacings. Setting a negative value will unset the + previous spacing and make the layout use the spacing provided by the current widget style. \sa setHorizontalSpacing(), setVerticalSpacing() */ @@ -413,14 +402,6 @@ void QGraphicsAnchorLayout::setSpacing(qreal spacing) { Q_D(QGraphicsAnchorLayout); - // ### Currently we do not support negative anchors inside the graph. - // To avoid those being created by a negative spacing, we must - // make this test. - if (spacing < 0) { - spacing = 0; - qWarning() << "QGraphicsAnchorLayout does not support negative spacing."; - } - d->spacings[0] = d->spacings[1] = spacing; invalidate(); } diff --git a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp index e2f87b8..aa67ac5 100644 --- a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp +++ b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp @@ -88,6 +88,7 @@ private slots: void spacingPersistency(); void snakeParallelWithLayout(); void parallelToHalfLayout(); + void globalSpacing(); }; class RectWidget : public QGraphicsWidget @@ -1976,5 +1977,45 @@ void tst_QGraphicsAnchorLayout::parallelToHalfLayout() QCOMPARE(maximumSizeHint, QSizeF(400, 100) + overhead); } +void tst_QGraphicsAnchorLayout::globalSpacing() +{ + QGraphicsWidget *a = createItem(); + QGraphicsWidget *b = createItem(); + + QGraphicsWidget w; + QGraphicsAnchorLayout *l = new QGraphicsAnchorLayout(&w); + + l->addCornerAnchors(l, Qt::TopLeftCorner, a, Qt::TopLeftCorner); + l->addCornerAnchors(a, Qt::BottomRightCorner, b, Qt::TopLeftCorner); + l->addCornerAnchors(b, Qt::BottomRightCorner, l, Qt::BottomRightCorner); + + w.resize(w.effectiveSizeHint(Qt::PreferredSize)); + qreal vSpacing = b->geometry().top() - a->geometry().bottom(); + qreal hSpacing = b->geometry().left() - a->geometry().right(); + + // Set spacings manually + l->setVerticalSpacing(vSpacing + 10); + l->setHorizontalSpacing(hSpacing + 5); + + w.resize(w.effectiveSizeHint(Qt::PreferredSize)); + qreal newVSpacing = b->geometry().top() - a->geometry().bottom(); + qreal newHSpacing = b->geometry().left() - a->geometry().right(); + + QCOMPARE(newVSpacing, vSpacing + 10); + QCOMPARE(newHSpacing, hSpacing + 5); + + // Set a negative spacing. This will unset the previous spacing and + // bring back the widget-defined spacing. + l->setSpacing(-1); + + w.resize(w.effectiveSizeHint(Qt::PreferredSize)); + newVSpacing = b->geometry().top() - a->geometry().bottom(); + newHSpacing = b->geometry().left() - a->geometry().right(); + + QCOMPARE(newVSpacing, vSpacing); + QCOMPARE(newHSpacing, hSpacing); +} + + QTEST_MAIN(tst_QGraphicsAnchorLayout) #include "tst_qgraphicsanchorlayout.moc" -- cgit v0.12 From a8893f8d17d37ac4a68fc2cf0bdec1c3b124c8f4 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Fri, 27 Nov 2009 14:52:21 -0300 Subject: QGAL (Test): Add QGraphicsAnchor handling test This test ensures that internal anchors are not exposed through the QGAL::anchor() API and also that the QGraphicsAnchors are deleted when the associated anchors are deleted internally. Signed-off-by: Eduardo M. Fleury Reviewed-by: Artur Duque de Souza --- .../tst_qgraphicsanchorlayout.cpp | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp index aa67ac5..a7297c9 100644 --- a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp +++ b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp @@ -89,6 +89,7 @@ private slots: void snakeParallelWithLayout(); void parallelToHalfLayout(); void globalSpacing(); + void graphicsAnchorHandling(); }; class RectWidget : public QGraphicsWidget @@ -2016,6 +2017,42 @@ void tst_QGraphicsAnchorLayout::globalSpacing() QCOMPARE(newHSpacing, hSpacing); } +void tst_QGraphicsAnchorLayout::graphicsAnchorHandling() +{ + QGraphicsAnchorLayout *l = new QGraphicsAnchorLayout(); + QGraphicsWidget *a = createItem(); + + l->addAnchors(l, a); + + QGraphicsAnchor *layoutAnchor = l->anchor(l, Qt::AnchorTop, l, Qt::AnchorBottom); + QGraphicsAnchor *itemAnchor = l->anchor(a, Qt::AnchorTop, a, Qt::AnchorBottom); + QGraphicsAnchor *invalidAnchor = l->anchor(a, Qt::AnchorTop, l, Qt::AnchorBottom); + + // Ensure none of these anchors are accessible. + QVERIFY(layoutAnchor == 0); + QVERIFY(itemAnchor == 0); + QVERIFY(invalidAnchor == 0); + + // Hook the anchors to a QObject + QObject object; + QGraphicsAnchor *userAnchor = l->anchor(l, Qt::AnchorTop, a, Qt::AnchorTop); + userAnchor->setParent(&object); + userAnchor = l->anchor(l, Qt::AnchorBottom, a, Qt::AnchorBottom); + userAnchor->setParent(&object); + userAnchor = l->anchor(l, Qt::AnchorRight, a, Qt::AnchorRight); + userAnchor->setParent(&object); + userAnchor = l->anchor(l, Qt::AnchorLeft, a, Qt::AnchorLeft); + userAnchor->setParent(&object); + + QCOMPARE(object.children().size(), 4); + + // Delete layout, this will cause all anchors to be deleted internally. + // We expect the public QGraphicsAnchor instances to be deleted too. + delete l; + QCOMPARE(object.children().size(), 0); + + delete a; +} QTEST_MAIN(tst_QGraphicsAnchorLayout) #include "tst_qgraphicsanchorlayout.moc" -- cgit v0.12 From 5b9d7f05aa369c07c2f498280d8a8b9c81f7aff1 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Fri, 27 Nov 2009 11:14:23 -0300 Subject: QGAL: Prevent internal anchors from being exposed by QGAL::anchor() Adding check to prevent the structural layout anchors as well as those internal to items, from being exposed through the public API. These checks also ensure no QGraphicsAnchor instances will be created for those anchors. Signed-off-by: Eduardo M. Fleury Reviewed-by: Artur Duque de Souza --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index a6f5992..137806a 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -1652,6 +1652,10 @@ QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::getAnchor(QGraphicsLayoutItem *fi QGraphicsLayoutItem *secondItem, Qt::AnchorPoint secondEdge) { + // Do not expose internal anchors + if (firstItem == secondItem) + return 0; + const Orientation orientation = edgeOrientation(firstEdge); AnchorVertex *v1 = internalVertex(firstItem, firstEdge); AnchorVertex *v2 = internalVertex(secondItem, secondEdge); @@ -1659,8 +1663,16 @@ QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::getAnchor(QGraphicsLayoutItem *fi QGraphicsAnchor *graphicsAnchor = 0; AnchorData *data = graph[orientation].edgeData(v1, v2); - if (data) - graphicsAnchor = acquireGraphicsAnchor(data); + if (data) { + // We could use "acquireGraphicsAnchor" here, but to avoid a regression where + // an internal anchor was wrongly exposed, I want to ensure no new + // QGraphicsAnchor instances are created by this call. + // This assumption must hold because anchors are either user-created (and already + // have their public object created), or they are internal (and must not reach + // this point). + Q_ASSERT(data->graphicsAnchor); + graphicsAnchor = data->graphicsAnchor; + } return graphicsAnchor; } -- cgit v0.12 From b70256432c2393afb5686c9ef61e38ff399be954 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Fri, 27 Nov 2009 15:38:48 -0300 Subject: QGAL: Fix memory management issues (leak + invalid read) Fixing QGraphicsAnchor memory leak and access to free'd region. -- Leak: User-created anchors have two representations in QGAL, one visible externally (QGraphicsAnchor) and other internal (AnchorData). When such anchors are removed externally (QGraphicsAnchor is deleted), the former implementation ensured that the internal representation would be deleted too. However the opposite was not true. In cases where the anchors are deleted internally (in the layout destructor, for instance, or when an item is removed through the removeAt API), the public QGraphicsAnchor object would leak. This commit ensures the deletion will happen in both directions and adds protection to avoid a deletion loop. -- Invalid read: In QGAL::removeAnchor(vertex1, vertex2), we read vertex information after calling removeAnchor_helper(vertex1, vertex2). The problem is that in cases where the removed anchor is the last anchor to connect to a center vertex, its removal will cause also the removal of such vertex. Thus, accessing the vertices after the removeAnchor_helper() call is unsafe. To solve that we cache the information we need and then clear the vertex pointers to avoid errors in the future. Signed-off-by: Eduardo M. Fleury Reviewed-by: Artur Duque de Souza --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 30 ++++++++++++++++++++---- src/gui/graphicsview/qgraphicsanchorlayout_p.h | 3 +-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 137806a..b698c02 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -62,7 +62,13 @@ QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) QGraphicsAnchorPrivate::~QGraphicsAnchorPrivate() { - layoutPrivate->removeAnchor(data->from, data->to); + if (data) { + // The QGraphicsAnchor was already deleted at this moment. We must clean + // the dangling pointer to avoid double deletion in the AnchorData dtor. + data->graphicsAnchor = 0; + + layoutPrivate->removeAnchor(data->from, data->to); + } } void QGraphicsAnchorPrivate::setSizePolicy(QSizePolicy::Policy policy) @@ -167,6 +173,18 @@ static void internalSizeHints(QSizePolicy::Policy policy, *prefSize = prefSizeHint; } +AnchorData::~AnchorData() +{ + if (graphicsAnchor) { + // Remove reference to ourself to avoid double removal in + // QGraphicsAnchorPrivate dtor. + graphicsAnchor->d_func()->data = 0; + + delete graphicsAnchor; + } +} + + void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) { QSizePolicy::Policy policy; @@ -1687,12 +1705,16 @@ void QGraphicsAnchorLayoutPrivate::removeAnchor(AnchorVertex *firstVertex, { Q_Q(QGraphicsAnchorLayout); - // Actually delete the anchor - removeAnchor_helper(firstVertex, secondVertex); - + // Save references to items while it's safe to assume the vertices exist QGraphicsLayoutItem *firstItem = firstVertex->m_item; QGraphicsLayoutItem *secondItem = secondVertex->m_item; + // Delete the anchor (may trigger deletion of center vertices) + removeAnchor_helper(firstVertex, secondVertex); + + // Ensure no dangling pointer is left behind + firstVertex = secondVertex = 0; + // Checking if the item stays in the layout or not bool keepFirstItem = false; bool keepSecondItem = false; diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h index 8529e2e..e96110c 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.h +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -128,12 +128,11 @@ struct AnchorData : public QSimplexVariable { type(Normal), isLayoutAnchor(false), isCenterAnchor(false), orientation(0), dependency(Independent) {} + virtual ~AnchorData(); virtual void updateChildrenSizes() {} void refreshSizeHints(const QLayoutStyleInfo *styleInfo = 0); - virtual ~AnchorData() {} - #ifdef QT_DEBUG void dump(int indent = 2); inline QString toString() const; -- cgit v0.12 From 7a930ec85684a38212165aff29ed84d95bd43d32 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Thu, 26 Nov 2009 20:13:35 -0300 Subject: QGAL (Test): Fix memory leaks in example and tests Some tests and the example were leaking memory what made it harder to investigate leaks on the layout itself. Those were simple errors like not deleting the layout at the end of the test and were corrected. Signed-off-by: Eduardo M. Fleury Reviewed-by: Artur Duque de Souza --- examples/graphicsview/anchorlayout/main.cpp | 4 +-- .../tst_qgraphicsanchorlayout.cpp | 19 ++++++++----- .../tst_qgraphicsanchorlayout1.cpp | 31 ++++++++++++++-------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/examples/graphicsview/anchorlayout/main.cpp b/examples/graphicsview/anchorlayout/main.cpp index f898d1d..389bc9e 100644 --- a/examples/graphicsview/anchorlayout/main.cpp +++ b/examples/graphicsview/anchorlayout/main.cpp @@ -122,8 +122,8 @@ int main(int argc, char **argv) scene.addItem(w); scene.setBackgroundBrush(Qt::darkGreen); - QGraphicsView *view = new QGraphicsView(&scene); - view->show(); + QGraphicsView view(&scene); + view.show(); return app.exec(); } diff --git a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp index a7297c9..d566498 100644 --- a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp +++ b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp @@ -1197,7 +1197,7 @@ void tst_QGraphicsAnchorLayout::styleDefaults() QSizeF pref(20, 20); QSizeF max (50, 50); - /* + /* create this layout, where a,b have controlType QSizePolicy::RadioButton c,d have controlType QSizePolicy::PushButton: +-------+ @@ -1244,9 +1244,9 @@ void tst_QGraphicsAnchorLayout::styleDefaults() scene.addItem(window); window->show(); - QGraphicsView *view = new QGraphicsView(&scene); - view->resize(200, 200); - view->show(); + QGraphicsView view(&scene); + view.resize(200, 200); + view.show(); window->adjustSize(); QCOMPARE(a->geometry(), QRectF(0, 3, 20, 20)); //radio @@ -1265,10 +1265,13 @@ void tst_QGraphicsAnchorLayout::styleDefaults() window->setStyle(style); window->adjustSize(); QCOMPARE(a->geometry(), QRectF(0, 3, 20, 20)); - QCOMPARE(b->geometry(), QRectF(21, 25, 20, 20)); + QCOMPARE(b->geometry(), QRectF(21, 25, 20, 20)); QCOMPARE(c->geometry(), QRectF(42, 47, 20, 20)); QCOMPARE(d->geometry(), QRectF(63, 69, 20, 20)); QCOMPARE(l->geometry(), QRectF(0, 0, 89, 98)); + + window->setStyle(0); + delete style; } @@ -1777,7 +1780,8 @@ void tst_QGraphicsAnchorLayout::simplificationVsOrder() QGraphicsWidget *b = createItem(min, pref, max, "B"); QGraphicsWidget *c = createItem(min, pref, max, "C"); - QGraphicsAnchorLayout *l = new QGraphicsAnchorLayout; + QGraphicsWidget frame; + QGraphicsAnchorLayout *l = new QGraphicsAnchorLayout(&frame); // Bulk anchors l->addAnchor(l, Qt::AnchorLeft, a, Qt::AnchorLeft); @@ -1847,7 +1851,8 @@ void tst_QGraphicsAnchorLayout::simplificationVsRedundance() QGraphicsWidget *b = createItem(min, pref, max, "B"); QGraphicsWidget *c = createItem(min, pref, max, "C"); - QGraphicsAnchorLayout *l = new QGraphicsAnchorLayout; + QGraphicsWidget frame; + QGraphicsAnchorLayout *l = new QGraphicsAnchorLayout(&frame); l->addCornerAnchors(a, Qt::TopLeftCorner, l, Qt::TopLeftCorner); l->addCornerAnchors(a, Qt::BottomLeftCorner, l, Qt::BottomLeftCorner); diff --git a/tests/auto/qgraphicsanchorlayout1/tst_qgraphicsanchorlayout1.cpp b/tests/auto/qgraphicsanchorlayout1/tst_qgraphicsanchorlayout1.cpp index baa1ba1..57dc90d 100644 --- a/tests/auto/qgraphicsanchorlayout1/tst_qgraphicsanchorlayout1.cpp +++ b/tests/auto/qgraphicsanchorlayout1/tst_qgraphicsanchorlayout1.cpp @@ -356,6 +356,8 @@ void tst_QGraphicsAnchorLayout1::testItemAt() QVERIFY( layout->itemAt(0) == widget2 ); + delete widget1; + widget->setLayout(layout); delete widget; } @@ -460,6 +462,12 @@ void tst_QGraphicsAnchorLayout1::testAddAndRemoveAnchor() QCOMPARE( layout->count(), 0 ); + delete widget1; + delete widget2; + delete widget3; + delete widget4; + delete widget5; + widget->setLayout(layout); delete widget; } @@ -1740,9 +1748,9 @@ void tst_QGraphicsAnchorLayout1::testBasicLayout() QRectF actual = truncate(widgets[item.index]->geometry()); QCOMPARE(actual, expected); - delete widgets[item.index]; } + qDeleteAll(widgets); delete widget; } @@ -2231,8 +2239,9 @@ void tst_QGraphicsAnchorLayout1::testRemoveCenterAnchor() const BasicLayoutTestResult item = result[i]; QCOMPARE(widgets[item.index]->geometry(), item.rect); - delete widgets[item.index]; } + + qDeleteAll(widgets); delete widget; } @@ -2360,7 +2369,7 @@ void tst_QGraphicsAnchorLayout1::testSingleSizePolicy() QFETCH(bool, valid); // create objects - QGraphicsWidget *widget = new QGraphicsWidget; + QGraphicsWidget widget; TheAnchorLayout *layout = new TheAnchorLayout; TestWidget *childWidget = new TestWidget; @@ -2370,11 +2379,11 @@ void tst_QGraphicsAnchorLayout1::testSingleSizePolicy() layout->setAnchor( layout, Qt::AnchorTop, childWidget, Qt::AnchorTop, 10 ); layout->setAnchor( childWidget, Qt::AnchorBottom, layout, Qt::AnchorBottom, 10 ); - widget->setLayout( layout ); + widget.setLayout( layout ); // set test case specific: policy and size childWidget->setSizePolicy( policy ); - widget->setGeometry( QRectF( QPoint(0,0), size ) ); + widget.setGeometry( QRectF( QPoint(0,0), size ) ); QCOMPARE( layout->isValid() , valid ); @@ -2516,7 +2525,7 @@ void tst_QGraphicsAnchorLayout1::testDoubleSizePolicy() QFETCH(qreal, width2); // create objects - QGraphicsWidget *widget = new QGraphicsWidget; + QGraphicsWidget widget; TheAnchorLayout *layout = new TheAnchorLayout; TestWidget *childWidget1 = new TestWidget; TestWidget *childWidget2 = new TestWidget; @@ -2526,13 +2535,13 @@ void tst_QGraphicsAnchorLayout1::testDoubleSizePolicy() layout->setAnchor( childWidget1, Qt::AnchorRight, childWidget2, Qt::AnchorLeft, 10 ); layout->setAnchor( childWidget2, Qt::AnchorRight, layout, Qt::AnchorRight, 10 ); - widget->setLayout( layout ); + widget.setLayout( layout ); // set test case specific: policy childWidget1->setSizePolicy( policy1 ); childWidget2->setSizePolicy( policy2 ); - widget->setGeometry( QRectF( QPoint(0,0), QSize( 100,100 ) ) ); + widget.setGeometry( QRectF( QPoint(0,0), QSize( 100,100 ) ) ); // check results: if ( width1 == -1.0f ) { @@ -2649,7 +2658,7 @@ void tst_QGraphicsAnchorLayout1::testSizeDistribution() QFETCH(qreal, width2); // create objects - QGraphicsWidget *widget = new QGraphicsWidget; + QGraphicsWidget widget; TheAnchorLayout *layout = new TheAnchorLayout; TestWidget *childWidget1 = new TestWidget; TestWidget *childWidget2 = new TestWidget; @@ -2659,7 +2668,7 @@ void tst_QGraphicsAnchorLayout1::testSizeDistribution() layout->setAnchor( childWidget1, Qt::AnchorRight, childWidget2, Qt::AnchorLeft, 10 ); layout->setAnchor( childWidget2, Qt::AnchorRight, layout, Qt::AnchorRight, 10 ); - widget->setLayout( layout ); + widget.setLayout( layout ); // set test case specific: size hints childWidget1->setMinimumWidth( sizeHints1.value( Qt::MinimumSize ) ); @@ -2670,7 +2679,7 @@ void tst_QGraphicsAnchorLayout1::testSizeDistribution() childWidget2->setPreferredWidth( sizeHints2.value( Qt::PreferredSize ) ); childWidget2->setMaximumWidth( sizeHints2.value( Qt::MaximumSize ) ); - widget->setGeometry( QRectF( QPoint(0,0), QSize( 100,100 ) ) ); + widget.setGeometry( QRectF( QPoint(0,0), QSize( 100,100 ) ) ); // check results: if ( width1 == -1.0f ) { -- cgit v0.12 From a801b57a403548cccd081d8a0ae1f174d2201f9a Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Thu, 19 Nov 2009 17:07:02 -0300 Subject: QGAL: Rename internalSizeHints to applySizePolicy Former name did not reflect actual work done by the function. Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 25 ++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index b698c02..d9f4e32 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -138,10 +138,10 @@ qreal QGraphicsAnchorPrivate::spacing() const } -static void internalSizeHints(QSizePolicy::Policy policy, - qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint, - qreal *minSize, qreal *prefSize, - qreal *maxSize) +static void applySizePolicy(QSizePolicy::Policy policy, + qreal minSizeHint, qreal prefSizeHint, qreal maxSizeHint, + qreal *minSize, qreal *prefSize, + qreal *maxSize) { // minSize, prefSize and maxSize are initialized // with item's preferred Size: this is QSizePolicy::Fixed. @@ -224,14 +224,18 @@ void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) // It is a user-created anchor, fetch size information from the associated QGraphicsAnchor Q_ASSERT(graphicsAnchor); QGraphicsAnchorPrivate *anchorPrivate = graphicsAnchor->d_func(); + + // Policy, min and max sizes are straightforward policy = anchorPrivate->sizePolicy; minSizeHint = 0; + maxSizeHint = QWIDGETSIZE_MAX; + + // Preferred Size if (anchorPrivate->hasSize) { - // One can only configure the preferred size of a normal anchor. Their minimum and - // maximum "size hints" are always 0 and QWIDGETSIZE_MAX, correspondingly. However, - // their effective size hints might be narrowed down due to their size policies. + // Anchor has user-defined size prefSizeHint = anchorPrivate->preferredSize; } else { + // Fetch size information from style const Qt::Orientation orient = Qt::Orientation(QGraphicsAnchorLayoutPrivate::edgeOrientation(from->m_edge) + 1); qreal s = styleInfo->defaultSpacing(orient); if (s < 0) { @@ -247,10 +251,11 @@ void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) } prefSizeHint = s; } - maxSizeHint = QWIDGETSIZE_MAX; } - internalSizeHints(policy, minSizeHint, prefSizeHint, maxSizeHint, - &minSize, &prefSize, &maxSize); + + // Fill minSize, prefSize and maxSize based on policy and sizeHints + applySizePolicy(policy, minSizeHint, prefSizeHint, maxSizeHint, + &minSize, &prefSize, &maxSize); // Set the anchor effective sizes to preferred. // -- cgit v0.12 From 2aa9303031d9d9f4b123034f4a7fd84f1d1270ba Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Tue, 27 Oct 2009 18:04:17 -0300 Subject: QGAL: Limit absolute size of anchors and add offset to calculation This commit is groundwork for the support of negative-sized anchors by the simplex solver. The idea is to add to all variables an offset large enough to ensure they are never negative. The implementation limits all variable sizes in the range [-limit, limit] and feed them into the simplex with an offset of "limit". Subtracting this offset later to find out the real values. "limit" is defined as QWIDGETSIZE_MAX for platforms where qreal is double and as QWIDGETSIZE_MAX / 32 when it is float. This is to avoid numerical errors in the simplex solver. This commit also modifies the ASSERT clause inside QSimplex so it becomes less prone to false positives due to numerical errors. Signed-off-by: Eduardo M. Fleury Signed-off-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 67 ++++++++++++++++++++---- src/gui/graphicsview/qsimplex_p.h | 2 +- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index d9f4e32..f3a1a69 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -49,9 +49,17 @@ #endif #include "qgraphicsanchorlayout_p.h" + #ifndef QT_NO_GRAPHICSVIEW QT_BEGIN_NAMESPACE +// To ensure that all variables inside the simplex solver are non-negative, +// we limit the size of anchors in the interval [-limit, limit]. Then before +// sending them to the simplex solver we add "limit" as an offset, so that +// they are actually calculated in the interval [0, 2 * limit] +// To avoid numerical errors in platforms where we use single precision, +// we use a tighter limit for the variables range. +const qreal limit = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32; QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) : QObjectPrivate(version), layoutPrivate(0), data(0), @@ -2086,6 +2094,25 @@ void QGraphicsAnchorLayoutPrivate::calculateGraphs( /*! \internal + Shift all the constraints by a certain amount. This allows us to deal with negative values in + the linear program if they are bounded by a certain limit. Functions should be careful to + call it again with a negative amount, to shift the constraints back. +*/ +static void shiftConstraints(const QList &constraints, qreal amount) +{ + for (int i = 0; i < constraints.count(); ++i) { + QSimplexConstraint *c = constraints.at(i); + qreal multiplier = 0; + foreach (qreal v, c->variables.values()) { + multiplier += v; + } + c->constant += multiplier * amount; + } +} + +/*! + \internal + Calculate the sizes for all anchors which are part of the trunk. This works on top of a (possibly) simplified graph. */ @@ -2106,6 +2133,8 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const QList sizeHintConstraints = constraintsFromSizeHints(variables); QList allConstraints = constraints + sizeHintConstraints; + shiftConstraints(allConstraints, limit); + // Solve min and max size hints qreal min, max; feasible = solveMinMax(allConstraints, path, &min, &max); @@ -2129,6 +2158,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const } qDeleteAll(sizeHintConstraints); + shiftConstraints(constraints, -limit); } else { // No Simplex is necessary because the path was simplified all the way to a single @@ -2160,6 +2190,10 @@ bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList &variables) { QList sizeHintConstraints = constraintsFromSizeHints(variables); + + shiftConstraints(sizeHintConstraints, limit); + shiftConstraints(constraints, limit); + bool feasible = solvePreferred(constraints + sizeHintConstraints, variables); if (feasible) { @@ -2174,6 +2208,9 @@ bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin if (ad->dependency == AnchorData::Slave) continue; - if ((ad->minSize == ad->maxSize) || qFuzzyCompare(ad->minSize, ad->maxSize)) { + // To use negative variables inside simplex, we shift them so the minimum negative value is + // mapped to zero before solving. To make sure that it works, we need to guarantee that the + // variables are all inside a certain boundary. + qreal boundedMin = qBound(-limit, ad->minSize, limit); + qreal boundedMax = qBound(-limit, ad->maxSize, limit); + + if ((boundedMin == boundedMax) || qFuzzyCompare(boundedMin, boundedMax)) { QSimplexConstraint *c = new QSimplexConstraint; c->variables.insert(ad, 1.0); - c->constant = ad->minSize; + c->constant = boundedMin; c->ratio = QSimplexConstraint::Equal; anchorConstraints += c; unboundedProblem = false; } else { QSimplexConstraint *c = new QSimplexConstraint; c->variables.insert(ad, 1.0); - c->constant = ad->minSize; + c->constant = boundedMin; c->ratio = QSimplexConstraint::MoreOrEqual; anchorConstraints += c; @@ -2359,7 +2402,7 @@ QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin c = new QSimplexConstraint; c->variables.insert(ad, 1.0); - c->constant = ad->maxSize; + c->constant = boundedMax; c->ratio = QSimplexConstraint::LessOrEqual; anchorConstraints += c; unboundedProblem = false; @@ -2370,7 +2413,8 @@ QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin if (unboundedProblem) { QSimplexConstraint *c = new QSimplexConstraint; c->variables.insert(layoutEdge, 1.0); - c->constant = QWIDGETSIZE_MAX; + // The maximum size that the layout can take + c->constant = limit; c->ratio = QSimplexConstraint::LessOrEqual; anchorConstraints += c; } @@ -2691,27 +2735,28 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList for (iter = path.negatives.constBegin(); iter != path.negatives.constEnd(); ++iter) objective.variables.insert(*iter, -1.0); + const qreal objectiveOffset = (path.positives.count() - path.negatives.count()) * limit; simplex.setObjective(&objective); // Calculate minimum values - *min = simplex.solveMin(); + *min = simplex.solveMin() - objectiveOffset; // Save sizeAtMinimum results QList variables = getVariables(constraints); for (int i = 0; i < variables.size(); ++i) { AnchorData *ad = static_cast(variables.at(i)); - ad->sizeAtMinimum = ad->result; + ad->sizeAtMinimum = ad->result - limit; Q_ASSERT(ad->sizeAtMinimum >= ad->minSize || qAbs(ad->sizeAtMinimum - ad->minSize) < 0.00000001); } // Calculate maximum values - *max = simplex.solveMax(); + *max = simplex.solveMax() - objectiveOffset; // Save sizeAtMaximum results for (int i = 0; i < variables.size(); ++i) { AnchorData *ad = static_cast(variables.at(i)); - ad->sizeAtMaximum = ad->result; + ad->sizeAtMaximum = ad->result - limit; // Q_ASSERT(ad->sizeAtMaximum <= ad->maxSize || // qAbs(ad->sizeAtMaximum - ad->maxSize) < 0.00000001); } @@ -2756,7 +2801,7 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QListvariables.insert(ad, 1.0); c->variables.insert(shrinker, 1.0); c->variables.insert(grower, -1.0); - c->constant = ad->prefSize; + c->constant = ad->prefSize + limit; preferredConstraints += c; preferredVariables += grower; @@ -2778,7 +2823,7 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QListsizeAtPreferred = ad->result; + ad->sizeAtPreferred = ad->result - limit; } // Make sure we delete the simplex solver -before- we delete the diff --git a/src/gui/graphicsview/qsimplex_p.h b/src/gui/graphicsview/qsimplex_p.h index a5816d1..2004471 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) || qAbs(leftHandSide - constant) < 0.00000001) + if ((leftHandSide == constant) || qAbs(leftHandSide - constant) < 0.0000001) return true; switch (ratio) { -- cgit v0.12 From 33116cf737b6ded48ccbb14af401ecb18adeec96 Mon Sep 17 00:00:00 2001 From: Jesus Sanchez-Palencia Date: Thu, 5 Nov 2009 13:41:36 -0300 Subject: QGAL: Do not revert negative anchors Since we now support anchors with size less then zero, we no longer need to revert anchors with negative spacing. Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 26 ++++-------------------- src/gui/graphicsview/qgraphicsanchorlayout_p.h | 1 - 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index f3a1a69..3154630 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -64,7 +64,7 @@ const qreal limit = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGE QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) : QObjectPrivate(version), layoutPrivate(0), data(0), sizePolicy(QSizePolicy::Fixed), preferredSize(0), - hasSize(true), reversed(false) + hasSize(true) { } @@ -94,27 +94,12 @@ void QGraphicsAnchorPrivate::setSpacing(qreal value) return; } - const qreal rawValue = reversed ? -preferredSize : preferredSize; - if (hasSize && (rawValue == value)) + if (hasSize && (preferredSize == value)) return; // The anchor has an user-defined size hasSize = true; - - // The simplex solver cannot handle negative sizes. To workaround that, - // if value is less than zero, we reverse the anchor and set the absolute - // value; - if (value >= 0) { - preferredSize = value; - if (reversed) - qSwap(data->from, data->to); - reversed = false; - } else { - preferredSize = -value; - if (!reversed) - qSwap(data->from, data->to); - reversed = true; - } + preferredSize = value; layoutPrivate->q_func()->invalidate(); } @@ -128,9 +113,6 @@ void QGraphicsAnchorPrivate::unsetSpacing() // Return to standard direction hasSize = false; - if (reversed) - qSwap(data->from, data->to); - reversed = false; layoutPrivate->q_func()->invalidate(); } @@ -142,7 +124,7 @@ qreal QGraphicsAnchorPrivate::spacing() const return 0; } - return reversed ? -preferredSize : preferredSize; + return preferredSize; } diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h index e96110c..925d7ef 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.h +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -342,7 +342,6 @@ public: qreal preferredSize; uint hasSize : 1; // if false, get size from style. - uint reversed : 1; // if true, the anchor was inverted to keep its value positive }; -- cgit v0.12 From 666576b39da04f4185036d9eee1b8edeae10ccf7 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Thu, 5 Nov 2009 11:54:33 -0300 Subject: QGAL: Support for out-of-order sequential anchors Now sequential anchors account for the fact they may be composed of anchors with different directions. Both refreshSizeHints and updateChildrenSizes have been updated to support this situation. Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 40 ++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 3154630..1f1769f 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -416,12 +416,27 @@ void SequentialAnchorData::updateChildrenSizes() const QPair maxFactor = getFactor(sizeAtMaximum, minSize, prefSize, maxSize); + // XXX This is not safe if Vertex simplification takes place after the sequential + // anchor is created. In that case, "prev" will be a group-vertex, different from + // "from" or "to", that _contains_ one of them. + AnchorVertex *prev = from; + for (int i = 0; i < m_edges.count(); ++i) { AnchorData *e = m_edges.at(i); - 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); + const bool edgeIsForward = (e->from == prev); + if (edgeIsForward) { + 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); + prev = e->to; + } else { + Q_ASSERT(prev == e->to); + e->sizeAtMinimum = interpolate(minFactor, e->maxSize, e->prefSize, e->minSize); + e->sizeAtPreferred = interpolate(prefFactor, e->maxSize, e->prefSize, e->minSize); + e->sizeAtMaximum = interpolate(maxFactor, e->maxSize, e->prefSize, e->minSize); + prev = e->from; + } e->updateChildrenSizes(); } @@ -433,11 +448,24 @@ void SequentialAnchorData::calculateSizeHints() prefSize = 0; maxSize = 0; + AnchorVertex *prev = from; + for (int i = 0; i < m_edges.count(); ++i) { AnchorData *edge = m_edges.at(i); - minSize += edge->minSize; - prefSize += edge->prefSize; - maxSize += edge->maxSize; + + const bool edgeIsForward = (edge->from == prev); + if (edgeIsForward) { + minSize += edge->minSize; + prefSize += edge->prefSize; + maxSize += edge->maxSize; + prev = edge->to; + } else { + Q_ASSERT(prev == edge->to); + minSize -= edge->maxSize; + prefSize -= edge->prefSize; + maxSize -= edge->minSize; + prev = edge->from; + } } // See comment in AnchorData::refreshSizeHints() about sizeAt* values -- cgit v0.12 From 89f83f99242d67b5079f16c8db0960fc71bda3f0 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Thu, 5 Nov 2009 23:19:29 -0300 Subject: QGAL: Enable out-of-order sequential simplification Modify the simplification code to allow the creation of out-of-order sequential anchors. Basically the "changedDirection" end-of-sequence condition was removed, also note that createSequence no longer bothers to revert the direction of anchors if the order it receives is not standard. Removing expect-fail clause from test since now we pass :-) Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 69 ++++++++-------------- .../tst_qgraphicsanchorlayout.cpp | 1 - 2 files changed, 23 insertions(+), 47 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 1f1769f..a69d8f3 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -669,24 +669,10 @@ static AnchorData *createSequence(Graph *graph, const QVector &vertices, AnchorVertex *after) { - AnchorData *data = graph->edgeData(before, vertices.first()); - Q_ASSERT(data); - - const bool forward = (before == data->from); - QVector orderedVertices; - - if (forward) { - orderedVertices = vertices; - } else { - qSwap(before, after); - for (int i = vertices.count() - 1; i >= 0; --i) - orderedVertices.append(vertices.at(i)); - } - #if defined(QT_DEBUG) && 0 QString strVertices; - for (int i = 0; i < orderedVertices.count(); ++i) { - strVertices += QString::fromAscii("%1 - ").arg(orderedVertices.at(i)->toString()); + for (int i = 0; i < vertices.count(); ++i) { + strVertices += QString::fromAscii("%1 - ").arg(vertices.at(i)->toString()); } QString strPath = QString::fromAscii("%1 - %2%3").arg(before->toString(), strVertices, after->toString()); qDebug("simplifying [%s] to [%s - %s]", qPrintable(strPath), qPrintable(before->toString()), qPrintable(after->toString())); @@ -695,15 +681,22 @@ static AnchorData *createSequence(Graph *graph, AnchorVertex *prev = before; QVector edges; - for (int i = 0; i <= orderedVertices.count(); ++i) { - AnchorVertex *next = (i < orderedVertices.count()) ? orderedVertices.at(i) : after; + // Take from the graph, the edges that will be simplificated + for (int i = 0; i < vertices.count(); ++i) { + AnchorVertex *next = vertices.at(i); AnchorData *ad = graph->takeEdge(prev, next); Q_ASSERT(ad); edges.append(ad); prev = next; } - SequentialAnchorData *sequence = new SequentialAnchorData(orderedVertices, edges); + // Take the last edge (not covered in the loop above) + AnchorData *ad = graph->takeEdge(vertices.last(), after); + Q_ASSERT(ad); + edges.append(ad); + + // Create sequence + SequentialAnchorData *sequence = new SequentialAnchorData(vertices, edges); sequence->from = before; sequence->to = after; @@ -963,7 +956,6 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP QStack > stack; stack.push(qMakePair(static_cast(0), layoutFirstVertex[orientation])); QVector candidates; - bool candidatesForward = true; // Walk depth-first, in the stack we store start of the candidate sequence (beforeSequence) // and the vertex to be visited. @@ -979,9 +971,8 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP // A vertex can trigger an end of sequence if // (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); - // (e) the next anchor is a center anchor. + // (c) its next adjacent is already visited (a cycle in the graph). + // (d) the next anchor is a center anchor. const QList &adjacents = g.adjacentVertices(v); const bool isLayoutVertex = v->m_item == q; @@ -996,19 +987,10 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP endOfSequence = isLayoutVertex || adjacents.count() != 2; if (!endOfSequence) { - // If this is the first vertice, determine what is the direction to use for this - // sequence. - if (candidates.isEmpty()) { - const AnchorData *data = g.edgeData(beforeSequence, v); - Q_ASSERT(data); - candidatesForward = (beforeSequence == data->from); - } - // This is a tricky part. We peek at the next vertex to find out whether // - // - 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. + // - we already visited the next vertex (c); + // - the next anchor is a center (d). // // 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. @@ -1026,22 +1008,17 @@ bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(QGraphicsAnchorLayoutP const AnchorData *data = g.edgeData(v, after); Q_ASSERT(data); - const bool willChangeDirection = (candidatesForward != (v == data->from)); const bool cycleFound = visited.contains(after); - // Now cases (c), (d) and (e)... - endOfSequence = willChangeDirection || cycleFound || data->isCenterAnchor; + // Now cases (c) and (d)... + endOfSequence = cycleFound || data->isCenterAnchor; - if (endOfSequence) { - if (!willChangeDirection) { - // If the direction will not change, we can add the current vertex to the - // candidates list and we know that 'after' can be used as afterSequence. - candidates.append(v); - afterSequence = after; - } - } else { + if (!endOfSequence) { // If it's not an end of sequence, then the vertex didn't trigger neither of the - // previously four cases, so it can be added to the candidates list. + // previously three cases, so it can be added to the candidates list. + candidates.append(v); + } else if (cycleFound && (beforeSequence != after)) { + afterSequence = after; candidates.append(v); } } diff --git a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp index d566498..6e78522 100644 --- a/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp +++ b/tests/auto/qgraphicsanchorlayout/tst_qgraphicsanchorlayout.cpp @@ -1806,7 +1806,6 @@ void tst_QGraphicsAnchorLayout::simplificationVsOrder() l->effectiveSizeHint(Qt::MinimumSize); if (hasSimplification) { - QEXPECT_FAIL("", "Sequential anchors cannot handle children of opposite directions", Continue); QCOMPARE(usedSimplex(l, Qt::Horizontal), false); QCOMPARE(usedSimplex(l, Qt::Vertical), false); } -- cgit v0.12 From 12341a4e33a498037679e27edee829fa245a0148 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Tue, 17 Nov 2009 16:35:28 -0300 Subject: QGAL: Use single arrow in "dumpGraph" Modify the "dot" format graph to use a single arrow between each pair of vertices. Previously we would use two anchors (a black forward one and a gray backwards). With a single arrow the graph loooks cleaner. Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraph_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/graphicsview/qgraph_p.h b/src/gui/graphicsview/qgraph_p.h index 0a2bf27..d429714 100644 --- a/src/gui/graphicsview/qgraph_p.h +++ b/src/gui/graphicsview/qgraph_p.h @@ -236,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]\" color=\"#000000\"] \n") .arg(v->toString()) .arg(v1->toString()) .arg(data->minSize) -- cgit v0.12 From d622aef01e5c2624cd64c92ce77064718c6576af Mon Sep 17 00:00:00 2001 From: Caio Marcelo de Oliveira Filho Date: Wed, 18 Nov 2009 16:00:32 -0300 Subject: QGAL: move second child direction check to a function In a parallel anchor, the second child anchor may be forward or backwards in relation to the parallel itself. Moves the directionality check to a function. It'll be useful in the next commit. Signed-off-by: Caio Marcelo de Oliveira Filho Reviewed-by: Eduardo M. Fleury --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 26 ++++++++++++++---------- src/gui/graphicsview/qgraphicsanchorlayout_p.h | 10 ++++++++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index a69d8f3..4ee58d1 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -265,13 +265,7 @@ void ParallelAnchorData::updateChildrenSizes() firstEdge->sizeAtPreferred = sizeAtPreferred; firstEdge->sizeAtMaximum = sizeAtMaximum; - // We have the convention that the first children will define the direction of the - // pararell group. So we can check whether the second edge is "forward" in relation - // to the group if it have the same direction as the first edge. Note that we don't - // use 'this->from' because it might be changed by vertex simplification. - const bool secondForward = (firstEdge->from == secondEdge->from); - - if (secondForward) { + if (secondForward()) { secondEdge->sizeAtMinimum = sizeAtMinimum; secondEdge->sizeAtPreferred = sizeAtPreferred; secondEdge->sizeAtMaximum = sizeAtMaximum; @@ -296,10 +290,20 @@ bool ParallelAnchorData::calculateSizeHints() // to a backwards anchor of size (-max, -pref, -min) // Also see comments in updateChildrenSizes(). - const bool secondForward = (firstEdge->from == secondEdge->from); - const qreal secondMin = secondForward ? secondEdge->minSize : -secondEdge->maxSize; - const qreal secondPref = secondForward ? secondEdge->prefSize : -secondEdge->prefSize; - const qreal secondMax = secondForward ? secondEdge->maxSize : -secondEdge->minSize; + + qreal secondMin; + qreal secondPref; + qreal secondMax; + + if (secondForward()) { + secondMin = secondEdge->minSize; + secondPref = secondEdge->prefSize; + secondMax = secondEdge->maxSize; + } else { + secondMin = -secondEdge->maxSize; + secondPref = -secondEdge->prefSize; + secondMax = -secondEdge->minSize; + } minSize = qMax(firstEdge->minSize, secondMin); maxSize = qMin(firstEdge->maxSize, secondMax); diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h index 925d7ef..8d2d691 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.h +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -212,7 +212,8 @@ struct ParallelAnchorData : public AnchorData 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 + // Our convention will be that the parallel group anchor will have the same + // direction as the first anchor. from = first->from; to = first->to; #ifdef QT_DEBUG @@ -223,6 +224,13 @@ struct ParallelAnchorData : public AnchorData virtual void updateChildrenSizes(); bool calculateSizeHints(); + bool secondForward() const { + // We have the convention that the first children will define the direction of the + // pararell group. Note that we can't rely on 'this->from' or 'this->to' because they + // might be changed by vertex simplification. + return firstEdge->from == secondEdge->from; + } + AnchorData* firstEdge; AnchorData* secondEdge; -- cgit v0.12 From c7d63a44b315bf945c055330c24b24bca6ef7fdb Mon Sep 17 00:00:00 2001 From: Caio Marcelo de Oliveira Filho Date: Wed, 18 Nov 2009 17:46:52 -0300 Subject: QGAL: parallel anchors sometimes have to fix constraints For an out-of-order parallel anchor, i.e. when the second child is backwards in relation to the parallel anchor, we need to fix the constraints that used to have the second child. The parallel anchor will replace its child, but we must change the multiplier signal, so the constraint remain valid. This commit also changes the order of restoration steps, since now we depend on correct vertex information to fix back the constraints, we do it before the restoring of pair vertices. Signed-off-by: Caio Marcelo de Oliveira Filho Reviewed-by: Eduardo M. Fleury --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 51 ++++++++++++++++-------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 4ee58d1..9114fc3 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -633,16 +633,25 @@ AnchorData *QGraphicsAnchorLayoutPrivate::addAnchorMaybeParallel(AnchorData *new AnchorData *child = children[i]; QList *childConstraints = childrenConstraints[i]; + // We need to fix the second child constraints if the parallel group will have the + // opposite direction of the second child anchor. For the point of view of external + // entities, this anchor was reversed. So if at some point we say that the parallel + // has a value of 20, this mean that the second child (when reversed) will be + // assigned -20. + const bool needsReverse = i == 1 && !parallel->secondForward(); + if (!child->isCenterAnchor) continue; parallel->isCenterAnchor = true; - for (int i = 0; i < constraints.count(); ++i) { - QSimplexConstraint *c = constraints[i]; + for (int j = 0; j < constraints.count(); ++j) { + QSimplexConstraint *c = constraints[j]; if (c->variables.contains(child)) { childConstraints->append(c); qreal v = c->variables.take(child); + if (needsReverse) + v *= -1; c->variables.insert(parallel, v); } } @@ -1165,9 +1174,15 @@ void QGraphicsAnchorLayoutPrivate::restoreSimplifiedConstraints(ParallelAnchorDa c->variables.insert(parallel->firstEdge, v); } + // When restoring, we might have to revert constraints back. See comments on + // addAnchorMaybeParallel(). + const bool needsReverse = !parallel->secondForward(); + for (int i = 0; i < parallel->m_secondConstraints.count(); ++i) { QSimplexConstraint *c = parallel->m_secondConstraints.at(i); qreal v = c->variables[parallel]; + if (needsReverse) + v *= -1; c->variables.remove(parallel); c->variables.insert(parallel->secondEdge, v); } @@ -1209,7 +1224,24 @@ void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) Graph &g = graph[orientation]; QList &toRestore = simplifiedVertices[orientation]; - // We will restore the vertices in the inverse order of creation, this way we ensure that + // Since we keep a list of parallel anchors and vertices that were created during vertex + // simplification, we can now iterate on those lists instead of traversing the graph + // recursively. + + // First, restore the constraints changed when we created parallel anchors. Note that this + // works at this point because the constraints doesn't depend on vertex information and at + // this point it's always safe to identify whether the second child is forward or backwards. + // In the next step, we'll change the anchors vertices so that would not be possible anymore. + QList ¶llelAnchors = anchorsFromSimplifiedVertices[orientation]; + + for (int i = parallelAnchors.count() - 1; i >= 0; --i) { + ParallelAnchorData *parallel = static_cast(parallelAnchors.at(i)); + restoreSimplifiedConstraints(parallel); + delete parallel; + } + parallelAnchors.clear(); + + // Then, 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); @@ -1254,19 +1286,6 @@ void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) 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 ¶llelAnchors = anchorsFromSimplifiedVertices[orientation]; - - for (int i = parallelAnchors.count() - 1; i >= 0; --i) { - ParallelAnchorData *parallel = static_cast(parallelAnchors.at(i)); - restoreSimplifiedConstraints(parallel); - delete parallel; - } - parallelAnchors.clear(); } QGraphicsAnchorLayoutPrivate::Orientation -- cgit v0.12 From 7fbb8aae666a86091cb7f7f3326e9e05781c02f3 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Wed, 18 Nov 2009 17:20:24 -0300 Subject: QGAL: Add AnchorData::minPrefSize and maxPrefSize With the addition of out-of-order sequential simplification, the calculation of preferred sizes became more complicated. In the past, when parallel anchors or the simplex solver had to decide between conflicting preferred sizes, they could assume that increasing the size of an anchor was better than decreasing it. That assumption comes from early discussions with Jan-Arve regarding the preferred size calculation algorithm. However, when ooo-sequential anchors exist, we can have a situation where increasing the size of an anchor can actually reduce the size of a simplified anchor inside it. To solve that, we need to expose some information regarding the internal anchors to the decision makers outside, ie. the simplex solver and parallel anchors. This information is now being provided in terms of two additional values present in each anchor, as follows: - minPrefSize: Always in the interval [minSize, prefSize]. Denotes the minimum size an anchor can assume as to avoid shrinking anchors below their preferred sizes. - maxPrefSize: Always in the interval [prefSize, maxSize]. Similar to the value above, but refering to the maximum size the anchor should assume. Some examples: 1) Standard anchor: 10 / 50 / 500 o----------------------------> Becomes: 10 / 50 / 50 / 500 / 500 o----------------------------> We'd rather grow than shrink, so we say that our preferred size is 50, but if we need to grow up to 500, that's OK. Note that we are still able to shrink all the way to 10, but it will hurt us more. 2) Two anchors: 100 / 200 / 500 10 / 20 / 40 o--------------------> <-------------------o Resulting sequential anchor: 60 / 160 / 180 / 480 / 490 o------------------------------------------> The resulting anchor have a preferred size of 180 but it can "easily" grow to 480 (only the first half grows). If it had to go all the way to 490 the second half would have to shrink below its preferred size. OTOH, if it had to shrink, it could go to 160 if the second half grew. However, shrinking even more, towards 60, would require the first half to shrink below its preferred size. With this information parallel and simplex are now able to choose the best solutions when solving conflicts. Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraph_p.h | 4 +- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 181 +++++++++++++++++------ src/gui/graphicsview/qgraphicsanchorlayout_p.h | 10 +- 3 files changed, 149 insertions(+), 46 deletions(-) diff --git a/src/gui/graphicsview/qgraph_p.h b/src/gui/graphicsview/qgraph_p.h index d429714..076b8fa 100644 --- a/src/gui/graphicsview/qgraph_p.h +++ b/src/gui/graphicsview/qgraph_p.h @@ -236,11 +236,13 @@ public: EdgeData *data = edgeData(v, v1); bool forward = data->from == v; if (forward) { - edges += QString::fromAscii("\"%1\"->\"%2\" [label=\"[%3,%4,%5]\" color=\"#000000\"] \n") + edges += QString::fromAscii("\"%1\"->\"%2\" [label=\"[%3,%4,%5,%6,%7]\" color=\"#000000\"] \n") .arg(v->toString()) .arg(v1->toString()) .arg(data->minSize) + .arg(data->minPrefSize) .arg(data->prefSize) + .arg(data->maxPrefSize) .arg(data->maxSize) ; } diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 9114fc3..6d90d3a 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -190,6 +190,9 @@ void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) maxSize = QWIDGETSIZE_MAX; if (isCenterAnchor) maxSize /= 2; + + minPrefSize = prefSize; + maxPrefSize = maxSize; return; } else { if (orientation == QGraphicsAnchorLayoutPrivate::Horizontal) { @@ -247,6 +250,9 @@ void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo) applySizePolicy(policy, minSizeHint, prefSizeHint, maxSizeHint, &minSize, &prefSize, &maxSize); + minPrefSize = prefSize; + maxPrefSize = maxSize; + // Set the anchor effective sizes to preferred. // // Note: The idea here is that all items should remain at their @@ -279,29 +285,38 @@ void ParallelAnchorData::updateChildrenSizes() secondEdge->updateChildrenSizes(); } -bool ParallelAnchorData::calculateSizeHints() -{ - // Note that parallel groups can lead to unfeasibility, so during calculation, we can - // find out one unfeasibility. Because of that this method return boolean. This can't - // happen in sequential, so there the method is void. - - // 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) +/* + \internal - // Also see comments in updateChildrenSizes(). + Initialize the parallel anchor size hints using the sizeHint information from + its children. + Note that parallel groups can lead to unfeasibility, so during calculation, we can + find out one unfeasibility. Because of that this method return boolean. This can't + happen in sequential, so there the method is void. + */ +bool ParallelAnchorData::calculateSizeHints() +{ + // Normalize second child sizes. + // A negative anchor of sizes min, minPref, pref, maxPref and max, is equivalent + // to a forward anchor of sizes -max, -maxPref, -pref, -minPref, -min qreal secondMin; + qreal secondMinPref; qreal secondPref; + qreal secondMaxPref; qreal secondMax; if (secondForward()) { secondMin = secondEdge->minSize; + secondMinPref = secondEdge->minPrefSize; secondPref = secondEdge->prefSize; + secondMaxPref = secondEdge->maxPrefSize; secondMax = secondEdge->maxSize; } else { secondMin = -secondEdge->maxSize; + secondMinPref = -secondEdge->maxPrefSize; secondPref = -secondEdge->prefSize; + secondMaxPref = -secondEdge->minPrefSize; secondMax = -secondEdge->minSize; } @@ -315,23 +330,72 @@ bool ParallelAnchorData::calculateSizeHints() return false; } - // 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. - - // ### This logic only holds if all anchors in the layout are "well-behaved" in the - // following terms: + // Preferred size calculation + // The calculation of preferred size is done as follows: + // + // 1) Check whether one of the child anchors is the layout structural anchor + // If so, we can simply copy the preferred information from the other child, + // after bounding it to our minimum and maximum sizes. + // If not, then we proceed with the actual calculations. // - // - There are no negative-sized anchors - // - All sequential anchors are composed of children in the same direction as the - // sequential anchor itself + // 2) The whole algorithm for preferred size calculation is based on the fact + // that, if a given anchor cannot remain at its preferred size, it'd rather + // grow than shrink. // - // 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); + // What happens though is that while this affirmative is true for simple + // anchors, it may not be true for sequential anchors that have one or more + // reversed anchors inside it. That happens because when a sequential anchor + // grows, any reversed anchors inside it may be required to shrink, something + // we try to avoid, as said above. + // + // To overcome this, besides their actual preferred size "prefSize", each anchor + // exports what we call "minPrefSize" and "maxPrefSize". These two values define + // a surrounding interval where, if required to move, the anchor would rather + // remain inside. + // + // For standard anchors, this area simply represents the region between + // prefSize and maxSize, which makes sense since our first affirmation. + // For composed anchors, these values are calculated as to reduce the global + // "damage", that is, to reduce the total deviation and the total amount of + // anchors that had to shrink. + + if (firstEdge->isLayoutAnchor) { + prefSize = qBound(minSize, secondPref, maxSize); + minPrefSize = qBound(minSize, secondMinPref, maxSize); + maxPrefSize = qBound(minSize, secondMaxPref, maxSize); + } else if (secondEdge->isLayoutAnchor) { + prefSize = qBound(minSize, firstEdge->prefSize, maxSize); + minPrefSize = qBound(minSize, firstEdge->minPrefSize, maxSize); + maxPrefSize = qBound(minSize, firstEdge->maxPrefSize, maxSize); + } else { + // Calculate the intersection between the "preferred" regions of each child + const qreal lowerBoundary = + qBound(minSize, qMax(firstEdge->minPrefSize, secondMinPref), maxSize); + const qreal upperBoundary = + qBound(minSize, qMin(firstEdge->maxPrefSize, secondMaxPref), maxSize); + const qreal prefMean = + qBound(minSize, (firstEdge->prefSize + secondPref) / 2, maxSize); + + if (lowerBoundary < upperBoundary) { + // If there is an intersection between the two regions, this intersection + // will be used as the preferred region of the parallel anchor itself. + // The preferred size will be the bounded average between the two preferred + // sizes. + prefSize = qBound(lowerBoundary, prefMean, upperBoundary); + minPrefSize = lowerBoundary; + maxPrefSize = upperBoundary; + } else { + // If there is no intersection, we have to attribute "damage" to at least + // one of the children. The minimum total damage is achieved in points + // inside the region that extends from (1) the upper boundary of the lower + // region to (2) the lower boundary of the upper region. + // Then, we expose this region as _our_ preferred region and once again, + // use the bounded average as our preferred size. + prefSize = qBound(upperBoundary, prefMean, lowerBoundary); + minPrefSize = upperBoundary; + maxPrefSize = lowerBoundary; + } + } // See comment in AnchorData::refreshSizeHints() about sizeAt* values sizeAtMinimum = prefSize; @@ -349,19 +413,28 @@ bool ParallelAnchorData::calculateSizeHints() 1 is at Maximum */ static QPair getFactor(qreal value, qreal min, - qreal pref, qreal max) + qreal minPref, qreal pref, + qreal maxPref, qreal max) { QGraphicsAnchorLayoutPrivate::Interval interval; qreal lower; qreal upper; - if (value < pref) { - interval = QGraphicsAnchorLayoutPrivate::MinToPreferred; + if (value < minPref) { + interval = QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred; lower = min; + upper = minPref; + } else if (value < pref) { + interval = QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred; + lower = minPref; upper = pref; - } else { - interval = QGraphicsAnchorLayoutPrivate::PreferredToMax; + } else if (value < maxPref) { + interval = QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred; lower = pref; + upper = maxPref; + } else { + interval = QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum; + lower = maxPref; upper = max; } @@ -376,19 +449,26 @@ static QPair getFactor(qreal valu } static qreal interpolate(const QPair &factor, - qreal min, qreal pref, - qreal max) + qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max) { qreal lower; qreal upper; switch (factor.first) { - case QGraphicsAnchorLayoutPrivate::MinToPreferred: + case QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred: lower = min; + upper = minPref; + break; + case QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred: + lower = minPref; upper = pref; break; - case QGraphicsAnchorLayoutPrivate::PreferredToMax: + case QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred: lower = pref; + upper = maxPref; + break; + case QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum: + lower = maxPref; upper = max; break; } @@ -414,11 +494,11 @@ void SequentialAnchorData::updateChildrenSizes() // band (the lower band) or the Preferred To Maximum (the upper band). const QPair minFactor = - getFactor(sizeAtMinimum, minSize, prefSize, maxSize); + getFactor(sizeAtMinimum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); const QPair prefFactor = - getFactor(sizeAtPreferred, minSize, prefSize, maxSize); + getFactor(sizeAtPreferred, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); const QPair maxFactor = - getFactor(sizeAtMaximum, minSize, prefSize, maxSize); + getFactor(sizeAtMaximum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize); // XXX This is not safe if Vertex simplification takes place after the sequential // anchor is created. In that case, "prev" will be a group-vertex, different from @@ -430,15 +510,21 @@ void SequentialAnchorData::updateChildrenSizes() const bool edgeIsForward = (e->from == prev); if (edgeIsForward) { - 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->sizeAtMinimum = interpolate(minFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + e->sizeAtPreferred = interpolate(prefFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); + e->sizeAtMaximum = interpolate(maxFactor, e->minSize, e->minPrefSize, + e->prefSize, e->maxPrefSize, e->maxSize); prev = e->to; } else { Q_ASSERT(prev == e->to); - e->sizeAtMinimum = interpolate(minFactor, e->maxSize, e->prefSize, e->minSize); - e->sizeAtPreferred = interpolate(prefFactor, e->maxSize, e->prefSize, e->minSize); - e->sizeAtMaximum = interpolate(maxFactor, e->maxSize, e->prefSize, e->minSize); + e->sizeAtMinimum = interpolate(minFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + e->sizeAtPreferred = interpolate(prefFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); + e->sizeAtMaximum = interpolate(maxFactor, e->maxSize, e->maxPrefSize, + e->prefSize, e->minPrefSize, e->minSize); prev = e->from; } @@ -451,6 +537,8 @@ void SequentialAnchorData::calculateSizeHints() minSize = 0; prefSize = 0; maxSize = 0; + minPrefSize = 0; + maxPrefSize = 0; AnchorVertex *prev = from; @@ -462,12 +550,16 @@ void SequentialAnchorData::calculateSizeHints() minSize += edge->minSize; prefSize += edge->prefSize; maxSize += edge->maxSize; + minPrefSize += edge->minPrefSize; + maxPrefSize += edge->maxPrefSize; prev = edge->to; } else { Q_ASSERT(prev == edge->to); minSize -= edge->maxSize; prefSize -= edge->prefSize; maxSize -= edge->minSize; + minPrefSize -= edge->maxPrefSize; + maxPrefSize -= edge->minPrefSize; prev = edge->from; } } @@ -2690,6 +2782,8 @@ void QGraphicsAnchorLayoutPrivate::setupEdgesInterpolation( result = getFactor(current, sizeHints[orientation][Qt::MinimumSize], sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::PreferredSize], + sizeHints[orientation][Qt::PreferredSize], sizeHints[orientation][Qt::MaximumSize]); interpolationInterval[orientation] = result.first; @@ -2718,6 +2812,7 @@ void QGraphicsAnchorLayoutPrivate::interpolateEdge(AnchorVertex *base, AnchorDat interpolationProgress[orientation]); qreal edgeDistance = interpolate(factor, edge->sizeAtMinimum, edge->sizeAtPreferred, + edge->sizeAtPreferred, edge->sizeAtPreferred, edge->sizeAtMaximum); Q_ASSERT(edge->from == base || edge->to == base); diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.h b/src/gui/graphicsview/qgraphicsanchorlayout_p.h index 8d2d691..3be9d41 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.h +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.h @@ -123,6 +123,7 @@ struct AnchorData : public QSimplexVariable { AnchorData() : QSimplexVariable(), from(0), to(0), minSize(0), prefSize(0), maxSize(0), + minPrefSize(0), maxPrefSize(0), sizeAtMinimum(0), sizeAtPreferred(0), sizeAtMaximum(0), item(0), graphicsAnchor(0), type(Normal), isLayoutAnchor(false), @@ -153,6 +154,9 @@ struct AnchorData : public QSimplexVariable { qreal prefSize; qreal maxSize; + qreal minPrefSize; + qreal maxPrefSize; + // Calculated sizes // These attributes define which sizes should that anchor be in when the // layout is at its minimum, preferred or maximum sizes. Values are @@ -371,8 +375,10 @@ public: // // Interval represents which interpolation interval are we operating in. enum Interval { - MinToPreferred = 0, - PreferredToMax + MinimumToMinPreferred = 0, + MinPreferredToPreferred, + PreferredToMaxPreferred, + MaxPreferredToMaximum }; // Several structures internal to the layout are duplicated to handle -- cgit v0.12 From f2eeef396296a3563c006b77c0ce3708d6256bd3 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Wed, 18 Nov 2009 17:15:23 -0300 Subject: QGAL: Refactor solvePreferred to support min/maxPrefSize With the addition of min and maxPrefSizeHints, the logic used in the preferred simplex had to be changed. The concept behind it is still the same, we minimize the deviation from each item's preferred size. Previously we would make an special effort to avoid items being shrunk, that's why the "shinker" simplex variables had larger multipliers in the objective function. Now we still have variables with large multipliers and others with small mutlipliers, but rather than differentiating between shrinker and/or grower variables, we classify them as "soft" or "hard". "Soft" variables, or slacks, are those that allow the anchor to change its value up to the minPref or maxPref boundaries, OTOH, "hard" slacks are those that allow the item to go all the way to its minimum or maximum sizes. Naturally, the "hard" slacks get large coeficients in the objective function while the "soft" ones get small ones. Now all the slack variables are restricted in size as to respect the boundaries of each interval: - Minimum to MinimumPreferred, - MinimumPreferred to Preferred, - Preferred to MaximumPreferred and - MaximumPreferred to Maximum With such limits, the SizeHint constraints became redundant in the calculation of preferred sizes so they are no longer used. Additionally, as an optimization, if a given interval is NULL (for instance, MinimumPreferred is equal to Preferred), then the associated slack variable would have its size restricted to zero, therefore it is not created at all. Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 93 ++++++++++++++++++------ 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 6d90d3a..2f4ec26 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -2242,7 +2242,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const feasible = solveMinMax(allConstraints, path, &min, &max); if (feasible) { - solvePreferred(allConstraints, variables); + solvePreferred(constraints, variables); // Calculate and set the preferred size for the layout, // from the edge sizes that were calculated above. @@ -2291,12 +2291,8 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList &constraints, const QList &variables) { - QList sizeHintConstraints = constraintsFromSizeHints(variables); - - shiftConstraints(sizeHintConstraints, limit); shiftConstraints(constraints, limit); - - bool feasible = solvePreferred(constraints + sizeHintConstraints, variables); + bool feasible = solvePreferred(constraints, variables); if (feasible) { // Propagate size at preferred to other sizes. Semi-floats always will be @@ -2309,10 +2305,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList return feasible; } +enum slackType { Grower = -1, Shrinker = 1 }; +static QPair createSlack(QSimplexConstraint *sizeConstraint, + qreal interval, slackType type) +{ + QSimplexVariable *slack = new QSimplexVariable; + sizeConstraint->variables.insert(slack, type); + + QSimplexConstraint *limit = new QSimplexConstraint; + limit->variables.insert(slack, 1.0); + limit->ratio = QSimplexConstraint::LessOrEqual; + limit->constant = interval; + + return qMakePair(slack, limit); +} + bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QList &constraints, const QList &variables) { @@ -2879,7 +2887,8 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QListisLayoutAnchor) continue; - QSimplexVariable *grower = new QSimplexVariable; - QSimplexVariable *shrinker = new QSimplexVariable; - QSimplexConstraint *c = new QSimplexConstraint; - c->variables.insert(ad, 1.0); - c->variables.insert(shrinker, 1.0); - c->variables.insert(grower, -1.0); - c->constant = ad->prefSize + limit; + // By default, all variables are equal to their preferred size. If they have room to + // grow or shrink, such flexibility will be added by the additional variables below. + QSimplexConstraint *sizeConstraint = new QSimplexConstraint; + preferredConstraints += sizeConstraint; + sizeConstraint->variables.insert(ad, 1.0); + sizeConstraint->constant = ad->prefSize + limit; + + // Can easily shrink + QPair slack; + const qreal softShrinkInterval = ad->prefSize - ad->minPrefSize; + if (softShrinkInterval) { + slack = createSlack(sizeConstraint, softShrinkInterval, Shrinker); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == 1 (soft) + objective.variables.insert(slack.first, 1.0); + } - preferredConstraints += c; - preferredVariables += grower; - preferredVariables += shrinker; + // Can easily grow + const qreal softGrowInterval = ad->maxPrefSize - ad->prefSize; + if (softGrowInterval) { + slack = createSlack(sizeConstraint, softGrowInterval, Grower); + preferredVariables += slack.first; + preferredConstraints += slack.second; - objective.variables.insert(grower, 1.0); - objective.variables.insert(shrinker, variables.size()); - } + // Add to objective with ratio == 1 (soft) + objective.variables.insert(slack.first, 1.0); + } + // Can shrink if really necessary + const qreal hardShrinkInterval = ad->minPrefSize - ad->minSize; + if (hardShrinkInterval) { + slack = createSlack(sizeConstraint, hardShrinkInterval, Shrinker); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == N (hard) + objective.variables.insert(slack.first, variables.size()); + } + + // Can grow if really necessary + const qreal hardGrowInterval = ad->maxSize - ad->maxPrefSize; + if (hardGrowInterval) { + slack = createSlack(sizeConstraint, hardGrowInterval, Grower); + preferredVariables += slack.first; + preferredConstraints += slack.second; + + // Add to objective with ratio == N (hard) + objective.variables.insert(slack.first, variables.size()); + } + } QSimplex *simplex = new QSimplex; bool feasible = simplex->setConstraints(constraints + preferredConstraints); -- cgit v0.12 From 89e26da4374563c9419da1357a24cf9618d804d6 Mon Sep 17 00:00:00 2001 From: "Eduardo M. Fleury" Date: Thu, 19 Nov 2009 19:35:53 -0300 Subject: QGAL: Remove deprecated assert clauses These assert messages are no longer valid since some anchors can have their size not respected if they are in parallel with the layout and have size equal to "QWIDGETSIZE_MAX". Signed-off-by: Eduardo M. Fleury Reviewed-by: Caio Marcelo de Oliveira Filho --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index 2f4ec26..b35ee46 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -478,18 +478,6 @@ static qreal interpolate(const QPair 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). @@ -2844,8 +2832,6 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList for (int i = 0; i < variables.size(); ++i) { AnchorData *ad = static_cast(variables.at(i)); ad->sizeAtMinimum = ad->result - limit; - Q_ASSERT(ad->sizeAtMinimum >= ad->minSize || - qAbs(ad->sizeAtMinimum - ad->minSize) < 0.00000001); } // Calculate maximum values @@ -2855,8 +2841,6 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList for (int i = 0; i < variables.size(); ++i) { AnchorData *ad = static_cast(variables.at(i)); ad->sizeAtMaximum = ad->result - limit; - // Q_ASSERT(ad->sizeAtMaximum <= ad->maxSize || - // qAbs(ad->sizeAtMaximum - ad->maxSize) < 0.00000001); } } return feasible; -- cgit v0.12 From e112bc5d2e1eb567aef3077a014a021244ed2ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Thu, 3 Dec 2009 10:03:19 +0100 Subject: Rename the global limit to g_offset. It is both a limit and an offset (the offset is dictated by the limit), but in the code is it interpreted as an offset. --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index b35ee46..b33e228 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE // they are actually calculated in the interval [0, 2 * limit] // To avoid numerical errors in platforms where we use single precision, // we use a tighter limit for the variables range. -const qreal limit = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32; +const qreal g_offset = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32; QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(int version) : QObjectPrivate(version), layoutPrivate(0), data(0), @@ -2223,7 +2223,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const QList sizeHintConstraints = constraintsFromSizeHints(variables); QList allConstraints = constraints + sizeHintConstraints; - shiftConstraints(allConstraints, limit); + shiftConstraints(allConstraints, g_offset); // Solve min and max size hints qreal min, max; @@ -2248,7 +2248,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const } qDeleteAll(sizeHintConstraints); - shiftConstraints(constraints, -limit); + shiftConstraints(constraints, -g_offset); } else { // No Simplex is necessary because the path was simplified all the way to a single @@ -2279,7 +2279,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateTrunk(Orientation orientation, const bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList &constraints, const QList &variables) { - shiftConstraints(constraints, limit); + shiftConstraints(constraints, g_offset); bool feasible = solvePreferred(constraints, variables); if (feasible) { @@ -2293,7 +2293,7 @@ bool QGraphicsAnchorLayoutPrivate::calculateNonTrunk(const QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin // To use negative variables inside simplex, we shift them so the minimum negative value is // mapped to zero before solving. To make sure that it works, we need to guarantee that the // variables are all inside a certain boundary. - qreal boundedMin = qBound(-limit, ad->minSize, limit); - qreal boundedMax = qBound(-limit, ad->maxSize, limit); + qreal boundedMin = qBound(-g_offset, ad->minSize, g_offset); + qreal boundedMax = qBound(-g_offset, ad->maxSize, g_offset); if ((boundedMin == boundedMax) || qFuzzyCompare(boundedMin, boundedMax)) { QSimplexConstraint *c = new QSimplexConstraint; @@ -2497,7 +2497,7 @@ QList QGraphicsAnchorLayoutPrivate::constraintsFromSizeHin QSimplexConstraint *c = new QSimplexConstraint; c->variables.insert(layoutEdge, 1.0); // The maximum size that the layout can take - c->constant = limit; + c->constant = g_offset; c->ratio = QSimplexConstraint::LessOrEqual; anchorConstraints += c; } @@ -2821,7 +2821,7 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList for (iter = path.negatives.constBegin(); iter != path.negatives.constEnd(); ++iter) objective.variables.insert(*iter, -1.0); - const qreal objectiveOffset = (path.positives.count() - path.negatives.count()) * limit; + const qreal objectiveOffset = (path.positives.count() - path.negatives.count()) * g_offset; simplex.setObjective(&objective); // Calculate minimum values @@ -2831,7 +2831,7 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList QList variables = getVariables(constraints); for (int i = 0; i < variables.size(); ++i) { AnchorData *ad = static_cast(variables.at(i)); - ad->sizeAtMinimum = ad->result - limit; + ad->sizeAtMinimum = ad->result - g_offset; } // Calculate maximum values @@ -2840,7 +2840,7 @@ bool QGraphicsAnchorLayoutPrivate::solveMinMax(const QList // Save sizeAtMaximum results for (int i = 0; i < variables.size(); ++i) { AnchorData *ad = static_cast(variables.at(i)); - ad->sizeAtMaximum = ad->result - limit; + ad->sizeAtMaximum = ad->result - g_offset; } } return feasible; @@ -2898,7 +2898,7 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QListvariables.insert(ad, 1.0); - sizeConstraint->constant = ad->prefSize + limit; + sizeConstraint->constant = ad->prefSize + g_offset; // Can easily shrink QPair slack; @@ -2957,7 +2957,7 @@ bool QGraphicsAnchorLayoutPrivate::solvePreferred(const QListsizeAtPreferred = ad->result - limit; + ad->sizeAtPreferred = ad->result - g_offset; } // Make sure we delete the simplex solver -before- we delete the -- cgit v0.12 From ac83e69ffbabff8a5e674d38f0bc90ff5f188f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Arve=20S=C3=A6ther?= Date: Fri, 4 Dec 2009 11:24:27 +0100 Subject: Made sure that we didn't reference dangling parallel anchors. We cannot delete the parallel anchors before we do the actual vertex restoration, since the parallel anchors created by vertex simplification will always be connected to some of the AnchorVertexPair found in simplifiedVertices that we iterate over later. Thus, we must delete the parallel anchors created by vertex simplification after we have done the restoration of the vertices. (the parallel anchor will be properly dealt with (i.e. taken out of the graph as we do the restoration)), so not deleting it won't cause it to be revisited later if that is a worry. Reviewed-by: Eduardo M. Fleury --- src/gui/graphicsview/qgraphicsanchorlayout_p.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp index b33e228..03ed63d 100644 --- a/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp +++ b/src/gui/graphicsview/qgraphicsanchorlayout_p.cpp @@ -1317,9 +1317,7 @@ void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) for (int i = parallelAnchors.count() - 1; i >= 0; --i) { ParallelAnchorData *parallel = static_cast(parallelAnchors.at(i)); restoreSimplifiedConstraints(parallel); - delete parallel; } - parallelAnchors.clear(); // Then, 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. @@ -1365,6 +1363,8 @@ void QGraphicsAnchorLayoutPrivate::restoreVertices(Orientation orientation) delete pair; } + qDeleteAll(parallelAnchors); + parallelAnchors.clear(); toRestore.clear(); } -- cgit v0.12