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