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