* Copyright 2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "ComplexLayouter.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <new>
#include <OS.h>
#include <Size.h>
#include <AutoDeleter.h>
#include "LayoutOptimizer.h"
#include "SimpleLayouter.h"
#if TRACE_COMPLEX_LAYOUTER
# define TRACE(format...) printf(format);
# define TRACE_ONLY(x) x
#else
# define TRACE(format...)
# define TRACE_ONLY(x)
#endif
using std::nothrow;
class ComplexLayouter::MyLayoutInfo : public LayoutInfo {
public:
MyLayoutInfo(int32 elementCount, int32 spacing)
: fCount(elementCount),
fSpacing(spacing)
{
fLocations = new(nothrow) int32[elementCount + 1];
}
~MyLayoutInfo()
{
delete[] fLocations;
}
void InitFromSizes(int32* sizes)
{
fLocations[0] = 0;
for (int32 i = 0; i < fCount; i++)
fLocations[i + 1] = fLocations[i] + sizes[i] + fSpacing;
}
virtual float ElementLocation(int32 element)
{
if (element < 0 || element >= fCount)
return 0;
return fLocations[element];
}
virtual float ElementSize(int32 element)
{
if (element < 0 || element >= fCount)
return -1;
return fLocations[element + 1] - fLocations[element] - 1
- fSpacing;
}
virtual float ElementRangeSize(int32 position, int32 length)
{
if (position < 0 || length < 0 || position + length > fCount)
return -1;
return fLocations[position + length] - fLocations[position] - 1
- fSpacing;
}
void Dump()
{
printf("ComplexLayouter::MyLayoutInfo(): %" B_PRId32 " elements:\n",
fCount);
for (int32 i = 0; i < fCount + 1; i++) {
printf(" %2" B_PRId32 ": location: %4" B_PRId32 "\n", i,
fLocations[i]);
}
}
public:
int32 fCount;
int32 fSpacing;
int32* fLocations;
};
struct ComplexLayouter::Constraint {
Constraint(int32 start, int32 end, int32 min, int32 max)
: start(start),
end(end),
min(min),
max(max),
next(NULL)
{
if (min > max)
max = min;
effectiveMax = max;
}
void Restrict(int32 newMin, int32 newMax)
{
if (newMin > min)
min = newMin;
if (newMax < max)
max = newMax;
if (min > max)
max = min;
effectiveMax = max;
}
bool IsSatisfied(int32* sumValues) const
{
int32 value = sumValues[end] - sumValues[start - 1];
return (value >= min && value <= max);
}
int32 start;
int32 end;
int32 min;
int32 max;
int32 effectiveMax;
Constraint* next;
};
struct ComplexLayouter::SumItem {
int32 min;
int32 max;
bool minDirty;
bool maxDirty;
};
struct ComplexLayouter::SumItemBackup {
int32 min;
int32 max;
};
ComplexLayouter::ComplexLayouter(int32 elementCount, float spacing)
: fElementCount(elementCount),
fSpacing((int32)spacing),
fConstraints(new(nothrow) Constraint*[elementCount]),
fWeights(new(nothrow) float[elementCount]),
fSums(new(nothrow) SumItem[elementCount + 1]),
fSumBackups(new(nothrow) SumItemBackup[elementCount + 1]),
fOptimizer(new(nothrow) LayoutOptimizer(elementCount)),
fUnlimited((int32)B_SIZE_UNLIMITED / (elementCount == 0 ? 1 : elementCount)),
fMinMaxValid(false),
fOptimizerConstraintsAdded(false)
{
if (fConstraints)
memset(fConstraints, 0, sizeof(Constraint*) * fElementCount);
if (fWeights) {
for (int32 i = 0; i < fElementCount; i++)
fWeights[i] = 1.0f;
}
}
ComplexLayouter::~ComplexLayouter()
{
for (int32 i = 0; i < fElementCount; i++) {
Constraint* constraint = fConstraints[i];
fConstraints[i] = NULL;
while (constraint != NULL) {
Constraint* next = constraint->next;
delete constraint;
constraint = next;
}
}
delete[] fConstraints;
delete[] fWeights;
delete[] fSums;
delete[] fSumBackups;
delete fOptimizer;
}
status_t
ComplexLayouter::InitCheck() const
{
if (!fConstraints || !fWeights || !fSums || !fSumBackups || !fOptimizer)
return B_NO_MEMORY;
return fOptimizer->InitCheck();
}
void
ComplexLayouter::AddConstraints(int32 element, int32 length,
float _min, float _max, float _preferred)
{
if (element < 0 || length <= 0 || element + length > fElementCount)
return;
TRACE("%p->ComplexLayouter::AddConstraints(%ld, %ld, %ld, %ld, %ld)\n",
this, element, length, (int32)_min, (int32)_max, (int32)_preferred);
int32 spacing = fSpacing * (length - 1);
int32 min = (int32)_min + 1 - spacing;
int32 max = (int32)_max + 1 - spacing;
if (min < 0)
min = 0;
if (max > fUnlimited)
max = fUnlimited;
int32 end = element + length - 1;
Constraint** slot = fConstraints + end;
while (*slot != NULL && (*slot)->start > element)
slot = &(*slot)->next;
if (*slot != NULL && (*slot)->start == element) {
(*slot)->Restrict(min, max);
} else {
Constraint* constraint = new(nothrow) Constraint(element, end, min,
max);
if (!constraint)
return;
constraint->next = *slot;
*slot = constraint;
}
fMinMaxValid = false;
}
void
ComplexLayouter::SetWeight(int32 element, float weight)
{
if (element < 0 || element >= fElementCount)
return;
fWeights[element] = max_c(weight, 0);
}
float
ComplexLayouter::MinSize()
{
_ValidateLayout();
return fMin;
}
float
ComplexLayouter::MaxSize()
{
_ValidateLayout();
return fMax;
}
float
ComplexLayouter::PreferredSize()
{
return fMin;
}
LayoutInfo*
ComplexLayouter::CreateLayoutInfo()
{
MyLayoutInfo* layoutInfo = new(nothrow) MyLayoutInfo(fElementCount,
fSpacing);
if (layoutInfo && !layoutInfo->fLocations) {
delete layoutInfo;
return NULL;
}
return layoutInfo;
}
void
ComplexLayouter::Layout(LayoutInfo* _layoutInfo, float _size)
{
TRACE("%p->ComplexLayouter::Layout(%ld)\n", this, (int32)_size);
if (fElementCount == 0)
return;
_ValidateLayout();
MyLayoutInfo* layoutInfo = (MyLayoutInfo*)_layoutInfo;
int32 min = fSums[fElementCount].min;
int32 max = fSums[fElementCount].max;
int32 size = (int32)_size + 1 - (fElementCount - 1) * fSpacing;
if (size < min)
size = min;
if (size > max)
size = max;
SumItem sums[fElementCount + 1];
memcpy(sums, fSums, (fElementCount + 1) * sizeof(SumItem));
sums[fElementCount].min = size;
sums[fElementCount].max = size;
sums[fElementCount].minDirty = (size != min);
sums[fElementCount].maxDirty = (size != max);
_PropagateChangesBack(sums, fElementCount - 1, NULL);
_PropagateChanges(sums, fElementCount - 1, NULL);
#if TRACE_COMPLEX_LAYOUTER
TRACE("Layout(%ld)\n", size);
for (int32 i = 0; i < fElementCount; i++) {
SumItem& sum = sums[i + 1];
TRACE("[%ld] minc = %4ld, maxc = %4ld\n", i + 1, sum.min, sum.max);
}
#endif
int32 sizes[fElementCount];
if (!_Layout(size, sums, sizes)) {
}
layoutInfo->InitFromSizes(sizes);
}
Layouter*
ComplexLayouter::CloneLayouter()
{
ComplexLayouter* layouter
= new(nothrow) ComplexLayouter(fElementCount, fSpacing);
ObjectDeleter<ComplexLayouter> layouterDeleter(layouter);
if (!layouter || layouter->InitCheck() != B_OK
|| !layouter->fOptimizer->AddConstraintsFrom(fOptimizer)) {
return NULL;
}
for (int32 i = 0; i < fElementCount; i++) {
Constraint* constraint = fConstraints[i];
Constraint** end = layouter->fConstraints + i;
while (constraint) {
*end = new(nothrow) Constraint(constraint->start, constraint->end,
constraint->min, constraint->max);
if (!*end)
return NULL;
end = &(*end)->next;
constraint = constraint->next;
}
}
memcpy(layouter->fWeights, fWeights, fElementCount * sizeof(float));
memcpy(layouter->fSums, fSums, (fElementCount + 1) * sizeof(SumItem));
memcpy(layouter->fSumBackups, fSumBackups,
(fElementCount + 1) * sizeof(SumItemBackup));
layouter->fMin = fMin;
layouter->fMax = fMax;
layouter->fMinMaxValid = fMinMaxValid;
return layouterDeleter.Detach();
}
bool
ComplexLayouter::_Layout(int32 size, SumItem* sums, int32* sizes)
{
SimpleLayouter::DistributeSize(size, fWeights, sizes, fElementCount);
if (_SatisfiesConstraints(sizes)) {
return true;
}
double realSizes[fElementCount];
for (int32 i = 0; i < fElementCount; i++)
realSizes[i] = sizes[i];
if (!_AddOptimizerConstraints())
return false;
double values[fElementCount];
for (int32 i = 0; i < fElementCount; i++)
values[i] = sums[i + 1].min - sums[i].min;
#if TRACE_COMPLEX_LAYOUTER
TRACE("feasible solution vs. desired solution:\n");
for (int32 i = 0; i < fElementCount; i++)
TRACE("%8.4f %8.4f\n", values[i], realSizes[i]);
#endif
TRACE_ONLY(bigtime_t time = system_time();)
if (!fOptimizer->Solve(realSizes, size, values))
return false;
TRACE_ONLY(time = system_time() - time;)
TRACE("computed solution in %lld us:\n", time);
double realSum = 0;
double previousSum = 0;
for (int32 i = 0; i < fElementCount; i++) {
realSum += values[i];
double roundedRealSum = floor(realSum);
if (fuzzy_equals(realSum, roundedRealSum + 1))
realSum = roundedRealSum + 1;
sizes[i] = int32(roundedRealSum - previousSum);
previousSum = roundedRealSum;
TRACE("x[%ld] = %8.4f %4ld\n", i, values[i], sizes[i]);
}
return _SatisfiesConstraints(sizes);
}
bool
ComplexLayouter::_AddOptimizerConstraints()
{
if (fOptimizerConstraintsAdded)
return true;
fOptimizer->RemoveAllConstraints();
for (int32 i = 0; i < fElementCount; i++) {
SumItem& sum = fSums[i + 1];
Constraint* constraint = fConstraints[i];
while (constraint != NULL) {
SumItem& base = fSums[constraint->start];
int32 sumMin = base.min + constraint->min;
int32 baseMax = sum.max - constraint->min;
bool minRedundant = (sumMin < sum.min && baseMax > base.max);
int32 sumMax = base.max + constraint->effectiveMax;
int32 baseMin = sum.min - constraint->effectiveMax;
bool maxRedundant = (sumMax > sum.max && baseMin < base.min);
if (!minRedundant || !maxRedundant) {
bool success = true;
if (constraint->min == constraint->effectiveMax) {
success = fOptimizer->AddConstraint(constraint->start - 1,
constraint->end, constraint->min, true);
} else {
if (!minRedundant) {
success |= fOptimizer->AddConstraint(
constraint->start - 1, constraint->end,
constraint->min, false);
}
if (!maxRedundant) {
success |= fOptimizer->AddConstraint(constraint->end,
constraint->start - 1,
-constraint->effectiveMax, false);
}
}
if (!success)
return false;
}
constraint = constraint->next;
}
}
fOptimizerConstraintsAdded = true;
return true;
}
bool
ComplexLayouter::_SatisfiesConstraints(int32* sizes) const
{
int32 sumValues[fElementCount + 1];
sumValues[0] = 0;
for (int32 i = 0; i < fElementCount; i++)
sumValues[i + 1] = sumValues[i] + sizes[i];
return _SatisfiesConstraintsSums(sumValues);
}
bool
ComplexLayouter::_SatisfiesConstraintsSums(int32* sumValues) const
{
for (int32 i = 0; i < fElementCount; i++) {
Constraint* constraint = fConstraints[i];
while (constraint) {
if (!constraint->IsSatisfied(sumValues))
return false;
constraint = constraint->next;
}
}
return true;
}
void
ComplexLayouter::_ValidateLayout()
{
if (fMinMaxValid)
return;
fSums[0].min = 0;
fSums[0].max = 0;
int32 maxSum = 0;
for (int32 i = 0; i < fElementCount; i++) {
SumItem& sum = fSums[i + 1];
sum.min = 0;
sum.max = maxSum += fUnlimited;
sum.minDirty = false;
sum.maxDirty = false;
}
for (int32 i = 0; i < fElementCount; i++) {
SumItem& sum = fSums[i + 1];
Constraint* constraint = fConstraints[i];
while (constraint != NULL) {
int32 minSum = fSums[constraint->start].min + constraint->min;
if (minSum < fSums[i].min) {
minSum = fSums[i].min;
}
if (minSum > sum.min) {
sum.min = minSum;
} else {
TRACE("min constraint is redundant: x%ld + ... + x%ld >= %ld\n",
constraint->start, constraint->end, constraint->min);
}
constraint = constraint->next;
}
}
for (int32 i = fElementCount - 1; i >= 0; i--) {
SumItem& sum = fSums[i + 1];
Constraint* constraint = fConstraints[i];
while (constraint != NULL) {
SumItem& base = fSums[constraint->start];
int32 baseMax = sum.max - constraint->min;
if (baseMax < base.max)
base.max = baseMax;
constraint = constraint->next;
}
}
for (int32 i = 0; i < fElementCount; i++) {
Constraint* constraint = fConstraints[i];
while (constraint != NULL) {
_ApplyMaxConstraint(constraint, i);
constraint = constraint->next;
}
}
#if TRACE_COMPLEX_LAYOUTER
for (int32 i = 0; i < fElementCount; i++) {
SumItem& sum = fSums[i + 1];
TRACE("[%ld] minc = %4ld, maxc = %4ld\n", i + 1, sum.min, sum.max);
}
#endif
if (fElementCount == 0) {
fMin = -1;
fMax = B_SIZE_UNLIMITED;
} else {
int32 spacing = (fElementCount - 1) * fSpacing;
fMin = fSums[fElementCount].min + spacing - 1;
fMax = fSums[fElementCount].max + spacing - 1;
if (fMax >= fUnlimited)
fMax = B_SIZE_UNLIMITED;
}
fOptimizerConstraintsAdded = false;
fMinMaxValid = true;
}
void
ComplexLayouter::_ApplyMaxConstraint(Constraint* currentConstraint, int32 index)
{
SumItem& sum = fSums[index + 1];
SumItem& base = fSums[currentConstraint->start];
int32 max = currentConstraint->effectiveMax;
int32 sumMax = base.max + max;
if (sumMax < sum.min) {
sumMax = sum.min;
max = sumMax - base.max;
}
int32 baseMin = sum.min - max;
if (baseMin > base.max) {
baseMin = base.max;
max = sum.min - baseMin;
sumMax = base.max + max;
}
if (currentConstraint->effectiveMax != max) {
TRACE("relaxing conflicting max constraint (1): "
"x%ld + ... + x%ld <= %ld -> %ld\n", currentConstraint->start,
currentConstraint->end, currentConstraint->effectiveMax, max);
}
currentConstraint->effectiveMax = max;
if (baseMin <= base.min && sumMax >= sum.max) {
TRACE("max constraint is redundant: x%ld + ... + x%ld <= %ld\n",
currentConstraint->start, currentConstraint->end,
currentConstraint->effectiveMax);
return;
}
_BackupValues(index);
int32 diff;
do {
int32 changedIndex = currentConstraint->start;
if (baseMin > base.min) {
base.min = baseMin;
base.minDirty = true;
}
if (sumMax < sum.max) {
changedIndex = index;
sum.max = sumMax;
sum.maxDirty = true;
}
_PropagateChangesBack(fSums, changedIndex, currentConstraint);
_PropagateChanges(fSums, index, currentConstraint);
diff = 0;
max = currentConstraint->effectiveMax;
sumMax = base.max + max;
if (sumMax < sum.max)
diff = sum.max - sumMax;
baseMin = sum.min - max;
if (baseMin > base.min)
diff = max_c(diff, baseMin - base.min);
for (int32 i = 0; i <= changedIndex; i++) {
SumItem& sum = fSums[i + 1];
sum.minDirty = false;
sum.maxDirty = false;
}
if (diff > 0) {
max += diff;
TRACE("relaxing conflicting max constraint (2): "
"x%ld + ... + x%ld <= %ld -> %ld\n", currentConstraint->start,
currentConstraint->end, currentConstraint->effectiveMax, max);
currentConstraint->effectiveMax = max;
_RestoreValues(index);
sumMax = base.max + max;
baseMin = sum.min - max;
if (baseMin <= base.min && sumMax >= sum.max)
return;
}
} while (diff > 0);
}
Beyond \a toIndex or at \a to toIndex after (and including)
\a lastMaxConstraint will be ignored. To have all constraints be
considered pass \c fElementCount and \c NULL.
*/
void
ComplexLayouter::_PropagateChanges(SumItem* sums, int32 toIndex,
Constraint* lastMaxConstraint)
{
for (int32 i = 0; i < fElementCount; i++) {
SumItem& sum = sums[i + 1];
bool ignoreMaxConstraints = (i > toIndex);
Constraint* constraint = fConstraints[i];
while (constraint != NULL) {
SumItem& base = sums[constraint->start];
if (constraint == lastMaxConstraint)
ignoreMaxConstraints = true;
if (base.minDirty) {
int32 sumMin = base.min + constraint->min;
if (sumMin > sum.min) {
sum.min = sumMin;
sum.minDirty = true;
}
}
if (base.maxDirty && !ignoreMaxConstraints) {
int32 sumMax = base.max + constraint->effectiveMax;
if (sumMax < sum.max) {
sum.max = sumMax;
sum.maxDirty = true;
}
}
constraint = constraint->next;
}
if (sum.minDirty || sum.maxDirty) {
if (sum.min > sum.max) {
TRACE("adjusted max in propagation phase: index: "
"%ld: %ld -> %ld\n", i, sum.max, sum.min);
sum.max = sum.min;
sum.maxDirty = true;
}
}
}
}
void
ComplexLayouter::_PropagateChangesBack(SumItem* sums, int32 changedIndex,
Constraint* lastMaxConstraint)
{
for (int32 i = changedIndex; i >= 0; i--) {
SumItem& sum = sums[i + 1];
bool ignoreMaxConstraints = false;
Constraint* constraint = fConstraints[i];
while (constraint != NULL) {
SumItem& base = sums[constraint->start];
if (constraint == lastMaxConstraint)
ignoreMaxConstraints = true;
if (sum.minDirty && !ignoreMaxConstraints) {
int32 baseMin = sum.min - constraint->effectiveMax;
if (baseMin > base.min) {
if (baseMin > base.max) {
TRACE("min above max in back propagation phase: index: "
"(%ld -> %ld), min: %ld, max: %ld\n", i,
constraint->start, baseMin, base.max);
}
base.min = baseMin;
base.minDirty = true;
}
}
if (sum.maxDirty) {
int32 baseMax = sum.max - constraint->min;
if (baseMax < base.max) {
if (baseMax < base.min) {
TRACE("max below min in back propagation phase: index: "
"(%ld -> %ld), max: %ld, min: %ld\n", i,
constraint->start, baseMax, base.min);
}
base.max = baseMax;
base.maxDirty = true;
}
}
constraint = constraint->next;
}
}
}
void
ComplexLayouter::_BackupValues(int32 maxIndex)
{
for (int32 i = 0; i <= maxIndex; i++) {
SumItem& sum = fSums[i + 1];
fSumBackups[i + 1].min = sum.min;
fSumBackups[i + 1].max = sum.max;
}
}
void
ComplexLayouter::_RestoreValues(int32 maxIndex)
{
for (int32 i = 0; i <= maxIndex; i++) {
SumItem& sum = fSums[i + 1];
sum.min = fSumBackups[i + 1].min;
sum.max = fSumBackups[i + 1].max;
}
}