diff options
Diffstat (limited to 'examples/tutorials/widgets/nestedlayouts/main.cpp')
0 files changed, 0 insertions, 0 deletions
![]() |
index : qt/qtbase.git | |
| Qt Base (Core, Gui, Widgets, Network, ...) |
| summaryrefslogtreecommitdiffstats |
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qgraphicsanchorlayout_p.h"
#include <QtWidgets/qwidget.h>
#include <QtWidgets/qapplication.h>
#include <QtCore/qstack.h>
#ifdef QT_DEBUG
#include <QtCore/qfile.h>
#endif
#include <numeric>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// 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 g_offset = (sizeof(qreal) == sizeof(double)) ? QWIDGETSIZE_MAX : QWIDGETSIZE_MAX / 32;
QGraphicsAnchorPrivate::QGraphicsAnchorPrivate(decltype(QObjectPrivateVersion) version)
: QObjectPrivate(version), layoutPrivate(nullptr), data(nullptr),
sizePolicy(QSizePolicy::Fixed), preferredSize(0),
hasSize(true)
{
}
QGraphicsAnchorPrivate::~QGraphicsAnchorPrivate()
{
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 = nullptr;
layoutPrivate->removeAnchor(data->from, data->to);
}
}
void QGraphicsAnchorPrivate::setSizePolicy(QSizePolicy::Policy policy)
{
if (sizePolicy != policy) {
sizePolicy = policy;
layoutPrivate->q_func()->invalidate();
}
}
void QGraphicsAnchorPrivate::setSpacing(qreal value)
{
if (!data) {
qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist.");
return;
}
if (hasSize && (preferredSize == value))
return;
// The anchor has an user-defined size
hasSize = true;
preferredSize = value;
layoutPrivate->q_func()->invalidate();
}
void QGraphicsAnchorPrivate::unsetSpacing()
{
if (!data) {
qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist.");
return;
}
// Return to standard direction
hasSize = false;
layoutPrivate->q_func()->invalidate();
}
qreal QGraphicsAnchorPrivate::spacing() const
{
if (!data) {
qWarning("QGraphicsAnchor::setSpacing: The anchor does not exist.");
return 0;
}
return preferredSize;
}
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.
//
// Then we check each flag to find the resultant QSizePolicy,
// according to the following table:
//
// constant value
// QSizePolicy::Fixed 0
// QSizePolicy::Minimum GrowFlag
// QSizePolicy::Maximum ShrinkFlag
// QSizePolicy::Preferred GrowFlag | ShrinkFlag
// QSizePolicy::Ignored GrowFlag | ShrinkFlag | IgnoreFlag
if (policy & QSizePolicy::ShrinkFlag)
*minSize = minSizeHint;
else
*minSize = prefSizeHint;
if (policy & QSizePolicy::GrowFlag)
*maxSize = maxSizeHint;
else
*maxSize = prefSizeHint;
// Note that these two initializations are affected by the previous flags
if (policy & QSizePolicy::IgnoreFlag)
*prefSize = *minSize;
else
*prefSize = prefSizeHint;
}
AnchorData::~AnchorData()
{
if (graphicsAnchor) {
// Remove reference to ourself to avoid double removal in
// QGraphicsAnchorPrivate dtor.
QGraphicsAnchorPrivate::get(graphicsAnchor)->data = nullptr;
delete graphicsAnchor;
}
}
void AnchorData::refreshSizeHints(const QLayoutStyleInfo *styleInfo)
{
QSizePolicy::Policy policy;
qreal minSizeHint;
qreal prefSizeHint;
qreal maxSizeHint;
if (item) {
// It is an internal anchor, fetch size information from the item
if (isLayoutAnchor) {
minSize = 0;
prefSize = 0;
maxSize = QWIDGETSIZE_MAX;
if (isCenterAnchor)
maxSize /= 2;
minPrefSize = prefSize;
maxPrefSize = maxSize;
return;
} else {
if (!isVertical) {
policy = item->sizePolicy().horizontalPolicy();
minSizeHint = item->effectiveSizeHint(Qt::MinimumSize).width();
prefSizeHint = item->effectiveSizeHint(Qt::PreferredSize).width();
maxSizeHint = item->effectiveSizeHint(Qt::MaximumSize).width();
} else {
policy = item->sizePolicy().verticalPolicy();
minSizeHint = item->effectiveSizeHint(Qt::MinimumSize).height();
prefSizeHint = item->effectiveSizeHint(Qt::PreferredSize).height();
maxSizeHint = item->effectiveSizeHint(Qt::MaximumSize).height();
}
if (isCenterAnchor) {
minSizeHint /= 2;
prefSizeHint /= 2;
maxSizeHint /= 2;
}
}
} else {
// It is a user-created anchor, fetch size information from the associated QGraphicsAnchor
Q_ASSERT(graphicsAnchor);
QGraphicsAnchorPrivate *anchorPrivate = QGraphicsAnchorPrivate::get(graphicsAnchor);
// Policy, min and max sizes are straightforward
policy = anchorPrivate->sizePolicy;
minSizeHint = 0;
maxSizeHint = QWIDGETSIZE_MAX;
// Preferred Size
if (anchorPrivate->hasSize) {
// Anchor has user-defined size
prefSizeHint = anchorPrivate->preferredSize;
} else if (styleInfo) {
// Fetch size information from style
const Qt::Orientation orient = QGraphicsAnchorLayoutPrivate::edgeOrientation(from->m_edge);
qreal s = styleInfo->defaultSpacing(orient);
if (s < 0) {
QSizePolicy::ControlType controlTypeFrom = from->m_item->sizePolicy().controlType();
QSizePolicy::ControlType controlTypeTo = to->m_item->sizePolicy().controlType();
s = styleInfo->perItemSpacing(controlTypeFrom, controlTypeTo, orient);
// ### Currently we do not support negative anchors inside the graph.
// To avoid those being created by a negative style spacing, we must
// make this test.
if (s < 0)
s = 0;
}
prefSizeHint = s;
} else {
prefSizeHint = 0;
}
}
// Fill minSize, prefSize and maxSize based on policy and sizeHints
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
// preferred size unless where that's impossible. In cases where
// the item is subject to restrictions (anchored to the layout
// edges, for instance), the simplex solver will be run to
// recalculate and override the values we set here.
sizeAtMinimum = prefSize;
sizeAtPreferred = prefSize;
sizeAtMaximum = prefSize;
}
void ParallelAnchorData::updateChildrenSizes()
{
firstEdge->sizeAtMinimum = sizeAtMinimum;
firstEdge->sizeAtPreferred = sizeAtPreferred;
firstEdge->sizeAtMaximum = sizeAtMaximum;
if (secondForward()) {
secondEdge->sizeAtMinimum = sizeAtMinimum;
secondEdge->sizeAtPreferred = sizeAtPreferred;
secondEdge->sizeAtMaximum = sizeAtMaximum;
} else {
secondEdge->sizeAtMinimum = -sizeAtMinimum;
secondEdge->sizeAtPreferred = -sizeAtPreferred;
secondEdge->sizeAtMaximum = -sizeAtMaximum;
}
firstEdge->updateChildrenSizes();
secondEdge->updateChildrenSizes();
}
/*
\internal
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;
}
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
// for this parallel setup.
if (minSize > maxSize) {
return false;
}
// 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.
//
// 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.
//
// 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;
sizeAtPreferred = prefSize;
sizeAtMaximum = prefSize;
return true;
}
/*!
\internal
returns the factor in the interval [-1, 1].
-1 is at Minimum
0 is at Preferred
1 is at Maximum
*/
static std::pair<QGraphicsAnchorLayoutPrivate::Interval, qreal> getFactor(qreal value, qreal min,
qreal minPref, qreal pref,
qreal maxPref, qreal max)
{
QGraphicsAnchorLayoutPrivate::Interval interval;
qreal lower;
qreal upper;
if (value < minPref) {
interval = QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred;
lower = min;
upper = minPref;
} else if (value < pref) {
interval = QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred;
lower = minPref;
upper = pref;
} else if (value < maxPref) {
interval = QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred;
lower = pref;
upper = maxPref;
} else {
interval = QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum;
lower = maxPref;
upper = max;
}
qreal progress;
if (upper == lower) {
progress = 0;
} else {
progress = (value - lower) / (upper - lower);
}
return std::pair(interval, progress);
}
static qreal interpolate(const std::pair<QGraphicsAnchorLayoutPrivate::Interval, qreal> &factor,
qreal min, qreal minPref, qreal pref, qreal maxPref, qreal max)
{
qreal lower = 0;
qreal upper = 0;
switch (factor.first) {
case QGraphicsAnchorLayoutPrivate::MinimumToMinPreferred:
lower = min;
upper = minPref;
break;
case QGraphicsAnchorLayoutPrivate::MinPreferredToPreferred:
lower = minPref;
upper = pref;
break;
case QGraphicsAnchorLayoutPrivate::PreferredToMaxPreferred:
lower = pref;
upper = maxPref;
break;
case QGraphicsAnchorLayoutPrivate::MaxPreferredToMaximum:
lower = maxPref;
upper = max;
break;
}
return lower + factor.second * (upper - lower);
}
void SequentialAnchorData::updateChildrenSizes()
{
// 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 std::pair<QGraphicsAnchorLayoutPrivate::Interval, qreal> minFactor =
getFactor(sizeAtMinimum, minSize, minPrefSize, prefSize, maxPrefSize, maxSize);
const std::pair<QGraphicsAnchorLayoutPrivate::Interval, qreal> prefFactor =
getFactor(sizeAtPreferred, minSize, minPrefSize, prefSize, maxPrefSize, maxSize);
const std::pair<QGraphicsAnchorLayoutPrivate::Interval, qreal> maxFactor =
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
// "from" or "to", that _contains_ one of them.
AnchorVertex *prev = from;
for (AnchorData *e : m_edges) {
const bool edgeIsForward = (e->from == prev);
if (edgeIsForward) {
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->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;
}
e->updateChildrenSizes();
}
}
void SequentialAnchorData::calculateSizeHints()
{
minSize = 0;
prefSize = 0;
maxSize = 0;
minPrefSize = 0;
maxPrefSize = 0;
AnchorVertex *prev = from;
for (AnchorData *edge : m_edges) {
const bool edgeIsForward = (edge->from == prev);
if (edgeIsForward) {
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;
}
}
// See comment in AnchorData::refreshSizeHints() about sizeAt* values
sizeAtMinimum = prefSize;
sizeAtPreferred = prefSize;
sizeAtMaximum = prefSize;
}
#ifdef QT_DEBUG
void AnchorData::dump(int indent) {
if (type == Parallel) {
qDebug("%*s type: parallel:", indent, "");
ParallelAnchorData *p = static_cast<ParallelAnchorData *>(this);
p->firstEdge->dump(indent+2);
p->secondEdge->dump(indent+2);
} else if (type == Sequential) {
const auto *s = static_cast<SequentialAnchorData *>(this);
qDebug("%*s type: sequential(%lld):", indent, "", qint64(s->m_edges.size()));
for (AnchorData *e : s->m_edges)
e->dump(indent + 2);
} else {
qDebug("%*s type: Normal:", indent, "");
}
}
#endif
QSimplexConstraint *GraphPath::constraint(const GraphPath &path) const
{
// Calculate
QSet<AnchorData *> cPositives;
QSet<AnchorData *> cNegatives;
QSet<AnchorData *> intersection;
cPositives = positives + path.negatives;
cNegatives = negatives + path.positives;
intersection = cPositives & cNegatives;
cPositives -= intersection;
cNegatives -= intersection;
// Fill
QSimplexConstraint *c = new QSimplexConstraint;
QSet<AnchorData *>::iterator i;
for (i = cPositives.begin(); i != cPositives.end(); ++i)
c->variables.insert(*i, 1.0);
for (i = cNegatives.begin(); i != cNegatives.end(); ++i)
c->variables.insert(*i, -1.0);
return c;
}
#ifdef QT_DEBUG
QString GraphPath::toString() const
{
QString string;
string += "Path: "_L1;
for (AnchorData *edge : positives)
string += QString::fromLatin1(" (+++) %1").arg(edge->toString());
for (AnchorData *edge : negatives)
string += QString::fromLatin1(" (---) %1").arg(edge->toString());
return string;
}
#endif
QGraphicsAnchorLayoutPrivate::QGraphicsAnchorLayoutPrivate()
: calculateGraphCacheDirty(true), styleInfoDirty(true)
{
}
Qt::AnchorPoint QGraphicsAnchorLayoutPrivate::oppositeEdge(Qt::AnchorPoint edge)
{
switch (edge) {
case Qt::AnchorLeft:
edge = Qt::AnchorRight;
break;
case Qt::AnchorRight:
edge = Qt::AnchorLeft;
break;
case Qt::AnchorTop:
edge = Qt::AnchorBottom;
break;
case Qt::AnchorBottom:
edge = Qt::AnchorTop;
break;
default:
break;
}
return edge;
}
/*!
\internal
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. 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.
*/
AnchorData *QGraphicsAnchorLayoutPrivate::addAnchorMaybeParallel(AnchorData *newAnchor, bool *feasible)
{
const Qt::Orientation orientation = newAnchor->isVertical ? Qt::Vertical : Qt::Horizontal;
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)) {
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];
// 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 j = 0; j < constraints.size(); ++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);
}
}
}
// 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->calculateSizeHints();
newAnchor = parallel;
}
g.createEdge(newAnchor->from, newAnchor->to, newAnchor);
return newAnchor;
}
/*!
\internal
Takes the sequence of vertices described by (\a before, \a vertices, \a after) and removes
all anchors connected to the vertices in \a vertices, returning one simplified anchor between
\a before and \a after.
Note that this function doesn't add the created anchor to the graph. This should be done by
the caller.
*/
static AnchorData *createSequence(Graph<AnchorVertex, AnchorData> *graph, AnchorVertex *before,
const QList<AnchorVertex *> &vertices, AnchorVertex *after)
{
#if defined(QT_DEBUG) && 0
QString strVertices;
for (int i = 0; i < vertices.count(); ++i) {
strVertices += QString::fromLatin1("%1 - ").arg(vertices.at(i)->toString());
}
QString strPath = QString::fromLatin1("%1 - %2%3").arg(before->toString(), strVertices, after->toString());
qDebug("simplifying [%s] to [%s - %s]", qPrintable(strPath), qPrintable(before->toString()), qPrintable(after->toString()));
#endif
AnchorVertex *prev = before;
QList<AnchorData *> edges;
edges.reserve(vertices.size() + 1);
const int numVertices = vertices.size();
edges.reserve(numVertices + 1);
// Take from the graph, the edges that will be simplificated
for (int i = 0; i < numVertices; ++i) {
AnchorVertex *next = vertices.at(i);
AnchorData *ad = graph->takeEdge(prev, next);
Q_ASSERT(ad);
edges.append(ad);
prev = next;
}
// 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;
sequence->calculateSizeHints();
return sequence;
}
/*!
\internal
The purpose of this function is to simplify the graph.
Simplification serves two purposes:
1. Reduce the number of edges in the graph, (thus the number of variables to the equation
solver is reduced, and the solver performs better).
2. Be able to do distribution of sequences of edges more intelligently (esp. with sequential
anchors)
It is essential that it must be possible to restore simplified anchors back to their "original"
form. This is done by restoreSimplifiedAnchor().
There are two types of simplification that can be done:
1. Sequential simplification
Sequential simplification means that all sequences of anchors will be merged into one single
anchor. Only anhcors that points in the same direction will be merged.
2. Parallel simplification
If a simplified sequential anchor is about to be inserted between two vertices in the graph
and there already exist an anchor between those two vertices, a parallel anchor will be
created that serves as a placeholder for the sequential anchor and the anchor that was
already between the two vertices.
The process of simplification can be described as:
1. Simplify all sequences of anchors into one anchor.
If no further simplification was done, go to (3)
- If there already exist an anchor where the sequential anchor is supposed to be inserted,
take that anchor out of the graph
- Then create a parallel anchor that holds the sequential anchor and the anchor just taken
out of the graph.
2. Go to (1)
3. Done
When creating the parallel anchors, the algorithm might identify unfeasible situations. In this
case the simplification process stops and returns \c false. Otherwise returns \c true.
*/
bool QGraphicsAnchorLayoutPrivate::simplifyGraph(Qt::Orientation orientation)
{
if (items.isEmpty())
return true;
#if defined(QT_DEBUG) && 0
qDebug("Simplifying Graph for %s",
orientation == Horizontal ? "Horizontal" : "Vertical");
static int count = 0;
if (orientation == Horizontal) {
count++;
dumpGraph(QString::fromLatin1("%1-full").arg(count));
}
#endif
// Vertex simplification
if (!simplifyVertices(orientation)) {
restoreVertices(orientation);
return false;
}
// Anchor simplification
bool dirty;
bool feasible = true;
do {
dirty = simplifyGraphIteration(orientation, &feasible);
} while (dirty && feasible);
// Note that if we are not feasible, we fallback and make sure that the graph is fully restored
if (!feasible) {
restoreSimplifiedGraph(orientation);
restoreVertices(orientation);
return false;
}
#if defined(QT_DEBUG) && 0
dumpGraph(QString::fromLatin1("%1-simplified-%2").arg(count).arg(
QString::fromLatin1(orientation == Horizontal ? "Horizontal" : "Vertical")));
#endif
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(Qt::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.size(); ++i) {
AnchorData *ad = edges[i];
AnchorVertex *otherV = replaceVertex_helper(ad, oldV, newV);
#if defined(QT_DEBUG)
ad->name = QString::fromLatin1("%1 --to--> %2").arg(ad->from->toString(), 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(Qt::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.size()) {
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.size(); ++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.size(); ++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 = nullptr;
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 \c true if another iteration is needed.
The algorithm walks the graph in depth-first order, and only collects vertices that has two
edges connected to it. If the vertex does not have two edges or if it is a layout edge, it
will take all the previously collected vertices and try to create a simplified sequential
anchor representing all the previously collected vertices. Once the simplified anchor is
inserted, the collected list is cleared in order to find the next sequence to simplify.
Note that there are some catches to this that are not covered by the above explanation, see
the function comments for more details.
*/
bool QGraphicsAnchorLayoutPrivate::simplifyGraphIteration(Qt::Orientation orientation,
bool *feasible)
{
Q_Q(QGraphicsAnchorLayout);
Graph<AnchorVertex, AnchorData> &g = graph[orientation];
QSet<AnchorVertex *> visited;
QStack<std::pair<AnchorVertex *, AnchorVertex *> > stack;
stack.push(std::pair(static_cast<AnchorVertex *>(nullptr), layoutFirstVertex[orientation]));
QList<AnchorVertex *> candidates;
// Walk depth-first, in the stack we store start of the candidate sequence (beforeSequence)
// and the vertex to be visited.
while (!stack.isEmpty()) {
std::pair<AnchorVertex *, AnchorVertex *> pair = stack.pop();
AnchorVertex *beforeSequence = pair.first;
AnchorVertex *v = pair.second;
// The basic idea is to determine whether we found an end of sequence,
// if that's the case, we stop adding vertices to the candidate list
// and do a simplification step.
//
// 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) its next adjacent is already visited (a cycle in the graph).
// (d) the next anchor is a center anchor.
const QList<AnchorVertex *> &adjacents = g.adjacentVertices(v);
const bool isLayoutVertex = v->m_item == q;
AnchorVertex *afterSequence = v;
bool endOfSequence = false;
//
// Identify the end cases.
//
// Identifies cases (a) and (b)
endOfSequence = isLayoutVertex || adjacents.size() != 2;
if (!endOfSequence) {
// This is a tricky part. We peek at the next vertex to find out whether
//
// - 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.
// Peek at the next vertex
AnchorVertex *after;
if (candidates.isEmpty())
after = (beforeSequence == adjacents.last() ? adjacents.first() : adjacents.last());
else
after = (candidates.constLast() == adjacents.last() ? adjacents.first() : adjacents.last());
// ### At this point we assumed that candidates will not contain 'after', this may not hold
// when simplifying FLOATing anchors.
Q_ASSERT(!candidates.contains(after));
const AnchorData *data = g.edgeData(v, after);
Q_ASSERT(data);
const bool cycleFound = visited.contains(after);
// Now cases (c) and (d)...
endOfSequence = cycleFound || data->isCenterAnchor;
if (!endOfSequence) {
// If it's not an end of sequence, then the vertex didn't trigger neither of the
// 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);
}
}
//
// Add next non-visited vertices to the stack.
//
for (int i = 0; i < adjacents.size(); ++i) {
AnchorVertex *next = adjacents.at(i);
if (visited.contains(next))
continue;
// If current vertex is an end of sequence, and it'll reset the candidates list. So
// the next vertices will build candidates lists with the current vertex as 'before'
// vertex. If it's not an end of sequence, we keep the original 'before' vertex,
// since we are keeping the candidates list.
if (endOfSequence)
stack.push(std::pair(v, next));
else
stack.push(std::pair(beforeSequence, next));
}
visited.insert(v);
if (!endOfSequence || candidates.isEmpty())
continue;
//
// Create a sequence for (beforeSequence, candidates, afterSequence).
//
// One restriction we have is to not simplify half of an anchor and let the other half
// unsimplified. So we remove center edges before and after the sequence.
const AnchorData *firstAnchor = g.edgeData(beforeSequence, candidates.constFirst());
if (firstAnchor->isCenterAnchor) {
beforeSequence = candidates.constFirst();
candidates.remove(0);
// If there's not candidates to be simplified, leave.
if (candidates.isEmpty())
continue;
}
const AnchorData *lastAnchor = g.edgeData(candidates.constLast(), afterSequence);
if (lastAnchor->isCenterAnchor) {
afterSequence = candidates.constLast();
candidates.remove(candidates.size() - 1);
if (candidates.isEmpty())
continue;
}
//
// Add the sequence to the graph.
//
AnchorData *sequence = createSequence(&g, beforeSequence, candidates, afterSequence);
// If 'beforeSequence' and 'afterSequence' already had an anchor between them, we'll
// create a parallel anchor between the new sequence and the old anchor.
bool newFeasible;
AnchorData *newAnchor = addAnchorMaybeParallel(sequence, &newFeasible);
if (!newFeasible) {
*feasible = false;
return false;
}
// When a new parallel anchor is create in the graph, we finish the iteration and return
// true to indicate a new iteration is needed. This happens because a parallel anchor
// changes the number of adjacents one vertex has, possibly opening up oportunities for
// building candidate lists (when adjacents == 2).
if (newAnchor != sequence)
return true;
// If there was no parallel simplification, we'll keep walking the graph. So we clear the
// candidates list to start again.
candidates.clear();
}
return false;
}
void QGraphicsAnchorLayoutPrivate::restoreSimplifiedAnchor(AnchorData *edge)
{
const Qt::Orientation orientation = edge->isVertical ? Qt::Vertical : Qt::Horizontal;
#if 0
static const char *anchortypes[] = {"Normal",
"Sequential",
"Parallel"};
qDebug("Restoring %s edge.", anchortypes[int(edge->type)]);
#endif
Graph<AnchorVertex, AnchorData> &g = graph[orientation];
if (edge->type == AnchorData::Normal) {
g.createEdge(edge->from, edge->to, edge);
} else if (edge->type == AnchorData::Sequential) {
const auto *sequence = static_cast<SequentialAnchorData *>(edge);
for (AnchorData *data : sequence->m_edges)
restoreSimplifiedAnchor(data);
delete sequence;
} else if (edge->type == AnchorData::Parallel) {
// 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[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.size(); ++i) {
QSimplexConstraint *c = parallel->m_firstConstraints.at(i);
qreal v = c->variables[parallel];
c->variables.remove(parallel);
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.size(); ++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);
}
}
void QGraphicsAnchorLayoutPrivate::restoreSimplifiedGraph(Qt::Orientation orientation)
{
#if 0
qDebug("Restoring Simplified Graph for %s",
orientation == Horizontal ? "Horizontal" : "Vertical");
#endif
// Restore anchor simplification
Graph<AnchorVertex, AnchorData> &g = graph[orientation];
QList<std::pair<AnchorVertex *, AnchorVertex *>> connections = g.connections();
for (int i = 0; i < connections.size(); ++i) {
AnchorVertex *v1 = connections.at(i).first;
AnchorVertex *v2 = connections.at(i).second;
AnchorData *edge = g.edgeData(v1, v2);
// 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(Qt::Orientation orientation)
{
Q_Q(QGraphicsAnchorLayout);
Graph<AnchorVertex, AnchorData> &g = graph[orientation];
QList<AnchorVertexPair *> &toRestore = simplifiedVertices[orientation];
// 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<AnchorData *> ¶llelAnchors = anchorsFromSimplifiedVertices[orientation];
for (int i = parallelAnchors.size() - 1; i >= 0; --i) {
ParallelAnchorData *parallel = static_cast<ParallelAnchorData *>(parallelAnchors.at(i));
restoreSimplifiedConstraints(parallel);
}
// 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.size() - 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.size(); ++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.size(); ++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.size(); ++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;
}
qDeleteAll(parallelAnchors);
parallelAnchors.clear();
toRestore.clear();
}
Qt::Orientation
QGraphicsAnchorLayoutPrivate::edgeOrientation(Qt::AnchorPoint edge) noexcept
{
return edge > Qt::AnchorRight ? Qt::Vertical : Qt::Horizontal;
}
/*!
\internal
Create internal anchors to connect the layout edges (Left to Right and
Top to Bottom).
These anchors doesn't have size restrictions, that will be enforced by
other anchors and items in the layout.
*/
void QGraphicsAnchorLayoutPrivate::createLayoutEdges()
{
Q_Q(QGraphicsAnchorLayout);
QGraphicsLayoutItem *layout = q;
// Horizontal
AnchorData *data = new AnchorData;
addAnchor_helper(layout, Qt::AnchorLeft, layout,
Qt::AnchorRight, data);
data->maxSize = QWIDGETSIZE_MAX;
// Save a reference to layout vertices
layoutFirstVertex[Qt::Horizontal] = internalVertex(layout, Qt::AnchorLeft);
layoutCentralVertex[Qt::Horizontal] = nullptr;
layoutLastVertex[Qt::Horizontal] = internalVertex(layout, Qt::AnchorRight);
// Vertical
data = new AnchorData;
addAnchor_helper(layout, Qt::AnchorTop, layout,
Qt::AnchorBottom, data);
data->maxSize = QWIDGETSIZE_MAX;
// Save a reference to layout vertices
layoutFirstVertex[Qt::Vertical] = internalVertex(layout, Qt::AnchorTop);
layoutCentralVertex[Qt::Vertical] = nullptr;
layoutLastVertex[Qt::Vertical] = internalVertex(layout, Qt::AnchorBottom);
}
void QGraphicsAnchorLayoutPrivate::deleteLayoutEdges()
{
Q_Q(QGraphicsAnchorLayout);
Q_ASSERT(!internalVertex(q, Qt::AnchorHorizontalCenter));
Q_ASSERT(!internalVertex(q, Qt::AnchorVerticalCenter));
removeAnchor_helper(internalVertex(q, Qt::AnchorLeft),
internalVertex(q, Qt::AnchorRight));
removeAnchor_helper(internalVertex(q, Qt::AnchorTop),
internalVertex(q, Qt::AnchorBottom));
}
void QGraphicsAnchorLayoutPrivate::createItemEdges(QGraphicsLayoutItem *item)
{
items.append(item);
// Create horizontal and vertical internal anchors for the item and
// refresh its size hint / policy values.
AnchorData *data = new AnchorData;
addAnchor_helper(item, Qt::AnchorLeft, item, Qt::AnchorRight, data);
data->refreshSizeHints();
data = new AnchorData;
addAnchor_helper(item, Qt::AnchorTop, item, Qt::AnchorBottom, data);
data->refreshSizeHints();
}
/*!
\internal
By default, each item in the layout is represented internally as
a single anchor in each direction. For instance, from Left to Right.
However, to support anchorage of items to the center of items, we
must split this internal anchor into two half-anchors. From Left
to Center and then from Center to Right, with the restriction that
these anchors must have the same time at all times.
*/
void QGraphicsAnchorLayoutPrivate::createCenterAnchors(
QGraphicsLayoutItem *item, Qt::AnchorPoint centerEdge)
{
Q_Q(QGraphicsAnchorLayout);
Qt::Orientation orientation;
switch (centerEdge) {
case Qt::AnchorHorizontalCenter:
orientation = Qt::Horizontal;
break;
case Qt::AnchorVerticalCenter:
orientation = Qt::Vertical;
break;
default:
// Don't create center edges unless needed
return;
}
// Check if vertex already exists
if (internalVertex(item, centerEdge))
return;
// Orientation code
Qt::AnchorPoint firstEdge;
Qt::AnchorPoint lastEdge;
if (orientation == Qt::Horizontal) {
firstEdge = Qt::AnchorLeft;
lastEdge = Qt::AnchorRight;
} else {
firstEdge = Qt::AnchorTop;
lastEdge = Qt::AnchorBottom;
}
AnchorVertex *first = internalVertex(item, firstEdge);
AnchorVertex *last = internalVertex(item, lastEdge);
Q_ASSERT(first && last);
// Create new anchors
QSimplexConstraint *c = new QSimplexConstraint;
AnchorData *data = new AnchorData;
c->variables.insert(data, 1.0);
addAnchor_helper(item, firstEdge, item, centerEdge, data);
data->isCenterAnchor = true;
data->dependency = AnchorData::Master;
data->refreshSizeHints();
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();
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);
Qt::Orientation orientation;
switch (centerEdge) {
case Qt::AnchorHorizontalCenter:
orientation = Qt::Horizontal;
break;
case Qt::AnchorVerticalCenter:
orientation = Qt::Vertical;
break;
default:
// Don't remove edges that not the center ones
return;
}
// Orientation code
Qt::AnchorPoint firstEdge;
Qt::AnchorPoint lastEdge;
if (orientation == Qt::Horizontal) {
firstEdge = Qt::AnchorLeft;
lastEdge = Qt::AnchorRight;
} else {
firstEdge = Qt::AnchorTop;
lastEdge = Qt::AnchorBottom;
}
AnchorVertex *center = internalVertex(item, centerEdge);
if (!center)
return;
AnchorVertex *first = internalVertex(item, firstEdge);
Q_ASSERT(first);
Q_ASSERT(center);
Graph<AnchorVertex, AnchorData> &g = graph[orientation];
AnchorData *oldData = g.edgeData(first, center);
// Remove center constraint
for (int i = itemCenterConstraints[orientation].size() - 1; i >= 0; --i) {
if (itemCenterConstraints[orientation].at(i)->variables.contains(oldData)) {
delete itemCenterConstraints[orientation].takeAt(i);
break;
}
}
if (substitute) {
// Create the new anchor that should substitute the left-center-right anchors.
AnchorData *data = new AnchorData;
addAnchor_helper(item, firstEdge, item, lastEdge, data);
data->refreshSizeHints();
// Remove old anchors
removeAnchor_helper(first, center);
removeAnchor_helper(center, internalVertex(item, lastEdge));
} else {
// this is only called from removeAnchors()
// first, remove all non-internal anchors
QList<AnchorVertex*> adjacents = g.adjacentVertices(center);
for (int i = 0; i < adjacents.size(); ++i) {
AnchorVertex *v = adjacents.at(i);
if (v->m_item != item) {
removeAnchor_helper(center, internalVertex(v->m_item, v->m_edge));
}
}
// when all non-internal anchors is removed it will automatically merge the
// center anchor into a left-right (or top-bottom) anchor. We must also delete that.
// 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] = nullptr;
}
}
void QGraphicsAnchorLayoutPrivate::removeCenterConstraints(QGraphicsLayoutItem *item,
Qt::Orientation orientation)
{
// Remove the item center constraints associated to this item
// ### This is a temporary solution. We should probably use a better
// data structure to hold items and/or their associated constraints
// so that we can remove those easily
AnchorVertex *first = internalVertex(item, orientation == Qt::Horizontal ?
Qt::AnchorLeft :
Qt::AnchorTop);
AnchorVertex *center = internalVertex(item, orientation == Qt::Horizontal ?
Qt::AnchorHorizontalCenter :
Qt::AnchorVerticalCenter);
// Skip if no center constraints exist
if (!center)
return;
Q_ASSERT(first);
AnchorData *internalAnchor = graph[orientation].edgeData(first, center);
// Look for our anchor in all item center constraints, then remove it
for (int i = 0; i < itemCenterConstraints[orientation].size(); ++i) {
if (itemCenterConstraints[orientation].at(i)->variables.contains(internalAnchor)) {
delete itemCenterConstraints[orientation].takeAt(i);
break;
}
}
}
/*!
* \internal
* Implements the high level "addAnchor" feature. Called by the public API
* addAnchor method.
*
* The optional \a spacing argument defines the size of the anchor. If not provided,
* the anchor size is either 0 or not-set, depending on type of anchor created (see
* matrix below).
*
* All anchors that remain with size not-set will assume the standard spacing,
* set either by the layout style or through the "setSpacing" layout API.
*/
QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::addAnchor(QGraphicsLayoutItem *firstItem,
Qt::AnchorPoint firstEdge,
QGraphicsLayoutItem *secondItem,
Qt::AnchorPoint secondEdge,
qreal *spacing)
{
Q_Q(QGraphicsAnchorLayout);
if ((firstItem == nullptr) || (secondItem == nullptr)) {
qWarning("QGraphicsAnchorLayout::addAnchor(): "
"Cannot anchor NULL items");
return nullptr;
}
if (firstItem == secondItem) {
qWarning("QGraphicsAnchorLayout::addAnchor(): "
"Cannot anchor the item to itself");
return nullptr;
}
if (edgeOrientation(secondEdge) != edgeOrientation(firstEdge)) {
qWarning("QGraphicsAnchorLayout::addAnchor(): "
"Cannot anchor edges of different orientations");
return nullptr;
}
const QGraphicsLayoutItem *parentWidget = q->parentLayoutItem();
if (firstItem == parentWidget || secondItem == parentWidget) {
qWarning("QGraphicsAnchorLayout::addAnchor(): "
"You cannot add the parent of the layout to the layout.");
return nullptr;
}
// In QGraphicsAnchorLayout, items are represented in its internal
// graph as four anchors that connect:
// - Left -> HCenter
// - HCenter-> Right
// - Top -> VCenter
// - VCenter -> Bottom
// Ensure that the internal anchors have been created for both items.
if (firstItem != q && !items.contains(firstItem)) {
createItemEdges(firstItem);
addChildLayoutItem(firstItem);
}
if (secondItem != q && !items.contains(secondItem)) {
createItemEdges(secondItem);
addChildLayoutItem(secondItem);
}
// Create center edges if needed
createCenterAnchors(firstItem, firstEdge);
createCenterAnchors(secondItem, secondEdge);
// Use heuristics to find out what the user meant with this anchor.
correctEdgeDirection(firstItem, firstEdge, secondItem, secondEdge);
AnchorData *data = new AnchorData;
QGraphicsAnchor *graphicsAnchor = acquireGraphicsAnchor(data);
addAnchor_helper(firstItem, firstEdge, secondItem, secondEdge, data);
if (spacing) {
graphicsAnchor->setSpacing(*spacing);
} else {
// If firstItem or secondItem is the layout itself, the spacing will default to 0.
// Otherwise, the following matrix is used (questionmark means that the spacing
// is queried from the style):
// from
// to Left HCenter Right
// Left 0 0 ?
// HCenter 0 0 0
// Right ? 0 0
if (firstItem == q
|| secondItem == q
|| pickEdge(firstEdge, Qt::Horizontal) == Qt::AnchorHorizontalCenter
|| oppositeEdge(firstEdge) != secondEdge) {
graphicsAnchor->setSpacing(0);
} else {
graphicsAnchor->unsetSpacing();
}
}
return graphicsAnchor;
}
/*
\internal
This method adds an AnchorData to the internal graph. It is responsible for doing
the boilerplate part of such task.
If another AnchorData exists between the mentioned vertices, it is deleted and
the new one is inserted.
*/
void QGraphicsAnchorLayoutPrivate::addAnchor_helper(QGraphicsLayoutItem *firstItem,
Qt::AnchorPoint firstEdge,
QGraphicsLayoutItem *secondItem,
Qt::AnchorPoint secondEdge,
AnchorData *data)
{
Q_Q(QGraphicsAnchorLayout);
const Qt::Orientation orientation = edgeOrientation(firstEdge);
// Create or increase the reference count for the related vertices.
AnchorVertex *v1 = addInternalVertex(firstItem, firstEdge);
AnchorVertex *v2 = addInternalVertex(secondItem, secondEdge);
// Remove previous anchor
if (graph[orientation].edgeData(v1, v2)) {
removeAnchor_helper(v1, v2);
}
// If its an internal anchor, set the associated item
if (firstItem == secondItem)
data->item = firstItem;
data->isVertical = orientation == Qt::Vertical;
// Create a bi-directional edge in the sense it can be transversed both
// from v1 or v2. "data" however is shared between the two references
// so we still know that the anchor direction is from 1 to 2.
data->from = v1;
data->to = v2;
#ifdef QT_DEBUG
data->name = QString::fromLatin1("%1 --to--> %2").arg(v1->toString(), v2->toString());
#endif
// ### bit to track internal anchors, since inside AnchorData methods
// we don't have access to the 'q' pointer.
data->isLayoutAnchor = (data->item == q);
graph[orientation].createEdge(v1, v2, data);
}
QGraphicsAnchor *QGraphicsAnchorLayoutPrivate::getAnchor(QGraphicsLayoutItem *firstItem,
Qt::AnchorPoint firstEdge,
QGraphicsLayoutItem *secondItem,
Qt::AnchorPoint secondEdge)
{
// Do not expose internal anchors
if (firstItem == secondItem)
return nullptr;
const Qt::Orientation orientation = edgeOrientation(firstEdge);
AnchorVertex *v1 = internalVertex(firstItem, firstEdge);
AnchorVertex *v2 = internalVertex(secondItem, secondEdge);
QGraphicsAnchor *graphicsAnchor = nullptr;
AnchorData *data = graph[orientation].edgeData(v1, v2);
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;
}
/*!
* \internal
*
* Implements the high level "removeAnchor" feature. Called by
* the QAnchorData destructor.
*/
void QGraphicsAnchorLayoutPrivate::removeAnchor(AnchorVertex *firstVertex,
AnchorVertex *secondVertex)
{
Q_Q(QGraphicsAnchorLayout);
// 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 = nullptr;
// Checking if the item stays in the layout or not
bool keepFirstItem = false;
bool keepSecondItem = false;
std::pair<AnchorVertex *, int> v;
int refcount = -1;
if (firstItem != q) {
for (int i = Qt::AnchorLeft; i <= Qt::AnchorBottom; ++i) {
v = m_vertexList.value(std::pair(firstItem, static_cast<Qt::AnchorPoint>(i)));
if (v.first) {
if (i == Qt::AnchorHorizontalCenter || i == Qt::AnchorVerticalCenter)
refcount = 2;
else
refcount = 1;
if (v.second > refcount) {
keepFirstItem = true;
break;
}
}
}
} else
keepFirstItem = true;
if (secondItem != q) {