1 // dialog.cxx: implementation of an XML-configurable dialog box.
7 #include <Input/input.hxx>
8 #include <Scripting/NasalSys.hxx>
11 #include "new_gui.hxx"
12 #include "AirportList.hxx"
13 #include "property_list.hxx"
17 enum format_type { f_INVALID, f_INT, f_LONG, f_FLOAT, f_DOUBLE, f_STRING };
18 static const int FORMAT_BUFSIZE = 255;
21 * Makes sure the format matches '%[ -+#]?\d*(\.\d*)?(l?[df]|s)', with
22 * only one number or string placeholder and otherwise arbitrary prefix
23 * and postfix containing only quoted percent signs (%%).
26 validate_format(const char *f)
40 if (*f == ' ' || *f == '+' || *f == '-' || *f == '#')
42 while (*f && isdigit(*f))
46 while (*f && isdigit(*f))
54 type = l ? f_LONG : f_INT;
56 type = l ? f_DOUBLE : f_FLOAT;
76 ////////////////////////////////////////////////////////////////////////
77 // Implementation of GUIInfo.
78 ////////////////////////////////////////////////////////////////////////
81 * User data for a GUI object.
85 GUIInfo(FGDialog * d);
87 char *format(SGPropertyNode *);
90 vector <SGBinding *> bindings;
97 GUIInfo::GUIInfo (FGDialog * d)
108 for (unsigned int i = 0; i < bindings.size(); i++) {
113 delete [] fmt_string;
116 char *GUIInfo::format(SGPropertyNode *n)
118 if (fmt_type == f_INT)
119 snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getIntValue());
120 else if (fmt_type == f_LONG)
121 snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getLongValue());
122 else if (fmt_type == f_FLOAT)
123 snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getFloatValue());
124 else if (fmt_type == f_DOUBLE)
125 snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getDoubleValue());
127 snprintf(text, FORMAT_BUFSIZE, fmt_string, n->getStringValue());
129 text[FORMAT_BUFSIZE] = '\0';
138 int fgPopup::checkKey(int key, int updown)
140 if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
143 puObject *input = getActiveInputField(this);
145 return input->checkKey(key, updown);
147 puObject *object = getKeyObject(this, key);
149 return puPopup::checkKey(key, updown);
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);
161 puObject *fgPopup::getKeyObject(puObject *object, int key)
164 if (object->getType() & PUCLASS_GROUP)
165 for (puObject *obj = ((puGroup *)object)->getFirstChild();
166 obj; obj = obj->getNextObject())
167 if ((ret = getKeyObject(obj, key)))
170 GUIInfo *info = (GUIInfo *)object->getUserData();
171 if (info && info->key == key)
177 puObject *fgPopup::getActiveInputField(puObject *object)
180 if (object->getType() & PUCLASS_GROUP)
181 for (puObject *obj = ((puGroup *)object)->getFirstChild();
182 obj; obj = obj->getNextObject())
183 if ((ret = getActiveInputField(obj)))
186 if (object->getType() & PUCLASS_INPUT && ((puInput *)object)->isAcceptingInput())
195 int fgPopup::checkHit(int button, int updown, int x, int y)
197 int result = puPopup::checkHit(button, updown, x, y);
202 // This is annoying. We would really want a true result from the
203 // superclass to indicate "handled by child object", but all it
204 // tells us is that the pointer is inside the dialog. So do the
205 // intersection test (again) to make sure we don't start a drag
206 // when inside controls.
208 if (updown == PU_DOWN && !_dragging) {
212 int hit = getHitObjects(this, x, y);
213 if (hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT))
217 getPosition(&px, &py);
221 } else if (updown == PU_DRAG && _dragging) {
222 setPosition(x + _dX, y + _dY);
229 int fgPopup::getHitObjects(puObject *object, int x, int y)
231 if (!object->isVisible())
235 if (object->getType() & PUCLASS_GROUP)
236 for (puObject *obj = ((puGroup *)object)->getFirstChild();
237 obj; obj = obj->getNextObject())
238 type |= getHitObjects(obj, x, y);
241 object->getAbsolutePosition(&cx, &cy);
242 object->getSize(&cw, &ch);
243 if (x >= cx && x < cx + cw && y >= cy && y < cy + ch)
244 type |= object->getType();
250 ////////////////////////////////////////////////////////////////////////
252 ////////////////////////////////////////////////////////////////////////
258 action_callback (puObject * object)
260 GUIInfo * info = (GUIInfo *)object->getUserData();
261 NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
262 gui->setActiveDialog(info->dialog);
263 int nBindings = info->bindings.size();
264 for (int i = 0; i < nBindings; i++) {
265 info->bindings[i]->fire();
266 if (gui->getActiveDialog() == 0)
269 gui->setActiveDialog(0);
274 ////////////////////////////////////////////////////////////////////////
275 // Static helper functions.
276 ////////////////////////////////////////////////////////////////////////
279 * Copy a property value to a PUI object.
282 copy_to_pui (SGPropertyNode * node, puObject * object)
284 // Treat puText objects specially, so their "values" can be set
286 if (object->getType() & PUCLASS_TEXT) {
287 GUIInfo *info = (GUIInfo *)object->getUserData();
288 if (info && info->fmt_string)
289 object->setLabel(info->format(node));
291 object->setLabel(node->getStringValue());
295 switch (node->getType()) {
296 case SGPropertyNode::BOOL:
297 case SGPropertyNode::INT:
298 case SGPropertyNode::LONG:
299 object->setValue(node->getIntValue());
301 case SGPropertyNode::FLOAT:
302 case SGPropertyNode::DOUBLE:
303 object->setValue(node->getFloatValue());
306 object->setValue(node->getStringValue());
313 copy_from_pui (puObject * object, SGPropertyNode * node)
315 // puText objects are immutable, so should not be copied out
316 if (object->getType() & PUCLASS_TEXT)
319 switch (node->getType()) {
320 case SGPropertyNode::BOOL:
321 case SGPropertyNode::INT:
322 case SGPropertyNode::LONG:
323 node->setIntValue(object->getIntegerValue());
325 case SGPropertyNode::FLOAT:
326 case SGPropertyNode::DOUBLE:
327 node->setFloatValue(object->getFloatValue());
330 // Special case to handle lists, as getStringValue cannot be overridden
331 if(object->getType() & PUCLASS_LIST)
333 const char *s = ((puList *) object)->getListStringValue();
335 node->setStringValue(s);
339 node->setStringValue(object->getStringValue());
347 ////////////////////////////////////////////////////////////////////////
348 // Implementation of FGDialog.
349 ////////////////////////////////////////////////////////////////////////
351 FGDialog::FGDialog (SGPropertyNode * props)
353 _gui((NewGUI *)globals->get_subsystem("gui")),
356 _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
357 SGPropertyNode *nasal = props->getNode("nasal");
359 _nasal_close = nasal->getNode("close");
360 SGPropertyNode *open = nasal->getNode("open");
362 const char *s = open->getStringValue();
363 FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
364 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
370 FGDialog::~FGDialog ()
373 _object->getAbsolutePosition(&x, &y);
374 _props->setIntValue("lastx", x);
375 _props->setIntValue("lasty", y);
377 FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
379 const char *s = _nasal_close->getStringValue();
380 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
382 nas->deleteModule(_module.c_str());
384 puDeleteObject(_object);
387 // Delete all the info objects we
388 // were forced to keep around because
389 // PUI cannot delete its own user data.
390 for (i = 0; i < _info.size(); i++) {
391 delete (GUIInfo *)_info[i];
394 // Finally, delete the property links.
395 for (i = 0; i < _propertyObjects.size(); i++) {
396 delete _propertyObjects[i];
397 _propertyObjects[i] = 0;
402 FGDialog::updateValues (const char * objectName)
404 if (objectName && !objectName[0])
407 for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
408 const string &name = _propertyObjects[i]->name;
409 if (objectName && name != objectName)
412 puObject *obj = _propertyObjects[i]->object;
413 if ((obj->getType() & PUCLASS_LIST) && (dynamic_cast<GUI_ID *>(obj)->id & FGCLASS_LIST)) {
414 fgList *pl = static_cast<fgList *>(obj);
417 copy_to_pui(_propertyObjects[i]->node, obj);
422 FGDialog::applyValues (const char * objectName)
424 if (objectName && !objectName[0])
427 for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
428 const string &name = _propertyObjects[i]->name;
429 if (objectName && name != objectName)
432 copy_from_pui(_propertyObjects[i]->object,
433 _propertyObjects[i]->node);
440 for (unsigned int i = 0; i < _liveObjects.size(); i++) {
441 puObject *obj = _liveObjects[i]->object;
442 if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
445 copy_to_pui(_liveObjects[i]->node, obj);
450 FGDialog::display (SGPropertyNode * props)
453 SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
457 int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
458 int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
460 bool userx = props->hasValue("x");
461 bool usery = props->hasValue("y");
462 bool userw = props->hasValue("width");
463 bool userh = props->hasValue("height");
465 // Let the layout widget work in the same property subtree.
466 LayoutWidget wid(props);
468 SGPropertyNode *fontnode = props->getNode("font");
470 FGFontCache *fc = globals->get_fontcache();
471 _font = fc->get(fontnode);
473 _font = _gui->getDefaultFont();
475 wid.setDefaultFont(_font, int(_font->getPointSize()));
478 int px, py, savex, savey;
480 wid.calcPrefSize(&pw, &ph);
481 pw = props->getIntValue("width", pw);
482 ph = props->getIntValue("height", ph);
483 px = savex = props->getIntValue("x", (screenw - pw) / 2);
484 py = savey = props->getIntValue("y", (screenh - ph) / 2);
486 // Negative x/y coordinates are interpreted as distance from the top/right
487 // corner rather than bottom/left.
489 px = screenw - pw + px;
491 py = screenh - ph + py;
493 // Define "x", "y", "width" and/or "height" in the property tree if they
494 // are not specified in the configuration file.
495 wid.layout(px, py, pw, ph);
497 // Use the dimension and location properties as specified in the
498 // configuration file or from the layout widget.
499 _object = makeObject(props, screenw, screenh);
501 // Remove automatically generated properties, so the layout looks
502 // the same next time around, or restore x and y to preserve negative coords.
504 props->setIntValue("x", savex);
506 props->removeChild("x");
509 props->setIntValue("y", savey);
511 props->removeChild("y");
513 if(!userw) props->removeChild("width");
514 if(!userh) props->removeChild("height");
519 SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
520 << props->getStringValue("name", "[unnamed]")
521 << " does not contain a proper GUI definition");
526 FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
528 if (props->getBoolValue("hide"))
531 bool presetSize = props->hasValue("width") && props->hasValue("height");
532 int width = props->getIntValue("width", parentWidth);
533 int height = props->getIntValue("height", parentHeight);
534 int x = props->getIntValue("x", (parentWidth - width) / 2);
535 int y = props->getIntValue("y", (parentHeight - height) / 2);
536 string type = props->getName();
541 if (type == "dialog") {
543 bool draggable = props->getBoolValue("draggable", true);
544 if (props->getBoolValue("modal", false))
545 obj = new puDialogBox(x, y);
547 obj = new fgPopup(x, y, draggable);
548 setupGroup(obj, props, width, height, true);
549 setColor(obj, props);
552 } else if (type == "group") {
553 puGroup * obj = new puGroup(x, y);
554 setupGroup(obj, props, width, height, false);
555 setColor(obj, props);
558 } else if (type == "frame") {
559 puGroup * obj = new puGroup(x, y);
560 setupGroup(obj, props, width, height, true);
561 setColor(obj, props);
564 } else if (type == "hrule" || type == "vrule") {
565 puFrame * obj = new puFrame(x, y, x + width, y + height);
566 obj->setBorderThickness(0);
567 setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
570 } else if (type == "list") {
571 int slider_width = props->getIntValue("slider", 20);
572 fgList * obj = new fgList(x, y, x + width, y + height, props, slider_width);
574 obj->setSize(width, height);
575 setupObject(obj, props);
576 setColor(obj, props);
579 } else if (type == "airport-list") {
580 AirportList * obj = new AirportList(x, y, x + width, y + height);
582 obj->setSize(width, height);
583 setupObject(obj, props);
584 setColor(obj, props);
587 } else if (type == "property-list") {
588 PropertyList * obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
590 obj->setSize(width, height);
591 setupObject(obj, props);
592 setColor(obj, props);
595 } else if (type == "input") {
596 puInput * obj = new puInput(x, y, x + width, y + height);
597 setupObject(obj, props);
598 setColor(obj, props, FOREGROUND|LABEL);
601 } else if (type == "text") {
602 puText * obj = new puText(x, y);
603 setupObject(obj, props);
605 // Layed-out objects need their size set, and non-layout ones
606 // get a different placement.
608 obj->setSize(width, height);
610 obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
611 setColor(obj, props, LABEL);
614 } else if (type == "checkbox") {
616 obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
617 setupObject(obj, props);
618 setColor(obj, props, FOREGROUND|LABEL);
621 } else if (type == "radio") {
623 obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
624 setupObject(obj, props);
625 setColor(obj, props, FOREGROUND|LABEL);
628 } else if (type == "button") {
630 const char * legend = props->getStringValue("legend", "[none]");
631 if (props->getBoolValue("one-shot", true))
632 obj = new puOneShot(x, y, legend);
634 obj = new puButton(x, y, legend);
636 obj->setSize(width, height);
637 setupObject(obj, props);
638 setColor(obj, props);
641 } else if (type == "combo") {
642 fgComboBox * obj = new fgComboBox(x, y, x + width, y + height, props,
643 props->getBoolValue("editable", false));
644 setupObject(obj, props);
645 #ifdef PUCOL_EDITFIELD // plib > 0.8.4
646 setColor(obj, props, EDITFIELD);
648 setColor(obj, props, FOREGROUND|LABEL);
652 } else if (type == "slider") {
653 bool vertical = props->getBoolValue("vertical", false);
654 puSlider * obj = new puSlider(x, y, (vertical ? height : width));
655 obj->setMinValue(props->getFloatValue("min", 0.0));
656 obj->setMaxValue(props->getFloatValue("max", 1.0));
657 setupObject(obj, props);
659 obj->setSize(width, height);
660 setColor(obj, props, FOREGROUND|LABEL);
663 } else if (type == "dial") {
664 puDial * obj = new puDial(x, y, width);
665 obj->setMinValue(props->getFloatValue("min", 0.0));
666 obj->setMaxValue(props->getFloatValue("max", 1.0));
667 obj->setWrap(props->getBoolValue("wrap", true));
668 setupObject(obj, props);
669 setColor(obj, props, FOREGROUND|LABEL);
672 } else if (type == "textbox") {
673 int slider_width = props->getIntValue("slider", 20);
674 int wrap = props->getBoolValue("wrap", true);
675 puaLargeInput * obj = new puaLargeInput(x, y,
676 x+width, x+height, 2, slider_width, wrap);
678 if (props->hasValue("editable")) {
679 if (props->getBoolValue("editable")==false)
685 obj->setSize(width, height);
686 setupObject(obj, props);
687 setColor(obj, props, FOREGROUND|LABEL);
690 } else if (type == "select") {
691 fgSelectBox * obj = new fgSelectBox(x, y, x + width, y + height, props);
692 setupObject(obj, props);
693 #ifdef PUCOL_EDITFIELD // plib > 0.8.4
694 setColor(obj, props, EDITFIELD);
696 setColor(obj, props, FOREGROUND|LABEL);
705 FGDialog::setupObject (puObject * object, SGPropertyNode * props)
707 string type = props->getName();
708 object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
710 if (props->hasValue("legend"))
711 object->setLegend(props->getStringValue("legend"));
713 if (props->hasValue("label"))
714 object->setLabel(props->getStringValue("label"));
716 if (props->hasValue("border"))
717 object->setBorderThickness( props->getIntValue("border", 2) );
719 if ( SGPropertyNode *nft = props->getNode("font", false) ) {
720 FGFontCache *fc = globals->get_fontcache();
721 puFont *lfnt = fc->get(nft);
722 object->setLabelFont(*lfnt);
724 object->setLabelFont(*_font);
727 if (props->hasValue("property")) {
728 const char * name = props->getStringValue("name");
731 const char * propname = props->getStringValue("property");
732 SGPropertyNode_ptr node = fgGetNode(propname, true);
733 copy_to_pui(node, object);
735 PropertyObject* po = new PropertyObject(name, object, node);
736 _propertyObjects.push_back(po);
737 if (props->getBoolValue("live"))
738 _liveObjects.push_back(po);
741 SGPropertyNode * dest = fgGetNode("/sim/bindings/gui", true);
742 vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
743 if (type == "text" || bindings.size() > 0) {
744 GUIInfo * info = new GUIInfo(this);
745 info->key = props->getIntValue("keynum", -1);
746 if (props->hasValue("key"))
747 info->key = getKeyCode(props->getStringValue("key", ""));
749 for (unsigned int i = 0; i < bindings.size(); i++) {
751 SGPropertyNode *binding;
752 while (dest->getChild("binding", j))
755 const char *cmd = bindings[i]->getStringValue("command");
756 if (!strcmp(cmd, "nasal"))
757 bindings[i]->setStringValue("module", _module.c_str());
759 binding = dest->getChild("binding", j, true);
760 copyProperties(bindings[i], binding);
761 info->bindings.push_back(new SGBinding(binding, globals->get_props()));
763 object->setCallback(action_callback);
765 if (type == "input" && props->getBoolValue("live"))
766 object->setDownCallback(action_callback);
768 if (type == "text") {
769 const char *format = props->getStringValue("format", 0);
771 info->fmt_type = validate_format(props->getStringValue("format", 0));
772 if (info->fmt_type != f_INVALID) {
773 info->text = new char[FORMAT_BUFSIZE + 1];
774 info->fmt_string = new char[strlen(format) + 1];
775 strcpy(info->fmt_string, format);
777 SG_LOG(SG_GENERAL, SG_ALERT, "DIALOG: invalid <format> '"
783 object->setUserData(info);
784 _info.push_back(info);
787 object->makeReturnDefault(props->getBoolValue("default"));
791 FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
792 int width, int height, bool makeFrame)
794 setupObject(group, props);
797 puFrame* f = new puFrame(0, 0, width, height);
801 int nChildren = props->nChildren();
802 for (int i = 0; i < nChildren; i++)
803 makeObject(props->getChild(i), width, height);
808 FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
810 string type = props->getName();
813 if (type == "textbox" && props->getBoolValue("editable"))
816 FGColor *c = new FGColor(_gui->getColor("background"));
817 c->merge(_gui->getColor(type));
818 c->merge(props->getNode("color"));
820 object->setColourScheme(c->red(), c->green(), c->blue(), c->alpha());
828 { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
829 { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
830 { HIGHLIGHT, PUCOL_HIGHLIGHT, "highlight", "color-highlight" },
831 { LABEL, PUCOL_LABEL, "label", "color-label" },
832 { LEGEND, PUCOL_LEGEND, "legend", "color-legend" },
833 { MISC, PUCOL_MISC, "misc", "color-misc" },
834 #ifdef PUCOL_EDITFIELD // plib > 0.8.4
835 { EDITFIELD, PUCOL_EDITFIELD, "editfield", "color-editfield" },
839 const int numcol = sizeof(pucol) / sizeof(pucol[0]);
841 for (int i = 0; i < numcol; i++) {
846 dirty |= c->merge(_gui->getColor(type + '-' + pucol[i].name));
847 if (which & pucol[i].mask)
848 dirty |= c->merge(props->getNode("color"));
850 if ((pucol[i].mask == LABEL) && !c->isValid())
851 dirty |= c->merge(_gui->getColor("label"));
853 dirty |= c->merge(props->getNode(pucol[i].cname));
855 if (c->isValid() && dirty)
856 object->setColor(pucol[i].id, c->red(), c->green(), c->blue(), c->alpha());
888 {"left", PU_KEY_LEFT},
890 {"right", PU_KEY_RIGHT},
891 {"down", PU_KEY_DOWN},
892 {"pageup", PU_KEY_PAGE_UP},
893 {"pagedn", PU_KEY_PAGE_DOWN},
894 {"home", PU_KEY_HOME},
896 {"insert", PU_KEY_INSERT},
901 FGDialog::getKeyCode(const char *str)
912 char *buf = new char[strlen(str) + 1];
914 char *s = buf + strlen(buf);
915 while (s > str && s[-1] == ' ')
922 if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
924 else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
925 s += 6, mod |= SHIFT;
926 else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
933 if (strlen(s) == 1 && isascii(*s)) {
938 key = toupper(key) - 64;
940 ; // Alt not propagated to the gui
942 for (char *t = s; *t; t++)
944 for (int i = 0; keymap[i].name; i++) {
945 if (!strcmp(s, keymap[i].name)) {
957 ////////////////////////////////////////////////////////////////////////
958 // Implementation of FGDialog::PropertyObject.
959 ////////////////////////////////////////////////////////////////////////
961 FGDialog::PropertyObject::PropertyObject (const char * n,
963 SGPropertyNode_ptr p)
973 ////////////////////////////////////////////////////////////////////////
974 // Implementation of fgValueList and derived pui widgets
975 ////////////////////////////////////////////////////////////////////////
978 fgValueList::fgValueList(SGPropertyNode *p) :
985 fgValueList::update()
991 fgValueList::~fgValueList()
997 fgValueList::make_list()
999 vector<SGPropertyNode_ptr> value_nodes = _props->getChildren("value");
1000 _list = new char *[value_nodes.size() + 1];
1002 for (i = 0; i < value_nodes.size(); i++)
1003 _list[i] = strdup((char *)value_nodes[i]->getStringValue());
1008 fgValueList::destroy_list()
1010 for (int i = 0; _list[i] != 0; i++)
1021 fgValueList::update();
1025 // end of dialog.cxx