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