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 while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0')
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 const char *s = object->getStringValue();
332 node->setStringValue(s);
339 ////////////////////////////////////////////////////////////////////////
340 // Implementation of FGDialog.
341 ////////////////////////////////////////////////////////////////////////
343 FGDialog::FGDialog (SGPropertyNode * props)
345 _gui((NewGUI *)globals->get_subsystem("gui")),
348 _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
349 SGPropertyNode *nasal = props->getNode("nasal");
351 _nasal_close = nasal->getNode("close");
352 SGPropertyNode *open = nasal->getNode("open");
354 const char *s = open->getStringValue();
355 FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
356 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
362 FGDialog::~FGDialog ()
365 _object->getAbsolutePosition(&x, &y);
366 _props->setIntValue("lastx", x);
367 _props->setIntValue("lasty", y);
369 FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
371 const char *s = _nasal_close->getStringValue();
372 nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
374 nas->deleteModule(_module.c_str());
376 puDeleteObject(_object);
379 // Delete all the info objects we
380 // were forced to keep around because
381 // PUI cannot delete its own user data.
382 for (i = 0; i < _info.size(); i++) {
383 delete (GUIInfo *)_info[i];
386 // Finally, delete the property links.
387 for (i = 0; i < _propertyObjects.size(); i++) {
388 delete _propertyObjects[i];
389 _propertyObjects[i] = 0;
394 FGDialog::updateValues (const char * objectName)
396 if (objectName && !objectName[0])
399 for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
400 const string &name = _propertyObjects[i]->name;
401 if (objectName && name != objectName)
404 puObject *obj = _propertyObjects[i]->object;
405 if ((obj->getType() & PUCLASS_LIST) && (dynamic_cast<GUI_ID *>(obj)->id & FGCLASS_LIST)) {
406 fgList *pl = static_cast<fgList *>(obj);
409 copy_to_pui(_propertyObjects[i]->node, obj);
414 FGDialog::applyValues (const char * objectName)
416 if (objectName && !objectName[0])
419 for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
420 const string &name = _propertyObjects[i]->name;
421 if (objectName && name != objectName)
424 copy_from_pui(_propertyObjects[i]->object,
425 _propertyObjects[i]->node);
432 for (unsigned int i = 0; i < _liveObjects.size(); i++) {
433 puObject *obj = _liveObjects[i]->object;
434 if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
437 copy_to_pui(_liveObjects[i]->node, obj);
442 FGDialog::display (SGPropertyNode * props)
445 SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
449 int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
450 int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
452 bool userx = props->hasValue("x");
453 bool usery = props->hasValue("y");
454 bool userw = props->hasValue("width");
455 bool userh = props->hasValue("height");
457 // Let the layout widget work in the same property subtree.
458 LayoutWidget wid(props);
460 SGPropertyNode *fontnode = props->getNode("font");
462 FGFontCache *fc = globals->get_fontcache();
463 _font = fc->get(fontnode);
465 _font = _gui->getDefaultFont();
467 wid.setDefaultFont(_font, int(_font->getPointSize()));
470 int px, py, savex, savey;
472 wid.calcPrefSize(&pw, &ph);
473 pw = props->getIntValue("width", pw);
474 ph = props->getIntValue("height", ph);
475 px = savex = props->getIntValue("x", (screenw - pw) / 2);
476 py = savey = props->getIntValue("y", (screenh - ph) / 2);
478 // Negative x/y coordinates are interpreted as distance from the top/right
479 // corner rather than bottom/left.
481 px = screenw - pw + px;
483 py = screenh - ph + py;
485 // Define "x", "y", "width" and/or "height" in the property tree if they
486 // are not specified in the configuration file.
487 wid.layout(px, py, pw, ph);
489 // Use the dimension and location properties as specified in the
490 // configuration file or from the layout widget.
491 _object = makeObject(props, screenw, screenh);
493 // Remove automatically generated properties, so the layout looks
494 // the same next time around, or restore x and y to preserve negative coords.
496 props->setIntValue("x", savex);
498 props->removeChild("x");
501 props->setIntValue("y", savey);
503 props->removeChild("y");
505 if(!userw) props->removeChild("width");
506 if(!userh) props->removeChild("height");
511 SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
512 << props->getStringValue("name", "[unnamed]")
513 << " does not contain a proper GUI definition");
518 FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
520 if (props->getBoolValue("hide"))
523 bool presetSize = props->hasValue("width") && props->hasValue("height");
524 int width = props->getIntValue("width", parentWidth);
525 int height = props->getIntValue("height", parentHeight);
526 int x = props->getIntValue("x", (parentWidth - width) / 2);
527 int y = props->getIntValue("y", (parentHeight - height) / 2);
528 string type = props->getName();
533 if (type == "dialog") {
535 bool draggable = props->getBoolValue("draggable", true);
536 if (props->getBoolValue("modal", false))
537 obj = new puDialogBox(x, y);
539 obj = new fgPopup(x, y, draggable);
540 setupGroup(obj, props, width, height, true);
541 setColor(obj, props);
544 } else if (type == "group") {
545 puGroup * obj = new puGroup(x, y);
546 setupGroup(obj, props, width, height, false);
547 setColor(obj, props);
550 } else if (type == "frame") {
551 puGroup * obj = new puGroup(x, y);
552 setupGroup(obj, props, width, height, true);
553 setColor(obj, props);
556 } else if (type == "hrule" || type == "vrule") {
557 puFrame * obj = new puFrame(x, y, x + width, y + height);
558 obj->setBorderThickness(0);
559 setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
562 } else if (type == "list") {
563 int slider_width = props->getIntValue("slider", 20);
564 fgList * obj = new fgList(x, y, x + width, y + height, props, slider_width);
566 obj->setSize(width, height);
567 setupObject(obj, props);
568 setColor(obj, props);
571 } else if (type == "airport-list") {
572 AirportList * obj = new AirportList(x, y, x + width, y + height);
574 obj->setSize(width, height);
575 setupObject(obj, props);
576 setColor(obj, props);
579 } else if (type == "property-list") {
580 PropertyList * obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
582 obj->setSize(width, height);
583 setupObject(obj, props);
584 setColor(obj, props);
587 } else if (type == "input") {
588 puInput * obj = new puInput(x, y, x + width, y + height);
589 setupObject(obj, props);
590 setColor(obj, props, FOREGROUND|LABEL);
593 } else if (type == "text") {
594 puText * obj = new puText(x, y);
595 setupObject(obj, props);
597 // Layed-out objects need their size set, and non-layout ones
598 // get a different placement.
600 obj->setSize(width, height);
602 obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
603 setColor(obj, props, LABEL);
606 } else if (type == "checkbox") {
608 obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
609 setupObject(obj, props);
610 setColor(obj, props, FOREGROUND|LABEL);
613 } else if (type == "radio") {
615 obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
616 setupObject(obj, props);
617 setColor(obj, props, FOREGROUND|LABEL);
620 } else if (type == "button") {
622 const char * legend = props->getStringValue("legend", "[none]");
623 if (props->getBoolValue("one-shot", true))
624 obj = new puOneShot(x, y, legend);
626 obj = new puButton(x, y, legend);
628 obj->setSize(width, height);
629 setupObject(obj, props);
630 setColor(obj, props);
633 } else if (type == "combo") {
634 fgComboBox * obj = new fgComboBox(x, y, x + width, y + height, props,
635 props->getBoolValue("editable", false));
636 setupObject(obj, props);
637 setColor(obj, props, EDITFIELD);
640 } else if (type == "slider") {
641 bool vertical = props->getBoolValue("vertical", false);
642 puSlider * obj = new puSlider(x, y, (vertical ? height : width));
643 obj->setMinValue(props->getFloatValue("min", 0.0));
644 obj->setMaxValue(props->getFloatValue("max", 1.0));
645 setupObject(obj, props);
647 obj->setSize(width, height);
648 setColor(obj, props, FOREGROUND|LABEL);
651 } else if (type == "dial") {
652 puDial * obj = new puDial(x, y, width);
653 obj->setMinValue(props->getFloatValue("min", 0.0));
654 obj->setMaxValue(props->getFloatValue("max", 1.0));
655 obj->setWrap(props->getBoolValue("wrap", true));
656 setupObject(obj, props);
657 setColor(obj, props, FOREGROUND|LABEL);
660 } else if (type == "textbox") {
661 int slider_width = props->getIntValue("slider", 20);
662 int wrap = props->getBoolValue("wrap", true);
663 puaLargeInput * obj = new puaLargeInput(x, y,
664 x+width, x+height, 2, slider_width, wrap);
666 if (props->hasValue("editable")) {
667 if (props->getBoolValue("editable")==false)
673 obj->setSize(width, height);
674 setupObject(obj, props);
675 setColor(obj, props, FOREGROUND|LABEL);
678 } else if (type == "select") {
679 fgSelectBox * obj = new fgSelectBox(x, y, x + width, y + height, props);
680 setupObject(obj, props);
681 setColor(obj, props, EDITFIELD);
689 FGDialog::setupObject (puObject * object, SGPropertyNode * props)
691 string type = props->getName();
692 object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
694 if (props->hasValue("legend"))
695 object->setLegend(props->getStringValue("legend"));
697 if (props->hasValue("label"))
698 object->setLabel(props->getStringValue("label"));
700 if (props->hasValue("border"))
701 object->setBorderThickness( props->getIntValue("border", 2) );
703 if ( SGPropertyNode *nft = props->getNode("font", false) ) {
704 FGFontCache *fc = globals->get_fontcache();
705 puFont *lfnt = fc->get(nft);
706 object->setLabelFont(*lfnt);
708 object->setLabelFont(*_font);
711 if (props->hasValue("property")) {
712 const char * name = props->getStringValue("name");
715 const char * propname = props->getStringValue("property");
716 SGPropertyNode_ptr node = fgGetNode(propname, true);
717 copy_to_pui(node, object);
719 PropertyObject* po = new PropertyObject(name, object, node);
720 _propertyObjects.push_back(po);
721 if (props->getBoolValue("live"))
722 _liveObjects.push_back(po);
725 SGPropertyNode * dest = fgGetNode("/sim/bindings/gui", true);
726 vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
727 if (type == "text" || bindings.size() > 0) {
728 GUIInfo * info = new GUIInfo(this);
729 info->key = props->getIntValue("keynum", -1);
730 if (props->hasValue("key"))
731 info->key = getKeyCode(props->getStringValue("key", ""));
733 for (unsigned int i = 0; i < bindings.size(); i++) {
735 SGPropertyNode_ptr binding;
736 while (dest->getChild("binding", j))
739 const char *cmd = bindings[i]->getStringValue("command");
740 if (!strcmp(cmd, "nasal"))
741 bindings[i]->setStringValue("module", _module.c_str());
743 binding = dest->getChild("binding", j, true);
744 copyProperties(bindings[i], binding);
745 info->bindings.push_back(new SGBinding(binding, globals->get_props()));
747 object->setCallback(action_callback);
749 if (type == "input" && props->getBoolValue("live"))
750 object->setDownCallback(action_callback);
752 if (type == "text") {
753 const char *format = props->getStringValue("format", 0);
755 info->fmt_type = validate_format(props->getStringValue("format", 0));
756 if (info->fmt_type != f_INVALID) {
757 info->text = new char[FORMAT_BUFSIZE + 1];
758 info->fmt_string = new char[strlen(format) + 1];
759 strcpy(info->fmt_string, format);
761 SG_LOG(SG_GENERAL, SG_ALERT, "DIALOG: invalid <format> '"
767 object->setUserData(info);
768 _info.push_back(info);
771 object->makeReturnDefault(props->getBoolValue("default"));
775 FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
776 int width, int height, bool makeFrame)
778 setupObject(group, props);
781 puFrame* f = new puFrame(0, 0, width, height);
785 int nChildren = props->nChildren();
786 for (int i = 0; i < nChildren; i++)
787 makeObject(props->getChild(i), width, height);
792 FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
794 string type = props->getName();
797 if (type == "textbox" && props->getBoolValue("editable"))
800 FGColor c(_gui->getColor("background"));
801 c.merge(_gui->getColor(type));
802 c.merge(props->getNode("color"));
804 object->setColourScheme(c.red(), c.green(), c.blue(), c.alpha());
812 { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
813 { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
814 { HIGHLIGHT, PUCOL_HIGHLIGHT, "highlight", "color-highlight" },
815 { LABEL, PUCOL_LABEL, "label", "color-label" },
816 { LEGEND, PUCOL_LEGEND, "legend", "color-legend" },
817 { MISC, PUCOL_MISC, "misc", "color-misc" },
818 { EDITFIELD, PUCOL_EDITFIELD, "editfield", "color-editfield" },
821 const int numcol = sizeof(pucol) / sizeof(pucol[0]);
823 for (int i = 0; i < numcol; i++) {
828 dirty |= c.merge(_gui->getColor(type + '-' + pucol[i].name));
829 if (which & pucol[i].mask)
830 dirty |= c.merge(props->getNode("color"));
832 if ((pucol[i].mask == LABEL) && !c.isValid())
833 dirty |= c.merge(_gui->getColor("label"));
835 dirty |= c.merge(props->getNode(pucol[i].cname));
837 if (c.isValid() && dirty)
838 object->setColor(pucol[i].id, c.red(), c.green(), c.blue(), c.alpha());
870 {"left", PU_KEY_LEFT},
872 {"right", PU_KEY_RIGHT},
873 {"down", PU_KEY_DOWN},
874 {"pageup", PU_KEY_PAGE_UP},
875 {"pagedn", PU_KEY_PAGE_DOWN},
876 {"home", PU_KEY_HOME},
878 {"insert", PU_KEY_INSERT},
883 FGDialog::getKeyCode(const char *str)
894 char *buf = new char[strlen(str) + 1];
896 char *s = buf + strlen(buf);
897 while (s > str && s[-1] == ' ')
904 if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
906 else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
907 s += 6, mod |= SHIFT;
908 else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
915 if (strlen(s) == 1 && isascii(*s)) {
920 key = toupper(key) - 64;
922 ; // Alt not propagated to the gui
924 for (char *t = s; *t; t++)
926 for (int i = 0; keymap[i].name; i++) {
927 if (!strcmp(s, keymap[i].name)) {
939 ////////////////////////////////////////////////////////////////////////
940 // Implementation of FGDialog::PropertyObject.
941 ////////////////////////////////////////////////////////////////////////
943 FGDialog::PropertyObject::PropertyObject (const char * n,
945 SGPropertyNode_ptr p)
955 ////////////////////////////////////////////////////////////////////////
956 // Implementation of fgValueList and derived pui widgets
957 ////////////////////////////////////////////////////////////////////////
960 fgValueList::fgValueList(SGPropertyNode *p) :
967 fgValueList::update()
973 fgValueList::~fgValueList()
979 fgValueList::make_list()
981 vector<SGPropertyNode_ptr> value_nodes = _props->getChildren("value");
982 _list = new char *[value_nodes.size() + 1];
984 for (i = 0; i < value_nodes.size(); i++)
985 _list[i] = strdup((char *)value_nodes[i]->getStringValue());
990 fgValueList::destroy_list()
992 for (int i = 0; _list[i] != 0; i++)
1003 fgValueList::update();
1004 int top = getTopItem();
1009 // end of dialog.cxx