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