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