]> git.mxchange.org Git - flightgear.git/blob - src/GUI/layout.cxx
Merge branch 'next' of git://gitorious.org/fg/flightgear into next
[flightgear.git] / src / GUI / layout.cxx
1
2 #ifdef HAVE_CONFIG_H
3 #  include <config.h>
4 #endif
5
6 #include "layout.hxx"
7
8 #include <simgear/math/SGMath.hxx>
9
10 // This file contains the actual layout engine.  It has no dependence
11 // on outside libraries; see layout-props.cxx for the glue code.
12
13 // Note, property names with leading double-underscores (__bx, etc...)
14 // are debugging information, and can be safely removed.
15
16 const int DEFAULT_PADDING = 2;
17
18 int LayoutWidget::UNIT = 5;
19
20 bool LayoutWidget::eq(const char* a, const char* b)
21 {
22     while(*a && (*a == *b)) { a++; b++; }
23     return *a == *b;
24 }
25
26 // Normal widgets get a padding of 4 pixels.  Layout groups shouldn't
27 // have visible padding by default, except for top-level dialog groups
28 // which need to leave two pixels for the puFrame's border.  This
29 // value can, of course, be overriden by the parent groups
30 // <default-padding> property, or per widget with <padding>.
31 int LayoutWidget::padding()
32 {
33     int pad = (isType("group") || isType("frame")) ? 0 : 4;
34     // As comments above note,  this was being set to 2.  For some
35     // reason this causes the dialogs to shrink on subsequent pops
36     // so for now we'll make "dialog" padding 0.
37     if(isType("dialog")) pad = 0;
38     if(hasParent() && parent().hasField("default-padding"))
39         pad = parent().getNum("default-padding");
40     if(hasField("padding"))
41         pad = getNum("padding");
42     return pad;
43 }
44
45 void LayoutWidget::calcPrefSize(int* w, int* h)
46 {
47     *w = *h = 0; // Ask for nothing by default
48
49     if (!getBool("enabled", true) || isType("nasal"))
50         return;
51
52     int legw = stringLength(getStr("legend"));
53     int labw = stringLength(getStr("label"));
54
55     if(isType("dialog") || isType("group") || isType("frame")) {
56         if(!hasField("layout")) {
57             // Legacy support for groups without layout managers.
58             if(hasField("width")) *w = getNum("width");
59             if(hasField("height")) *h = getNum("height");
60         } else {
61             const char* layout = getStr("layout");
62             if     (eq(layout, "hbox" )) doHVBox(false, false, w, h);
63             else if(eq(layout, "vbox" )) doHVBox(false, true, w, h);
64             else if(eq(layout, "table")) doTable(false, w, h);
65         }
66     } else if (isType("text")) {
67         *w = labw;
68         *h = 3*UNIT; // FIXME: multi line height?
69     } else if (isType("button")) {
70         *w = legw + 6*UNIT + (labw ? labw + UNIT : 0);
71         *h = 6*UNIT;
72     } else if (isType("checkbox") || isType("radio")) {
73         *w = 3*UNIT + (labw ? (3*UNIT + labw) : 0);
74         *h = 3*UNIT;
75     } else if (isType("input") || isType("combo") || isType("select")) {
76         *w = 17*UNIT;
77         *h = 6*UNIT;
78     } else if (isType("slider")) {
79         *w = *h = 17*UNIT;
80         if(getBool("vertical")) *w = 4*UNIT;
81         else                    *h = 4*UNIT;
82     } else if (isType("list") || isType("airport-list")
83             || isType("property-list") || isType("dial")) {
84         *w = *h = 12*UNIT;
85     } else if (isType("hrule")) {
86         *h = 1;
87     } else if (isType("vrule")) {
88         *w = 1;
89     }
90
91     // Throw it all out if the user specified a fixed preference
92     if(hasField("pref-width"))  *w = getNum("pref-width");
93     if(hasField("pref-height")) *h = getNum("pref-height");
94
95     // And finally correct for cell padding
96     int pad = 2*padding();
97     *w += pad;
98     *h += pad;
99
100     // Store what we calculated
101     setNum("__pw", *w);
102     setNum("__ph", *h);
103 }
104
105 // Set up geometry such that the widget lives "inside" the specified 
106 void LayoutWidget::layout(int x, int y, int w, int h)
107 {
108     if (!getBool("enabled", true) || isType("nasal"))
109         return;
110
111     setNum("__bx", x);
112     setNum("__by", y);
113     setNum("__bw", w);
114     setNum("__bh", h);
115
116     // Correct for padding.
117     int pad = padding();
118     x += pad;
119     y += pad;
120     w -= 2*pad;
121     h -= 2*pad;
122
123     int prefw = 0, prefh = 0;
124     calcPrefSize(&prefw, &prefh);
125     prefw -= 2*pad;
126     prefh -= 2*pad;
127
128     // "Parent Set" values override widget preferences
129     if(hasField("_psw")) prefw = getNum("_psw");
130     if(hasField("_psh")) prefh = getNum("_psh");
131
132     bool isGroup = isType("dialog") || isType("group") || isType("frame");
133
134     // Correct our box for alignment.  The values above correspond to
135     // a "fill" alignment.
136     const char* halign = (isGroup || isType("hrule")) ? "fill" : "center";
137     if(hasField("halign")) halign = getStr("halign");
138     if(eq(halign, "left")) {
139         w = prefw;
140     } else if(eq(halign, "right")) {
141         x += w - prefw;
142         w = prefw;
143     } else if(eq(halign, "center")) {
144         x += (w - prefw)/2;
145         w = prefw;
146     }
147     const char* valign = (isGroup || isType("vrule")) ? "fill" : "center";
148     if(hasField("valign")) valign = getStr("valign");
149     if(eq(valign, "bottom")) {
150         h = prefh;
151     } else if(eq(valign, "top")) {
152         y += h - prefh;
153         h = prefh;
154     } else if(eq(valign, "center")) {
155         y += (h - prefh)/2;
156         h = prefh;
157     }
158
159     // PUI widgets interpret their size differently depending on
160     // type, so diddle the values as needed to fit the widget into
161     // the x/y/w/h box we have calculated.
162     if (isType("text")) {
163         // puText labels are layed out to the right of the box, so
164         // zero the width. Also subtract PUSTR_RGAP from the x
165         // coordinate to compensate for the added gap between the
166         // label and its empty puObject.
167         x -= 5;
168         w = 0;
169     } else if (isType("input") || isType("combo") || isType("select")) {
170         // Fix the height to a constant
171         y += (h - 6*UNIT) / 2;
172         h = 6*UNIT;
173     } else if (isType("checkbox") || isType("radio")) {
174         // The PUI dimensions are of the check area only.  Center it
175         // on the left side of our box.
176         y += (h - 3*UNIT) / 2;
177         w = h = 3*UNIT;
178     } else if (isType("slider")) {
179         // Fix the thickness to a constant
180         if(getBool("vertical")) { x += (w-4*UNIT)/2; w = 4*UNIT; }
181         else                    { y += (h-4*UNIT)/2; h = 4*UNIT; }
182     }
183
184     // Set out output geometry
185     setNum("x", x);
186     setNum("y", y);
187     setNum("width", w);
188     setNum("height", h);
189
190     // Finally, if we are ourselves a layout object, do the actual layout.
191     if(isGroup && hasField("layout")) {
192         const char* layout = getStr("layout");
193         if     (eq(layout, "hbox" )) doHVBox(true, false);
194         else if(eq(layout, "vbox" )) doHVBox(true, true);
195         else if(eq(layout, "table")) doTable(true);
196     }
197 }
198
199 // Convention: the "A" cooridinate refers to the major axis of the
200 // container (width, for an hbox), "B" is minor.
201 void LayoutWidget::doHVBox(bool doLayout, bool vertical, int* w, int* h)
202 {
203     int nc = nChildren();
204     int* prefA = doLayout ? new int[nc] : 0;
205     int i, totalA = 0, maxB = 0, nStretch = 0;
206     int nEq = 0, eqA = 0, eqB = 0, eqTotalA = 0;
207     for(i=0; i<nc; i++) {
208         LayoutWidget child = getChild(i);
209         if (!child.getBool("enabled", true))
210             continue;
211
212         int a, b;
213         child.calcPrefSize(vertical ? &b : &a, vertical ? &a : &b);
214         if(doLayout) prefA[i] = a;
215         totalA += a;
216         if(b > maxB) maxB = b;
217         if(child.getBool("stretch")) {
218             nStretch++;
219         } else if(child.getBool("equal")) {
220             int pad = child.padding();
221             nEq++;
222             eqTotalA += a - 2*pad;
223             if(a-2*pad > eqA) eqA = a - 2*pad;
224             if(b-2*pad > eqB) eqB = b - 2*pad;
225         }
226     }
227     if(nStretch == 0) nStretch = nc;
228     totalA += nEq * eqA - eqTotalA; 
229
230     if(!doLayout) {
231         if(vertical) { *w = maxB;   *h = totalA; }
232         else         { *w = totalA; *h = maxB; }
233         return;
234     }
235
236     int currA = 0;
237     int availA = getNum(vertical ? "height" : "width");
238     int availB = getNum(vertical ? "width" : "height");
239     bool stretchAll = nStretch == nc ? true : false;
240     int stretch = availA - totalA;
241     if(stretch < 0) stretch = 0;
242     for(i=0; i<nc; i++) {
243         // Swap the child order for vertical boxes, so we lay out
244         // from top to bottom instead of along the cartesian Y axis.
245         int idx = vertical ? (nc-i-1) : i;
246         LayoutWidget child = getChild(idx);
247         if (!child.getBool("enabled", true))
248             continue;
249
250         if(child.getBool("equal")) {
251             int pad = child.padding();
252             prefA[idx] = eqA + 2*pad;
253             // Use "parent set" values to communicate the setting to
254             // the child.
255             child.setNum(vertical ? "_psh" : "_psw", eqA);
256             child.setNum(vertical ? "_psw" : "_psh", eqB);
257         }
258         if(stretchAll || child.getBool("stretch")) {
259             int chunk = stretch / nStretch;
260             stretch -= chunk;
261             nStretch--;
262             prefA[idx] += chunk;
263             child.setNum("__stretch", chunk);
264         }
265         if(vertical) child.layout(    0, currA,   availB, prefA[idx]);
266         else         child.layout(currA,     0, prefA[idx],   availB);
267         currA += prefA[idx];
268     }
269
270     delete[] prefA;
271 }
272
273 struct TabCell {
274     TabCell() {}
275     LayoutWidget child;
276     int w, h, row, col, rspan, cspan;
277 };
278
279 void LayoutWidget::doTable(bool doLayout, int* w, int* h)
280 {
281     int i, j, nc = nChildren();
282     TabCell* children = new TabCell[nc];
283     
284     // Pass 1: initialize bookeeping structures
285     int rows = 0, cols = 0;
286     for(i=0; i<nc; i++) {
287         TabCell* cell = &children[i];
288         cell->child = getChild(i);
289         cell->child.calcPrefSize(&cell->w, &cell->h);
290         cell->row = cell->child.getNum("row");
291         cell->col = cell->child.getNum("col");
292         cell->rspan = cell->child.hasField("rowspan") ? cell->child.getNum("rowspan") : 1;
293         cell->cspan = cell->child.hasField("colspan") ? cell->child.getNum("colspan") : 1;
294         if(cell->row + cell->rspan > rows) rows = cell->row + cell->rspan;
295         if(cell->col + cell->cspan > cols) cols = cell->col + cell->cspan;
296     }
297     int* rowSizes = new int[rows];
298     int* colSizes = new int[cols];
299     for(i=0; i<rows; i++) rowSizes[i] = 0;
300     for(i=0; i<cols; i++) colSizes[i] = 0;
301
302     // Pass 1a (hack): we want row zero to be the top, not the
303     // (cartesian: y==0) bottom, so reverse the sense of the row
304     // numbers.
305     for(i=0; i<nc; i++) {
306         TabCell* cell = &children[i];
307         cell->row = rows - cell->row - cell->rspan;
308     }
309     
310     // Pass 2: get sizes for single-cell children
311     for(i=0; i<nc; i++) {
312         TabCell* cell = &children[i];
313         if(cell->rspan < 2 && cell->h > rowSizes[cell->row])
314             rowSizes[cell->row] = cell->h;
315         if(cell->cspan < 2 && cell->w > colSizes[cell->col])
316             colSizes[cell->col] = cell->w;
317     }
318     
319     // Pass 3: multi-cell children, make space as needed
320     for(i=0; i<nc; i++) {
321         TabCell* cell = &children[i];
322         if(cell->rspan > 1) {
323             int total = 0;
324             for(j=0; j<cell->rspan; j++)
325                 total += rowSizes[cell->row + j];
326             int extra = cell->h - total;
327             if(extra > 0) {
328                 for(j=0; j<cell->rspan; j++) {
329                     int chunk = extra / (cell->rspan - j);
330                     rowSizes[cell->row + j] += chunk;
331                     extra -= chunk;
332                 }
333             }
334         }
335         if(cell->cspan > 1) {
336             int total = 0;
337             for(j=0; j<cell->cspan; j++)
338                 total += colSizes[cell->col + j];
339             int extra = cell->w - total;
340             if(extra > 0) {
341                 for(j=0; j<cell->cspan; j++) {
342                     int chunk = extra / (cell->cspan - j);
343                     colSizes[cell->col + j] += chunk;
344                     extra -= chunk;
345                 }
346             }
347         }
348     }
349
350     // Calculate our preferred sizes, and return if we aren't doing layout
351     int prefw=0, prefh=0;
352     for(i=0; i<cols; i++) prefw += colSizes[i];
353     for(i=0; i<rows; i++) prefh += rowSizes[i];
354
355     if(!doLayout) {
356         *w = prefw; *h = prefh;
357         delete[] children; delete[] rowSizes; delete[] colSizes;
358         return;
359     }
360
361     // Allocate extra space
362     int pad = 2*padding();
363     int extra = getNum("height") - pad - prefh;
364     for(i=0; i<rows; i++) {
365         int chunk = extra / (rows - i);
366         rowSizes[i] += chunk;
367         extra -= chunk;
368     }
369     extra = getNum("width") - pad - prefw;
370     for(i=0; i<cols; i++) {
371         int chunk = extra / (cols - i);
372         colSizes[i] += chunk;
373         extra -= chunk;
374     }
375
376     // Finally, lay out the children (with just two more temporary
377     // arrays for calculating coordinates)
378     int* rowY = new int[rows];
379     int total = 0;
380     for(i=0; i<rows; i++) { rowY[i] = total; total += rowSizes[i]; }
381
382     int* colX = new int[cols];
383     total = 0;
384     for(i=0; i<cols; i++) { colX[i] = total; total += colSizes[i]; }
385
386     for(i=0; i<nc; i++) {
387         TabCell* cell = &children[i];
388         int w = 0, h = 0;
389         for(j=0; j<cell->rspan; j++) h += rowSizes[cell->row + j];
390         for(j=0; j<cell->cspan; j++) w += colSizes[cell->col + j];
391         int x = colX[cell->col];
392         int y = rowY[cell->row];
393         cell->child.layout(x, y, w, h);
394     }    
395
396     // Clean up
397     delete[] children;
398     delete[] rowSizes;
399     delete[] colSizes;
400     delete[] rowY;
401     delete[] colX;
402 }