1 // dialog.cxx: implementation of an XML-configurable dialog box.
3 #include <stdlib.h> // atof()
5 #include <Input/input.hxx>
11 #include "AirportList.hxx"
15 * User data for a GUI object.
19 GUIInfo (FGDialog * d);
23 vector <FGBinding *> bindings;
31 int fgPopup::checkKey(int key, int updown)
33 if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
36 puObject *input = getActiveInputField(this);
38 return input->checkKey(key, updown);
40 puObject *object = getKeyObject(this, key);
42 return puPopup::checkKey(key, updown);
44 object->invokeCallback() ;
48 puObject *fgPopup::getKeyObject(puObject *object, int key)
51 if(object->getType() & PUCLASS_GROUP)
52 for (puObject *obj = ((puGroup *)object)->getFirstChild();
53 obj; obj = obj->getNextObject())
54 if ((ret = getKeyObject(obj, key)))
57 GUIInfo *info = (GUIInfo *)object->getUserData();
58 if (info && info->key == key)
64 puObject *fgPopup::getActiveInputField(puObject *object)
66 if(object->getType() & PUCLASS_GROUP)
67 for (puObject *obj = ((puGroup *)object)->getFirstChild();
68 obj; obj = obj->getNextObject())
69 if (getActiveInputField(obj))
72 if (object->getType() & PUCLASS_INPUT && ((puInput *)object)->isAcceptingInput())
81 int fgPopup::checkHit(int button, int updown, int x, int y)
83 int result = puPopup::checkHit(button, updown, x, y);
88 // This is annoying. We would really want a true result from the
89 // superclass to indicate "handled by child object", but all it
90 // tells us is that the pointer is inside the dialog. So do the
91 // intersection test (again) to make sure we don't start a drag
92 // when inside controls.
94 if(updown == PU_DOWN && !_dragging) {
98 int hit = getHitObjects(this, x, y);
99 if(hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT))
103 getPosition(&px, &py);
107 } else if(updown == PU_DRAG && _dragging) {
108 setPosition(x + _dX, y + _dY);
115 int fgPopup::getHitObjects(puObject *object, int x, int y)
118 if(object->getType() & PUCLASS_GROUP)
119 for (puObject *obj = ((puGroup *)object)->getFirstChild();
120 obj; obj = obj->getNextObject())
121 type |= getHitObjects(obj, x, y);
124 object->getAbsolutePosition(&cx, &cy);
125 object->getSize(&cw, &ch);
126 if(x >= cx && x < cx + cw && y >= cy && y < cy + ch)
127 type |= object->getType();
133 ////////////////////////////////////////////////////////////////////////
135 ////////////////////////////////////////////////////////////////////////
141 action_callback (puObject * object)
143 GUIInfo * info = (GUIInfo *)object->getUserData();
144 NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
145 gui->setActiveDialog(info->dialog);
146 int nBindings = info->bindings.size();
147 for (int i = 0; i < nBindings; i++) {
148 info->bindings[i]->fire();
149 if (gui->getActiveDialog() == 0)
152 gui->setActiveDialog(0);
157 format_callback(puObject *obj, int dx, int dy, void *n)
159 SGPropertyNode *node = (SGPropertyNode *)n;
160 const char *format = node->getStringValue("format"), *f = format;
161 bool number, l = false;
162 // make sure the format matches '[ -+#]?\d*(\.\d*)?l?[fs]'
173 if (*f == ' ' || *f == '+' || *f == '-' || *f == '#')
175 while (*f && isdigit(*f))
179 while (*f && isdigit(*f))
187 else if (*f == 's') {
204 const char *src = obj->getLabel();
207 float value = atof(src);
208 snprintf(buf, 256, format, value);
210 snprintf(buf, 256, format, src);
215 SGPropertyNode *result = node->getNode("formatted", true);
216 result->setStringValue(buf);
217 obj->setLabel(result->getStringValue());
222 ////////////////////////////////////////////////////////////////////////
223 // Static helper functions.
224 ////////////////////////////////////////////////////////////////////////
227 * Copy a property value to a PUI object.
230 copy_to_pui (SGPropertyNode * node, puObject * object)
232 // Treat puText objects specially, so their "values" can be set
234 if(object->getType() & PUCLASS_TEXT) {
235 object->setLabel(node->getStringValue());
239 switch (node->getType()) {
240 case SGPropertyNode::BOOL:
241 case SGPropertyNode::INT:
242 case SGPropertyNode::LONG:
243 object->setValue(node->getIntValue());
245 case SGPropertyNode::FLOAT:
246 case SGPropertyNode::DOUBLE:
247 object->setValue(node->getFloatValue());
250 object->setValue(node->getStringValue());
257 copy_from_pui (puObject * object, SGPropertyNode * node)
259 // puText objects are immutable, so should not be copied out
260 if(object->getType() & PUCLASS_TEXT)
263 switch (node->getType()) {
264 case SGPropertyNode::BOOL:
265 case SGPropertyNode::INT:
266 case SGPropertyNode::LONG:
267 node->setIntValue(object->getIntegerValue());
269 case SGPropertyNode::FLOAT:
270 case SGPropertyNode::DOUBLE:
271 node->setFloatValue(object->getFloatValue());
274 // Special case to handle lists, as getStringValue cannot be overridden
275 if(object->getType() & PUCLASS_LIST)
277 node->setStringValue(((puList *) object)->getListStringValue());
281 node->setStringValue(object->getStringValue());
289 ////////////////////////////////////////////////////////////////////////
290 // Implementation of GUIInfo.
291 ////////////////////////////////////////////////////////////////////////
293 GUIInfo::GUIInfo (FGDialog * d)
301 for (unsigned int i = 0; i < bindings.size(); i++) {
309 ////////////////////////////////////////////////////////////////////////
310 // Implementation of FGDialog.
311 ////////////////////////////////////////////////////////////////////////
313 FGDialog::FGDialog (SGPropertyNode * props)
315 _gui((NewGUI *)globals->get_subsystem("gui"))
317 char* envp = ::getenv( "FG_FONTS" );
318 if ( envp != NULL ) {
319 _font_path.set( envp );
321 _font_path.set( globals->get_fg_root() );
322 _font_path.append( "Fonts" );
328 FGDialog::~FGDialog ()
330 puDeleteObject(_object);
334 // Delete all the arrays we made
335 // and were forced to keep around
336 // because PUI won't do its own
337 // memory management.
338 for (i = 0; i < _char_arrays.size(); i++) {
339 for (int j = 0; _char_arrays[i][j] != 0; j++)
340 free(_char_arrays[i][j]); // added with strdup
341 delete[] _char_arrays[i];
344 // Delete all the info objects we
345 // were forced to keep around because
346 // PUI cannot delete its own user data.
347 for (i = 0; i < _info.size(); i++) {
348 delete (GUIInfo *)_info[i];
352 // Finally, delete the property links.
353 for (i = 0; i < _propertyObjects.size(); i++) {
354 delete _propertyObjects[i];
355 _propertyObjects[i] = 0;
360 FGDialog::updateValue (const char * objectName)
362 for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
363 const string &name = _propertyObjects[i]->name;
364 if (name == objectName)
365 copy_to_pui(_propertyObjects[i]->node,
366 _propertyObjects[i]->object);
371 FGDialog::applyValue (const char * objectName)
373 for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
374 if (_propertyObjects[i]->name == objectName)
375 copy_from_pui(_propertyObjects[i]->object,
376 _propertyObjects[i]->node);
381 FGDialog::updateValues ()
383 for (unsigned int i = 0; i < _propertyObjects.size(); i++)
384 copy_to_pui(_propertyObjects[i]->node, _propertyObjects[i]->object);
388 FGDialog::applyValues ()
390 for (unsigned int i = 0; i < _propertyObjects.size(); i++)
391 copy_from_pui(_propertyObjects[i]->object,
392 _propertyObjects[i]->node);
398 for (unsigned int i = 0; i < _liveObjects.size(); i++) {
399 puObject *obj = _liveObjects[i]->object;
400 if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
403 copy_to_pui(_liveObjects[i]->node, obj);
408 FGDialog::display (SGPropertyNode * props)
411 SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
415 int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
416 int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
418 bool userx = props->hasValue("x");
419 bool usery = props->hasValue("y");
420 bool userw = props->hasValue("width");
421 bool userh = props->hasValue("height");
423 // Let the layout widget work in the same property subtree.
424 LayoutWidget wid(props);
426 puFont *fnt = _gui->getDefaultFont();
427 wid.setDefaultFont(fnt, int(fnt->getPointSize()));
431 wid.calcPrefSize(&pw, &ph);
432 pw = props->getIntValue("width", pw);
433 ph = props->getIntValue("height", ph);
434 int px = props->getIntValue("x", (screenw - pw) / 2);
435 int py = props->getIntValue("y", (screenh - ph) / 2);
437 // Define "x", "y", "width" and/or "height" in the property tree if they
438 // are not specified in the configuration file.
439 wid.layout(px, py, pw, ph);
441 // Use the dimension and location properties as specified in the
442 // configuration file or from the layout widget.
443 _object = makeObject(props, screenw, screenh);
445 // Remove automatically generated properties, so the layout looks
446 // the same next time around.
447 if(!userx) props->removeChild("x");
448 if(!usery) props->removeChild("y");
449 if(!userw) props->removeChild("width");
450 if(!userh) props->removeChild("height");
455 SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
456 << props->getStringValue("name", "[unnamed]")
457 << " does not contain a proper GUI definition");
462 FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
464 if (props->getBoolValue("hide"))
467 bool presetSize = props->hasValue("width") && props->hasValue("height");
468 int width = props->getIntValue("width", parentWidth);
469 int height = props->getIntValue("height", parentHeight);
470 int x = props->getIntValue("x", (parentWidth - width) / 2);
471 int y = props->getIntValue("y", (parentHeight - height) / 2);
472 string type = props->getName();
477 if (type == "dialog") {
479 bool draggable = props->getBoolValue("draggable", true);
480 if (props->getBoolValue("modal", false))
481 obj = new puDialogBox(x, y);
483 obj = new fgPopup(x, y, draggable);
484 setupGroup(obj, props, width, height, true);
485 setColor(obj, props);
488 } else if (type == "group") {
489 puGroup * obj = new puGroup(x, y);
490 setupGroup(obj, props, width, height, false);
491 setColor(obj, props);
494 } else if (type == "frame") {
495 puGroup * obj = new puGroup(x, y);
496 setupGroup(obj, props, width, height, true);
497 setColor(obj, props);
500 } else if (type == "hrule" || type == "vrule") {
501 puFrame * obj = new puFrame(x, y, x + width, y + height);
502 obj->setBorderThickness(0);
503 setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
506 } else if (type == "list") {
507 puList * obj = new puList(x, y, x + width, y + height);
508 setupObject(obj, props);
509 setColor(obj, props);
512 } else if (type == "airport-list") {
513 AirportList * obj = new AirportList(x, y, x + width, y + height);
514 setupObject(obj, props);
515 setColor(obj, props);
518 } else if (type == "input") {
519 puInput * obj = new puInput(x, y, x + width, y + height);
520 setupObject(obj, props);
521 setColor(obj, props, FOREGROUND|LABEL);
524 } else if (type == "text") {
525 puText * obj = new puText(x, y);
526 setupObject(obj, props);
528 if (props->getNode("format")) {
529 SGPropertyNode *live = props->getNode("live");
530 if (live && live->getBoolValue())
531 obj->setRenderCallback(format_callback, props);
533 format_callback(obj, x, y, props);
535 // Layed-out objects need their size set, and non-layout ones
536 // get a different placement.
537 if(presetSize) obj->setSize(width, height);
538 else obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
539 setColor(obj, props, LABEL);
542 } else if (type == "checkbox") {
544 obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
545 setupObject(obj, props);
546 setColor(obj, props, FOREGROUND|LABEL);
549 } else if (type == "radio") {
551 obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
552 setupObject(obj, props);
553 setColor(obj, props, FOREGROUND|LABEL);
556 } else if (type == "button") {
558 const char * legend = props->getStringValue("legend", "[none]");
559 if (props->getBoolValue("one-shot", true))
560 obj = new puOneShot(x, y, legend);
562 obj = new puButton(x, y, legend);
564 obj->setSize(width, height);
565 setupObject(obj, props);
566 setColor(obj, props);
569 } else if (type == "combo") {
570 vector<SGPropertyNode_ptr> value_nodes = props->getChildren("value");
571 char ** entries = make_char_array(value_nodes.size());
572 for (unsigned int i = 0, j = value_nodes.size() - 1;
573 i < value_nodes.size();
575 entries[i] = strdup((char *)value_nodes[i]->getStringValue());
576 puComboBox * obj = new puComboBox(x, y, x + width, y + height, entries,
577 props->getBoolValue("editable", false));
578 setupObject(obj, props);
579 setColor(obj, props, FOREGROUND|LABEL);
582 } else if (type == "slider") {
583 bool vertical = props->getBoolValue("vertical", false);
584 puSlider * obj = new puSlider(x, y, (vertical ? height : width));
585 obj->setMinValue(props->getFloatValue("min", 0.0));
586 obj->setMaxValue(props->getFloatValue("max", 1.0));
587 setupObject(obj, props);
589 obj->setSize(width, height);
590 setColor(obj, props, FOREGROUND|LABEL);
593 } else if (type == "dial") {
594 puDial * obj = new puDial(x, y, width);
595 obj->setMinValue(props->getFloatValue("min", 0.0));
596 obj->setMaxValue(props->getFloatValue("max", 1.0));
597 obj->setWrap(props->getBoolValue("wrap", true));
598 setupObject(obj, props);
599 setColor(obj, props, FOREGROUND|LABEL);
602 } else if (type == "textbox") {
603 int slider_width = props->getIntValue("slider", parentHeight);
604 int wrap = props->getBoolValue("wrap", true);
605 if (slider_width==0) slider_width=20;
606 puLargeInput * obj = new puLargeInput(x, y,
607 x+width, x+height, 2, slider_width, wrap);
609 if (props->hasValue("editable")) {
610 if (props->getBoolValue("editable")==false)
615 setupObject(obj, props);
616 setColor(obj, props, FOREGROUND|LABEL);
619 } else if (type == "select") {
620 vector<SGPropertyNode_ptr> value_nodes;
621 SGPropertyNode * selection_node =
622 fgGetNode(props->getChild("selection")->getStringValue(), true);
624 for (int q = 0; q < selection_node->nChildren(); q++)
625 value_nodes.push_back(selection_node->getChild(q));
627 char ** entries = make_char_array(value_nodes.size());
628 for (unsigned int i = 0, j = value_nodes.size() - 1;
629 i < value_nodes.size();
631 entries[i] = strdup((char *)value_nodes[i]->getName());
633 new puSelectBox(x, y, x + width, y + height, entries);
634 setupObject(obj, props);
635 setColor(obj, props, FOREGROUND|LABEL);
643 FGDialog::setupObject (puObject * object, SGPropertyNode * props)
645 object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
647 if (props->hasValue("legend"))
648 object->setLegend(props->getStringValue("legend"));
650 if (props->hasValue("label"))
651 object->setLabel(props->getStringValue("label"));
653 if (props->hasValue("border"))
654 object->setBorderThickness( props->getIntValue("border", 2) );
656 if ( SGPropertyNode *nft = props->getNode("font", false) ) {
657 SGPath path( _font_path );
658 const char *name = nft->getStringValue("name", "default");
659 float size = nft->getFloatValue("size", 13.0);
660 float slant = nft->getFloatValue("slant", 0.0);
662 path.concat( ".txf" );
664 fntFont *font = new fntTexFont;
665 font->load( (char *)path.c_str() );
667 puFont lfnt(font, size, slant);
668 object->setLabelFont( lfnt );
671 if (props->hasValue("property")) {
672 const char * name = props->getStringValue("name");
675 const char * propname = props->getStringValue("property");
676 SGPropertyNode_ptr node = fgGetNode(propname, true);
677 copy_to_pui(node, object);
678 PropertyObject* po = new PropertyObject(name, object, node);
679 _propertyObjects.push_back(po);
680 if(props->getBoolValue("live"))
681 _liveObjects.push_back(po);
684 SGPropertyNode * dest = fgGetNode("/sim/bindings/gui", true);
685 vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
686 if (bindings.size() > 0) {
687 GUIInfo * info = new GUIInfo(this);
688 info->key = props->getIntValue("keynum", -1);
690 for (unsigned int i = 0; i < bindings.size(); i++) {
692 SGPropertyNode *binding;
693 while (dest->getChild("binding", j))
696 binding = dest->getChild("binding", j, true);
697 copyProperties(bindings[i], binding);
698 info->bindings.push_back(new FGBinding(binding));
700 object->setCallback(action_callback);
701 object->setUserData(info);
702 _info.push_back(info);
705 object->makeReturnDefault(props->getBoolValue("default"));
709 FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
710 int width, int height, bool makeFrame)
712 setupObject(group, props);
715 puFrame* f = new puFrame(0, 0, width, height);
719 int nChildren = props->nChildren();
720 for (int i = 0; i < nChildren; i++)
721 makeObject(props->getChild(i), width, height);
726 FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
728 string type = props->getName();
732 FGColor *c = new FGColor(_gui->getColor("background"));
733 c->merge(_gui->getColor(type));
734 c->merge(props->getNode("color"));
736 object->setColourScheme(c->red(), c->green(), c->blue(), c->alpha());
744 { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
745 { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
746 { HIGHLIGHT, PUCOL_HIGHLIGHT, "highlight", "color-highlight" },
747 { LABEL, PUCOL_LABEL, "label", "color-label" },
748 { LEGEND, PUCOL_LEGEND, "legend", "color-legend" },
749 { MISC, PUCOL_MISC, "misc", "color-misc" }
752 const int numcol = sizeof(pucol) / sizeof(pucol[0]);
754 for (int i = 0; i < numcol; i++) {
759 dirty |= c->merge(_gui->getColor(type + '-' + pucol[i].name));
760 if (which & pucol[i].mask)
761 dirty |= c->merge(props->getNode("color"));
763 if ((pucol[i].mask == LABEL) && !c->isValid())
764 dirty |= c->merge(_gui->getColor("label"));
766 if (c->isValid() && dirty)
767 object->setColor(pucol[i].id, c->red(), c->green(), c->blue(), c->alpha());
772 FGDialog::make_char_array (int size)
774 char ** list = new char*[size+1];
775 for (int i = 0; i <= size; i++)
777 _char_arrays.push_back(list);
783 ////////////////////////////////////////////////////////////////////////
784 // Implementation of FGDialog::PropertyObject.
785 ////////////////////////////////////////////////////////////////////////
787 FGDialog::PropertyObject::PropertyObject (const char * n,
789 SGPropertyNode_ptr p)