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