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