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