1
0
Fork 0
flightgear/src/GUI/layout.cxx
ehofman 1a87ef23f5 Jim Wilson:
This is a workaround for an issue where the xml dialogs were shrinking on
subsequent pops.

Andy Ross says:

That looks like it should be fine for a release-time workaround.  The
2 pixel border on dialogs is at best a minor feature, and probably
invisible since the sub-frames all have their own padding.

Clearly the right fix would be to find out where the code is getting
confused by the previous layout.  In principle, the layout should be
idempotent: if you don't change the layout constraints, it shouldn't
change its layout.  There's still a bug in there somewhere.
2004-07-23 07:36:32 +00:00

375 lines
12 KiB
C++

#include "layout.hxx"
// This file contains the actual layout engine. It has no dependence
// on outside libraries; see layout-props.cxx for the glue code.
// Note, property names with leading double-underscores (__bx, etc...)
// are debugging information, and can be safely removed.
const int DEFAULT_PADDING = 2;
int LayoutWidget::UNIT = 5;
bool LayoutWidget::eq(const char* a, const char* b)
{
while(*a && (*a == *b)) { a++; b++; }
return *a == *b;
}
// Normal widgets get a padding of 4 pixels. Layout groups shouldn't
// have visible padding by default, except for top-level dialog groups
// which need to leave two pixels for the puFrame's border. This
// value can, of course, be overriden by the parent groups
// <default-padding> property, or per widget with <padding>.
int LayoutWidget::padding()
{
int pad = isType("group") ? 0 : 4;
// As comments above note, this was being set to 2. For some
// reason this causes the dialogs to shrink on subsequent pops
// so for now we'll make "dialog" padding 0.
if(isType("dialog")) pad = 0;
if(hasParent() && parent().hasField("default-padding"))
pad = parent().getNum("default-padding");
if(hasField("padding"))
pad = getNum("padding");
return pad;
}
void LayoutWidget::calcPrefSize(int* w, int* h)
{
*w = *h = 0; // Ask for nothing by default
int legw = stringLength(getStr("legend"));
int labw = stringLength(getStr("label"));
if(isType("dialog") || isType("group")) {
if(!hasField("layout")) {
// Legacy support for groups without layout managers.
if(hasField("width")) *w = getNum("width");
if(hasField("height")) *h = getNum("height");
} else {
const char* layout = getStr("layout");
if (eq(layout, "hbox" )) doHVBox(false, false, w, h);
else if(eq(layout, "vbox" )) doHVBox(false, true, w, h);
else if(eq(layout, "table")) doTable(false, w, h);
}
} else if (isType("text")) {
*w = labw;
*h = 4*UNIT; // FIXME: multi line height?
} else if (isType("button")) {
*w = legw + 6*UNIT + (labw ? labw + UNIT : 0);
*h = 6*UNIT;
} else if (isType("checkbox") || isType("radio")) {
*w = 3*UNIT + (labw ? (3*UNIT + labw) : 0);
*h = 3*UNIT;
} else if (isType("input") || isType("combo") || isType("select")) {
*w = 17*UNIT;
*h = 6*UNIT;
} else if (isType("slider")) {
*w = *h = 17*UNIT;
if(getBool("vertical")) *w = 4*UNIT;
else *h = 4*UNIT;
} else if (isType("list") || isType("airport-list") || isType("dial")) {
*w = *h = 12*UNIT;
}
// Throw it all out if the user specified a fixed preference
if(hasField("pref-width")) *w = getNum("pref-width");
if(hasField("pref-height")) *h = getNum("pref-height");
// And finally correct for cell padding
int pad = 2*padding();
*w += pad;
*h += pad;
// Store what we calculated
setNum("__pw", *w);
setNum("__ph", *h);
}
// Set up geometry such that the widget lives "inside" the specified
void LayoutWidget::layout(int x, int y, int w, int h)
{
setNum("__bx", x);
setNum("__by", y);
setNum("__bw", w);
setNum("__bh", h);
// Correct for padding.
int pad = padding();
x += pad;
y += pad;
w -= 2*pad;
h -= 2*pad;
int prefw = 0, prefh = 0;
calcPrefSize(&prefw, &prefh);
prefw -= 2*pad;
prefh -= 2*pad;
// "Parent Set" values override widget preferences
if(hasField("_psw")) prefw = getNum("_psw");
if(hasField("_psh")) prefh = getNum("_psh");
bool isGroup = isType("dialog") || isType("group");
// Correct our box for alignment. The values above correspond to
// a "fill" alignment.
const char* halign = isGroup ? "fill" : "center";
if(hasField("halign")) halign = getStr("halign");
if(eq(halign, "left")) {
w = prefw;
} else if(eq(halign, "right")) {
x += w - prefw;
w = prefw;
} else if(eq(halign, "center")) {
x += (w - prefw)/2;
w = prefw;
}
const char* valign = isGroup ? "fill" : "center";
if(hasField("valign")) valign = getStr("valign");
if(eq(valign, "bottom")) {
h = prefh;
} else if(eq(valign, "top")) {
y += h - prefh;
h = prefh;
} else if(eq(valign, "center")) {
y += (h - prefh)/2;
h = prefh;
}
// PUI widgets interpret their size differently depending on
// type, so diddle the values as needed to fit the widget into
// the x/y/w/h box we have calculated.
if (isType("text")) {
// puText labels are layed out to the right of the box, so
// zero the width.
w = 0;
} else if (isType("input") || isType("combo") || isType("select")) {
// Fix the height to a constant
y += (h - 6*UNIT) / 2;
h = 6*UNIT;
} else if (isType("checkbox") || isType("radio")) {
// The PUI dimensions are of the check area only. Center it
// on the left side of our box.
y += (h - 3*UNIT) / 2;
w = h = 3*UNIT;
} else if (isType("slider")) {
// Fix the thickness to a constant
if(getBool("vertical")) { x += (w-4*UNIT)/2; w = 4*UNIT; }
else { y += (h-4*UNIT)/2; h = 4*UNIT; }
}
// Set out output geometry
setNum("x", x);
setNum("y", y);
setNum("width", w);
setNum("height", h);
// Finally, if we are ourselves a layout object, do the actual layout.
if(isGroup && hasField("layout")) {
const char* layout = getStr("layout");
if (eq(layout, "hbox" )) doHVBox(true, false);
else if(eq(layout, "vbox" )) doHVBox(true, true);
else if(eq(layout, "table")) doTable(true);
}
}
// Convention: the "A" cooridinate refers to the major axis of the
// container (width, for an hbox), "B" is minor.
void LayoutWidget::doHVBox(bool doLayout, bool vertical, int* w, int* h)
{
int nc = nChildren();
int* prefA = doLayout ? new int[nc] : 0;
int i, totalA = 0, maxB = 0, nStretch = 0;
int nEq = 0, eqA = 0, eqB = 0, eqTotalA = 0;
for(i=0; i<nc; i++) {
LayoutWidget child = getChild(i);
int a, b;
child.calcPrefSize(vertical ? &b : &a, vertical ? &a : &b);
if(doLayout) prefA[i] = a;
totalA += a;
if(b > maxB) maxB = b;
if(child.getBool("stretch")) {
nStretch++;
} else if(child.getBool("equal")) {
int pad = child.padding();
nEq++;
eqTotalA += a - 2*pad;
if(a-2*pad > eqA) eqA = a - 2*pad;
if(b-2*pad > eqB) eqB = b - 2*pad;
}
}
if(nStretch == 0) nStretch = nc;
totalA += nEq * eqA - eqTotalA;
if(!doLayout) {
if(vertical) { *w = maxB; *h = totalA; }
else { *w = totalA; *h = maxB; }
return;
}
int currA = 0;
int availA = getNum(vertical ? "height" : "width");
int availB = getNum(vertical ? "width" : "height");
bool stretchAll = nStretch == nc ? true : false;
int stretch = availA - totalA;
if(stretch < 0) stretch = 0;
for(i=0; i<nc; i++) {
// Swap the child order for vertical boxes, so we lay out
// from top to bottom instead of along the cartesian Y axis.
int idx = vertical ? (nc-i-1) : i;
LayoutWidget child = getChild(idx);
if(child.getBool("equal")) {
int pad = child.padding();
prefA[idx] = eqA + 2*pad;
// Use "parent set" values to communicate the setting to
// the child.
child.setNum(vertical ? "_psh" : "_psw", eqA);
child.setNum(vertical ? "_psw" : "_psh", eqB);
}
if(stretchAll || child.getBool("stretch")) {
int chunk = stretch / nStretch;
stretch -= chunk;
nStretch--;
prefA[idx] += chunk;
child.setNum("__stretch", chunk);
}
if(vertical) child.layout( 0, currA, availB, prefA[idx]);
else child.layout(currA, 0, prefA[idx], availB);
currA += prefA[idx];
}
delete[] prefA;
}
struct TabCell {
TabCell() {}
LayoutWidget child;
int w, h, row, col, rspan, cspan;
};
void LayoutWidget::doTable(bool doLayout, int* w, int* h)
{
int i, j, nc = nChildren();
TabCell* children = new TabCell[nc];
// Pass 1: initialize bookeeping structures
int rows = 0, cols = 0;
for(i=0; i<nc; i++) {
TabCell* cell = &children[i];
cell->child = getChild(i);
cell->child.calcPrefSize(&cell->w, &cell->h);
cell->row = cell->child.getNum("row");
cell->col = cell->child.getNum("col");
cell->rspan = cell->child.hasField("rowspan") ? cell->child.getNum("rowspan") : 1;
cell->cspan = cell->child.hasField("colspan") ? cell->child.getNum("colspan") : 1;
if(cell->row + cell->rspan > rows) rows = cell->row + cell->rspan;
if(cell->col + cell->cspan > cols) cols = cell->col + cell->cspan;
}
int* rowSizes = new int[rows];
int* colSizes = new int[cols];
for(i=0; i<rows; i++) rowSizes[i] = 0;
for(i=0; i<cols; i++) colSizes[i] = 0;
// Pass 1a (hack): we want row zero to be the top, not the
// (cartesian: y==0) bottom, so reverse the sense of the row
// numbers.
for(i=0; i<nc; i++) {
TabCell* cell = &children[i];
cell->row = rows - cell->row - cell->rspan;
}
// Pass 2: get sizes for single-cell children
for(i=0; i<nc; i++) {
TabCell* cell = &children[i];
if(cell->rspan < 2 && cell->h > rowSizes[cell->row])
rowSizes[cell->row] = cell->h;
if(cell->cspan < 2 && cell->w > colSizes[cell->col])
colSizes[cell->col] = cell->w;
}
// Pass 3: multi-cell children, make space as needed
for(i=0; i<nc; i++) {
TabCell* cell = &children[i];
if(cell->rspan > 1) {
int total = 0;
for(j=0; j<cell->rspan; j++)
total += rowSizes[cell->row + j];
int extra = total - cell->h;
if(extra > 0) {
for(j=0; j<cell->rspan; j++) {
int chunk = extra / (cell->rspan - j);
rowSizes[cell->row + j] += chunk;
extra -= chunk;
}
}
}
if(cell->cspan > 1) {
int total = 0;
for(j=0; j<cell->cspan; j++)
total += colSizes[cell->col + j];
int extra = total - cell->w;
if(extra > 0) {
for(j=0; j<cell->cspan; j++) {
int chunk = extra / (cell->cspan - j);
colSizes[cell->col + j] += chunk;
extra -= chunk;
}
}
}
}
// Calculate our preferred sizes, and return if we aren't doing layout
int prefw=0, prefh=0;
for(i=0; i<cols; i++) prefw += colSizes[i];
for(i=0; i<rows; i++) prefh += rowSizes[i];
if(!doLayout) {
*w = prefw; *h = prefh;
delete[] children; delete[] rowSizes; delete[] colSizes;
return;
}
// Allocate extra space
int pad = 2*padding();
int extra = getNum("height") - pad - prefh;
for(i=0; i<rows; i++) {
int chunk = extra / (rows - i);
rowSizes[i] += chunk;
extra -= chunk;
}
extra = getNum("width") - pad - prefw;
for(i=0; i<cols; i++) {
int chunk = extra / (cols - i);
colSizes[i] += chunk;
extra -= chunk;
}
// Finally, lay out the children (with just two more temporary
// arrays for calculating coordinates)
int* rowY = new int[rows];
int total = 0;
for(i=0; i<rows; i++) { rowY[i] = total; total += rowSizes[i]; }
int* colX = new int[cols];
total = 0;
for(i=0; i<cols; i++) { colX[i] = total; total += colSizes[i]; }
for(i=0; i<nc; i++) {
TabCell* cell = &children[i];
int w = 0, h = 0;
for(j=0; j<cell->rspan; j++) h += rowSizes[cell->row + j];
for(j=0; j<cell->cspan; j++) w += colSizes[cell->col + j];
int x = colX[cell->col];
int y = rowY[cell->row];
cell->child.layout(x, y, w, h);
}
// Clean up
delete[] children;
delete[] rowSizes;
delete[] colSizes;
delete[] rowY;
delete[] colX;
}