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