]> git.mxchange.org Git - flightgear.git/blob - src/GUI/dialog.cxx
Merge branch 'ehofman/generic'
[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 <simgear/structure/SGBinding.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 #include "WaypointList.hxx"
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             int posX = x + _dlgX - _startX,
299               posY = y + _dlgY - _startY;
300             setPosition(posX, posY);
301             
302             GUIInfo *info = (GUIInfo *)getUserData();
303             if (info && info->node) {
304                 info->node->setIntValue("x", posX);
305                 info->node->setIntValue("y", posY);
306             }
307         } // re-positioning
308
309     } else if (_dragging) {
310         fgSetMouseCursor(_start_cursor);
311         _dragging = false;
312     }
313     return result;
314 }
315
316 int fgPopup::getHitObjects(puObject *object, int x, int y)
317 {
318     if (!object->isVisible())
319         return 0;
320
321     int type = 0;
322     if (object->getType() & PUCLASS_GROUP)
323         for (puObject *obj = ((puGroup *)object)->getFirstChild();
324                 obj; obj = obj->getNextObject())
325             type |= getHitObjects(obj, x, y);
326
327     int cx, cy, cw, ch;
328     object->getAbsolutePosition(&cx, &cy);
329     object->getSize(&cw, &ch);
330     if (x >= cx && x < cx + cw && y >= cy && y < cy + ch)
331         type |= object->getType();
332     return type;
333 }
334
335 void fgPopup::applySize(puObject *object)
336 {
337     // compound plib widgets use setUserData() for internal purposes, so refuse
338     // to descend into anything that has other bits set than the following
339     const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
340             |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
341             |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
342
343     int type = object->getType();
344     if (type & PUCLASS_GROUP && !(type & ~validUserData))
345         for (puObject *obj = ((puGroup *)object)->getFirstChild();
346                 obj; obj = obj->getNextObject())
347             applySize(obj);
348
349     GUIInfo *info = (GUIInfo *)object->getUserData();
350     if (!info)
351         return;
352
353     SGPropertyNode *n = info->node;
354     if (!n) {
355         SG_LOG(SG_GENERAL, SG_ALERT, "fgPopup::applySize: no props");
356         return;
357     }
358     int x = n->getIntValue("x");
359     int y = n->getIntValue("y");
360     int w = n->getIntValue("width", 4);
361     int h = n->getIntValue("height", 4);
362     object->setPosition(x, y);
363     object->setSize(w, h);
364 }
365
366 ////////////////////////////////////////////////////////////////////////
367
368 void FGDialog::ConditionalObject::update(FGDialog* aDlg)
369 {
370   if (_name == "visible") {
371     bool wasVis = _pu->isVisible();
372     bool newVis = test();
373     
374     if (newVis == wasVis) {
375       return;
376     }
377     
378     if (newVis) { // puObject needs a setVisible. Oh well.
379     _pu->reveal();
380     } else {
381       _pu->hide();
382     }
383   } else if (_name == "enable") {
384     bool wasEnabled = _pu->isActive();
385     bool newEnable = test();
386     
387     if (wasEnabled == newEnable) {
388       return;
389     }
390     
391     if (newEnable) {
392       _pu->activate();
393     } else {
394       _pu->greyOut();
395     }
396   }
397   
398   aDlg->setNeedsLayout();
399 }
400 \f////////////////////////////////////////////////////////////////////////
401 // Callbacks.
402 ////////////////////////////////////////////////////////////////////////
403
404 /**
405  * Action callback.
406  */
407 static void
408 action_callback (puObject *object)
409 {
410     GUIInfo *info = (GUIInfo *)object->getUserData();
411     NewGUI *gui = (NewGUI *)globals->get_subsystem("gui");
412     gui->setActiveDialog(info->dialog);
413     int nBindings = info->bindings.size();
414     for (int i = 0; i < nBindings; i++) {
415         info->bindings[i]->fire();
416         if (gui->getActiveDialog() == 0)
417             break;
418     }
419     gui->setActiveDialog(0);
420 }
421
422
423 \f
424 ////////////////////////////////////////////////////////////////////////
425 // Static helper functions.
426 ////////////////////////////////////////////////////////////////////////
427
428 /**
429  * Copy a property value to a PUI object.
430  */
431 static void
432 copy_to_pui (SGPropertyNode *node, puObject *object)
433 {
434     using namespace simgear;
435     GUIInfo *info = (GUIInfo *)object->getUserData();
436     if (!info) {
437         SG_LOG(SG_GENERAL, SG_ALERT, "dialog: widget without GUIInfo!");
438         return;   // this can't really happen
439     }
440
441     // Treat puText objects specially, so their "values" can be set
442     // from properties.
443     if (object->getType() & PUCLASS_TEXT) {
444         if (info->fmt_type != f_INVALID)
445             info->apply_format(node);
446         else
447             info->text = node->getStringValue();
448
449         object->setLabel(info->text.c_str());
450         return;
451     }
452
453     switch (node->getType()) {
454     case props::BOOL:
455     case props::INT:
456     case props::LONG:
457         object->setValue(node->getIntValue());
458         break;
459     case props::FLOAT:
460     case props::DOUBLE:
461         object->setValue(node->getFloatValue());
462         break;
463     default:
464         info->text = node->getStringValue();
465         object->setValue(info->text.c_str());
466         break;
467     }
468 }
469
470
471 static void
472 copy_from_pui (puObject *object, SGPropertyNode *node)
473 {
474     using namespace simgear;
475     // puText objects are immutable, so should not be copied out
476     if (object->getType() & PUCLASS_TEXT)
477         return;
478
479     switch (node->getType()) {
480     case props::BOOL:
481     case props::INT:
482     case props::LONG:
483         node->setIntValue(object->getIntegerValue());
484         break;
485     case props::FLOAT:
486     case props::DOUBLE:
487         node->setFloatValue(object->getFloatValue());
488         break;
489     default:
490         const char *s = object->getStringValue();
491         if (s)
492             node->setStringValue(s);
493         break;
494     }
495 }
496
497
498 \f
499 ////////////////////////////////////////////////////////////////////////
500 // Implementation of FGDialog.
501 ////////////////////////////////////////////////////////////////////////
502
503 FGDialog::FGDialog (SGPropertyNode *props) :
504     _object(0),
505     _gui((NewGUI *)globals->get_subsystem("gui")),
506     _props(props),
507     _needsRelayout(false)
508 {
509     _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
510         
511     SGPropertyNode *nasal = props->getNode("nasal");
512     if (nasal) {
513         _nasal_close = nasal->getNode("close");
514         SGPropertyNode *open = nasal->getNode("open");
515         if (open) {
516             const char *s = open->getStringValue();
517             FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
518             if (nas)
519                 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
520         }
521     }
522     display(props);
523 }
524
525 FGDialog::~FGDialog ()
526 {
527     int x, y;
528     _object->getAbsolutePosition(&x, &y);
529     _props->setIntValue("lastx", x);
530     _props->setIntValue("lasty", y);
531
532     FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
533     if (nas) {
534         if (_nasal_close) {
535             const char *s = _nasal_close->getStringValue();
536             nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
537         }
538         nas->deleteModule(_module.c_str());
539     }
540
541     puDeleteObject(_object);
542
543     unsigned int i;
544                                 // Delete all the info objects we
545                                 // were forced to keep around because
546                                 // PUI cannot delete its own user data.
547     for (i = 0; i < _info.size(); i++) {
548         delete (GUIInfo *)_info[i];
549         _info[i] = 0;
550     }
551                                 // Finally, delete the property links.
552     for (i = 0; i < _propertyObjects.size(); i++) {
553         delete _propertyObjects[i];
554         _propertyObjects[i] = 0;
555     }
556 }
557
558 void
559 FGDialog::updateValues (const char *objectName)
560 {
561     if (objectName && !objectName[0])
562         objectName = 0;
563
564   for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
565     const string &name = _propertyObjects[i]->name;
566     if (objectName && name != objectName) {
567       continue;
568     }
569     
570     puObject *widget = _propertyObjects[i]->object;
571     int widgetType = widget->getType();
572     if (widgetType & PUCLASS_LIST) {
573       GUI_ID* gui_id = dynamic_cast<GUI_ID *>(widget);
574       if (gui_id && (gui_id->id & FGCLASS_LIST)) {
575         fgList *pl = static_cast<fgList*>(widget);
576         pl->update();
577       } else {
578         copy_to_pui(_propertyObjects[i]->node, widget);
579       }
580     } else if (widgetType & PUCLASS_COMBOBOX) {
581       fgComboBox* combo = static_cast<fgComboBox*>(widget);
582       combo->update();
583     } else {
584       copy_to_pui(_propertyObjects[i]->node, widget);
585     }
586   } // of property objects iteration
587 }
588
589 void
590 FGDialog::applyValues (const char *objectName)
591 {
592     if (objectName && !objectName[0])
593         objectName = 0;
594
595     for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
596         const string &name = _propertyObjects[i]->name;
597         if (objectName && name != objectName)
598             continue;
599
600         copy_from_pui(_propertyObjects[i]->object,
601                       _propertyObjects[i]->node);
602     }
603 }
604
605 void
606 FGDialog::update ()
607 {
608     for (unsigned int i = 0; i < _liveObjects.size(); i++) {
609         puObject *obj = _liveObjects[i]->object;
610         if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
611             continue;
612
613         copy_to_pui(_liveObjects[i]->node, obj);
614     }
615     
616   for (unsigned int j=0; j < _conditionalObjects.size(); ++j) {
617     _conditionalObjects[j]->update(this);
618   }
619   
620   if (_needsRelayout) {
621     relayout();
622   }
623 }
624
625 void
626 FGDialog::display (SGPropertyNode *props)
627 {
628     if (_object != 0) {
629         SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
630         return;
631     }
632
633     int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
634     int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
635
636     bool userx = props->hasValue("x");
637     bool usery = props->hasValue("y");
638     bool userw = props->hasValue("width");
639     bool userh = props->hasValue("height");
640
641     // Let the layout widget work in the same property subtree.
642     LayoutWidget wid(props);
643
644     SGPropertyNode *fontnode = props->getNode("font");
645     if (fontnode) {
646         FGFontCache *fc = globals->get_fontcache();
647         _font = fc->get(fontnode);
648     } else {
649         _font = _gui->getDefaultFont();
650     }
651     wid.setDefaultFont(_font, int(_font->getPointSize()));
652
653     int pw = 0, ph = 0;
654     int px, py, savex, savey;
655     if (!userw || !userh)
656         wid.calcPrefSize(&pw, &ph);
657     pw = props->getIntValue("width", pw);
658     ph = props->getIntValue("height", ph);
659     px = savex = props->getIntValue("x", (screenw - pw) / 2);
660     py = savey = props->getIntValue("y", (screenh - ph) / 2);
661
662     // Negative x/y coordinates are interpreted as distance from the top/right
663     // corner rather than bottom/left.
664     if (userx && px < 0)
665         px = screenw - pw + px;
666     if (usery && py < 0)
667         py = screenh - ph + py;
668
669     // Define "x", "y", "width" and/or "height" in the property tree if they
670     // are not specified in the configuration file.
671     wid.layout(px, py, pw, ph);
672
673     // Use the dimension and location properties as specified in the
674     // configuration file or from the layout widget.
675     _object = makeObject(props, screenw, screenh);
676
677     // Remove automatically generated properties, so the layout looks
678     // the same next time around, or restore x and y to preserve negative coords.
679     if (userx)
680         props->setIntValue("x", savex);
681     else
682         props->removeChild("x");
683
684     if (usery)
685         props->setIntValue("y", savey);
686     else
687         props->removeChild("y");
688
689     if (!userw) props->removeChild("width");
690     if (!userh) props->removeChild("height");
691
692     if (_object != 0) {
693         _object->reveal();
694     } else {
695         SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
696                << props->getStringValue("name", "[unnamed]")
697                << " does not contain a proper GUI definition");
698     }
699 }
700
701 puObject *
702 FGDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeight)
703 {
704     if (!props->getBoolValue("enabled", true))
705         return 0;
706
707     bool presetSize = props->hasValue("width") && props->hasValue("height");
708     int width = props->getIntValue("width", parentWidth);
709     int height = props->getIntValue("height", parentHeight);
710     int x = props->getIntValue("x", (parentWidth - width) / 2);
711     int y = props->getIntValue("y", (parentHeight - height) / 2);
712     string type = props->getName();
713
714     if (type.empty())
715         type = "dialog";
716
717     if (type == "dialog") {
718         puPopup *obj;
719         bool draggable = props->getBoolValue("draggable", true);
720         bool resizable = props->getBoolValue("resizable", false);
721         if (props->getBoolValue("modal", false))
722             obj = new puDialogBox(x, y);
723         else
724             obj = new fgPopup(x, y, resizable, draggable);
725         setupGroup(obj, props, width, height, true);
726         setColor(obj, props);
727         return obj;
728
729     } else if (type == "group") {
730         puGroup *obj = new puGroup(x, y);
731         setupGroup(obj, props, width, height, false);
732         setColor(obj, props);
733         return obj;
734
735     } else if (type == "frame") {
736         puGroup *obj = new puGroup(x, y);
737         setupGroup(obj, props, width, height, true);
738         setColor(obj, props);
739         return obj;
740
741     } else if (type == "hrule" || type == "vrule") {
742         puFrame *obj = new puFrame(x, y, x + width, y + height);
743         obj->setBorderThickness(0);
744         setupObject(obj, props);
745         setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
746         return obj;
747
748     } else if (type == "list") {
749         int slider_width = props->getIntValue("slider", 20);
750         fgList *obj = new fgList(x, y, x + width, y + height, props, slider_width);
751         if (presetSize)
752             obj->setSize(width, height);
753         setupObject(obj, props);
754         setColor(obj, props);
755         return obj;
756
757     } else if (type == "airport-list") {
758         AirportList *obj = new AirportList(x, y, x + width, y + height);
759         if (presetSize)
760             obj->setSize(width, height);
761         setupObject(obj, props);
762         setColor(obj, props);
763         return obj;
764
765     } else if (type == "property-list") {
766         PropertyList *obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
767         if (presetSize)
768             obj->setSize(width, height);
769         setupObject(obj, props);
770         setColor(obj, props);
771         return obj;
772
773     } else if (type == "input") {
774         puInput *obj = new puInput(x, y, x + width, y + height);
775         setupObject(obj, props);
776         setColor(obj, props, FOREGROUND|LABEL);
777         return obj;
778
779     } else if (type == "text") {
780         puText *obj = new puText(x, y);
781         setupObject(obj, props);
782
783         // Layed-out objects need their size set, and non-layout ones
784         // get a different placement.
785         if (presetSize)
786             obj->setSize(width, height);
787         else
788             obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
789         setColor(obj, props, LABEL);
790         return obj;
791
792     } else if (type == "checkbox") {
793         puButton *obj;
794         obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
795         setupObject(obj, props);
796         setColor(obj, props, FOREGROUND|LABEL);
797         return obj;
798
799     } else if (type == "radio") {
800         puButton *obj;
801         obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
802         setupObject(obj, props);
803         setColor(obj, props, FOREGROUND|LABEL);
804         return obj;
805
806     } else if (type == "button") {
807         puButton *obj;
808         const char *legend = props->getStringValue("legend", "[none]");
809         if (props->getBoolValue("one-shot", true))
810             obj = new puOneShot(x, y, legend);
811         else
812             obj = new puButton(x, y, legend);
813         if (presetSize)
814             obj->setSize(width, height);
815         setupObject(obj, props);
816         setColor(obj, props);
817         return obj;
818
819     } else if (type == "combo") {
820         fgComboBox *obj = new fgComboBox(x, y, x + width, y + height, props,
821                 props->getBoolValue("editable", false));
822         setupObject(obj, props);
823         setColor(obj, props, EDITFIELD);
824         return obj;
825
826     } else if (type == "slider") {
827         bool vertical = props->getBoolValue("vertical", false);
828         puSlider *obj = new puSlider(x, y, (vertical ? height : width), vertical);
829         obj->setMinValue(props->getFloatValue("min", 0.0));
830         obj->setMaxValue(props->getFloatValue("max", 1.0));
831         obj->setStepSize(props->getFloatValue("step"));
832         obj->setSliderFraction(props->getFloatValue("fraction"));
833 #if PLIB_VERSION > 185
834         obj->setPageStepSize(props->getFloatValue("pagestep"));
835 #endif
836         setupObject(obj, props);
837         if (presetSize)
838             obj->setSize(width, height);
839         setColor(obj, props, FOREGROUND|LABEL);
840         return obj;
841
842     } else if (type == "dial") {
843         puDial *obj = new puDial(x, y, width);
844         obj->setMinValue(props->getFloatValue("min", 0.0));
845         obj->setMaxValue(props->getFloatValue("max", 1.0));
846         obj->setWrap(props->getBoolValue("wrap", true));
847         setupObject(obj, props);
848         setColor(obj, props, FOREGROUND|LABEL);
849         return obj;
850
851     } else if (type == "textbox") {
852         int slider_width = props->getIntValue("slider", 20);
853         int wrap = props->getBoolValue("wrap", true);
854 #if PLIB_VERSION > 185
855         puaLargeInput *obj = new puaLargeInput(x, y,
856                 x + width, x + height, 11, slider_width, wrap);
857 #else
858         puaLargeInput *obj = new puaLargeInput(x, y,
859                 x + width, x + height, 2, slider_width, wrap);
860 #endif
861
862         if (props->getBoolValue("editable"))
863             obj->enableInput();
864         else
865             obj->disableInput();
866
867         if (presetSize)
868             obj->setSize(width, height);
869         setupObject(obj, props);
870         setColor(obj, props, FOREGROUND|LABEL);
871
872         int top = props->getIntValue("top-line", 0);
873         obj->setTopLineInWindow(top < 0 ? unsigned(-1) >> 1 : top);
874         return obj;
875
876     } else if (type == "select") {
877         fgSelectBox *obj = new fgSelectBox(x, y, x + width, y + height, props);
878         setupObject(obj, props);
879         setColor(obj, props, EDITFIELD);
880         return obj;
881     } else if (type == "waypointlist") {
882         ScrolledWaypointList* obj = new ScrolledWaypointList(x, y, width, height);
883         setupObject(obj, props);
884         return obj;
885     } else {
886         return 0;
887     }
888 }
889
890 void
891 FGDialog::setupObject (puObject *object, SGPropertyNode *props)
892 {
893     GUIInfo *info = new GUIInfo(this);
894     object->setUserData(info);
895     _info.push_back(info);
896     object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
897     object->makeReturnDefault(props->getBoolValue("default"));
898     info->node = props;
899
900     if (props->hasValue("legend")) {
901         info->legend = props->getStringValue("legend");
902         object->setLegend(info->legend.c_str());
903     }
904
905     if (props->hasValue("label")) {
906         info->label = props->getStringValue("label");
907         object->setLabel(info->label.c_str());
908     }
909
910     if (props->hasValue("border"))
911         object->setBorderThickness( props->getIntValue("border", 2) );
912
913     if (SGPropertyNode *nft = props->getNode("font", false)) {
914        FGFontCache *fc = globals->get_fontcache();
915        puFont *lfnt = fc->get(nft);
916        object->setLabelFont(*lfnt);
917        object->setLegendFont(*lfnt);
918     } else {
919        object->setLabelFont(*_font);
920     }
921
922     if (props->hasChild("visible")) {
923       ConditionalObject* cnd = new ConditionalObject("visible", object);
924       cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("visible")));
925       _conditionalObjects.push_back(cnd);
926     }
927
928     if (props->hasChild("enable")) {
929       ConditionalObject* cnd = new ConditionalObject("enable", object);
930       cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("enable")));
931       _conditionalObjects.push_back(cnd);
932     }
933
934     string type = props->getName();
935     if (type == "input" && props->getBoolValue("live"))
936         object->setDownCallback(action_callback);
937
938     if (type == "text") {
939         const char *format = props->getStringValue("format", 0);
940         if (format) {
941             info->fmt_type = validate_format(format);
942             if (info->fmt_type != f_INVALID)
943                 info->format = format;
944             else
945                 SG_LOG(SG_GENERAL, SG_ALERT, "DIALOG: invalid <format> '"
946                         << format << '\'');
947         }
948     }
949
950     if (props->hasValue("property")) {
951         const char *name = props->getStringValue("name");
952         if (name == 0)
953             name = "";
954         const char *propname = props->getStringValue("property");
955         SGPropertyNode_ptr node = fgGetNode(propname, true);
956         copy_to_pui(node, object);
957
958         PropertyObject *po = new PropertyObject(name, object, node);
959         _propertyObjects.push_back(po);
960         if (props->getBoolValue("live"))
961             _liveObjects.push_back(po);
962     }
963
964     SGPropertyNode *dest = fgGetNode("/sim/bindings/gui", true);
965     vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
966     if (bindings.size() > 0) {
967         info->key = props->getIntValue("keynum", -1);
968         if (props->hasValue("key"))
969             info->key = getKeyCode(props->getStringValue("key", ""));
970
971         for (unsigned int i = 0; i < bindings.size(); i++) {
972             unsigned int j = 0;
973             SGPropertyNode_ptr binding;
974             while (dest->getChild("binding", j))
975                 j++;
976
977             const char *cmd = bindings[i]->getStringValue("command");
978             if (!strcmp(cmd, "nasal"))
979                 bindings[i]->setStringValue("module", _module.c_str());
980
981             binding = dest->getChild("binding", j, true);
982             copyProperties(bindings[i], binding);
983             info->bindings.push_back(new SGBinding(binding, globals->get_props()));
984         }
985         object->setCallback(action_callback);
986     }
987 }
988
989 void
990 FGDialog::setupGroup(puGroup *group, SGPropertyNode *props,
991        int width, int height, bool makeFrame)
992 {
993     setupObject(group, props);
994
995     if (makeFrame) {
996         puFrame* f = new puFrame(0, 0, width, height);
997         setColor(f, props);
998     }
999
1000     int nChildren = props->nChildren();
1001     for (int i = 0; i < nChildren; i++)
1002         makeObject(props->getChild(i), width, height);
1003     group->close();
1004 }
1005
1006 void
1007 FGDialog::setColor(puObject *object, SGPropertyNode *props, int which)
1008 {
1009     string type = props->getName();
1010     if (type.empty())
1011         type = "dialog";
1012     if (type == "textbox" && props->getBoolValue("editable"))
1013         type += "-editable";
1014
1015     FGColor c(_gui->getColor("background"));
1016     c.merge(_gui->getColor(type));
1017     c.merge(props->getNode("color"));
1018     if (c.isValid())
1019         object->setColourScheme(c.red(), c.green(), c.blue(), c.alpha());
1020
1021     const struct {
1022         int mask;
1023         int id;
1024         const char *name;
1025         const char *cname;
1026     } pucol[] = {
1027         { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
1028         { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
1029         { HIGHLIGHT,  PUCOL_HIGHLIGHT,  "highlight",  "color-highlight" },
1030         { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
1031         { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
1032         { MISC,       PUCOL_MISC,       "misc",       "color-misc" },
1033         { EDITFIELD,  PUCOL_EDITFIELD,  "editfield",  "color-editfield" },
1034     };
1035
1036     const int numcol = sizeof(pucol) / sizeof(pucol[0]);
1037
1038     for (int i = 0; i < numcol; i++) {
1039         bool dirty = false;
1040         c.clear();
1041         c.setAlpha(1.0);
1042
1043         dirty |= c.merge(_gui->getColor(type + '-' + pucol[i].name));
1044         if (which & pucol[i].mask)
1045             dirty |= c.merge(props->getNode("color"));
1046
1047         if ((pucol[i].mask == LABEL) && !c.isValid())
1048             dirty |= c.merge(_gui->getColor("label"));
1049
1050         dirty |= c.merge(props->getNode(pucol[i].cname));
1051
1052         if (c.isValid() && dirty)
1053             object->setColor(pucol[i].id, c.red(), c.green(), c.blue(), c.alpha());
1054     }
1055 }
1056
1057
1058 static struct {
1059     const char *name;
1060     int key;
1061 } keymap[] = {
1062     {"backspace", 8},
1063     {"tab", 9},
1064     {"return", 13},
1065     {"enter", 13},
1066     {"esc", 27},
1067     {"escape", 27},
1068     {"space", ' '},
1069     {"&amp;", '&'},
1070     {"and", '&'},
1071     {"&lt;", '<'},
1072     {"&gt;", '>'},
1073     {"f1", PU_KEY_F1},
1074     {"f2", PU_KEY_F2},
1075     {"f3", PU_KEY_F3},
1076     {"f4", PU_KEY_F4},
1077     {"f5", PU_KEY_F5},
1078     {"f6", PU_KEY_F6},
1079     {"f7", PU_KEY_F7},
1080     {"f8", PU_KEY_F8},
1081     {"f9", PU_KEY_F9},
1082     {"f10", PU_KEY_F10},
1083     {"f11", PU_KEY_F11},
1084     {"f12", PU_KEY_F12},
1085     {"left", PU_KEY_LEFT},
1086     {"up", PU_KEY_UP},
1087     {"right", PU_KEY_RIGHT},
1088     {"down", PU_KEY_DOWN},
1089     {"pageup", PU_KEY_PAGE_UP},
1090     {"pagedn", PU_KEY_PAGE_DOWN},
1091     {"home", PU_KEY_HOME},
1092     {"end", PU_KEY_END},
1093     {"insert", PU_KEY_INSERT},
1094     {0, -1},
1095 };
1096
1097 int
1098 FGDialog::getKeyCode(const char *str)
1099 {
1100     enum {
1101         CTRL = 0x1,
1102         SHIFT = 0x2,
1103         ALT = 0x4,
1104     };
1105
1106     while (*str == ' ')
1107         str++;
1108
1109     char *buf = new char[strlen(str) + 1];
1110     strcpy(buf, str);
1111     char *s = buf + strlen(buf);
1112     while (s > str && s[-1] == ' ')
1113         s--;
1114     *s = 0;
1115     s = buf;
1116
1117     int mod = 0;
1118     while (1) {
1119         if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
1120             s += 5, mod |= CTRL;
1121         else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
1122             s += 6, mod |= SHIFT;
1123         else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
1124             s += 4, mod |= ALT;
1125         else
1126             break;
1127     }
1128
1129     int key = -1;
1130     if (strlen(s) == 1 && isascii(*s)) {
1131         key = *s;
1132         if (mod & SHIFT)
1133             key = toupper(key);
1134         if (mod & CTRL)
1135             key = toupper(key) - '@';
1136         /* if (mod & ALT)
1137             ;   // Alt not propagated to the gui
1138         */
1139     } else {
1140         for (char *t = s; *t; t++)
1141             *t = tolower(*t);
1142         for (int i = 0; keymap[i].name; i++) {
1143             if (!strcmp(s, keymap[i].name)) {
1144                 key = keymap[i].key;
1145                 break;
1146             }
1147         }
1148     }
1149     delete[] buf;
1150     return key;
1151 }
1152
1153 void\fFGDialog::relayout()
1154 {
1155   _needsRelayout = false;
1156   
1157   int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
1158   int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
1159
1160   bool userx = _props->hasValue("x");
1161   bool usery = _props->hasValue("y");
1162   bool userw = _props->hasValue("width");
1163   bool userh = _props->hasValue("height");
1164
1165   // Let the layout widget work in the same property subtree.
1166   LayoutWidget wid(_props);
1167   wid.setDefaultFont(_font, int(_font->getPointSize()));
1168
1169   int pw = 0, ph = 0;
1170   int px, py, savex, savey;
1171   if (!userw || !userh) {
1172     wid.calcPrefSize(&pw, &ph);
1173   }
1174   
1175   pw = _props->getIntValue("width", pw);
1176   ph = _props->getIntValue("height", ph);
1177   px = savex = _props->getIntValue("x", (screenw - pw) / 2);
1178   py = savey = _props->getIntValue("y", (screenh - ph) / 2);
1179
1180   // Negative x/y coordinates are interpreted as distance from the top/right
1181   // corner rather than bottom/left.
1182   if (userx && px < 0)
1183     px = screenw - pw + px;
1184   if (usery && py < 0)
1185     py = screenh - ph + py;
1186
1187   // Define "x", "y", "width" and/or "height" in the property tree if they
1188   // are not specified in the configuration file.
1189   wid.layout(px, py, pw, ph);
1190
1191   applySize(_object);
1192   
1193   // Remove automatically generated properties, so the layout looks
1194   // the same next time around, or restore x and y to preserve negative coords.
1195   if (userx)
1196       _props->setIntValue("x", savex);
1197   else
1198       _props->removeChild("x");
1199
1200   if (usery)
1201       _props->setIntValue("y", savey);
1202   else
1203       _props->removeChild("y");
1204
1205   if (!userw) _props->removeChild("width");
1206   if (!userh) _props->removeChild("height");
1207 }
1208
1209 void
1210 FGDialog::applySize(puObject *object)
1211 {
1212   // compound plib widgets use setUserData() for internal purposes, so refuse
1213   // to descend into anything that has other bits set than the following
1214   const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
1215           |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
1216           |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
1217
1218   int type = object->getType();
1219   if ((type & PUCLASS_GROUP) && !(type & ~validUserData)) {
1220     puObject* c = ((puGroup *)object)->getFirstChild();
1221     for (; c != NULL; c = c->getNextObject()) {
1222       applySize(c);
1223     } // of child iteration
1224   } // of group object case
1225   
1226   GUIInfo *info = (GUIInfo *)object->getUserData();
1227   if (!info)
1228     return;
1229
1230   SGPropertyNode *n = info->node;
1231   if (!n) {
1232     SG_LOG(SG_GENERAL, SG_ALERT, "FGDialog::applySize: no props");
1233     return;
1234   }
1235   
1236   int x = n->getIntValue("x");
1237   int y = n->getIntValue("y");
1238   int w = n->getIntValue("width", 4);
1239   int h = n->getIntValue("height", 4);
1240   object->setPosition(x, y);
1241   object->setSize(w, h);
1242 }
1243
1244 ////////////////////////////////////////////////////////////////////////
1245 // Implementation of FGDialog::PropertyObject.
1246 ////////////////////////////////////////////////////////////////////////
1247
1248 FGDialog::PropertyObject::PropertyObject(const char *n,
1249         puObject *o, SGPropertyNode_ptr p) :
1250     name(n),
1251     object(o),
1252     node(p)
1253 {
1254 }
1255
1256
1257
1258 \f
1259 ////////////////////////////////////////////////////////////////////////
1260 // Implementation of fgValueList and derived pui widgets
1261 ////////////////////////////////////////////////////////////////////////
1262
1263
1264 fgValueList::fgValueList(SGPropertyNode *p) :
1265     _props(p)
1266 {
1267     make_list();
1268 }
1269
1270 void
1271 fgValueList::update()
1272 {
1273     destroy_list();
1274     make_list();
1275 }
1276
1277 fgValueList::~fgValueList()
1278 {
1279     destroy_list();
1280 }
1281
1282 void
1283 fgValueList::make_list()
1284 {
1285   SGPropertyNode_ptr values = _props;
1286   const char* vname = "value";
1287   
1288   if (_props->hasChild("properties")) {
1289     // dynamic values, read from a property's children
1290     const char* path = _props->getStringValue("properties");
1291     values = fgGetNode(path, true);
1292   }
1293   
1294   if (_props->hasChild("property-name")) {
1295     vname = _props->getStringValue("property-name");
1296   }
1297   
1298   vector<SGPropertyNode_ptr> value_nodes = values->getChildren(vname);
1299   _list = new char *[value_nodes.size() + 1];
1300   unsigned int i;
1301   for (i = 0; i < value_nodes.size(); i++) {
1302     _list[i] = strdup((char *)value_nodes[i]->getStringValue());
1303   }
1304   _list[i] = 0;
1305 }
1306
1307 void
1308 fgValueList::destroy_list()
1309 {
1310     for (int i = 0; _list[i] != 0; i++)
1311         if (_list[i])
1312             free(_list[i]);
1313     delete[] _list;
1314 }
1315
1316
1317
1318 void
1319 fgList::update()
1320 {
1321     fgValueList::update();
1322     int top = getTopItem();
1323     newList(_list);
1324     setTopItem(top);
1325 }
1326
1327 void fgComboBox::update()
1328 {
1329   fgValueList::update();
1330   newList(_list);
1331 }
1332
1333 // end of dialog.cxx