]> git.mxchange.org Git - flightgear.git/blob - src/GUI/FGPUIDialog.cxx
Forward mouse events from (PUI) CanvasWidget to Canvas for new DOM like callbacks
[flightgear.git] / src / GUI / FGPUIDialog.cxx
1 // dialog.cxx: implementation of an XML-configurable dialog box.
2
3 #ifdef HAVE_CONFIG_H
4 #  include "config.h"
5 #endif
6
7 #include <simgear/structure/SGBinding.hxx>
8 #include <simgear/props/props_io.hxx>
9
10 #include <Scripting/NasalSys.hxx>
11 #include <Main/fg_os.hxx>
12 #include <Main/globals.hxx>
13 #include <Main/fg_props.hxx>
14
15 #include "FGPUIDialog.hxx"
16 #include "new_gui.hxx"
17 #include "AirportList.hxx"
18 #include "property_list.hxx"
19 #include "layout.hxx"
20 #include "WaypointList.hxx"
21 #include "CanvasWidget.hxx"
22 #include "MapWidget.hxx"
23 #include "FGFontCache.hxx"
24 #include "FGColor.hxx"
25
26 enum format_type { f_INVALID, f_INT, f_LONG, f_FLOAT, f_DOUBLE, f_STRING };
27 static const int FORMAT_BUFSIZE = 255;
28 static const int RESIZE_MARGIN = 7;
29
30
31 /**
32  * Makes sure the format matches '%[ -+#]?\d*(\.\d*)?(l?[df]|s)', with
33  * only one number or string placeholder and otherwise arbitrary prefix
34  * and postfix containing only quoted percent signs (%%).
35  */
36 static format_type
37 validate_format(const char *f)
38 {
39     bool l = false;
40     format_type type;
41     for (; *f; f++) {
42         if (*f == '%') {
43             if (f[1] == '%')
44                 f++;
45             else
46                 break;
47         }
48     }
49     if (*f++ != '%')
50         return f_INVALID;
51     while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0')
52         f++;
53     while (*f && isdigit(*f))
54         f++;
55     if (*f == '.') {
56         f++;
57         while (*f && isdigit(*f))
58             f++;
59     }
60
61     if (*f == 'l')
62         l = true, f++;
63
64     if (*f == 'd') {
65         type = l ? f_LONG : f_INT;
66     } else if (*f == 'f')
67         type = l ? f_DOUBLE : f_FLOAT;
68     else if (*f == 's') {
69         if (l)
70             return f_INVALID;
71         type = f_STRING;
72     } else
73         return f_INVALID;
74
75     for (++f; *f; f++) {
76         if (*f == '%') {
77             if (f[1] == '%')
78                 f++;
79             else
80                 return f_INVALID;
81         }
82     }
83     return type;
84 }
85
86
87 ////////////////////////////////////////////////////////////////////////
88 // Implementation of GUIInfo.
89 ////////////////////////////////////////////////////////////////////////
90
91 /**
92  * User data for a GUI object.
93  */
94 struct GUIInfo
95 {
96     GUIInfo(FGPUIDialog *d);
97     virtual ~GUIInfo();
98     void apply_format(SGPropertyNode *);
99
100     FGPUIDialog *dialog;
101     SGPropertyNode_ptr node;
102     vector <SGBinding *> bindings;
103     int key;
104     string label, legend, text, format;
105     format_type fmt_type;
106 };
107
108 GUIInfo::GUIInfo (FGPUIDialog *d) :
109     dialog(d),
110     key(-1),
111     fmt_type(f_INVALID)
112 {
113 }
114
115 GUIInfo::~GUIInfo ()
116 {
117     for (unsigned int i = 0; i < bindings.size(); i++) {
118         delete bindings[i];
119         bindings[i] = 0;
120     }
121 }
122
123 void GUIInfo::apply_format(SGPropertyNode *n)
124 {
125     char buf[FORMAT_BUFSIZE + 1];
126     if (fmt_type == f_INT)
127         snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getIntValue());
128     else if (fmt_type == f_LONG)
129         snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getLongValue());
130     else if (fmt_type == f_FLOAT)
131         snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getFloatValue());
132     else if (fmt_type == f_DOUBLE)
133         snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getDoubleValue());
134     else
135         snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getStringValue());
136
137     buf[FORMAT_BUFSIZE] = '\0';
138     text = buf;
139 }
140
141
142 /**
143  * Key handler.
144  */
145 int fgPopup::checkKey(int key, int updown)
146 {
147     if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
148         return false;
149
150     puObject *input = getActiveInputField(this);
151     if (input)
152         return input->checkKey(key, updown);
153
154     puObject *object = getKeyObject(this, key);
155     if (!object)
156         return puPopup::checkKey(key, updown);
157
158     // invokeCallback() isn't enough; we need to simulate a mouse button press
159     object->checkHit(PU_LEFT_BUTTON, PU_DOWN,
160             (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
161             (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
162     object->checkHit(PU_LEFT_BUTTON, PU_UP,
163             (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
164             (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
165     return true;
166 }
167
168 puObject *fgPopup::getKeyObject(puObject *object, int key)
169 {
170     puObject *ret;
171     if (object->getType() & PUCLASS_GROUP)
172         for (puObject *obj = ((puGroup *)object)->getFirstChild();
173                 obj; obj = obj->getNextObject())
174             if ((ret = getKeyObject(obj, key)))
175                 return ret;
176
177     GUIInfo *info = (GUIInfo *)object->getUserData();
178     if (info && info->key == key)
179         return object;
180
181     return 0;
182 }
183
184 puObject *fgPopup::getActiveInputField(puObject *object)
185 {
186     puObject *ret;
187     if (object->getType() & PUCLASS_GROUP)
188         for (puObject *obj = ((puGroup *)object)->getFirstChild();
189                 obj; obj = obj->getNextObject())
190             if ((ret = getActiveInputField(obj)))
191                 return ret;
192
193     if (object->getType() & (PUCLASS_INPUT|PUCLASS_LARGEINPUT)
194             && ((puInput *)object)->isAcceptingInput())
195         return object;
196
197     return 0;
198 }
199
200 /**
201  * Mouse handler.
202  */
203 int fgPopup::checkHit(int button, int updown, int x, int y)
204 {
205     int result = 0;
206     if (updown != PU_DRAG && !_dragging)
207         result = puPopup::checkHit(button, updown, x, y);
208
209     if (!_draggable)
210        return result;
211
212     // This is annoying.  We would really want a true result from the
213     // superclass to indicate "handled by child object", but all it
214     // tells us is that the pointer is inside the dialog.  So do the
215     // intersection test (again) to make sure we don't start a drag
216     // when inside controls.
217
218     if (updown == PU_DOWN && !_dragging) {
219         if (!result)
220             return 0;
221         int global_drag = fgGetKeyModifiers() & KEYMOD_SHIFT;
222         int global_resize = fgGetKeyModifiers() & KEYMOD_CTRL;
223
224         int hit = getHitObjects(this, x, y);
225         if (hit & PUCLASS_LIST)  // ctrl-click in property browser (toggle bool)
226             return result;
227         if(    !global_resize
228             && (  (hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
229                          |PUCLASS_LARGEINPUT|PUCLASS_SCROLLBAR))
230                   // The canvas should handle drag events on its own so exit
231                   // here if mouse is over a CanvasWidget
232                || (!global_drag && checkHitCanvas(this, x, y))
233                ) )
234         {
235             return result;
236         }
237
238         getPosition(&_dlgX, &_dlgY);
239         getSize(&_dlgW, &_dlgH);
240         _start_cursor = fgGetMouseCursor();
241         _dragging = true;
242         _startX = x;
243         _startY = y;
244
245         // check and prepare for resizing
246         static const int cursor[] = {
247             MOUSE_CURSOR_POINTER, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
248             MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
249             MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT, 0,
250         };
251
252         _resizing = 0;
253         if (!global_drag && _resizable) {
254             int hmargin = global_resize ? _dlgW / 3 : RESIZE_MARGIN;
255             int vmargin = global_resize ? _dlgH / 3 : RESIZE_MARGIN;
256
257             if (y - _dlgY < vmargin)
258                 _resizing |= BOTTOM;
259             else if (_dlgY + _dlgH - y < vmargin)
260                 _resizing |= TOP;
261
262             if (x - _dlgX < hmargin)
263                 _resizing |= LEFT;
264             else if (_dlgX + _dlgW - x < hmargin)
265                 _resizing |= RIGHT;
266
267             if (!_resizing && global_resize)
268                 _resizing = BOTTOM|RIGHT;
269
270             _cursor = cursor[_resizing];
271            if (_resizing && _resizable)
272                 fgSetMouseCursor(_cursor);
273        }
274
275     } else if (updown == PU_DRAG && _dragging) {
276         if (_resizing) {
277             GUIInfo *info = (GUIInfo *)getUserData();
278             if (_resizable && info && info->node) {
279                 int w = _dlgW;
280                 int h = _dlgH;
281                 if (_resizing & LEFT)
282                     w += _startX - x;
283                 if (_resizing & RIGHT)
284                     w += x - _startX;
285                 if (_resizing & TOP)
286                     h += y - _startY;
287                 if (_resizing & BOTTOM)
288                     h += _startY - y;
289
290                 int prefw, prefh;
291                 LayoutWidget wid(info->node);
292                 wid.calcPrefSize(&prefw, &prefh);
293                 if (w < prefw)
294                     w = prefw;
295                 if (h < prefh)
296                     h = prefh;
297
298                 int x = _dlgX;
299                 int y = _dlgY;
300                 if (_resizing & LEFT)
301                     x += _dlgW - w;
302                 if (_resizing & BOTTOM)
303                     y += _dlgH - h;
304
305                 wid.layout(x, y, w, h);
306                 setSize(w, h);
307                 setPosition(x, y);
308                 applySize(static_cast<puObject *>(this));
309                 getFirstChild()->setSize(w, h); // dialog background puFrame
310             }
311         } else {
312             int posX = x + _dlgX - _startX,
313               posY = y + _dlgY - _startY;
314             setPosition(posX, posY);
315             
316             GUIInfo *info = (GUIInfo *)getUserData();
317             if (info && info->node) {
318                 info->node->setIntValue("x", posX);
319                 info->node->setIntValue("y", posY);
320             }
321         } // re-positioning
322
323     } else if (_dragging) {
324         fgSetMouseCursor(_start_cursor);
325         _dragging = false;
326     }
327     return result;
328 }
329
330 int fgPopup::getHitObjects(puObject *object, int x, int y)
331 {
332     if (!object->isVisible())
333         return 0;
334
335     int type = 0;
336     if (object->getType() & PUCLASS_GROUP)
337         for (puObject *obj = ((puGroup *)object)->getFirstChild();
338                 obj; obj = obj->getNextObject())
339             type |= getHitObjects(obj, x, y);
340
341     int cx, cy, cw, ch;
342     object->getAbsolutePosition(&cx, &cy);
343     object->getSize(&cw, &ch);
344     if (x >= cx && x < cx + cw && y >= cy && y < cy + ch)
345         type |= object->getType();
346     return type;
347 }
348
349 bool fgPopup::checkHitCanvas(puObject* object, int x, int y)
350 {
351   if( !object->isVisible() )
352     return 0;
353
354   if( object->getType() & PUCLASS_GROUP )
355   {
356     for( puObject* obj = ((puGroup*)object)->getFirstChild();
357                    obj;
358                    obj = obj->getNextObject() )
359     {
360       if( checkHitCanvas(obj, x, y) )
361         return true;
362     }
363   }
364
365   int cx, cy, cw, ch;
366   object->getAbsolutePosition(&cx, &cy);
367   object->getSize(&cw, &ch);
368   if(    x >= cx && x < cx + cw
369       && y >= cy && y < cy + ch
370       && dynamic_cast<CanvasWidget*>(object) )
371     return true;
372   return false;
373 }
374
375 void fgPopup::applySize(puObject *object)
376 {
377     // compound plib widgets use setUserData() for internal purposes, so refuse
378     // to descend into anything that has other bits set than the following
379     const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
380             |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
381             |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
382
383     int type = object->getType();
384     if (type & PUCLASS_GROUP && !(type & ~validUserData))
385         for (puObject *obj = ((puGroup *)object)->getFirstChild();
386                 obj; obj = obj->getNextObject())
387             applySize(obj);
388
389     GUIInfo *info = (GUIInfo *)object->getUserData();
390     if (!info)
391         return;
392
393     SGPropertyNode *n = info->node;
394     if (!n) {
395         SG_LOG(SG_GENERAL, SG_ALERT, "fgPopup::applySize: no props");
396         return;
397     }
398     int x = n->getIntValue("x");
399     int y = n->getIntValue("y");
400     int w = n->getIntValue("width", 4);
401     int h = n->getIntValue("height", 4);
402     object->setPosition(x, y);
403     object->setSize(w, h);
404 }
405
406 ////////////////////////////////////////////////////////////////////////
407
408 void FGPUIDialog::ConditionalObject::update(FGPUIDialog* aDlg)
409 {
410   if (_name == "visible") {
411     bool wasVis = _pu->isVisible() != 0;
412     bool newVis = test();
413     
414     if (newVis == wasVis) {
415       return;
416     }
417     
418     if (newVis) { // puObject needs a setVisible. Oh well.
419     _pu->reveal();
420     } else {
421       _pu->hide();
422     }
423   } else if (_name == "enable") {
424     bool wasEnabled = _pu->isActive() != 0;
425     bool newEnable = test();
426     
427     if (wasEnabled == newEnable) {
428       return;
429     }
430     
431     if (newEnable) {
432       _pu->activate();
433     } else {
434       _pu->greyOut();
435     }
436   }
437   
438   aDlg->setNeedsLayout();
439 }
440
441 ////////////////////////////////////////////////////////////////////////
442 // Callbacks.
443 ////////////////////////////////////////////////////////////////////////
444
445 /**
446  * Action callback.
447  */
448 static void
449 action_callback (puObject *object)
450 {
451     GUIInfo *info = (GUIInfo *)object->getUserData();
452     NewGUI *gui = (NewGUI *)globals->get_subsystem("gui");
453     gui->setActiveDialog(info->dialog);
454     int nBindings = info->bindings.size();
455     for (int i = 0; i < nBindings; i++) {
456         info->bindings[i]->fire();
457         if (gui->getActiveDialog() == 0)
458             break;
459     }
460     gui->setActiveDialog(0);
461 }
462
463
464 ////////////////////////////////////////////////////////////////////////
465 // Static helper functions.
466 ////////////////////////////////////////////////////////////////////////
467
468 /**
469  * Copy a property value to a PUI object.
470  */
471 static void
472 copy_to_pui (SGPropertyNode *node, puObject *object)
473 {
474     using namespace simgear;
475     GUIInfo *info = (GUIInfo *)object->getUserData();
476     if (!info) {
477         SG_LOG(SG_GENERAL, SG_ALERT, "dialog: widget without GUIInfo!");
478         return;   // this can't really happen
479     }
480
481     // Treat puText objects specially, so their "values" can be set
482     // from properties.
483     if (object->getType() & PUCLASS_TEXT) {
484         if (info->fmt_type != f_INVALID)
485             info->apply_format(node);
486         else
487             info->text = node->getStringValue();
488
489         object->setLabel(info->text.c_str());
490         return;
491     }
492
493     switch (node->getType()) {
494     case props::BOOL:
495     case props::INT:
496     case props::LONG:
497         object->setValue(node->getIntValue());
498         break;
499     case props::FLOAT:
500     case props::DOUBLE:
501         object->setValue(node->getFloatValue());
502         break;
503     default:
504         info->text = node->getStringValue();
505         object->setValue(info->text.c_str());
506         break;
507     }
508 }
509
510
511 static void
512 copy_from_pui (puObject *object, SGPropertyNode *node)
513 {
514     using namespace simgear;
515     // puText objects are immutable, so should not be copied out
516     if (object->getType() & PUCLASS_TEXT)
517         return;
518
519     switch (node->getType()) {
520     case props::BOOL:
521     case props::INT:
522     case props::LONG:
523         node->setIntValue(object->getIntegerValue());
524         break;
525     case props::FLOAT:
526         node->setFloatValue(object->getFloatValue());
527         break;
528     case props::DOUBLE:
529     {
530         // puObject only provides float, not double, which causes precision/rounding issues
531         // with some numerical values (try "114.2").
532         // Work around: obtain string value, and manually convert with proper double precision.
533         const char *s = object->getStringValue();
534         node->setDoubleValue(atof(s));
535         break;
536     }
537     default:
538         const char *s = object->getStringValue();
539         if (s)
540             node->setStringValue(s);
541         break;
542     }
543 }
544
545
546 ////////////////////////////////////////////////////////////////////////
547 // Implementation of FGDialog.
548 ////////////////////////////////////////////////////////////////////////
549
550 FGPUIDialog::FGPUIDialog (SGPropertyNode *props) :
551     FGDialog(props),
552     _object(0),
553     _gui((NewGUI *)globals->get_subsystem("gui")),
554     _props(props),
555     _needsRelayout(false)
556 {
557     _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
558         
559     SGPropertyNode *nasal = props->getNode("nasal");
560     if (nasal) {
561         _nasal_close = nasal->getNode("close");
562         SGPropertyNode *open = nasal->getNode("open");
563         if (open) {
564             const char *s = open->getStringValue();
565             FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
566             if (nas)
567                 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
568         }
569     }
570     display(props);
571 }
572
573 FGPUIDialog::~FGPUIDialog ()
574 {
575     int x, y;
576     _object->getAbsolutePosition(&x, &y);
577     _props->setIntValue("lastx", x);
578     _props->setIntValue("lasty", y);
579
580     FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
581     if (nas) {
582         if (_nasal_close) {
583             const char *s = _nasal_close->getStringValue();
584             nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
585         }
586         nas->deleteModule(_module.c_str());
587     }
588
589     puDeleteObject(_object);
590
591     unsigned int i;
592                                 // Delete all the info objects we
593                                 // were forced to keep around because
594                                 // PUI cannot delete its own user data.
595     for (i = 0; i < _info.size(); i++) {
596         delete (GUIInfo *)_info[i];
597         _info[i] = 0;
598     }
599                                 // Finally, delete the property links.
600     for (i = 0; i < _propertyObjects.size(); i++) {
601         delete _propertyObjects[i];
602         _propertyObjects[i] = 0;
603     }
604 }
605
606 void
607 FGPUIDialog::updateValues (const char *objectName)
608 {
609     if (objectName && !objectName[0])
610         objectName = 0;
611
612   for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
613     const string &name = _propertyObjects[i]->name;
614     if (objectName && name != objectName) {
615       continue;
616     }
617     
618     puObject *widget = _propertyObjects[i]->object;
619     int widgetType = widget->getType();
620     if (widgetType & PUCLASS_LIST) {
621       GUI_ID* gui_id = dynamic_cast<GUI_ID *>(widget);
622       if (gui_id && (gui_id->id & FGCLASS_LIST)) {
623         fgList *pl = static_cast<fgList*>(widget);
624         pl->update();
625       } else {
626         copy_to_pui(_propertyObjects[i]->node, widget);
627       }
628     } else if (widgetType & PUCLASS_COMBOBOX) {
629       fgComboBox* combo = static_cast<fgComboBox*>(widget);
630       combo->update();
631     } else {
632       copy_to_pui(_propertyObjects[i]->node, widget);
633     }
634   } // of property objects iteration
635 }
636
637 void
638 FGPUIDialog::applyValues (const char *objectName)
639 {
640     if (objectName && !objectName[0])
641         objectName = 0;
642
643     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
644         const string &name = _propertyObjects[i]->name;
645         if (objectName && name != objectName)
646             continue;
647
648         copy_from_pui(_propertyObjects[i]->object,
649                       _propertyObjects[i]->node);
650     }
651 }
652
653 void
654 FGPUIDialog::update ()
655 {
656     for (unsigned int i = 0; i < _liveObjects.size(); i++) {
657         puObject *obj = _liveObjects[i]->object;
658         if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
659             continue;
660
661         copy_to_pui(_liveObjects[i]->node, obj);
662     }
663     
664   for (unsigned int j=0; j < _conditionalObjects.size(); ++j) {
665     _conditionalObjects[j]->update(this);
666   }
667   
668   if (_needsRelayout) {
669     relayout();
670   }
671 }
672
673 void
674 FGPUIDialog::display (SGPropertyNode *props)
675 {
676     if (_object != 0) {
677         SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
678         return;
679     }
680
681     int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
682     int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
683
684     bool userx = props->hasValue("x");
685     bool usery = props->hasValue("y");
686     bool userw = props->hasValue("width");
687     bool userh = props->hasValue("height");
688
689     // Let the layout widget work in the same property subtree.
690     LayoutWidget wid(props);
691
692     SGPropertyNode *fontnode = props->getNode("font");
693     if (fontnode) {
694         FGFontCache *fc = globals->get_fontcache();
695         _font = fc->get(fontnode);
696     } else {
697         _font = _gui->getDefaultFont();
698     }
699     wid.setDefaultFont(_font, int(_font->getPointSize()));
700
701     int pw = 0, ph = 0;
702     int px, py, savex, savey;
703     if (!userw || !userh)
704         wid.calcPrefSize(&pw, &ph);
705     pw = props->getIntValue("width", pw);
706     ph = props->getIntValue("height", ph);
707     px = savex = props->getIntValue("x", (screenw - pw) / 2);
708     py = savey = props->getIntValue("y", (screenh - ph) / 2);
709
710     // Negative x/y coordinates are interpreted as distance from the top/right
711     // corner rather than bottom/left.
712     if (userx && px < 0)
713         px = screenw - pw + px;
714     if (usery && py < 0)
715         py = screenh - ph + py;
716
717     // Define "x", "y", "width" and/or "height" in the property tree if they
718     // are not specified in the configuration file.
719     wid.layout(px, py, pw, ph);
720
721     // Use the dimension and location properties as specified in the
722     // configuration file or from the layout widget.
723     _object = makeObject(props, screenw, screenh);
724
725     // Remove automatically generated properties, so the layout looks
726     // the same next time around, or restore x and y to preserve negative coords.
727     if (userx)
728         props->setIntValue("x", savex);
729     else
730         props->removeChild("x");
731
732     if (usery)
733         props->setIntValue("y", savey);
734     else
735         props->removeChild("y");
736
737     if (!userw) props->removeChild("width");
738     if (!userh) props->removeChild("height");
739
740     if (_object != 0) {
741         _object->reveal();
742     } else {
743         SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
744                << props->getStringValue("name", "[unnamed]")
745                << " does not contain a proper GUI definition");
746     }
747 }
748
749 puObject *
750 FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeight)
751 {
752     if (!props->getBoolValue("enabled", true))
753         return 0;
754
755     bool presetSize = props->hasValue("width") && props->hasValue("height");
756     int width = props->getIntValue("width", parentWidth);
757     int height = props->getIntValue("height", parentHeight);
758     int x = props->getIntValue("x", (parentWidth - width) / 2);
759     int y = props->getIntValue("y", (parentHeight - height) / 2);
760     string type = props->getName();
761
762     if (type.empty())
763         type = "dialog";
764
765     if (type == "dialog") {
766         puPopup *obj;
767         bool draggable = props->getBoolValue("draggable", true);
768         bool resizable = props->getBoolValue("resizable", false);
769         if (props->getBoolValue("modal", false))
770             obj = new puDialogBox(x, y);
771         else
772             obj = new fgPopup(x, y, resizable, draggable);
773         setupGroup(obj, props, width, height, true);
774         setColor(obj, props);
775         return obj;
776
777     } else if (type == "group") {
778         puGroup *obj = new puGroup(x, y);
779         setupGroup(obj, props, width, height, false);
780         setColor(obj, props);
781         return obj;
782
783     } else if (type == "frame") {
784         puGroup *obj = new puGroup(x, y);
785         setupGroup(obj, props, width, height, true);
786         setColor(obj, props);
787         return obj;
788
789     } else if (type == "hrule" || type == "vrule") {
790         puFrame *obj = new puFrame(x, y, x + width, y + height);
791         obj->setBorderThickness(0);
792         setupObject(obj, props);
793         setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
794         return obj;
795
796     } else if (type == "list") {
797         int slider_width = props->getIntValue("slider", 20);
798         fgList *obj = new fgList(x, y, x + width, y + height, props, slider_width);
799         if (presetSize)
800             obj->setSize(width, height);
801         setupObject(obj, props);
802         setColor(obj, props);
803         return obj;
804
805     } else if (type == "airport-list") {
806         AirportList *obj = new AirportList(x, y, x + width, y + height);
807         if (presetSize)
808             obj->setSize(width, height);
809         setupObject(obj, props);
810         setColor(obj, props);
811         return obj;
812
813     } else if (type == "property-list") {
814         PropertyList *obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
815         if (presetSize)
816             obj->setSize(width, height);
817         setupObject(obj, props);
818         setColor(obj, props);
819         return obj;
820
821     } else if (type == "input") {
822         puInput *obj = new puInput(x, y, x + width, y + height);
823         setupObject(obj, props);
824         setColor(obj, props, FOREGROUND|LABEL);
825         return obj;
826
827     } else if (type == "text") {
828         puText *obj = new puText(x, y);
829         setupObject(obj, props);
830
831         // Layed-out objects need their size set, and non-layout ones
832         // get a different placement.
833         if (presetSize)
834             obj->setSize(width, height);
835         else
836             obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
837         setColor(obj, props, LABEL);
838         return obj;
839
840     } else if (type == "checkbox") {
841         puButton *obj;
842         obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
843         setupObject(obj, props);
844         setColor(obj, props, FOREGROUND|LABEL);
845         return obj;
846
847     } else if (type == "radio") {
848         puButton *obj;
849         obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
850         setupObject(obj, props);
851         setColor(obj, props, FOREGROUND|LABEL);
852         return obj;
853
854     } else if (type == "button") {
855         puButton *obj;
856         const char *legend = props->getStringValue("legend", "[none]");
857         if (props->getBoolValue("one-shot", true))
858             obj = new puOneShot(x, y, legend);
859         else
860             obj = new puButton(x, y, legend);
861         if (presetSize)
862             obj->setSize(width, height);
863         setupObject(obj, props);
864         setColor(obj, props);
865         return obj;
866     } else if (type == "map") {
867         MapWidget* mapWidget = new MapWidget(x, y, x + width, y + height);
868         setupObject(mapWidget, props);
869         return mapWidget;
870     } else if (type == "canvas") {
871         CanvasWidget* canvasWidget = new CanvasWidget( x, y,
872                                                        x + width, y + height,
873                                                        props,
874                                                        _module );
875         setupObject(canvasWidget, props);
876         return canvasWidget;
877     } else if (type == "combo") {
878         fgComboBox *obj = new fgComboBox(x, y, x + width, y + height, props,
879                 props->getBoolValue("editable", false));
880         setupObject(obj, props);
881         setColor(obj, props, EDITFIELD);
882         return obj;
883
884     } else if (type == "slider") {
885         bool vertical = props->getBoolValue("vertical", false);
886         puSlider *obj = new puSlider(x, y, (vertical ? height : width), vertical);
887         obj->setMinValue(props->getFloatValue("min", 0.0));
888         obj->setMaxValue(props->getFloatValue("max", 1.0));
889         obj->setStepSize(props->getFloatValue("step"));
890         obj->setSliderFraction(props->getFloatValue("fraction"));
891 #if PLIB_VERSION > 185
892         obj->setPageStepSize(props->getFloatValue("pagestep"));
893 #endif
894         setupObject(obj, props);
895         if (presetSize)
896             obj->setSize(width, height);
897         setColor(obj, props, FOREGROUND|LABEL);
898         return obj;
899
900     } else if (type == "dial") {
901         puDial *obj = new puDial(x, y, width);
902         obj->setMinValue(props->getFloatValue("min", 0.0));
903         obj->setMaxValue(props->getFloatValue("max", 1.0));
904         obj->setWrap(props->getBoolValue("wrap", true));
905         setupObject(obj, props);
906         setColor(obj, props, FOREGROUND|LABEL);
907         return obj;
908
909     } else if (type == "textbox") {
910         int slider_width = props->getIntValue("slider", 20);
911         int wrap = props->getBoolValue("wrap", true);
912 #if PLIB_VERSION > 185
913         puaLargeInput *obj = new puaLargeInput(x, y,
914                 x + width, x + height, 11, slider_width, wrap);
915 #else
916         puaLargeInput *obj = new puaLargeInput(x, y,
917                 x + width, x + height, 2, slider_width, wrap);
918 #endif
919
920         if (props->getBoolValue("editable"))
921             obj->enableInput();
922         else
923             obj->disableInput();
924
925         if (presetSize)
926             obj->setSize(width, height);
927         setupObject(obj, props);
928         setColor(obj, props, FOREGROUND|LABEL);
929
930         int top = props->getIntValue("top-line", 0);
931         obj->setTopLineInWindow(top < 0 ? unsigned(-1) >> 1 : top);
932         return obj;
933
934     } else if (type == "select") {
935         fgSelectBox *obj = new fgSelectBox(x, y, x + width, y + height, props);
936         setupObject(obj, props);
937         setColor(obj, props, EDITFIELD);
938         return obj;
939     } else if (type == "waypointlist") {
940         ScrolledWaypointList* obj = new ScrolledWaypointList(x, y, width, height);
941         setupObject(obj, props);
942         return obj;
943     } else {
944         return 0;
945     }
946 }
947
948 void
949 FGPUIDialog::setupObject (puObject *object, SGPropertyNode *props)
950 {
951     GUIInfo *info = new GUIInfo(this);
952     object->setUserData(info);
953     _info.push_back(info);
954     object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
955     object->makeReturnDefault(props->getBoolValue("default"));
956     info->node = props;
957
958     if (props->hasValue("legend")) {
959         info->legend = props->getStringValue("legend");
960         object->setLegend(info->legend.c_str());
961     }
962
963     if (props->hasValue("label")) {
964         info->label = props->getStringValue("label");
965         object->setLabel(info->label.c_str());
966     }
967
968     if (props->hasValue("border"))
969         object->setBorderThickness( props->getIntValue("border", 2) );
970
971     if (SGPropertyNode *nft = props->getNode("font", false)) {
972        FGFontCache *fc = globals->get_fontcache();
973        puFont *lfnt = fc->get(nft);
974        object->setLabelFont(*lfnt);
975        object->setLegendFont(*lfnt);
976     } else {
977        object->setLabelFont(*_font);
978     }
979
980     if (props->hasChild("visible")) {
981       ConditionalObject* cnd = new ConditionalObject("visible", object);
982       cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("visible")));
983       _conditionalObjects.push_back(cnd);
984     }
985
986     if (props->hasChild("enable")) {
987       ConditionalObject* cnd = new ConditionalObject("enable", object);
988       cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("enable")));
989       _conditionalObjects.push_back(cnd);
990     }
991
992     string type = props->getName();
993     if (type == "input" && props->getBoolValue("live"))
994         object->setDownCallback(action_callback);
995
996     if (type == "text") {
997         const char *format = props->getStringValue("format", 0);
998         if (format) {
999             info->fmt_type = validate_format(format);
1000             if (info->fmt_type != f_INVALID)
1001                 info->format = format;
1002             else
1003                 SG_LOG(SG_GENERAL, SG_ALERT, "DIALOG: invalid <format> '"
1004                         << format << '\'');
1005         }
1006     }
1007
1008     if (props->hasValue("property")) {
1009         const char *name = props->getStringValue("name");
1010         if (name == 0)
1011             name = "";
1012         const char *propname = props->getStringValue("property");
1013         SGPropertyNode_ptr node = fgGetNode(propname, true);
1014         if (type == "map") {
1015           // mapWidget binds to a sub-tree of properties, and
1016           // ignores the puValue mechanism, so special case things here
1017           MapWidget* mw = static_cast<MapWidget*>(object);
1018           mw->setProperty(node);
1019         } else {
1020             // normal widget, creating PropertyObject
1021             copy_to_pui(node, object);
1022             PropertyObject *po = new PropertyObject(name, object, node);
1023             _propertyObjects.push_back(po);
1024             if (props->getBoolValue("live"))
1025                 _liveObjects.push_back(po);
1026         }
1027         
1028
1029     }
1030
1031     SGPropertyNode *dest = fgGetNode("/sim/bindings/gui", true);
1032     vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
1033     if (bindings.size() > 0) {
1034         info->key = props->getIntValue("keynum", -1);
1035         if (props->hasValue("key"))
1036             info->key = getKeyCode(props->getStringValue("key", ""));
1037
1038         for (unsigned int i = 0; i < bindings.size(); i++) {
1039             unsigned int j = 0;
1040             SGPropertyNode_ptr binding;
1041             while (dest->getChild("binding", j))
1042                 j++;
1043
1044             const char *cmd = bindings[i]->getStringValue("command");
1045             if (!strcmp(cmd, "nasal"))
1046                 bindings[i]->setStringValue("module", _module.c_str());
1047
1048             binding = dest->getChild("binding", j, true);
1049             copyProperties(bindings[i], binding);
1050             info->bindings.push_back(new SGBinding(binding, globals->get_props()));
1051         }
1052         object->setCallback(action_callback);
1053     }
1054 }
1055
1056 void
1057 FGPUIDialog::setupGroup(puGroup *group, SGPropertyNode *props,
1058        int width, int height, bool makeFrame)
1059 {
1060     setupObject(group, props);
1061
1062     if (makeFrame) {
1063         puFrame* f = new puFrame(0, 0, width, height);
1064         setColor(f, props);
1065     }
1066
1067     int nChildren = props->nChildren();
1068     for (int i = 0; i < nChildren; i++)
1069         makeObject(props->getChild(i), width, height);
1070     group->close();
1071 }
1072
1073 void
1074 FGPUIDialog::setColor(puObject *object, SGPropertyNode *props, int which)
1075 {
1076     string type = props->getName();
1077     if (type.empty())
1078         type = "dialog";
1079     if (type == "textbox" && props->getBoolValue("editable"))
1080         type += "-editable";
1081
1082     FGColor c(_gui->getColor("background"));
1083     c.merge(_gui->getColor(type));
1084     c.merge(props->getNode("color"));
1085     if (c.isValid())
1086         object->setColourScheme(c.red(), c.green(), c.blue(), c.alpha());
1087
1088     const struct {
1089         int mask;
1090         int id;
1091         const char *name;
1092         const char *cname;
1093     } pucol[] = {
1094         { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
1095         { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
1096         { HIGHLIGHT,  PUCOL_HIGHLIGHT,  "highlight",  "color-highlight" },
1097         { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
1098         { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
1099         { MISC,       PUCOL_MISC,       "misc",       "color-misc" },
1100         { EDITFIELD,  PUCOL_EDITFIELD,  "editfield",  "color-editfield" },
1101     };
1102
1103     const int numcol = sizeof(pucol) / sizeof(pucol[0]);
1104
1105     for (int i = 0; i < numcol; i++) {
1106         bool dirty = false;
1107         c.clear();
1108         c.setAlpha(1.0);
1109
1110         dirty |= c.merge(_gui->getColor(type + '-' + pucol[i].name));
1111         if (which & pucol[i].mask)
1112             dirty |= c.merge(props->getNode("color"));
1113
1114         if ((pucol[i].mask == LABEL) && !c.isValid())
1115             dirty |= c.merge(_gui->getColor("label"));
1116
1117         dirty |= c.merge(props->getNode(pucol[i].cname));
1118
1119         if (c.isValid() && dirty)
1120             object->setColor(pucol[i].id, c.red(), c.green(), c.blue(), c.alpha());
1121     }
1122 }
1123
1124
1125 static struct {
1126     const char *name;
1127     int key;
1128 } keymap[] = {
1129     {"backspace", 8},
1130     {"tab", 9},
1131     {"return", 13},
1132     {"enter", 13},
1133     {"esc", 27},
1134     {"escape", 27},
1135     {"space", ' '},
1136     {"&amp;", '&'},
1137     {"and", '&'},
1138     {"&lt;", '<'},
1139     {"&gt;", '>'},
1140     {"f1", PU_KEY_F1},
1141     {"f2", PU_KEY_F2},
1142     {"f3", PU_KEY_F3},
1143     {"f4", PU_KEY_F4},
1144     {"f5", PU_KEY_F5},
1145     {"f6", PU_KEY_F6},
1146     {"f7", PU_KEY_F7},
1147     {"f8", PU_KEY_F8},
1148     {"f9", PU_KEY_F9},
1149     {"f10", PU_KEY_F10},
1150     {"f11", PU_KEY_F11},
1151     {"f12", PU_KEY_F12},
1152     {"left", PU_KEY_LEFT},
1153     {"up", PU_KEY_UP},
1154     {"right", PU_KEY_RIGHT},
1155     {"down", PU_KEY_DOWN},
1156     {"pageup", PU_KEY_PAGE_UP},
1157     {"pagedn", PU_KEY_PAGE_DOWN},
1158     {"home", PU_KEY_HOME},
1159     {"end", PU_KEY_END},
1160     {"insert", PU_KEY_INSERT},
1161     {0, -1},
1162 };
1163
1164 int
1165 FGPUIDialog::getKeyCode(const char *str)
1166 {
1167     enum {
1168         CTRL = 0x1,
1169         SHIFT = 0x2,
1170         ALT = 0x4,
1171     };
1172
1173     while (*str == ' ')
1174         str++;
1175
1176     char *buf = new char[strlen(str) + 1];
1177     strcpy(buf, str);
1178     char *s = buf + strlen(buf);
1179     while (s > str && s[-1] == ' ')
1180         s--;
1181     *s = 0;
1182     s = buf;
1183
1184     int mod = 0;
1185     while (1) {
1186         if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
1187             s += 5, mod |= CTRL;
1188         else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
1189             s += 6, mod |= SHIFT;
1190         else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
1191             s += 4, mod |= ALT;
1192         else
1193             break;
1194     }
1195
1196     int key = -1;
1197     if (strlen(s) == 1 && isascii(*s)) {
1198         key = *s;
1199         if (mod & SHIFT)
1200             key = toupper(key);
1201         if (mod & CTRL)
1202             key = toupper(key) - '@';
1203         /* if (mod & ALT)
1204             ;   // Alt not propagated to the gui
1205         */
1206     } else {
1207         for (char *t = s; *t; t++)
1208             *t = tolower(*t);
1209         for (int i = 0; keymap[i].name; i++) {
1210             if (!strcmp(s, keymap[i].name)) {
1211                 key = keymap[i].key;
1212                 break;
1213             }
1214         }
1215     }
1216     delete[] buf;
1217     return key;
1218 }
1219
1220 void FGPUIDialog::relayout()
1221 {
1222   _needsRelayout = false;
1223   
1224   int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
1225   int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
1226
1227   bool userx = _props->hasValue("x");
1228   bool usery = _props->hasValue("y");
1229   bool userw = _props->hasValue("width");
1230   bool userh = _props->hasValue("height");
1231
1232   // Let the layout widget work in the same property subtree.
1233   LayoutWidget wid(_props);
1234   wid.setDefaultFont(_font, int(_font->getPointSize()));
1235
1236   int pw = 0, ph = 0;
1237   int px, py, savex, savey;
1238   if (!userw || !userh) {
1239     wid.calcPrefSize(&pw, &ph);
1240   }
1241   
1242   pw = _props->getIntValue("width", pw);
1243   ph = _props->getIntValue("height", ph);
1244   px = savex = _props->getIntValue("x", (screenw - pw) / 2);
1245   py = savey = _props->getIntValue("y", (screenh - ph) / 2);
1246
1247   // Negative x/y coordinates are interpreted as distance from the top/right
1248   // corner rather than bottom/left.
1249   if (userx && px < 0)
1250     px = screenw - pw + px;
1251   if (usery && py < 0)
1252     py = screenh - ph + py;
1253
1254   // Define "x", "y", "width" and/or "height" in the property tree if they
1255   // are not specified in the configuration file.
1256   wid.layout(px, py, pw, ph);
1257
1258   applySize(_object);
1259   
1260   // Remove automatically generated properties, so the layout looks
1261   // the same next time around, or restore x and y to preserve negative coords.
1262   if (userx)
1263       _props->setIntValue("x", savex);
1264   else
1265       _props->removeChild("x");
1266
1267   if (usery)
1268       _props->setIntValue("y", savey);
1269   else
1270       _props->removeChild("y");
1271
1272   if (!userw) _props->removeChild("width");
1273   if (!userh) _props->removeChild("height");
1274 }
1275
1276 void
1277 FGPUIDialog::applySize(puObject *object)
1278 {
1279   // compound plib widgets use setUserData() for internal purposes, so refuse
1280   // to descend into anything that has other bits set than the following
1281   const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
1282           |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
1283           |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
1284
1285   int type = object->getType();
1286   if ((type & PUCLASS_GROUP) && !(type & ~validUserData)) {
1287     puObject* c = ((puGroup *)object)->getFirstChild();
1288     for (; c != NULL; c = c->getNextObject()) {
1289       applySize(c);
1290     } // of child iteration
1291   } // of group object case
1292   
1293   GUIInfo *info = (GUIInfo *)object->getUserData();
1294   if (!info)
1295     return;
1296
1297   SGPropertyNode *n = info->node;
1298   if (!n) {
1299     SG_LOG(SG_GENERAL, SG_ALERT, "FGDialog::applySize: no props");
1300     return;
1301   }
1302   
1303   int x = n->getIntValue("x");
1304   int y = n->getIntValue("y");
1305   int w = n->getIntValue("width", 4);
1306   int h = n->getIntValue("height", 4);
1307   object->setPosition(x, y);
1308   object->setSize(w, h);
1309 }
1310
1311 ////////////////////////////////////////////////////////////////////////
1312 // Implementation of FGDialog::PropertyObject.
1313 ////////////////////////////////////////////////////////////////////////
1314
1315 FGPUIDialog::PropertyObject::PropertyObject(const char *n,
1316         puObject *o, SGPropertyNode_ptr p) :
1317     name(n),
1318     object(o),
1319     node(p)
1320 {
1321 }
1322
1323
1324
1325 ////////////////////////////////////////////////////////////////////////
1326 // Implementation of fgValueList and derived pui widgets
1327 ////////////////////////////////////////////////////////////////////////
1328
1329
1330 fgValueList::fgValueList(SGPropertyNode *p) :
1331     _props(p)
1332 {
1333     make_list();
1334 }
1335
1336 void
1337 fgValueList::update()
1338 {
1339     destroy_list();
1340     make_list();
1341 }
1342
1343 fgValueList::~fgValueList()
1344 {
1345     destroy_list();
1346 }
1347
1348 void
1349 fgValueList::make_list()
1350 {
1351   SGPropertyNode_ptr values = _props;
1352   const char* vname = "value";
1353   
1354   if (_props->hasChild("properties")) {
1355     // dynamic values, read from a property's children
1356     const char* path = _props->getStringValue("properties");
1357     values = fgGetNode(path, true);
1358   }
1359   
1360   if (_props->hasChild("property-name")) {
1361     vname = _props->getStringValue("property-name");
1362   }
1363   
1364   vector<SGPropertyNode_ptr> value_nodes = values->getChildren(vname);
1365   _list = new char *[value_nodes.size() + 1];
1366   unsigned int i;
1367   for (i = 0; i < value_nodes.size(); i++) {
1368     _list[i] = strdup((char *)value_nodes[i]->getStringValue());
1369   }
1370   _list[i] = 0;
1371 }
1372
1373 void
1374 fgValueList::destroy_list()
1375 {
1376     for (int i = 0; _list[i] != 0; i++)
1377         if (_list[i])
1378             free(_list[i]);
1379     delete[] _list;
1380 }
1381
1382
1383
1384 void
1385 fgList::update()
1386 {
1387     fgValueList::update();
1388     int top = getTopItem();
1389     newList(_list);
1390     setTopItem(top);
1391 }
1392
1393 void fgComboBox::update()
1394 {
1395   if (_inHit) {
1396     return;
1397   }
1398   
1399   std::string curValue(getStringValue());
1400   fgValueList::update();
1401   newList(_list);
1402   int currentItem = puaComboBox::getCurrentItem();
1403
1404   
1405 // look for the previous value, in the new list
1406   for (int i = 0; _list[i] != 0; i++) {
1407     if (_list[i] == curValue) {
1408     // don't set the current item again; this is important to avoid
1409     // recursion here if the combo callback ultimately causes another dialog-update
1410       if (i != currentItem) {
1411         puaComboBox::setCurrentItem(i);
1412       }
1413       
1414       return;
1415     }
1416   } // of list values iteration
1417   
1418 // cound't find current item, default to first
1419   if (currentItem != 0) {
1420     puaComboBox::setCurrentItem(0);
1421   }
1422 }
1423
1424 int fgComboBox::checkHit(int b, int up, int x, int y)
1425 {
1426   _inHit = true;
1427   int r = puaComboBox::checkHit(b, up, x, y);
1428   _inHit = false;
1429   return r;
1430 }
1431
1432 void fgComboBox::setSize(int w, int h)
1433 {
1434   puaComboBox::setSize(w, h);
1435   recalc_bbox();
1436 }
1437
1438 void fgComboBox::recalc_bbox()
1439 {
1440 // bug-fix for issue #192
1441 // http://code.google.com/p/flightgear-bugs/issues/detail?id=192
1442 // puaComboBox is including the height of its popupMenu in the height
1443 // computation, which breaks layout computations.
1444 // this implementation skips popup-menus
1445   
1446   puBox contents;
1447   contents.empty();
1448   
1449   for (puObject *bo = dlist; bo != NULL; bo = bo -> getNextObject()) {
1450     if (bo->getType() & PUCLASS_POPUPMENU) {
1451       continue;
1452     }
1453     
1454     contents.extend (bo -> getBBox()) ;
1455   }
1456   
1457   abox.max[0] = abox.min[0] + contents.max[0] ;
1458   abox.max[1] = abox.min[1] + contents.max[1] ;
1459
1460   puObject::recalc_bbox () ;
1461
1462 }
1463
1464 // end of FGPUIDialog.cxx