]> git.mxchange.org Git - flightgear.git/blob - src/GUI/dialog.cxx
MS Windows can't handle the symbols ... sigh
[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 <stdlib.h>             // atof()
8
9 #include <Input/input.hxx>
10 #include <Scripting/NasalSys.hxx>
11
12 #include "dialog.hxx"
13 #include "new_gui.hxx"
14
15 #include "AirportList.hxx"
16 #include "property_list.hxx"
17 #include "layout.hxx"
18
19
20 enum format_type { f_INVALID, f_INT, f_LONG, f_FLOAT, f_DOUBLE, f_STRING };
21 static const int FORMAT_BUFSIZE = 255;
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     if (*f == ' ' || *f == '+' || *f == '-' || *f == '#')
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     char *format(SGPropertyNode *);
91
92     FGDialog * dialog;
93     vector <SGBinding *> bindings;
94     int key;
95     char *text;
96     char *fmt_string;
97     format_type fmt_type;
98 };
99
100 GUIInfo::GUIInfo (FGDialog * d)
101     : dialog(d),
102       key(-1),
103       text(0),
104       fmt_string(0),
105       fmt_type(f_INVALID)
106 {
107 }
108
109 GUIInfo::~GUIInfo ()
110 {
111     for (unsigned int i = 0; i < bindings.size(); i++) {
112         delete bindings[i];
113         bindings[i] = 0;
114     }
115     delete [] text;
116     delete [] fmt_string;
117 }
118
119 char *GUIInfo::format(SGPropertyNode *n)
120 {
121     if (fmt_type == f_INT)
122         snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getIntValue());
123     else if (fmt_type == f_LONG)
124         snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getLongValue());
125     else if (fmt_type == f_FLOAT)
126         snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getFloatValue());
127     else if (fmt_type == f_DOUBLE)
128         snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getDoubleValue());
129     else
130         snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getStringValue());
131
132     text[FORMAT_BUFSIZE] = '\0';
133     return text;
134 }
135
136
137 \f
138 /**
139  * Key handler.
140  */
141 int fgPopup::checkKey(int key, int updown)
142 {
143     if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
144         return false;
145
146     puObject *input = getActiveInputField(this);
147     if (input)
148         return input->checkKey(key, updown);
149
150     puObject *object = getKeyObject(this, key);
151     if (!object)
152         return puPopup::checkKey(key, updown);
153
154     // invokeCallback() isn't enough; we need to simulate a mouse button press
155     object->checkHit(PU_LEFT_BUTTON, PU_DOWN,
156             (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
157             (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
158     object->checkHit(PU_LEFT_BUTTON, PU_UP,
159             (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
160             (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
161     return true;
162 }
163
164 puObject *fgPopup::getKeyObject(puObject *object, int key)
165 {
166     puObject *ret;
167     if(object->getType() & PUCLASS_GROUP)
168         for (puObject *obj = ((puGroup *)object)->getFirstChild();
169                 obj; obj = obj->getNextObject())
170             if ((ret = getKeyObject(obj, key)))
171                 return ret;
172
173     GUIInfo *info = (GUIInfo *)object->getUserData();
174     if (info && info->key == key)
175         return object;
176
177     return 0;
178 }
179
180 puObject *fgPopup::getActiveInputField(puObject *object)
181 {
182     puObject *ret;
183     if(object->getType() & PUCLASS_GROUP)
184         for (puObject *obj = ((puGroup *)object)->getFirstChild();
185                 obj; obj = obj->getNextObject())
186             if ((ret = getActiveInputField(obj)))
187                 return ret;
188
189     if (object->getType() & PUCLASS_INPUT && ((puInput *)object)->isAcceptingInput())
190         return object;
191
192     return 0;
193 }
194
195 /**
196  * Mouse handler.
197  */
198 int fgPopup::checkHit(int button, int updown, int x, int y)
199 {
200     int 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
215         int hit = getHitObjects(this, x, y);
216         if(hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT))
217             return result;
218
219         int px, py;
220         getPosition(&px, &py);
221         _dragging = true;
222         _dX = px - x;
223         _dY = py - y;
224     } else if(updown == PU_DRAG && _dragging) {
225         setPosition(x + _dX, y + _dY);
226     } else {
227         _dragging = false;
228     }
229     return result;
230 }
231
232 int fgPopup::getHitObjects(puObject *object, int x, int y)
233 {
234     if (!object->isVisible())
235         return 0;
236
237     int type = 0;
238     if(object->getType() & PUCLASS_GROUP)
239         for (puObject *obj = ((puGroup *)object)->getFirstChild();
240                 obj; obj = obj->getNextObject())
241             type |= getHitObjects(obj, x, y);
242
243     int cx, cy, cw, ch;
244     object->getAbsolutePosition(&cx, &cy);
245     object->getSize(&cw, &ch);
246     if(x >= cx && x < cx + cw && y >= cy && y < cy + ch)
247         type |= object->getType();
248     return type;
249 }
250
251
252 \f
253 ////////////////////////////////////////////////////////////////////////
254 // Callbacks.
255 ////////////////////////////////////////////////////////////////////////
256
257 /**
258  * Action callback.
259  */
260 static void
261 action_callback (puObject * object)
262 {
263     GUIInfo * info = (GUIInfo *)object->getUserData();
264     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
265     gui->setActiveDialog(info->dialog);
266     int nBindings = info->bindings.size();
267     for (int i = 0; i < nBindings; i++) {
268         info->bindings[i]->fire();
269         if (gui->getActiveDialog() == 0)
270             break;
271     }
272     gui->setActiveDialog(0);
273 }
274
275
276 \f
277 ////////////////////////////////////////////////////////////////////////
278 // Static helper functions.
279 ////////////////////////////////////////////////////////////////////////
280
281 /**
282  * Copy a property value to a PUI object.
283  */
284 static void
285 copy_to_pui (SGPropertyNode * node, puObject * object)
286 {
287     // Treat puText objects specially, so their "values" can be set
288     // from properties.
289     if (object->getType() & PUCLASS_TEXT) {
290         GUIInfo *info = (GUIInfo *)object->getUserData();
291         if (info && info->fmt_string) {
292             object->setLabel(info->format(node));
293         } else {
294             object->setLabel(node->getStringValue());
295         }
296         return;
297     }
298
299     switch (node->getType()) {
300     case SGPropertyNode::BOOL:
301     case SGPropertyNode::INT:
302     case SGPropertyNode::LONG:
303         object->setValue(node->getIntValue());
304         break;
305     case SGPropertyNode::FLOAT:
306     case SGPropertyNode::DOUBLE:
307         object->setValue(node->getFloatValue());
308         break;
309     default:
310         object->setValue(node->getStringValue());
311         break;
312     }
313 }
314
315
316 static void
317 copy_from_pui (puObject * object, SGPropertyNode * node)
318 {
319     // puText objects are immutable, so should not be copied out
320     if (object->getType() & PUCLASS_TEXT)
321         return;
322
323     switch (node->getType()) {
324     case SGPropertyNode::BOOL:
325     case SGPropertyNode::INT:
326     case SGPropertyNode::LONG:
327         node->setIntValue(object->getIntegerValue());
328         break;
329     case SGPropertyNode::FLOAT:
330     case SGPropertyNode::DOUBLE:
331         node->setFloatValue(object->getFloatValue());
332         break;
333     default:
334         // Special case to handle lists, as getStringValue cannot be overridden
335         if(object->getType() & PUCLASS_LIST)
336         {
337             const char *s = ((puList *) object)->getListStringValue();
338             if (s)
339                 node->setStringValue(s);
340         }
341         else
342         {
343             node->setStringValue(object->getStringValue());
344         }
345         break;
346     }
347 }
348
349
350 \f
351 ////////////////////////////////////////////////////////////////////////
352 // Implementation of FGDialog.
353 ////////////////////////////////////////////////////////////////////////
354
355 FGDialog::FGDialog (SGPropertyNode * props)
356     : _object(0),
357       _gui((NewGUI *)globals->get_subsystem("gui")),
358       _props(props)
359 {
360     _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
361     SGPropertyNode *nasal = props->getNode("nasal");
362     if (nasal) {
363         _nasal_close = nasal->getNode("close");
364         SGPropertyNode *open = nasal->getNode("open");
365         if (open) {
366             const char *s = open->getStringValue();
367             FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
368             nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
369         }
370     }
371     display(props);
372 }
373
374 FGDialog::~FGDialog ()
375 {
376     int x, y;
377     _object->getAbsolutePosition(&x, &y);
378     _props->setIntValue("lastx", x);
379     _props->setIntValue("lasty", y);
380
381     FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
382     if (_nasal_close) {
383         const char *s = _nasal_close->getStringValue();
384         nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
385     }
386     nas->deleteModule(_module.c_str());
387
388     puDeleteObject(_object);
389
390     unsigned int i;
391                                 // Delete all the info objects we
392                                 // were forced to keep around because
393                                 // PUI cannot delete its own user data.
394     for (i = 0; i < _info.size(); i++) {
395         delete (GUIInfo *)_info[i];
396         _info[i] = 0;
397     }
398                                 // Finally, delete the property links.
399     for (i = 0; i < _propertyObjects.size(); i++) {
400         delete _propertyObjects[i];
401         _propertyObjects[i] = 0;
402     }
403 }
404
405 void
406 FGDialog::updateValues (const char * objectName)
407 {
408     if (objectName && !objectName[0])
409         objectName = 0;
410
411     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
412         const string &name = _propertyObjects[i]->name;
413         if (objectName && name != objectName)
414             continue;
415
416         puObject *obj = _propertyObjects[i]->object;
417         if ((obj->getType() & PUCLASS_LIST) && (dynamic_cast<GUI_ID *>(obj)->id & FGCLASS_LIST)) {
418             fgList *pl = static_cast<fgList *>(obj);
419             pl->update();
420         } else
421             copy_to_pui(_propertyObjects[i]->node, obj);
422     }
423 }
424
425 void
426 FGDialog::applyValues (const char * objectName)
427 {
428     if (objectName && !objectName[0])
429         objectName = 0;
430
431     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
432         const string &name = _propertyObjects[i]->name;
433         if (objectName && name != objectName)
434             continue;
435
436         copy_from_pui(_propertyObjects[i]->object,
437                       _propertyObjects[i]->node);
438     }
439 }
440
441 void
442 FGDialog::update ()
443 {
444     for (unsigned int i = 0; i < _liveObjects.size(); i++) {
445         puObject *obj = _liveObjects[i]->object;
446         if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
447             continue;
448
449         copy_to_pui(_liveObjects[i]->node, obj);
450     }
451 }
452
453 void
454 FGDialog::display (SGPropertyNode * props)
455 {
456     if (_object != 0) {
457         SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
458         return;
459     }
460
461     int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
462     int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
463
464     bool userx = props->hasValue("x");
465     bool usery = props->hasValue("y");
466     bool userw = props->hasValue("width");
467     bool userh = props->hasValue("height");
468
469     // Let the layout widget work in the same property subtree.
470     LayoutWidget wid(props);
471
472     SGPropertyNode *fontnode = props->getNode("font");
473     if (fontnode) {
474         FGFontCache *fc = globals->get_fontcache();
475         _font = fc->get(fontnode);
476     } else {
477         _font = _gui->getDefaultFont();
478     }
479     wid.setDefaultFont(_font, int(_font->getPointSize()));
480
481     int pw=0, ph=0;
482     int px, py, savex, savey;
483     if(!userw || !userh)
484         wid.calcPrefSize(&pw, &ph);
485     pw = props->getIntValue("width", pw);
486     ph = props->getIntValue("height", ph);
487     px = savex = props->getIntValue("x", (screenw - pw) / 2);
488     py = savey = props->getIntValue("y", (screenh - ph) / 2);
489
490     // Negative x/y coordinates are interpreted as distance from the top/right
491     // corner rather than bottom/left.
492     if (userx && px < 0)
493         px = screenw - pw + px;
494     if (usery && py < 0)
495         py = screenh - ph + py;
496
497     // Define "x", "y", "width" and/or "height" in the property tree if they
498     // are not specified in the configuration file.
499     wid.layout(px, py, pw, ph);
500
501     // Use the dimension and location properties as specified in the
502     // configuration file or from the layout widget.
503     _object = makeObject(props, screenw, screenh);
504
505     // Remove automatically generated properties, so the layout looks
506     // the same next time around, or restore x and y to preserve negative coords.
507     if(userx)
508         props->setIntValue("x", savex);
509     else
510         props->removeChild("x");
511
512     if(usery)
513         props->setIntValue("y", savey);
514     else
515         props->removeChild("y");
516
517     if(!userw) props->removeChild("width");
518     if(!userh) props->removeChild("height");
519
520     if (_object != 0) {
521         _object->reveal();
522     } else {
523         SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
524                << props->getStringValue("name", "[unnamed]")
525                << " does not contain a proper GUI definition");
526     }
527 }
528
529 puObject *
530 FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
531 {
532     if (props->getBoolValue("hide"))
533         return 0;
534
535     bool presetSize = props->hasValue("width") && props->hasValue("height");
536     int width = props->getIntValue("width", parentWidth);
537     int height = props->getIntValue("height", parentHeight);
538     int x = props->getIntValue("x", (parentWidth - width) / 2);
539     int y = props->getIntValue("y", (parentHeight - height) / 2);
540     string type = props->getName();
541
542     if (type.empty())
543         type = "dialog";
544
545     if (type == "dialog") {
546         puPopup * obj;
547         bool draggable = props->getBoolValue("draggable", true);
548         if (props->getBoolValue("modal", false))
549             obj = new puDialogBox(x, y);
550         else
551             obj = new fgPopup(x, y, draggable);
552         setupGroup(obj, props, width, height, true);
553         setColor(obj, props);
554         return obj;
555
556     } else if (type == "group") {
557         puGroup * obj = new puGroup(x, y);
558         setupGroup(obj, props, width, height, false);
559         setColor(obj, props);
560         return obj;
561
562     } else if (type == "frame") {
563         puGroup * obj = new puGroup(x, y);
564         setupGroup(obj, props, width, height, true);
565         setColor(obj, props);
566         return obj;
567
568     } else if (type == "hrule" || type == "vrule") {
569         puFrame * obj = new puFrame(x, y, x + width, y + height);
570         obj->setBorderThickness(0);
571         setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
572         return obj;
573
574     } else if (type == "list") {
575         int slider_width = props->getIntValue("slider", 20);
576         fgList * obj = new fgList(x, y, x + width, y + height, props, slider_width);
577         if (presetSize)
578             obj->setSize(width, height);
579         setupObject(obj, props);
580         setColor(obj, props);
581         return obj;
582
583     } else if (type == "airport-list") {
584         AirportList * obj = new AirportList(x, y, x + width, y + height);
585         if (presetSize)
586             obj->setSize(width, height);
587         setupObject(obj, props);
588         setColor(obj, props);
589         return obj;
590
591     } else if (type == "property-list") {
592         PropertyList * obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
593         if (presetSize)
594             obj->setSize(width, height);
595         setupObject(obj, props);
596         setColor(obj, props);
597         return obj;
598
599     } else if (type == "input") {
600         puInput * obj = new puInput(x, y, x + width, y + height);
601         setupObject(obj, props);
602         setColor(obj, props, FOREGROUND|LABEL);
603         return obj;
604
605     } else if (type == "text") {
606         puText * obj = new puText(x, y);
607         setupObject(obj, props);
608
609         // Layed-out objects need their size set, and non-layout ones
610         // get a different placement.
611         if (presetSize)
612             obj->setSize(width, height);
613         else
614             obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
615         setColor(obj, props, LABEL);
616         return obj;
617
618     } else if (type == "checkbox") {
619         puButton * obj;
620         obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
621         setupObject(obj, props);
622         setColor(obj, props, FOREGROUND|LABEL);
623         return obj;
624
625     } else if (type == "radio") {
626         puButton * obj;
627         obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
628         setupObject(obj, props);
629         setColor(obj, props, FOREGROUND|LABEL);
630         return obj;
631
632     } else if (type == "button") {
633         puButton * obj;
634         const char * legend = props->getStringValue("legend", "[none]");
635         if (props->getBoolValue("one-shot", true))
636             obj = new puOneShot(x, y, legend);
637         else
638             obj = new puButton(x, y, legend);
639         if (presetSize)
640             obj->setSize(width, height);
641         setupObject(obj, props);
642         setColor(obj, props);
643         return obj;
644
645     } else if (type == "combo") {
646         fgComboBox * obj = new fgComboBox(x, y, x + width, y + height, props,
647                            props->getBoolValue("editable", false));
648         setupObject(obj, props);
649 #ifdef PUCOL_EDITFIELD  // plib > 0.8.4
650         setColor(obj, props, EDITFIELD);
651 #else
652         setColor(obj, props, FOREGROUND|LABEL);
653 #endif
654         return obj;
655
656     } else if (type == "slider") {
657         bool vertical = props->getBoolValue("vertical", false);
658         puSlider * obj = new puSlider(x, y, (vertical ? height : width));
659         obj->setMinValue(props->getFloatValue("min", 0.0));
660         obj->setMaxValue(props->getFloatValue("max", 1.0));
661         setupObject(obj, props);
662         if (presetSize)
663             obj->setSize(width, height);
664         setColor(obj, props, FOREGROUND|LABEL);
665         return obj;
666
667     } else if (type == "dial") {
668         puDial * obj = new puDial(x, y, width);
669         obj->setMinValue(props->getFloatValue("min", 0.0));
670         obj->setMaxValue(props->getFloatValue("max", 1.0));
671         obj->setWrap(props->getBoolValue("wrap", true));
672         setupObject(obj, props);
673         setColor(obj, props, FOREGROUND|LABEL);
674         return obj;
675
676     } else if (type == "textbox") {
677         int slider_width = props->getIntValue("slider", 20);
678         int wrap = props->getBoolValue("wrap", true);
679         puaLargeInput * obj = new puaLargeInput(x, y,
680                 x+width, x+height, 2, slider_width, wrap);
681
682         if (props->hasValue("editable")) {
683             if (props->getBoolValue("editable")==false)
684                 obj->disableInput();
685             else
686                 obj->enableInput();
687         }
688         if (presetSize)
689             obj->setSize(width, height);
690         setupObject(obj, props);
691         setColor(obj, props, FOREGROUND|LABEL);
692         return obj;
693
694     } else if (type == "select") {
695         fgSelectBox * obj = new fgSelectBox(x, y, x + width, y + height, props);
696         setupObject(obj, props);
697 #ifdef PUCOL_EDITFIELD  // plib > 0.8.4
698         setColor(obj, props, EDITFIELD);
699 #else
700         setColor(obj, props, FOREGROUND|LABEL);
701 #endif
702         return obj;
703     } else {
704         return 0;
705     }
706 }
707
708 void
709 FGDialog::setupObject (puObject * object, SGPropertyNode * props)
710 {
711     string type = props->getName();
712     object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
713
714     if (props->hasValue("legend"))
715         object->setLegend(props->getStringValue("legend"));
716
717     if (props->hasValue("label"))
718         object->setLabel(props->getStringValue("label"));
719
720     if (props->hasValue("border"))
721         object->setBorderThickness( props->getIntValue("border", 2) );
722
723     if ( SGPropertyNode *nft = props->getNode("font", false) ) {
724        FGFontCache *fc = globals->get_fontcache();
725        puFont *lfnt = fc->get(nft);
726        object->setLabelFont(*lfnt);
727     } else {
728        object->setLabelFont(*_font);
729     }
730
731     if (props->hasValue("property")) {
732         const char * name = props->getStringValue("name");
733         if (name == 0)
734             name = "";
735         const char * propname = props->getStringValue("property");
736         SGPropertyNode_ptr node = fgGetNode(propname, true);
737         copy_to_pui(node, object);
738
739         PropertyObject* po = new PropertyObject(name, object, node);
740         _propertyObjects.push_back(po);
741         if(props->getBoolValue("live"))
742             _liveObjects.push_back(po);
743     }
744
745     SGPropertyNode * dest = fgGetNode("/sim/bindings/gui", true);
746     vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
747     if (type == "text" || bindings.size() > 0) {
748         GUIInfo * info = new GUIInfo(this);
749         info->key = props->getIntValue("keynum", -1);
750         if (props->hasValue("key"))
751             info->key = getKeyCode(props->getStringValue("key", ""));
752
753         for (unsigned int i = 0; i < bindings.size(); i++) {
754             unsigned int j = 0;
755             SGPropertyNode *binding;
756             while (dest->getChild("binding", j))
757                 j++;
758
759             const char *cmd = bindings[i]->getStringValue("command");
760             if (!strcmp(cmd, "nasal"))
761                 bindings[i]->setStringValue("module", _module.c_str());
762
763             binding = dest->getChild("binding", j, true);
764             copyProperties(bindings[i], binding);
765             info->bindings.push_back(new SGBinding(binding, globals->get_props()));
766         }
767         object->setCallback(action_callback);
768
769         if (type == "input" && props->getBoolValue("live"))
770             object->setDownCallback(action_callback);
771
772         if (type == "text") {
773             const char *format = props->getStringValue("format", 0);
774             if (format) {
775                 info->fmt_type = validate_format(props->getStringValue("format", 0));
776                 if (info->fmt_type != f_INVALID) {
777                     info->text = new char[FORMAT_BUFSIZE + 1];
778                     info->fmt_string = new char[strlen(format) + 1];
779                     strcpy(info->fmt_string, format);
780                 }
781             }
782         }
783
784         object->setUserData(info);
785         _info.push_back(info);
786     }
787
788     object->makeReturnDefault(props->getBoolValue("default"));
789 }
790
791 void
792 FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
793                     int width, int height, bool makeFrame)
794 {
795     setupObject(group, props);
796
797     if (makeFrame) {
798         puFrame* f = new puFrame(0, 0, width, height);
799         setColor(f, props);
800     }
801
802     int nChildren = props->nChildren();
803     for (int i = 0; i < nChildren; i++)
804         makeObject(props->getChild(i), width, height);
805     group->close();
806 }
807
808 void
809 FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
810 {
811     string type = props->getName();
812     if (type.empty())
813         type = "dialog";
814     if (type == "textbox" && props->getBoolValue("editable"))
815         type += "-editable";
816
817     FGColor *c = new FGColor(_gui->getColor("background"));
818     c->merge(_gui->getColor(type));
819     c->merge(props->getNode("color"));
820     if (c->isValid())
821         object->setColourScheme(c->red(), c->green(), c->blue(), c->alpha());
822
823     const struct {
824         int mask;
825         int id;
826         const char *name;
827         const char *cname;
828     } pucol[] = {
829         { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
830         { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
831         { HIGHLIGHT,  PUCOL_HIGHLIGHT,  "highlight",  "color-highlight" },
832         { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
833         { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
834         { MISC,       PUCOL_MISC,       "misc",       "color-misc" },
835 #ifdef PUCOL_EDITFIELD  // plib > 0.8.4
836         { EDITFIELD,  PUCOL_EDITFIELD,  "editfield",  "color-editfield" },
837 #endif
838     };
839
840     const int numcol = sizeof(pucol) / sizeof(pucol[0]);
841
842     for (int i = 0; i < numcol; i++) {
843         bool dirty = false;
844         c->clear();
845         c->setAlpha(1.0);
846
847         dirty |= c->merge(_gui->getColor(type + '-' + pucol[i].name));
848         if (which & pucol[i].mask)
849             dirty |= c->merge(props->getNode("color"));
850
851         if ((pucol[i].mask == LABEL) && !c->isValid())
852             dirty |= c->merge(_gui->getColor("label"));
853
854         dirty |= c->merge(props->getNode(pucol[i].cname));
855
856         if (c->isValid() && dirty)
857             object->setColor(pucol[i].id, c->red(), c->green(), c->blue(), c->alpha());
858     }
859 }
860
861
862 static struct {
863     const char *name;
864     int key;
865 } keymap[] = {
866     {"backspace", 8},
867     {"tab", 9},
868     {"return", 13},
869     {"enter", 13},
870     {"esc", 27},
871     {"escape", 27},
872     {"space", ' '},
873     {"&amp;", '&'},
874     {"and", '&'},
875     {"&lt;", '<'},
876     {"&gt;", '>'},
877     {"f1", PU_KEY_F1},
878     {"f2", PU_KEY_F2},
879     {"f3", PU_KEY_F3},
880     {"f4", PU_KEY_F4},
881     {"f5", PU_KEY_F5},
882     {"f6", PU_KEY_F6},
883     {"f7", PU_KEY_F7},
884     {"f8", PU_KEY_F8},
885     {"f9", PU_KEY_F9},
886     {"f10", PU_KEY_F10},
887     {"f11", PU_KEY_F11},
888     {"f12", PU_KEY_F12},
889     {"left", PU_KEY_LEFT},
890     {"up", PU_KEY_UP},
891     {"right", PU_KEY_RIGHT},
892     {"down", PU_KEY_DOWN},
893     {"pageup", PU_KEY_PAGE_UP},
894     {"pagedn", PU_KEY_PAGE_DOWN},
895     {"home", PU_KEY_HOME},
896     {"end", PU_KEY_END},
897     {"insert", PU_KEY_INSERT},
898     {0, -1},
899 };
900
901 int
902 FGDialog::getKeyCode(const char *str)
903 {
904     enum {
905         CTRL = 0x1,
906         SHIFT = 0x2,
907         ALT = 0x4,
908     };
909
910     while (*str == ' ')
911         str++;
912
913     char *buf = new char[strlen(str) + 1];
914     strcpy(buf, str);
915     char *s = buf + strlen(buf);
916     while (s > str && s[-1] == ' ')
917         s--;
918     *s = 0;
919     s = buf;
920
921     int mod = 0;
922     while (1) {
923         if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
924             s += 5, mod |= CTRL;
925         else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
926             s += 6, mod |= SHIFT;
927         else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
928             s += 4, mod |= ALT;
929         else
930             break;
931     }
932
933     int key = -1;
934     if (strlen(s) == 1 && isascii(*s)) {
935         key = *s;
936         if (mod & SHIFT)
937             key = toupper(key);
938         if (mod & CTRL)
939             key = toupper(key) - 64;
940         if (mod & ALT)
941             ;   // Alt not propagated to the gui
942     } else {
943         for (char *t = s; *t; t++)
944             *t = tolower(*t);
945         for (int i = 0; keymap[i].name; i++) {
946             if (!strcmp(s, keymap[i].name)) {
947                 key = keymap[i].key;
948                 break;
949             }
950         }
951     }
952     delete[] buf;
953     return key;
954 }
955
956
957 \f
958 ////////////////////////////////////////////////////////////////////////
959 // Implementation of FGDialog::PropertyObject.
960 ////////////////////////////////////////////////////////////////////////
961
962 FGDialog::PropertyObject::PropertyObject (const char * n,
963                                            puObject * o,
964                                            SGPropertyNode_ptr p)
965     : name(n),
966       object(o),
967       node(p)
968 {
969 }
970
971
972
973 \f
974 ////////////////////////////////////////////////////////////////////////
975 // Implementation of fgValueList and derived pui widgets
976 ////////////////////////////////////////////////////////////////////////
977
978
979 fgValueList::fgValueList(SGPropertyNode *p) :
980     _props(p)
981 {
982     make_list();
983 }
984
985 void
986 fgValueList::update()
987 {
988     destroy_list();
989     make_list();
990 }
991
992 fgValueList::~fgValueList()
993 {
994     destroy_list();
995 }
996
997 void
998 fgValueList::make_list()
999 {
1000     vector<SGPropertyNode_ptr> value_nodes = _props->getChildren("value");
1001     _list = new char *[value_nodes.size() + 1];
1002     unsigned int i;
1003     for (i = 0; i < value_nodes.size(); i++)
1004         _list[i] = strdup((char *)value_nodes[i]->getStringValue());
1005     _list[i] = 0;
1006 }
1007
1008 void
1009 fgValueList::destroy_list()
1010 {
1011     for (int i = 0; _list[i] != 0; i++)
1012         if (_list[i])
1013             free(_list[i]);
1014     delete[] _list;
1015 }
1016
1017
1018
1019 void
1020 fgList::update()
1021 {
1022     fgValueList::update();
1023     newList(_list);
1024 }
1025
1026 // end of dialog.cxx