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