]> git.mxchange.org Git - flightgear.git/commitdiff
Make FGDialog an interface, moving existing code to FGPUIDialog implementation. ...
authorJames Turner <zakalawe@mac.com>
Sat, 19 Nov 2011 20:46:17 +0000 (20:46 +0000)
committerJames Turner <zakalawe@mac.com>
Sat, 19 Nov 2011 20:46:17 +0000 (20:46 +0000)
src/GUI/AirportList.hxx
src/GUI/CMakeLists.txt
src/GUI/FGPUIDialog.cxx [new file with mode: 0644]
src/GUI/FGPUIDialog.hxx [new file with mode: 0644]
src/GUI/WaypointList.hxx
src/GUI/dialog.cxx
src/GUI/dialog.hxx
src/GUI/new_gui.cxx
src/GUI/property_list.hxx

index bc745930c3e7601041dfc39c5374738eec2db735..99d131f567148a212e1d96f89d3fc303d758a130 100644 (file)
@@ -5,7 +5,7 @@
 
 #include <simgear/compiler.h>
 #include <plib/puAux.h>
-#include "dialog.hxx"
+#include "FGPUIDialog.hxx"
 
 class FGAirportList;
 
index eee0fcd41ab0bb88fbbb0167b4767b3c436acc2d..6fd3f47043a918105b48b6e21a7d245f182b9e2a 100644 (file)
@@ -6,6 +6,7 @@ set(SOURCES
        SafeTexFont.cxx
        WaypointList.cxx
        dialog.cxx
+       FGPUIDialog.cxx
        fonts.cxx
        gui.cxx
        gui_funcs.cxx
@@ -24,6 +25,7 @@ set(HEADERS
        SafeTexFont.hxx
        WaypointList.hxx
        dialog.hxx
+       FGPUIDialog.hxx
        gui.h
        layout.hxx
        menubar.hxx
diff --git a/src/GUI/FGPUIDialog.cxx b/src/GUI/FGPUIDialog.cxx
new file mode 100644 (file)
index 0000000..61bba82
--- /dev/null
@@ -0,0 +1,1418 @@
+// dialog.cxx: implementation of an XML-configurable dialog box.
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <simgear/structure/SGBinding.hxx>
+#include <simgear/props/props_io.hxx>
+
+#include <Scripting/NasalSys.hxx>
+#include <Main/fg_os.hxx>
+#include <Main/globals.hxx>
+#include <Main/fg_props.hxx>
+
+#include "FGPUIDialog.hxx"
+#include "new_gui.hxx"
+#include "AirportList.hxx"
+#include "property_list.hxx"
+#include "layout.hxx"
+#include "WaypointList.hxx"
+#include "MapWidget.hxx"
+#include "FGFontCache.hxx"
+#include "FGColor.hxx"
+
+enum format_type { f_INVALID, f_INT, f_LONG, f_FLOAT, f_DOUBLE, f_STRING };
+static const int FORMAT_BUFSIZE = 255;
+static const int RESIZE_MARGIN = 7;
+
+
+/**
+ * Makes sure the format matches '%[ -+#]?\d*(\.\d*)?(l?[df]|s)', with
+ * only one number or string placeholder and otherwise arbitrary prefix
+ * and postfix containing only quoted percent signs (%%).
+ */
+static format_type
+validate_format(const char *f)
+{
+    bool l = false;
+    format_type type;
+    for (; *f; f++) {
+        if (*f == '%') {
+            if (f[1] == '%')
+                f++;
+            else
+                break;
+        }
+    }
+    if (*f++ != '%')
+        return f_INVALID;
+    while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0')
+        f++;
+    while (*f && isdigit(*f))
+        f++;
+    if (*f == '.') {
+        f++;
+        while (*f && isdigit(*f))
+            f++;
+    }
+
+    if (*f == 'l')
+        l = true, f++;
+
+    if (*f == 'd') {
+        type = l ? f_LONG : f_INT;
+    } else if (*f == 'f')
+        type = l ? f_DOUBLE : f_FLOAT;
+    else if (*f == 's') {
+        if (l)
+            return f_INVALID;
+        type = f_STRING;
+    } else
+        return f_INVALID;
+
+    for (++f; *f; f++) {
+        if (*f == '%') {
+            if (f[1] == '%')
+                f++;
+            else
+                return f_INVALID;
+        }
+    }
+    return type;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// Implementation of GUIInfo.
+////////////////////////////////////////////////////////////////////////
+
+/**
+ * User data for a GUI object.
+ */
+struct GUIInfo
+{
+    GUIInfo(FGPUIDialog *d);
+    virtual ~GUIInfo();
+    void apply_format(SGPropertyNode *);
+
+    FGPUIDialog *dialog;
+    SGPropertyNode_ptr node;
+    vector <SGBinding *> bindings;
+    int key;
+    string label, legend, text, format;
+    format_type fmt_type;
+};
+
+GUIInfo::GUIInfo (FGPUIDialog *d) :
+    dialog(d),
+    key(-1),
+    fmt_type(f_INVALID)
+{
+}
+
+GUIInfo::~GUIInfo ()
+{
+    for (unsigned int i = 0; i < bindings.size(); i++) {
+        delete bindings[i];
+        bindings[i] = 0;
+    }
+}
+
+void GUIInfo::apply_format(SGPropertyNode *n)
+{
+    char buf[FORMAT_BUFSIZE + 1];
+    if (fmt_type == f_INT)
+        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getIntValue());
+    else if (fmt_type == f_LONG)
+        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getLongValue());
+    else if (fmt_type == f_FLOAT)
+        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getFloatValue());
+    else if (fmt_type == f_DOUBLE)
+        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getDoubleValue());
+    else
+        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getStringValue());
+
+    buf[FORMAT_BUFSIZE] = '\0';
+    text = buf;
+}
+
+
+\f
+/**
+ * Key handler.
+ */
+int fgPopup::checkKey(int key, int updown)
+{
+    if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
+        return false;
+
+    puObject *input = getActiveInputField(this);
+    if (input)
+        return input->checkKey(key, updown);
+
+    puObject *object = getKeyObject(this, key);
+    if (!object)
+        return puPopup::checkKey(key, updown);
+
+    // invokeCallback() isn't enough; we need to simulate a mouse button press
+    object->checkHit(PU_LEFT_BUTTON, PU_DOWN,
+            (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
+            (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
+    object->checkHit(PU_LEFT_BUTTON, PU_UP,
+            (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
+            (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
+    return true;
+}
+
+puObject *fgPopup::getKeyObject(puObject *object, int key)
+{
+    puObject *ret;
+    if (object->getType() & PUCLASS_GROUP)
+        for (puObject *obj = ((puGroup *)object)->getFirstChild();
+                obj; obj = obj->getNextObject())
+            if ((ret = getKeyObject(obj, key)))
+                return ret;
+
+    GUIInfo *info = (GUIInfo *)object->getUserData();
+    if (info && info->key == key)
+        return object;
+
+    return 0;
+}
+
+puObject *fgPopup::getActiveInputField(puObject *object)
+{
+    puObject *ret;
+    if (object->getType() & PUCLASS_GROUP)
+        for (puObject *obj = ((puGroup *)object)->getFirstChild();
+                obj; obj = obj->getNextObject())
+            if ((ret = getActiveInputField(obj)))
+                return ret;
+
+    if (object->getType() & (PUCLASS_INPUT|PUCLASS_LARGEINPUT)
+            && ((puInput *)object)->isAcceptingInput())
+        return object;
+
+    return 0;
+}
+
+/**
+ * Mouse handler.
+ */
+int fgPopup::checkHit(int button, int updown, int x, int y)
+{
+    int result = 0;
+    if (updown != PU_DRAG && !_dragging)
+        result = puPopup::checkHit(button, updown, x, y);
+
+    if (!_draggable)
+       return result;
+
+    // This is annoying.  We would really want a true result from the
+    // superclass to indicate "handled by child object", but all it
+    // tells us is that the pointer is inside the dialog.  So do the
+    // intersection test (again) to make sure we don't start a drag
+    // when inside controls.
+
+    if (updown == PU_DOWN && !_dragging) {
+        if (!result)
+            return 0;
+        int global_drag = fgGetKeyModifiers() & KEYMOD_SHIFT;
+        int global_resize = fgGetKeyModifiers() & KEYMOD_CTRL;
+
+        int hit = getHitObjects(this, x, y);
+        if (hit & PUCLASS_LIST)  // ctrl-click in property browser (toggle bool)
+            return result;
+        if (!global_resize && hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
+                |PUCLASS_LARGEINPUT|PUCLASS_SCROLLBAR))
+            return result;
+
+        getPosition(&_dlgX, &_dlgY);
+        getSize(&_dlgW, &_dlgH);
+        _start_cursor = fgGetMouseCursor();
+        _dragging = true;
+        _startX = x;
+        _startY = y;
+
+        // check and prepare for resizing
+        static const int cursor[] = {
+            MOUSE_CURSOR_POINTER, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
+            MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
+            MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT, 0,
+        };
+
+        _resizing = 0;
+        if (!global_drag && _resizable) {
+            int hmargin = global_resize ? _dlgW / 3 : RESIZE_MARGIN;
+            int vmargin = global_resize ? _dlgH / 3 : RESIZE_MARGIN;
+
+            if (y - _dlgY < vmargin)
+                _resizing |= BOTTOM;
+            else if (_dlgY + _dlgH - y < vmargin)
+                _resizing |= TOP;
+
+            if (x - _dlgX < hmargin)
+                _resizing |= LEFT;
+            else if (_dlgX + _dlgW - x < hmargin)
+                _resizing |= RIGHT;
+
+            if (!_resizing && global_resize)
+                _resizing = BOTTOM|RIGHT;
+
+            _cursor = cursor[_resizing];
+           if (_resizing && _resizable)
+                fgSetMouseCursor(_cursor);
+       }
+
+    } else if (updown == PU_DRAG && _dragging) {
+        if (_resizing) {
+            GUIInfo *info = (GUIInfo *)getUserData();
+            if (_resizable && info && info->node) {
+                int w = _dlgW;
+                int h = _dlgH;
+                if (_resizing & LEFT)
+                    w += _startX - x;
+                if (_resizing & RIGHT)
+                    w += x - _startX;
+                if (_resizing & TOP)
+                    h += y - _startY;
+                if (_resizing & BOTTOM)
+                    h += _startY - y;
+
+                int prefw, prefh;
+                LayoutWidget wid(info->node);
+                wid.calcPrefSize(&prefw, &prefh);
+                if (w < prefw)
+                    w = prefw;
+                if (h < prefh)
+                    h = prefh;
+
+                int x = _dlgX;
+                int y = _dlgY;
+                if (_resizing & LEFT)
+                    x += _dlgW - w;
+                if (_resizing & BOTTOM)
+                    y += _dlgH - h;
+
+                wid.layout(x, y, w, h);
+                setSize(w, h);
+                setPosition(x, y);
+                applySize(static_cast<puObject *>(this));
+                getFirstChild()->setSize(w, h); // dialog background puFrame
+            }
+        } else {
+            int posX = x + _dlgX - _startX,
+              posY = y + _dlgY - _startY;
+            setPosition(posX, posY);
+            
+            GUIInfo *info = (GUIInfo *)getUserData();
+            if (info && info->node) {
+                info->node->setIntValue("x", posX);
+                info->node->setIntValue("y", posY);
+            }
+        } // re-positioning
+
+    } else if (_dragging) {
+        fgSetMouseCursor(_start_cursor);
+        _dragging = false;
+    }
+    return result;
+}
+
+int fgPopup::getHitObjects(puObject *object, int x, int y)
+{
+    if (!object->isVisible())
+        return 0;
+
+    int type = 0;
+    if (object->getType() & PUCLASS_GROUP)
+        for (puObject *obj = ((puGroup *)object)->getFirstChild();
+                obj; obj = obj->getNextObject())
+            type |= getHitObjects(obj, x, y);
+
+    int cx, cy, cw, ch;
+    object->getAbsolutePosition(&cx, &cy);
+    object->getSize(&cw, &ch);
+    if (x >= cx && x < cx + cw && y >= cy && y < cy + ch)
+        type |= object->getType();
+    return type;
+}
+
+void fgPopup::applySize(puObject *object)
+{
+    // compound plib widgets use setUserData() for internal purposes, so refuse
+    // to descend into anything that has other bits set than the following
+    const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
+            |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
+            |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
+
+    int type = object->getType();
+    if (type & PUCLASS_GROUP && !(type & ~validUserData))
+        for (puObject *obj = ((puGroup *)object)->getFirstChild();
+                obj; obj = obj->getNextObject())
+            applySize(obj);
+
+    GUIInfo *info = (GUIInfo *)object->getUserData();
+    if (!info)
+        return;
+
+    SGPropertyNode *n = info->node;
+    if (!n) {
+        SG_LOG(SG_GENERAL, SG_ALERT, "fgPopup::applySize: no props");
+        return;
+    }
+    int x = n->getIntValue("x");
+    int y = n->getIntValue("y");
+    int w = n->getIntValue("width", 4);
+    int h = n->getIntValue("height", 4);
+    object->setPosition(x, y);
+    object->setSize(w, h);
+}
+
+////////////////////////////////////////////////////////////////////////
+
+void FGPUIDialog::ConditionalObject::update(FGPUIDialog* aDlg)
+{
+  if (_name == "visible") {
+    bool wasVis = _pu->isVisible() != 0;
+    bool newVis = test();
+    
+    if (newVis == wasVis) {
+      return;
+    }
+    
+    if (newVis) { // puObject needs a setVisible. Oh well.
+    _pu->reveal();
+    } else {
+      _pu->hide();
+    }
+  } else if (_name == "enable") {
+    bool wasEnabled = _pu->isActive() != 0;
+    bool newEnable = test();
+    
+    if (wasEnabled == newEnable) {
+      return;
+    }
+    
+    if (newEnable) {
+      _pu->activate();
+    } else {
+      _pu->greyOut();
+    }
+  }
+  
+  aDlg->setNeedsLayout();
+}
+\f////////////////////////////////////////////////////////////////////////
+// Callbacks.
+////////////////////////////////////////////////////////////////////////
+
+/**
+ * Action callback.
+ */
+static void
+action_callback (puObject *object)
+{
+    GUIInfo *info = (GUIInfo *)object->getUserData();
+    NewGUI *gui = (NewGUI *)globals->get_subsystem("gui");
+    gui->setActiveDialog(info->dialog);
+    int nBindings = info->bindings.size();
+    for (int i = 0; i < nBindings; i++) {
+        info->bindings[i]->fire();
+        if (gui->getActiveDialog() == 0)
+            break;
+    }
+    gui->setActiveDialog(0);
+}
+
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Static helper functions.
+////////////////////////////////////////////////////////////////////////
+
+/**
+ * Copy a property value to a PUI object.
+ */
+static void
+copy_to_pui (SGPropertyNode *node, puObject *object)
+{
+    using namespace simgear;
+    GUIInfo *info = (GUIInfo *)object->getUserData();
+    if (!info) {
+        SG_LOG(SG_GENERAL, SG_ALERT, "dialog: widget without GUIInfo!");
+        return;   // this can't really happen
+    }
+
+    // Treat puText objects specially, so their "values" can be set
+    // from properties.
+    if (object->getType() & PUCLASS_TEXT) {
+        if (info->fmt_type != f_INVALID)
+            info->apply_format(node);
+        else
+            info->text = node->getStringValue();
+
+        object->setLabel(info->text.c_str());
+        return;
+    }
+
+    switch (node->getType()) {
+    case props::BOOL:
+    case props::INT:
+    case props::LONG:
+        object->setValue(node->getIntValue());
+        break;
+    case props::FLOAT:
+    case props::DOUBLE:
+        object->setValue(node->getFloatValue());
+        break;
+    default:
+        info->text = node->getStringValue();
+        object->setValue(info->text.c_str());
+        break;
+    }
+}
+
+
+static void
+copy_from_pui (puObject *object, SGPropertyNode *node)
+{
+    using namespace simgear;
+    // puText objects are immutable, so should not be copied out
+    if (object->getType() & PUCLASS_TEXT)
+        return;
+
+    switch (node->getType()) {
+    case props::BOOL:
+    case props::INT:
+    case props::LONG:
+        node->setIntValue(object->getIntegerValue());
+        break;
+    case props::FLOAT:
+    case props::DOUBLE:
+        node->setFloatValue(object->getFloatValue());
+        break;
+    default:
+        const char *s = object->getStringValue();
+        if (s)
+            node->setStringValue(s);
+        break;
+    }
+}
+
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of FGDialog.
+////////////////////////////////////////////////////////////////////////
+
+FGPUIDialog::FGPUIDialog (SGPropertyNode *props) :
+    FGDialog(props),
+    _object(0),
+    _gui((NewGUI *)globals->get_subsystem("gui")),
+    _props(props),
+    _needsRelayout(false)
+{
+    _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
+        
+    SGPropertyNode *nasal = props->getNode("nasal");
+    if (nasal) {
+        _nasal_close = nasal->getNode("close");
+        SGPropertyNode *open = nasal->getNode("open");
+        if (open) {
+            const char *s = open->getStringValue();
+            FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
+            if (nas)
+                nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
+        }
+    }
+    display(props);
+}
+
+FGPUIDialog::~FGPUIDialog ()
+{
+    int x, y;
+    _object->getAbsolutePosition(&x, &y);
+    _props->setIntValue("lastx", x);
+    _props->setIntValue("lasty", y);
+
+    FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
+    if (nas) {
+        if (_nasal_close) {
+            const char *s = _nasal_close->getStringValue();
+            nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
+        }
+        nas->deleteModule(_module.c_str());
+    }
+
+    puDeleteObject(_object);
+
+    unsigned int i;
+                                // Delete all the info objects we
+                                // were forced to keep around because
+                                // PUI cannot delete its own user data.
+    for (i = 0; i < _info.size(); i++) {
+        delete (GUIInfo *)_info[i];
+        _info[i] = 0;
+    }
+                                // Finally, delete the property links.
+    for (i = 0; i < _propertyObjects.size(); i++) {
+        delete _propertyObjects[i];
+        _propertyObjects[i] = 0;
+    }
+}
+
+void
+FGPUIDialog::updateValues (const char *objectName)
+{
+    if (objectName && !objectName[0])
+        objectName = 0;
+
+  for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
+    const string &name = _propertyObjects[i]->name;
+    if (objectName && name != objectName) {
+      continue;
+    }
+    
+    puObject *widget = _propertyObjects[i]->object;
+    int widgetType = widget->getType();
+    if (widgetType & PUCLASS_LIST) {
+      GUI_ID* gui_id = dynamic_cast<GUI_ID *>(widget);
+      if (gui_id && (gui_id->id & FGCLASS_LIST)) {
+        fgList *pl = static_cast<fgList*>(widget);
+        pl->update();
+      } else {
+        copy_to_pui(_propertyObjects[i]->node, widget);
+      }
+    } else if (widgetType & PUCLASS_COMBOBOX) {
+      fgComboBox* combo = static_cast<fgComboBox*>(widget);
+      combo->update();
+    } else {
+      copy_to_pui(_propertyObjects[i]->node, widget);
+    }
+  } // of property objects iteration
+}
+
+void
+FGPUIDialog::applyValues (const char *objectName)
+{
+    if (objectName && !objectName[0])
+        objectName = 0;
+
+    for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
+        const string &name = _propertyObjects[i]->name;
+        if (objectName && name != objectName)
+            continue;
+
+        copy_from_pui(_propertyObjects[i]->object,
+                      _propertyObjects[i]->node);
+    }
+}
+
+void
+FGPUIDialog::update ()
+{
+    for (unsigned int i = 0; i < _liveObjects.size(); i++) {
+        puObject *obj = _liveObjects[i]->object;
+        if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
+            continue;
+
+        copy_to_pui(_liveObjects[i]->node, obj);
+    }
+    
+  for (unsigned int j=0; j < _conditionalObjects.size(); ++j) {
+    _conditionalObjects[j]->update(this);
+  }
+  
+  if (_needsRelayout) {
+    relayout();
+  }
+}
+
+void
+FGPUIDialog::display (SGPropertyNode *props)
+{
+    if (_object != 0) {
+        SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
+        return;
+    }
+
+    int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
+    int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
+
+    bool userx = props->hasValue("x");
+    bool usery = props->hasValue("y");
+    bool userw = props->hasValue("width");
+    bool userh = props->hasValue("height");
+
+    // Let the layout widget work in the same property subtree.
+    LayoutWidget wid(props);
+
+    SGPropertyNode *fontnode = props->getNode("font");
+    if (fontnode) {
+        FGFontCache *fc = globals->get_fontcache();
+        _font = fc->get(fontnode);
+    } else {
+        _font = _gui->getDefaultFont();
+    }
+    wid.setDefaultFont(_font, int(_font->getPointSize()));
+
+    int pw = 0, ph = 0;
+    int px, py, savex, savey;
+    if (!userw || !userh)
+        wid.calcPrefSize(&pw, &ph);
+    pw = props->getIntValue("width", pw);
+    ph = props->getIntValue("height", ph);
+    px = savex = props->getIntValue("x", (screenw - pw) / 2);
+    py = savey = props->getIntValue("y", (screenh - ph) / 2);
+
+    // Negative x/y coordinates are interpreted as distance from the top/right
+    // corner rather than bottom/left.
+    if (userx && px < 0)
+        px = screenw - pw + px;
+    if (usery && py < 0)
+        py = screenh - ph + py;
+
+    // Define "x", "y", "width" and/or "height" in the property tree if they
+    // are not specified in the configuration file.
+    wid.layout(px, py, pw, ph);
+
+    // Use the dimension and location properties as specified in the
+    // configuration file or from the layout widget.
+    _object = makeObject(props, screenw, screenh);
+
+    // Remove automatically generated properties, so the layout looks
+    // the same next time around, or restore x and y to preserve negative coords.
+    if (userx)
+        props->setIntValue("x", savex);
+    else
+        props->removeChild("x");
+
+    if (usery)
+        props->setIntValue("y", savey);
+    else
+        props->removeChild("y");
+
+    if (!userw) props->removeChild("width");
+    if (!userh) props->removeChild("height");
+
+    if (_object != 0) {
+        _object->reveal();
+    } else {
+        SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
+               << props->getStringValue("name", "[unnamed]")
+               << " does not contain a proper GUI definition");
+    }
+}
+
+puObject *
+FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeight)
+{
+    if (!props->getBoolValue("enabled", true))
+        return 0;
+
+    bool presetSize = props->hasValue("width") && props->hasValue("height");
+    int width = props->getIntValue("width", parentWidth);
+    int height = props->getIntValue("height", parentHeight);
+    int x = props->getIntValue("x", (parentWidth - width) / 2);
+    int y = props->getIntValue("y", (parentHeight - height) / 2);
+    string type = props->getName();
+
+    if (type.empty())
+        type = "dialog";
+
+    if (type == "dialog") {
+        puPopup *obj;
+        bool draggable = props->getBoolValue("draggable", true);
+        bool resizable = props->getBoolValue("resizable", false);
+        if (props->getBoolValue("modal", false))
+            obj = new puDialogBox(x, y);
+        else
+            obj = new fgPopup(x, y, resizable, draggable);
+        setupGroup(obj, props, width, height, true);
+        setColor(obj, props);
+        return obj;
+
+    } else if (type == "group") {
+        puGroup *obj = new puGroup(x, y);
+        setupGroup(obj, props, width, height, false);
+        setColor(obj, props);
+        return obj;
+
+    } else if (type == "frame") {
+        puGroup *obj = new puGroup(x, y);
+        setupGroup(obj, props, width, height, true);
+        setColor(obj, props);
+        return obj;
+
+    } else if (type == "hrule" || type == "vrule") {
+        puFrame *obj = new puFrame(x, y, x + width, y + height);
+        obj->setBorderThickness(0);
+        setupObject(obj, props);
+        setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
+        return obj;
+
+    } else if (type == "list") {
+        int slider_width = props->getIntValue("slider", 20);
+        fgList *obj = new fgList(x, y, x + width, y + height, props, slider_width);
+        if (presetSize)
+            obj->setSize(width, height);
+        setupObject(obj, props);
+        setColor(obj, props);
+        return obj;
+
+    } else if (type == "airport-list") {
+        AirportList *obj = new AirportList(x, y, x + width, y + height);
+        if (presetSize)
+            obj->setSize(width, height);
+        setupObject(obj, props);
+        setColor(obj, props);
+        return obj;
+
+    } else if (type == "property-list") {
+        PropertyList *obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
+        if (presetSize)
+            obj->setSize(width, height);
+        setupObject(obj, props);
+        setColor(obj, props);
+        return obj;
+
+    } else if (type == "input") {
+        puInput *obj = new puInput(x, y, x + width, y + height);
+        setupObject(obj, props);
+        setColor(obj, props, FOREGROUND|LABEL);
+        return obj;
+
+    } else if (type == "text") {
+        puText *obj = new puText(x, y);
+        setupObject(obj, props);
+
+        // Layed-out objects need their size set, and non-layout ones
+        // get a different placement.
+        if (presetSize)
+            obj->setSize(width, height);
+        else
+            obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
+        setColor(obj, props, LABEL);
+        return obj;
+
+    } else if (type == "checkbox") {
+        puButton *obj;
+        obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
+        setupObject(obj, props);
+        setColor(obj, props, FOREGROUND|LABEL);
+        return obj;
+
+    } else if (type == "radio") {
+        puButton *obj;
+        obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
+        setupObject(obj, props);
+        setColor(obj, props, FOREGROUND|LABEL);
+        return obj;
+
+    } else if (type == "button") {
+        puButton *obj;
+        const char *legend = props->getStringValue("legend", "[none]");
+        if (props->getBoolValue("one-shot", true))
+            obj = new puOneShot(x, y, legend);
+        else
+            obj = new puButton(x, y, legend);
+        if (presetSize)
+            obj->setSize(width, height);
+        setupObject(obj, props);
+        setColor(obj, props);
+        return obj;
+    } else if (type == "map") {
+        MapWidget* mapWidget = new MapWidget(x, y, x + width, y + height);
+        setupObject(mapWidget, props);
+        return mapWidget;
+    } else if (type == "combo") {
+        fgComboBox *obj = new fgComboBox(x, y, x + width, y + height, props,
+                props->getBoolValue("editable", false));
+        setupObject(obj, props);
+        setColor(obj, props, EDITFIELD);
+        return obj;
+
+    } else if (type == "slider") {
+        bool vertical = props->getBoolValue("vertical", false);
+        puSlider *obj = new puSlider(x, y, (vertical ? height : width), vertical);
+        obj->setMinValue(props->getFloatValue("min", 0.0));
+        obj->setMaxValue(props->getFloatValue("max", 1.0));
+        obj->setStepSize(props->getFloatValue("step"));
+        obj->setSliderFraction(props->getFloatValue("fraction"));
+#if PLIB_VERSION > 185
+        obj->setPageStepSize(props->getFloatValue("pagestep"));
+#endif
+        setupObject(obj, props);
+        if (presetSize)
+            obj->setSize(width, height);
+        setColor(obj, props, FOREGROUND|LABEL);
+        return obj;
+
+    } else if (type == "dial") {
+        puDial *obj = new puDial(x, y, width);
+        obj->setMinValue(props->getFloatValue("min", 0.0));
+        obj->setMaxValue(props->getFloatValue("max", 1.0));
+        obj->setWrap(props->getBoolValue("wrap", true));
+        setupObject(obj, props);
+        setColor(obj, props, FOREGROUND|LABEL);
+        return obj;
+
+    } else if (type == "textbox") {
+        int slider_width = props->getIntValue("slider", 20);
+        int wrap = props->getBoolValue("wrap", true);
+#if PLIB_VERSION > 185
+        puaLargeInput *obj = new puaLargeInput(x, y,
+                x + width, x + height, 11, slider_width, wrap);
+#else
+        puaLargeInput *obj = new puaLargeInput(x, y,
+                x + width, x + height, 2, slider_width, wrap);
+#endif
+
+        if (props->getBoolValue("editable"))
+            obj->enableInput();
+        else
+            obj->disableInput();
+
+        if (presetSize)
+            obj->setSize(width, height);
+        setupObject(obj, props);
+        setColor(obj, props, FOREGROUND|LABEL);
+
+        int top = props->getIntValue("top-line", 0);
+        obj->setTopLineInWindow(top < 0 ? unsigned(-1) >> 1 : top);
+        return obj;
+
+    } else if (type == "select") {
+        fgSelectBox *obj = new fgSelectBox(x, y, x + width, y + height, props);
+        setupObject(obj, props);
+        setColor(obj, props, EDITFIELD);
+        return obj;
+    } else if (type == "waypointlist") {
+        ScrolledWaypointList* obj = new ScrolledWaypointList(x, y, width, height);
+        setupObject(obj, props);
+        return obj;
+    } else {
+        return 0;
+    }
+}
+
+void
+FGPUIDialog::setupObject (puObject *object, SGPropertyNode *props)
+{
+    GUIInfo *info = new GUIInfo(this);
+    object->setUserData(info);
+    _info.push_back(info);
+    object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
+    object->makeReturnDefault(props->getBoolValue("default"));
+    info->node = props;
+
+    if (props->hasValue("legend")) {
+        info->legend = props->getStringValue("legend");
+        object->setLegend(info->legend.c_str());
+    }
+
+    if (props->hasValue("label")) {
+        info->label = props->getStringValue("label");
+        object->setLabel(info->label.c_str());
+    }
+
+    if (props->hasValue("border"))
+        object->setBorderThickness( props->getIntValue("border", 2) );
+
+    if (SGPropertyNode *nft = props->getNode("font", false)) {
+       FGFontCache *fc = globals->get_fontcache();
+       puFont *lfnt = fc->get(nft);
+       object->setLabelFont(*lfnt);
+       object->setLegendFont(*lfnt);
+    } else {
+       object->setLabelFont(*_font);
+    }
+
+    if (props->hasChild("visible")) {
+      ConditionalObject* cnd = new ConditionalObject("visible", object);
+      cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("visible")));
+      _conditionalObjects.push_back(cnd);
+    }
+
+    if (props->hasChild("enable")) {
+      ConditionalObject* cnd = new ConditionalObject("enable", object);
+      cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("enable")));
+      _conditionalObjects.push_back(cnd);
+    }
+
+    string type = props->getName();
+    if (type == "input" && props->getBoolValue("live"))
+        object->setDownCallback(action_callback);
+
+    if (type == "text") {
+        const char *format = props->getStringValue("format", 0);
+        if (format) {
+            info->fmt_type = validate_format(format);
+            if (info->fmt_type != f_INVALID)
+                info->format = format;
+            else
+                SG_LOG(SG_GENERAL, SG_ALERT, "DIALOG: invalid <format> '"
+                        << format << '\'');
+        }
+    }
+
+    if (props->hasValue("property")) {
+        const char *name = props->getStringValue("name");
+        if (name == 0)
+            name = "";
+        const char *propname = props->getStringValue("property");
+        SGPropertyNode_ptr node = fgGetNode(propname, true);
+        if (type == "map") {
+          // mapWidget binds to a sub-tree of properties, and
+          // ignores the puValue mechanism, so special case things here
+          MapWidget* mw = static_cast<MapWidget*>(object);
+          mw->setProperty(node);
+        } else {
+            // normal widget, creating PropertyObject
+            copy_to_pui(node, object);
+            PropertyObject *po = new PropertyObject(name, object, node);
+            _propertyObjects.push_back(po);
+            if (props->getBoolValue("live"))
+                _liveObjects.push_back(po);
+        }
+        
+
+    }
+
+    SGPropertyNode *dest = fgGetNode("/sim/bindings/gui", true);
+    vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
+    if (bindings.size() > 0) {
+        info->key = props->getIntValue("keynum", -1);
+        if (props->hasValue("key"))
+            info->key = getKeyCode(props->getStringValue("key", ""));
+
+        for (unsigned int i = 0; i < bindings.size(); i++) {
+            unsigned int j = 0;
+            SGPropertyNode_ptr binding;
+            while (dest->getChild("binding", j))
+                j++;
+
+            const char *cmd = bindings[i]->getStringValue("command");
+            if (!strcmp(cmd, "nasal"))
+                bindings[i]->setStringValue("module", _module.c_str());
+
+            binding = dest->getChild("binding", j, true);
+            copyProperties(bindings[i], binding);
+            info->bindings.push_back(new SGBinding(binding, globals->get_props()));
+        }
+        object->setCallback(action_callback);
+    }
+}
+
+void
+FGPUIDialog::setupGroup(puGroup *group, SGPropertyNode *props,
+       int width, int height, bool makeFrame)
+{
+    setupObject(group, props);
+
+    if (makeFrame) {
+        puFrame* f = new puFrame(0, 0, width, height);
+        setColor(f, props);
+    }
+
+    int nChildren = props->nChildren();
+    for (int i = 0; i < nChildren; i++)
+        makeObject(props->getChild(i), width, height);
+    group->close();
+}
+
+void
+FGPUIDialog::setColor(puObject *object, SGPropertyNode *props, int which)
+{
+    string type = props->getName();
+    if (type.empty())
+        type = "dialog";
+    if (type == "textbox" && props->getBoolValue("editable"))
+        type += "-editable";
+
+    FGColor c(_gui->getColor("background"));
+    c.merge(_gui->getColor(type));
+    c.merge(props->getNode("color"));
+    if (c.isValid())
+        object->setColourScheme(c.red(), c.green(), c.blue(), c.alpha());
+
+    const struct {
+        int mask;
+        int id;
+        const char *name;
+        const char *cname;
+    } pucol[] = {
+        { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
+        { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
+        { HIGHLIGHT,  PUCOL_HIGHLIGHT,  "highlight",  "color-highlight" },
+        { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
+        { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
+        { MISC,       PUCOL_MISC,       "misc",       "color-misc" },
+        { EDITFIELD,  PUCOL_EDITFIELD,  "editfield",  "color-editfield" },
+    };
+
+    const int numcol = sizeof(pucol) / sizeof(pucol[0]);
+
+    for (int i = 0; i < numcol; i++) {
+        bool dirty = false;
+        c.clear();
+        c.setAlpha(1.0);
+
+        dirty |= c.merge(_gui->getColor(type + '-' + pucol[i].name));
+        if (which & pucol[i].mask)
+            dirty |= c.merge(props->getNode("color"));
+
+        if ((pucol[i].mask == LABEL) && !c.isValid())
+            dirty |= c.merge(_gui->getColor("label"));
+
+        dirty |= c.merge(props->getNode(pucol[i].cname));
+
+        if (c.isValid() && dirty)
+            object->setColor(pucol[i].id, c.red(), c.green(), c.blue(), c.alpha());
+    }
+}
+
+
+static struct {
+    const char *name;
+    int key;
+} keymap[] = {
+    {"backspace", 8},
+    {"tab", 9},
+    {"return", 13},
+    {"enter", 13},
+    {"esc", 27},
+    {"escape", 27},
+    {"space", ' '},
+    {"&amp;", '&'},
+    {"and", '&'},
+    {"&lt;", '<'},
+    {"&gt;", '>'},
+    {"f1", PU_KEY_F1},
+    {"f2", PU_KEY_F2},
+    {"f3", PU_KEY_F3},
+    {"f4", PU_KEY_F4},
+    {"f5", PU_KEY_F5},
+    {"f6", PU_KEY_F6},
+    {"f7", PU_KEY_F7},
+    {"f8", PU_KEY_F8},
+    {"f9", PU_KEY_F9},
+    {"f10", PU_KEY_F10},
+    {"f11", PU_KEY_F11},
+    {"f12", PU_KEY_F12},
+    {"left", PU_KEY_LEFT},
+    {"up", PU_KEY_UP},
+    {"right", PU_KEY_RIGHT},
+    {"down", PU_KEY_DOWN},
+    {"pageup", PU_KEY_PAGE_UP},
+    {"pagedn", PU_KEY_PAGE_DOWN},
+    {"home", PU_KEY_HOME},
+    {"end", PU_KEY_END},
+    {"insert", PU_KEY_INSERT},
+    {0, -1},
+};
+
+int
+FGPUIDialog::getKeyCode(const char *str)
+{
+    enum {
+        CTRL = 0x1,
+        SHIFT = 0x2,
+        ALT = 0x4,
+    };
+
+    while (*str == ' ')
+        str++;
+
+    char *buf = new char[strlen(str) + 1];
+    strcpy(buf, str);
+    char *s = buf + strlen(buf);
+    while (s > str && s[-1] == ' ')
+        s--;
+    *s = 0;
+    s = buf;
+
+    int mod = 0;
+    while (1) {
+        if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
+            s += 5, mod |= CTRL;
+        else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
+            s += 6, mod |= SHIFT;
+        else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
+            s += 4, mod |= ALT;
+        else
+            break;
+    }
+
+    int key = -1;
+    if (strlen(s) == 1 && isascii(*s)) {
+        key = *s;
+        if (mod & SHIFT)
+            key = toupper(key);
+        if (mod & CTRL)
+            key = toupper(key) - '@';
+        /* if (mod & ALT)
+            ;   // Alt not propagated to the gui
+        */
+    } else {
+        for (char *t = s; *t; t++)
+            *t = tolower(*t);
+        for (int i = 0; keymap[i].name; i++) {
+            if (!strcmp(s, keymap[i].name)) {
+                key = keymap[i].key;
+                break;
+            }
+        }
+    }
+    delete[] buf;
+    return key;
+}
+
+void\fFGPUIDialog::relayout()
+{
+  _needsRelayout = false;
+  
+  int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
+  int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
+
+  bool userx = _props->hasValue("x");
+  bool usery = _props->hasValue("y");
+  bool userw = _props->hasValue("width");
+  bool userh = _props->hasValue("height");
+
+  // Let the layout widget work in the same property subtree.
+  LayoutWidget wid(_props);
+  wid.setDefaultFont(_font, int(_font->getPointSize()));
+
+  int pw = 0, ph = 0;
+  int px, py, savex, savey;
+  if (!userw || !userh) {
+    wid.calcPrefSize(&pw, &ph);
+  }
+  
+  pw = _props->getIntValue("width", pw);
+  ph = _props->getIntValue("height", ph);
+  px = savex = _props->getIntValue("x", (screenw - pw) / 2);
+  py = savey = _props->getIntValue("y", (screenh - ph) / 2);
+
+  // Negative x/y coordinates are interpreted as distance from the top/right
+  // corner rather than bottom/left.
+  if (userx && px < 0)
+    px = screenw - pw + px;
+  if (usery && py < 0)
+    py = screenh - ph + py;
+
+  // Define "x", "y", "width" and/or "height" in the property tree if they
+  // are not specified in the configuration file.
+  wid.layout(px, py, pw, ph);
+
+  applySize(_object);
+  
+  // Remove automatically generated properties, so the layout looks
+  // the same next time around, or restore x and y to preserve negative coords.
+  if (userx)
+      _props->setIntValue("x", savex);
+  else
+      _props->removeChild("x");
+
+  if (usery)
+      _props->setIntValue("y", savey);
+  else
+      _props->removeChild("y");
+
+  if (!userw) _props->removeChild("width");
+  if (!userh) _props->removeChild("height");
+}
+
+void
+FGPUIDialog::applySize(puObject *object)
+{
+  // compound plib widgets use setUserData() for internal purposes, so refuse
+  // to descend into anything that has other bits set than the following
+  const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
+          |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
+          |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
+
+  int type = object->getType();
+  if ((type & PUCLASS_GROUP) && !(type & ~validUserData)) {
+    puObject* c = ((puGroup *)object)->getFirstChild();
+    for (; c != NULL; c = c->getNextObject()) {
+      applySize(c);
+    } // of child iteration
+  } // of group object case
+  
+  GUIInfo *info = (GUIInfo *)object->getUserData();
+  if (!info)
+    return;
+
+  SGPropertyNode *n = info->node;
+  if (!n) {
+    SG_LOG(SG_GENERAL, SG_ALERT, "FGDialog::applySize: no props");
+    return;
+  }
+  
+  int x = n->getIntValue("x");
+  int y = n->getIntValue("y");
+  int w = n->getIntValue("width", 4);
+  int h = n->getIntValue("height", 4);
+  object->setPosition(x, y);
+  object->setSize(w, h);
+}
+
+////////////////////////////////////////////////////////////////////////
+// Implementation of FGDialog::PropertyObject.
+////////////////////////////////////////////////////////////////////////
+
+FGPUIDialog::PropertyObject::PropertyObject(const char *n,
+        puObject *o, SGPropertyNode_ptr p) :
+    name(n),
+    object(o),
+    node(p)
+{
+}
+
+
+
+\f
+////////////////////////////////////////////////////////////////////////
+// Implementation of fgValueList and derived pui widgets
+////////////////////////////////////////////////////////////////////////
+
+
+fgValueList::fgValueList(SGPropertyNode *p) :
+    _props(p)
+{
+    make_list();
+}
+
+void
+fgValueList::update()
+{
+    destroy_list();
+    make_list();
+}
+
+fgValueList::~fgValueList()
+{
+    destroy_list();
+}
+
+void
+fgValueList::make_list()
+{
+  SGPropertyNode_ptr values = _props;
+  const char* vname = "value";
+  
+  if (_props->hasChild("properties")) {
+    // dynamic values, read from a property's children
+    const char* path = _props->getStringValue("properties");
+    values = fgGetNode(path, true);
+  }
+  
+  if (_props->hasChild("property-name")) {
+    vname = _props->getStringValue("property-name");
+  }
+  
+  vector<SGPropertyNode_ptr> value_nodes = values->getChildren(vname);
+  _list = new char *[value_nodes.size() + 1];
+  unsigned int i;
+  for (i = 0; i < value_nodes.size(); i++) {
+    _list[i] = strdup((char *)value_nodes[i]->getStringValue());
+  }
+  _list[i] = 0;
+}
+
+void
+fgValueList::destroy_list()
+{
+    for (int i = 0; _list[i] != 0; i++)
+        if (_list[i])
+            free(_list[i]);
+    delete[] _list;
+}
+
+
+
+void
+fgList::update()
+{
+    fgValueList::update();
+    int top = getTopItem();
+    newList(_list);
+    setTopItem(top);
+}
+
+void fgComboBox::update()
+{
+  if (_inHit) {
+    return;
+  }
+  
+  std::string curValue(getStringValue());
+  fgValueList::update();
+  newList(_list);
+  int currentItem = puaComboBox::getCurrentItem();
+
+  
+// look for the previous value, in the new list
+  for (int i = 0; _list[i] != 0; i++) {
+    if (_list[i] == curValue) {
+    // don't set the current item again; this is important to avoid
+    // recursion here if the combo callback ultimately causes another dialog-update
+      if (i != currentItem) {
+        puaComboBox::setCurrentItem(i);
+      }
+      
+      return;
+    }
+  } // of list values iteration
+  
+// cound't find current item, default to first
+  if (currentItem != 0) {
+    puaComboBox::setCurrentItem(0);
+  }
+}
+
+int fgComboBox::checkHit(int b, int up, int x, int y)
+{
+  _inHit = true;
+  int r = puaComboBox::checkHit(b, up, x, y);
+  _inHit = false;
+  return r;
+}
+
+void fgComboBox::setSize(int w, int h)
+{
+  puaComboBox::setSize(w, h);
+  recalc_bbox();
+}
+
+void fgComboBox::recalc_bbox()
+{
+// bug-fix for issue #192
+// http://code.google.com/p/flightgear-bugs/issues/detail?id=192
+// puaComboBox is including the height of its popupMenu in the height
+// computation, which breaks layout computations.
+// this implementation skips popup-menus
+  
+  puBox contents;
+  contents.empty();
+  
+  for (puObject *bo = dlist; bo != NULL; bo = bo -> getNextObject()) {
+    if (bo->getType() & PUCLASS_POPUPMENU) {
+      continue;
+    }
+    
+    contents.extend (bo -> getBBox()) ;
+  }
+  
+  abox.max[0] = abox.min[0] + contents.max[0] ;
+  abox.max[1] = abox.min[1] + contents.max[1] ;
+
+  puObject::recalc_bbox () ;
+
+}
+
+// end of FGPUIDialog.cxx
diff --git a/src/GUI/FGPUIDialog.hxx b/src/GUI/FGPUIDialog.hxx
new file mode 100644 (file)
index 0000000..e3a158e
--- /dev/null
@@ -0,0 +1,277 @@
+// FGPUIDialog.hxx - XML-configured dialog box.
+
+#ifndef FG_PUI_DIALOG_HXX
+#define FG_PUI_DIALOG_HXX 1
+
+#include "dialog.hxx"
+
+#include <plib/puAux.h>
+
+#include <simgear/props/props.hxx>
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/props/condition.hxx>
+
+#include <vector>
+
+
+// ugly temporary workaround for plib's lack of user defined class ids  FIXME
+#define FGCLASS_LIST          0x00000001
+#define FGCLASS_AIRPORTLIST   0x00000002
+#define FGCLASS_PROPERTYLIST  0x00000004
+#define FGCLASS_WAYPOINTLIST  0x00000008
+
+class GUI_ID { public: GUI_ID(int id) : id(id) {} virtual ~GUI_ID() {} int id; };
+
+
+
+class NewGUI;
+class FGColor;
+class puObject;
+class puFont;
+
+/**
+ * An XML-configured dialog box.
+ *
+ * The GUI manager stores only the property tree for the dialog
+ * boxes.  This class creates a PUI dialog box on demand from
+ * the properties in that tree.  The manager recreates the dialog
+ * every time it needs to show it.
+ */
+class FGPUIDialog : public FGDialog
+{
+public:
+
+    /**
+     * Construct a new GUI widget configured by a property tree.
+     *
+     * The configuration properties are not part of the main
+     * FlightGear property tree; the GUI manager reads them
+     * from individual configuration files.
+     *
+     * @param props A property tree describing the dialog.
+     */
+    FGPUIDialog (SGPropertyNode * props);
+
+
+    /**
+     * Destructor.
+     */
+    virtual ~FGPUIDialog ();
+
+
+    /**
+     * Update the values of all GUI objects with a specific name,
+     * or all if name is 0 (default).
+     *
+     * This method copies values from the FlightGear property tree to
+     * the GUI object(s).
+     *
+     * @param objectName The name of the GUI object(s) to update.
+     *        Use the empty name for all unnamed objects.
+     */
+    virtual void updateValues (const char * objectName = 0);
+
+
+    /**
+     * Apply the values of all GUI objects with a specific name,
+     * or all if name is 0 (default)
+     *
+     * This method copies values from the GUI object(s) to the
+     * FlightGear property tree.
+     *
+     * @param objectName The name of the GUI object(s) to update.
+     *        Use the empty name for all unnamed objects.
+     */
+    virtual void applyValues (const char * objectName = 0);
+
+
+    /**
+     * Update state.  Called on active dialogs before rendering.
+     */
+    virtual void update ();
+
+    /**
+     * Recompute the dialog's layout
+     */
+    void relayout();
+    
+    
+    void setNeedsLayout() {
+      _needsRelayout = true;
+    }
+private:
+
+    enum {
+        BACKGROUND = 0x01,
+        FOREGROUND = 0x02,
+        HIGHLIGHT = 0x04,
+        LABEL = 0x08,
+        LEGEND = 0x10,
+        MISC = 0x20,
+        EDITFIELD = 0x40
+    };
+
+    // Show the dialog.
+    void display (SGPropertyNode * props);
+
+    // Build the dialog or a subobject of it.
+    puObject * makeObject (SGPropertyNode * props,
+                           int parentWidth, int parentHeight);
+
+    // Common configuration for all GUI objects.
+    void setupObject (puObject * object, SGPropertyNode * props);
+
+    // Common configuration for all GUI group objects.
+    void setupGroup (puGroup * group, SGPropertyNode * props,
+                     int width, int height, bool makeFrame = false);
+
+    // Set object colors: the "which" argument defines which color qualities
+    // (PUCOL_LABEL, etc.) should pick up the <color> property.
+    void setColor(puObject * object, SGPropertyNode * props, int which = 0);
+
+    // return key code number for keystring
+    int getKeyCode(const char *keystring);
+
+    /**
+     * Apply layout sizes to a tree of puObjects
+     */
+    void applySize(puObject *object);
+
+    // The top-level PUI object.
+    puObject * _object;
+
+    // The GUI subsystem.
+    NewGUI * _gui;
+
+    // The dialog font. Defaults to the global gui font, but can get
+    // overridden by a top level font definition.
+    puFont * _font;
+
+    // The source xml tree, so that we can pass data back, such as the
+    // last position.
+    SGPropertyNode_ptr _props;
+
+    bool _needsRelayout;
+
+    // Nasal module.
+    std::string _module;
+    SGPropertyNode_ptr _nasal_close;
+
+    // PUI provides no way for userdata to be deleted automatically
+    // with a GUI object, so we have to keep track of all the special
+    // data we allocated and then free it manually when the dialog
+    // closes.
+    std::vector<void *> _info;
+    struct PropertyObject {
+        PropertyObject (const char * name,
+                        puObject * object,
+                        SGPropertyNode_ptr node);
+        std::string name;
+        puObject * object;
+        SGPropertyNode_ptr node;
+    };
+    std::vector<PropertyObject *> _propertyObjects;
+    std::vector<PropertyObject *> _liveObjects;
+    
+    class ConditionalObject : public SGConditional
+    {
+    public:
+      ConditionalObject(const std::string& aName, puObject* aPu) :
+        _name(aName),
+        _pu(aPu)
+      { ; }
+    
+      void update(FGPUIDialog* aDlg);
+    
+    private:
+      const std::string _name;
+      puObject* _pu;      
+    };
+    
+    typedef SGSharedPtr<ConditionalObject> ConditionalObjectRef;
+    std::vector<ConditionalObjectRef> _conditionalObjects;
+};
+
+//
+// Custom subclass of puPopup to implement "draggable" windows in the
+// interface.  Note that this is a subclass of puPopup, not
+// puDialogBox.  Sadly, PUI (mis)uses subclassing to implement a
+// boolean property: modality.  That means that we can't implement
+// dragging of both puPopup windows and puDialogBoxes with the same
+// code.  Rather than duplicate code, I've chosen to implement only
+// "non-modal dragability" here.  Modal dialog boxes (like the exit
+// confirmation) are not draggable.
+//
+class fgPopup : public puPopup {
+public:
+    fgPopup(int x, int y, bool r = true, bool d = true) :
+            puPopup(x, y), _draggable(d), _resizable(r), _dragging(false)
+    {}
+    int checkHit(int b, int up, int x, int y);
+    int checkKey(int key, int updown);
+    int getHitObjects(puObject *, int x, int y);
+    puObject *getKeyObject(puObject *, int key);
+    puObject *getActiveInputField(puObject *);
+    void applySize(puObject *);
+private:
+    enum { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 };
+    bool _draggable;
+    bool _resizable;
+    bool _dragging;
+    int _resizing;
+    int _start_cursor;
+    int _cursor;
+    int _dlgX, _dlgY, _dlgW, _dlgH;
+    int _startX, _startY;
+};
+
+
+class fgValueList {
+public:
+    fgValueList(SGPropertyNode *p);
+    virtual ~fgValueList();
+    virtual void update();
+
+protected:
+    char **_list;
+
+private:
+    void make_list();
+    void destroy_list();
+    SGPropertyNode_ptr _props;
+};
+
+
+class fgList : public fgValueList, public puaList, public GUI_ID {
+public:
+    fgList(int x1, int y1, int x2, int y2, SGPropertyNode *p, int sw) :
+            fgValueList(p), puaList(x1, y1, x2, y2, _list, sw), GUI_ID(FGCLASS_LIST) {}
+    void update();
+};
+
+class fgComboBox : public fgValueList, public puaComboBox {
+public:
+    fgComboBox(int x1, int y1, int x2, int y2, SGPropertyNode *p, bool editable) :
+        fgValueList(p), 
+        puaComboBox(x1, y1, x2, y2, _list, editable),
+        _inHit(false)
+      {}
+        
+    void update();
+    
+    virtual void setSize(int w, int h);
+    
+    virtual int checkHit(int b, int up, int x, int y);
+    
+    virtual void recalc_bbox();
+private:
+    bool _inHit;
+};
+
+class fgSelectBox : public fgValueList, public puaSelectBox {
+public:
+    fgSelectBox(int x1, int y1, int x2, int y2, SGPropertyNode *p) :
+        fgValueList(p), puaSelectBox(x1, y1, x2, y2, _list) {}
+};
+
+#endif // __DIALOG_HXX
index 46a386b59601d93a64e8a095e920b2046139e4fe..4176d751ff63d10594e03e55c87f375e02a9799a 100644 (file)
@@ -10,7 +10,7 @@
 
 #include <plib/pu.h>
 
-#include "dialog.hxx" // for GUI_ID
+#include "FGPUIDialog.hxx" // for GUI_ID
 
 // forward decls
 class puaScrollBar;
index 539002992d90ec5fbd5950ba159d40b117d1fb8a..43827164c84544db685041a0201108af6f5fa821 100644 (file)
 #  include "config.h"
 #endif
 
-#include <simgear/structure/SGBinding.hxx>
-#include <simgear/props/props_io.hxx>
-
-#include <Scripting/NasalSys.hxx>
-#include <Main/fg_os.hxx>
-#include <Main/globals.hxx>
-#include <Main/fg_props.hxx>
-
 #include "dialog.hxx"
-#include "new_gui.hxx"
-#include "AirportList.hxx"
-#include "property_list.hxx"
-#include "layout.hxx"
-#include "WaypointList.hxx"
-#include "MapWidget.hxx"
-#include "FGFontCache.hxx"
-#include "FGColor.hxx"
-
-enum format_type { f_INVALID, f_INT, f_LONG, f_FLOAT, f_DOUBLE, f_STRING };
-static const int FORMAT_BUFSIZE = 255;
-static const int RESIZE_MARGIN = 7;
 
-
-/**
- * Makes sure the format matches '%[ -+#]?\d*(\.\d*)?(l?[df]|s)', with
- * only one number or string placeholder and otherwise arbitrary prefix
- * and postfix containing only quoted percent signs (%%).
- */
-static format_type
-validate_format(const char *f)
+FGDialog::FGDialog (SGPropertyNode *)
 {
-    bool l = false;
-    format_type type;
-    for (; *f; f++) {
-        if (*f == '%') {
-            if (f[1] == '%')
-                f++;
-            else
-                break;
-        }
-    }
-    if (*f++ != '%')
-        return f_INVALID;
-    while (*f == ' ' || *f == '+' || *f == '-' || *f == '#' || *f == '0')
-        f++;
-    while (*f && isdigit(*f))
-        f++;
-    if (*f == '.') {
-        f++;
-        while (*f && isdigit(*f))
-            f++;
-    }
-
-    if (*f == 'l')
-        l = true, f++;
-
-    if (*f == 'd') {
-        type = l ? f_LONG : f_INT;
-    } else if (*f == 'f')
-        type = l ? f_DOUBLE : f_FLOAT;
-    else if (*f == 's') {
-        if (l)
-            return f_INVALID;
-        type = f_STRING;
-    } else
-        return f_INVALID;
-
-    for (++f; *f; f++) {
-        if (*f == '%') {
-            if (f[1] == '%')
-                f++;
-            else
-                return f_INVALID;
-        }
-    }
-    return type;
-}
-
-
-////////////////////////////////////////////////////////////////////////
-// Implementation of GUIInfo.
-////////////////////////////////////////////////////////////////////////
-
-/**
- * User data for a GUI object.
- */
-struct GUIInfo
-{
-    GUIInfo(FGDialog *d);
-    virtual ~GUIInfo();
-    void apply_format(SGPropertyNode *);
-
-    FGDialog *dialog;
-    SGPropertyNode_ptr node;
-    vector <SGBinding *> bindings;
-    int key;
-    string label, legend, text, format;
-    format_type fmt_type;
-};
-
-GUIInfo::GUIInfo (FGDialog *d) :
-    dialog(d),
-    key(-1),
-    fmt_type(f_INVALID)
-{
-}
-
-GUIInfo::~GUIInfo ()
-{
-    for (unsigned int i = 0; i < bindings.size(); i++) {
-        delete bindings[i];
-        bindings[i] = 0;
-    }
-}
-
-void GUIInfo::apply_format(SGPropertyNode *n)
-{
-    char buf[FORMAT_BUFSIZE + 1];
-    if (fmt_type == f_INT)
-        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getIntValue());
-    else if (fmt_type == f_LONG)
-        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getLongValue());
-    else if (fmt_type == f_FLOAT)
-        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getFloatValue());
-    else if (fmt_type == f_DOUBLE)
-        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getDoubleValue());
-    else
-        snprintf(buf, FORMAT_BUFSIZE, format.c_str(), n->getStringValue());
-
-    buf[FORMAT_BUFSIZE] = '\0';
-    text = buf;
-}
-
-
-\f
-/**
- * Key handler.
- */
-int fgPopup::checkKey(int key, int updown)
-{
-    if (updown == PU_UP || !isVisible() || !isActive() || window != puGetWindow())
-        return false;
-
-    puObject *input = getActiveInputField(this);
-    if (input)
-        return input->checkKey(key, updown);
-
-    puObject *object = getKeyObject(this, key);
-    if (!object)
-        return puPopup::checkKey(key, updown);
-
-    // invokeCallback() isn't enough; we need to simulate a mouse button press
-    object->checkHit(PU_LEFT_BUTTON, PU_DOWN,
-            (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
-            (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
-    object->checkHit(PU_LEFT_BUTTON, PU_UP,
-            (object->getABox()->min[0] + object->getABox()->max[0]) / 2,
-            (object->getABox()->min[1] + object->getABox()->max[1]) / 2);
-    return true;
-}
-
-puObject *fgPopup::getKeyObject(puObject *object, int key)
-{
-    puObject *ret;
-    if (object->getType() & PUCLASS_GROUP)
-        for (puObject *obj = ((puGroup *)object)->getFirstChild();
-                obj; obj = obj->getNextObject())
-            if ((ret = getKeyObject(obj, key)))
-                return ret;
-
-    GUIInfo *info = (GUIInfo *)object->getUserData();
-    if (info && info->key == key)
-        return object;
-
-    return 0;
-}
-
-puObject *fgPopup::getActiveInputField(puObject *object)
-{
-    puObject *ret;
-    if (object->getType() & PUCLASS_GROUP)
-        for (puObject *obj = ((puGroup *)object)->getFirstChild();
-                obj; obj = obj->getNextObject())
-            if ((ret = getActiveInputField(obj)))
-                return ret;
-
-    if (object->getType() & (PUCLASS_INPUT|PUCLASS_LARGEINPUT)
-            && ((puInput *)object)->isAcceptingInput())
-        return object;
-
-    return 0;
-}
-
-/**
- * Mouse handler.
- */
-int fgPopup::checkHit(int button, int updown, int x, int y)
-{
-    int result = 0;
-    if (updown != PU_DRAG && !_dragging)
-        result = puPopup::checkHit(button, updown, x, y);
-
-    if (!_draggable)
-       return result;
-
-    // This is annoying.  We would really want a true result from the
-    // superclass to indicate "handled by child object", but all it
-    // tells us is that the pointer is inside the dialog.  So do the
-    // intersection test (again) to make sure we don't start a drag
-    // when inside controls.
-
-    if (updown == PU_DOWN && !_dragging) {
-        if (!result)
-            return 0;
-        int global_drag = fgGetKeyModifiers() & KEYMOD_SHIFT;
-        int global_resize = fgGetKeyModifiers() & KEYMOD_CTRL;
-
-        int hit = getHitObjects(this, x, y);
-        if (hit & PUCLASS_LIST)  // ctrl-click in property browser (toggle bool)
-            return result;
-        if (!global_resize && hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
-                |PUCLASS_LARGEINPUT|PUCLASS_SCROLLBAR))
-            return result;
-
-        getPosition(&_dlgX, &_dlgY);
-        getSize(&_dlgW, &_dlgH);
-        _start_cursor = fgGetMouseCursor();
-        _dragging = true;
-        _startX = x;
-        _startY = y;
-
-        // check and prepare for resizing
-        static const int cursor[] = {
-            MOUSE_CURSOR_POINTER, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
-            MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
-            MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT, 0,
-        };
-
-        _resizing = 0;
-        if (!global_drag && _resizable) {
-            int hmargin = global_resize ? _dlgW / 3 : RESIZE_MARGIN;
-            int vmargin = global_resize ? _dlgH / 3 : RESIZE_MARGIN;
-
-            if (y - _dlgY < vmargin)
-                _resizing |= BOTTOM;
-            else if (_dlgY + _dlgH - y < vmargin)
-                _resizing |= TOP;
-
-            if (x - _dlgX < hmargin)
-                _resizing |= LEFT;
-            else if (_dlgX + _dlgW - x < hmargin)
-                _resizing |= RIGHT;
-
-            if (!_resizing && global_resize)
-                _resizing = BOTTOM|RIGHT;
-
-            _cursor = cursor[_resizing];
-           if (_resizing && _resizable)
-                fgSetMouseCursor(_cursor);
-       }
-
-    } else if (updown == PU_DRAG && _dragging) {
-        if (_resizing) {
-            GUIInfo *info = (GUIInfo *)getUserData();
-            if (_resizable && info && info->node) {
-                int w = _dlgW;
-                int h = _dlgH;
-                if (_resizing & LEFT)
-                    w += _startX - x;
-                if (_resizing & RIGHT)
-                    w += x - _startX;
-                if (_resizing & TOP)
-                    h += y - _startY;
-                if (_resizing & BOTTOM)
-                    h += _startY - y;
-
-                int prefw, prefh;
-                LayoutWidget wid(info->node);
-                wid.calcPrefSize(&prefw, &prefh);
-                if (w < prefw)
-                    w = prefw;
-                if (h < prefh)
-                    h = prefh;
-
-                int x = _dlgX;
-                int y = _dlgY;
-                if (_resizing & LEFT)
-                    x += _dlgW - w;
-                if (_resizing & BOTTOM)
-                    y += _dlgH - h;
-
-                wid.layout(x, y, w, h);
-                setSize(w, h);
-                setPosition(x, y);
-                applySize(static_cast<puObject *>(this));
-                getFirstChild()->setSize(w, h); // dialog background puFrame
-            }
-        } else {
-            int posX = x + _dlgX - _startX,
-              posY = y + _dlgY - _startY;
-            setPosition(posX, posY);
-            
-            GUIInfo *info = (GUIInfo *)getUserData();
-            if (info && info->node) {
-                info->node->setIntValue("x", posX);
-                info->node->setIntValue("y", posY);
-            }
-        } // re-positioning
-
-    } else if (_dragging) {
-        fgSetMouseCursor(_start_cursor);
-        _dragging = false;
-    }
-    return result;
-}
-
-int fgPopup::getHitObjects(puObject *object, int x, int y)
-{
-    if (!object->isVisible())
-        return 0;
-
-    int type = 0;
-    if (object->getType() & PUCLASS_GROUP)
-        for (puObject *obj = ((puGroup *)object)->getFirstChild();
-                obj; obj = obj->getNextObject())
-            type |= getHitObjects(obj, x, y);
-
-    int cx, cy, cw, ch;
-    object->getAbsolutePosition(&cx, &cy);
-    object->getSize(&cw, &ch);
-    if (x >= cx && x < cx + cw && y >= cy && y < cy + ch)
-        type |= object->getType();
-    return type;
-}
-
-void fgPopup::applySize(puObject *object)
-{
-    // compound plib widgets use setUserData() for internal purposes, so refuse
-    // to descend into anything that has other bits set than the following
-    const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
-            |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
-            |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
-
-    int type = object->getType();
-    if (type & PUCLASS_GROUP && !(type & ~validUserData))
-        for (puObject *obj = ((puGroup *)object)->getFirstChild();
-                obj; obj = obj->getNextObject())
-            applySize(obj);
-
-    GUIInfo *info = (GUIInfo *)object->getUserData();
-    if (!info)
-        return;
-
-    SGPropertyNode *n = info->node;
-    if (!n) {
-        SG_LOG(SG_GENERAL, SG_ALERT, "fgPopup::applySize: no props");
-        return;
-    }
-    int x = n->getIntValue("x");
-    int y = n->getIntValue("y");
-    int w = n->getIntValue("width", 4);
-    int h = n->getIntValue("height", 4);
-    object->setPosition(x, y);
-    object->setSize(w, h);
-}
-
-////////////////////////////////////////////////////////////////////////
-
-void FGDialog::ConditionalObject::update(FGDialog* aDlg)
-{
-  if (_name == "visible") {
-    bool wasVis = _pu->isVisible() != 0;
-    bool newVis = test();
-    
-    if (newVis == wasVis) {
-      return;
-    }
-    
-    if (newVis) { // puObject needs a setVisible. Oh well.
-    _pu->reveal();
-    } else {
-      _pu->hide();
-    }
-  } else if (_name == "enable") {
-    bool wasEnabled = _pu->isActive() != 0;
-    bool newEnable = test();
-    
-    if (wasEnabled == newEnable) {
-      return;
-    }
-    
-    if (newEnable) {
-      _pu->activate();
-    } else {
-      _pu->greyOut();
-    }
-  }
-  
-  aDlg->setNeedsLayout();
-}
-\f////////////////////////////////////////////////////////////////////////
-// Callbacks.
-////////////////////////////////////////////////////////////////////////
-
-/**
- * Action callback.
- */
-static void
-action_callback (puObject *object)
-{
-    GUIInfo *info = (GUIInfo *)object->getUserData();
-    NewGUI *gui = (NewGUI *)globals->get_subsystem("gui");
-    gui->setActiveDialog(info->dialog);
-    int nBindings = info->bindings.size();
-    for (int i = 0; i < nBindings; i++) {
-        info->bindings[i]->fire();
-        if (gui->getActiveDialog() == 0)
-            break;
-    }
-    gui->setActiveDialog(0);
-}
-
-
-\f
-////////////////////////////////////////////////////////////////////////
-// Static helper functions.
-////////////////////////////////////////////////////////////////////////
-
-/**
- * Copy a property value to a PUI object.
- */
-static void
-copy_to_pui (SGPropertyNode *node, puObject *object)
-{
-    using namespace simgear;
-    GUIInfo *info = (GUIInfo *)object->getUserData();
-    if (!info) {
-        SG_LOG(SG_GENERAL, SG_ALERT, "dialog: widget without GUIInfo!");
-        return;   // this can't really happen
-    }
-
-    // Treat puText objects specially, so their "values" can be set
-    // from properties.
-    if (object->getType() & PUCLASS_TEXT) {
-        if (info->fmt_type != f_INVALID)
-            info->apply_format(node);
-        else
-            info->text = node->getStringValue();
-
-        object->setLabel(info->text.c_str());
-        return;
-    }
-
-    switch (node->getType()) {
-    case props::BOOL:
-    case props::INT:
-    case props::LONG:
-        object->setValue(node->getIntValue());
-        break;
-    case props::FLOAT:
-    case props::DOUBLE:
-        object->setValue(node->getFloatValue());
-        break;
-    default:
-        info->text = node->getStringValue();
-        object->setValue(info->text.c_str());
-        break;
-    }
-}
-
-
-static void
-copy_from_pui (puObject *object, SGPropertyNode *node)
-{
-    using namespace simgear;
-    // puText objects are immutable, so should not be copied out
-    if (object->getType() & PUCLASS_TEXT)
-        return;
-
-    switch (node->getType()) {
-    case props::BOOL:
-    case props::INT:
-    case props::LONG:
-        node->setIntValue(object->getIntegerValue());
-        break;
-    case props::FLOAT:
-    case props::DOUBLE:
-        node->setFloatValue(object->getFloatValue());
-        break;
-    default:
-        const char *s = object->getStringValue();
-        if (s)
-            node->setStringValue(s);
-        break;
-    }
-}
-
-
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of FGDialog.
-////////////////////////////////////////////////////////////////////////
-
-FGDialog::FGDialog (SGPropertyNode *props) :
-    _object(0),
-    _gui((NewGUI *)globals->get_subsystem("gui")),
-    _props(props),
-    _needsRelayout(false)
-{
-    _module = string("__dlg:") + props->getStringValue("name", "[unnamed]");
-        
-    SGPropertyNode *nasal = props->getNode("nasal");
-    if (nasal) {
-        _nasal_close = nasal->getNode("close");
-        SGPropertyNode *open = nasal->getNode("open");
-        if (open) {
-            const char *s = open->getStringValue();
-            FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
-            if (nas)
-                nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
-        }
-    }
-    display(props);
-}
-
-FGDialog::~FGDialog ()
-{
-    int x, y;
-    _object->getAbsolutePosition(&x, &y);
-    _props->setIntValue("lastx", x);
-    _props->setIntValue("lasty", y);
-
-    FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
-    if (nas) {
-        if (_nasal_close) {
-            const char *s = _nasal_close->getStringValue();
-            nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
-        }
-        nas->deleteModule(_module.c_str());
-    }
-
-    puDeleteObject(_object);
-
-    unsigned int i;
-                                // Delete all the info objects we
-                                // were forced to keep around because
-                                // PUI cannot delete its own user data.
-    for (i = 0; i < _info.size(); i++) {
-        delete (GUIInfo *)_info[i];
-        _info[i] = 0;
-    }
-                                // Finally, delete the property links.
-    for (i = 0; i < _propertyObjects.size(); i++) {
-        delete _propertyObjects[i];
-        _propertyObjects[i] = 0;
-    }
-}
-
-void
-FGDialog::updateValues (const char *objectName)
-{
-    if (objectName && !objectName[0])
-        objectName = 0;
-
-  for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
-    const string &name = _propertyObjects[i]->name;
-    if (objectName && name != objectName) {
-      continue;
-    }
-    
-    puObject *widget = _propertyObjects[i]->object;
-    int widgetType = widget->getType();
-    if (widgetType & PUCLASS_LIST) {
-      GUI_ID* gui_id = dynamic_cast<GUI_ID *>(widget);
-      if (gui_id && (gui_id->id & FGCLASS_LIST)) {
-        fgList *pl = static_cast<fgList*>(widget);
-        pl->update();
-      } else {
-        copy_to_pui(_propertyObjects[i]->node, widget);
-      }
-    } else if (widgetType & PUCLASS_COMBOBOX) {
-      fgComboBox* combo = static_cast<fgComboBox*>(widget);
-      combo->update();
-    } else {
-      copy_to_pui(_propertyObjects[i]->node, widget);
-    }
-  } // of property objects iteration
-}
-
-void
-FGDialog::applyValues (const char *objectName)
-{
-    if (objectName && !objectName[0])
-        objectName = 0;
-
-    for (unsigned int i = 0; i < _propertyObjects.size(); i++) {
-        const string &name = _propertyObjects[i]->name;
-        if (objectName && name != objectName)
-            continue;
-
-        copy_from_pui(_propertyObjects[i]->object,
-                      _propertyObjects[i]->node);
-    }
-}
-
-void
-FGDialog::update ()
-{
-    for (unsigned int i = 0; i < _liveObjects.size(); i++) {
-        puObject *obj = _liveObjects[i]->object;
-        if (obj->getType() & PUCLASS_INPUT && ((puInput *)obj)->isAcceptingInput())
-            continue;
-
-        copy_to_pui(_liveObjects[i]->node, obj);
-    }
     
-  for (unsigned int j=0; j < _conditionalObjects.size(); ++j) {
-    _conditionalObjects[j]->update(this);
-  }
-  
-  if (_needsRelayout) {
-    relayout();
-  }
-}
-
-void
-FGDialog::display (SGPropertyNode *props)
-{
-    if (_object != 0) {
-        SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
-        return;
-    }
-
-    int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
-    int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
-
-    bool userx = props->hasValue("x");
-    bool usery = props->hasValue("y");
-    bool userw = props->hasValue("width");
-    bool userh = props->hasValue("height");
-
-    // Let the layout widget work in the same property subtree.
-    LayoutWidget wid(props);
-
-    SGPropertyNode *fontnode = props->getNode("font");
-    if (fontnode) {
-        FGFontCache *fc = globals->get_fontcache();
-        _font = fc->get(fontnode);
-    } else {
-        _font = _gui->getDefaultFont();
-    }
-    wid.setDefaultFont(_font, int(_font->getPointSize()));
-
-    int pw = 0, ph = 0;
-    int px, py, savex, savey;
-    if (!userw || !userh)
-        wid.calcPrefSize(&pw, &ph);
-    pw = props->getIntValue("width", pw);
-    ph = props->getIntValue("height", ph);
-    px = savex = props->getIntValue("x", (screenw - pw) / 2);
-    py = savey = props->getIntValue("y", (screenh - ph) / 2);
-
-    // Negative x/y coordinates are interpreted as distance from the top/right
-    // corner rather than bottom/left.
-    if (userx && px < 0)
-        px = screenw - pw + px;
-    if (usery && py < 0)
-        py = screenh - ph + py;
-
-    // Define "x", "y", "width" and/or "height" in the property tree if they
-    // are not specified in the configuration file.
-    wid.layout(px, py, pw, ph);
-
-    // Use the dimension and location properties as specified in the
-    // configuration file or from the layout widget.
-    _object = makeObject(props, screenw, screenh);
-
-    // Remove automatically generated properties, so the layout looks
-    // the same next time around, or restore x and y to preserve negative coords.
-    if (userx)
-        props->setIntValue("x", savex);
-    else
-        props->removeChild("x");
-
-    if (usery)
-        props->setIntValue("y", savey);
-    else
-        props->removeChild("y");
-
-    if (!userw) props->removeChild("width");
-    if (!userh) props->removeChild("height");
-
-    if (_object != 0) {
-        _object->reveal();
-    } else {
-        SG_LOG(SG_GENERAL, SG_ALERT, "Widget "
-               << props->getStringValue("name", "[unnamed]")
-               << " does not contain a proper GUI definition");
-    }
 }
 
-puObject *
-FGDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeight)
+FGDialog::~FGDialog()
 {
-    if (!props->getBoolValue("enabled", true))
-        return 0;
-
-    bool presetSize = props->hasValue("width") && props->hasValue("height");
-    int width = props->getIntValue("width", parentWidth);
-    int height = props->getIntValue("height", parentHeight);
-    int x = props->getIntValue("x", (parentWidth - width) / 2);
-    int y = props->getIntValue("y", (parentHeight - height) / 2);
-    string type = props->getName();
-
-    if (type.empty())
-        type = "dialog";
-
-    if (type == "dialog") {
-        puPopup *obj;
-        bool draggable = props->getBoolValue("draggable", true);
-        bool resizable = props->getBoolValue("resizable", false);
-        if (props->getBoolValue("modal", false))
-            obj = new puDialogBox(x, y);
-        else
-            obj = new fgPopup(x, y, resizable, draggable);
-        setupGroup(obj, props, width, height, true);
-        setColor(obj, props);
-        return obj;
-
-    } else if (type == "group") {
-        puGroup *obj = new puGroup(x, y);
-        setupGroup(obj, props, width, height, false);
-        setColor(obj, props);
-        return obj;
-
-    } else if (type == "frame") {
-        puGroup *obj = new puGroup(x, y);
-        setupGroup(obj, props, width, height, true);
-        setColor(obj, props);
-        return obj;
-
-    } else if (type == "hrule" || type == "vrule") {
-        puFrame *obj = new puFrame(x, y, x + width, y + height);
-        obj->setBorderThickness(0);
-        setupObject(obj, props);
-        setColor(obj, props, BACKGROUND|FOREGROUND|HIGHLIGHT);
-        return obj;
-
-    } else if (type == "list") {
-        int slider_width = props->getIntValue("slider", 20);
-        fgList *obj = new fgList(x, y, x + width, y + height, props, slider_width);
-        if (presetSize)
-            obj->setSize(width, height);
-        setupObject(obj, props);
-        setColor(obj, props);
-        return obj;
-
-    } else if (type == "airport-list") {
-        AirportList *obj = new AirportList(x, y, x + width, y + height);
-        if (presetSize)
-            obj->setSize(width, height);
-        setupObject(obj, props);
-        setColor(obj, props);
-        return obj;
-
-    } else if (type == "property-list") {
-        PropertyList *obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
-        if (presetSize)
-            obj->setSize(width, height);
-        setupObject(obj, props);
-        setColor(obj, props);
-        return obj;
-
-    } else if (type == "input") {
-        puInput *obj = new puInput(x, y, x + width, y + height);
-        setupObject(obj, props);
-        setColor(obj, props, FOREGROUND|LABEL);
-        return obj;
-
-    } else if (type == "text") {
-        puText *obj = new puText(x, y);
-        setupObject(obj, props);
-
-        // Layed-out objects need their size set, and non-layout ones
-        // get a different placement.
-        if (presetSize)
-            obj->setSize(width, height);
-        else
-            obj->setLabelPlace(PUPLACE_LABEL_DEFAULT);
-        setColor(obj, props, LABEL);
-        return obj;
-
-    } else if (type == "checkbox") {
-        puButton *obj;
-        obj = new puButton(x, y, x + width, y + height, PUBUTTON_XCHECK);
-        setupObject(obj, props);
-        setColor(obj, props, FOREGROUND|LABEL);
-        return obj;
-
-    } else if (type == "radio") {
-        puButton *obj;
-        obj = new puButton(x, y, x + width, y + height, PUBUTTON_CIRCLE);
-        setupObject(obj, props);
-        setColor(obj, props, FOREGROUND|LABEL);
-        return obj;
-
-    } else if (type == "button") {
-        puButton *obj;
-        const char *legend = props->getStringValue("legend", "[none]");
-        if (props->getBoolValue("one-shot", true))
-            obj = new puOneShot(x, y, legend);
-        else
-            obj = new puButton(x, y, legend);
-        if (presetSize)
-            obj->setSize(width, height);
-        setupObject(obj, props);
-        setColor(obj, props);
-        return obj;
-    } else if (type == "map") {
-        MapWidget* mapWidget = new MapWidget(x, y, x + width, y + height);
-        setupObject(mapWidget, props);
-        return mapWidget;
-    } else if (type == "combo") {
-        fgComboBox *obj = new fgComboBox(x, y, x + width, y + height, props,
-                props->getBoolValue("editable", false));
-        setupObject(obj, props);
-        setColor(obj, props, EDITFIELD);
-        return obj;
-
-    } else if (type == "slider") {
-        bool vertical = props->getBoolValue("vertical", false);
-        puSlider *obj = new puSlider(x, y, (vertical ? height : width), vertical);
-        obj->setMinValue(props->getFloatValue("min", 0.0));
-        obj->setMaxValue(props->getFloatValue("max", 1.0));
-        obj->setStepSize(props->getFloatValue("step"));
-        obj->setSliderFraction(props->getFloatValue("fraction"));
-#if PLIB_VERSION > 185
-        obj->setPageStepSize(props->getFloatValue("pagestep"));
-#endif
-        setupObject(obj, props);
-        if (presetSize)
-            obj->setSize(width, height);
-        setColor(obj, props, FOREGROUND|LABEL);
-        return obj;
-
-    } else if (type == "dial") {
-        puDial *obj = new puDial(x, y, width);
-        obj->setMinValue(props->getFloatValue("min", 0.0));
-        obj->setMaxValue(props->getFloatValue("max", 1.0));
-        obj->setWrap(props->getBoolValue("wrap", true));
-        setupObject(obj, props);
-        setColor(obj, props, FOREGROUND|LABEL);
-        return obj;
-
-    } else if (type == "textbox") {
-        int slider_width = props->getIntValue("slider", 20);
-        int wrap = props->getBoolValue("wrap", true);
-#if PLIB_VERSION > 185
-        puaLargeInput *obj = new puaLargeInput(x, y,
-                x + width, x + height, 11, slider_width, wrap);
-#else
-        puaLargeInput *obj = new puaLargeInput(x, y,
-                x + width, x + height, 2, slider_width, wrap);
-#endif
-
-        if (props->getBoolValue("editable"))
-            obj->enableInput();
-        else
-            obj->disableInput();
-
-        if (presetSize)
-            obj->setSize(width, height);
-        setupObject(obj, props);
-        setColor(obj, props, FOREGROUND|LABEL);
-
-        int top = props->getIntValue("top-line", 0);
-        obj->setTopLineInWindow(top < 0 ? unsigned(-1) >> 1 : top);
-        return obj;
-
-    } else if (type == "select") {
-        fgSelectBox *obj = new fgSelectBox(x, y, x + width, y + height, props);
-        setupObject(obj, props);
-        setColor(obj, props, EDITFIELD);
-        return obj;
-    } else if (type == "waypointlist") {
-        ScrolledWaypointList* obj = new ScrolledWaypointList(x, y, width, height);
-        setupObject(obj, props);
-        return obj;
-    } else {
-        return 0;
-    }
-}
-
-void
-FGDialog::setupObject (puObject *object, SGPropertyNode *props)
-{
-    GUIInfo *info = new GUIInfo(this);
-    object->setUserData(info);
-    _info.push_back(info);
-    object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
-    object->makeReturnDefault(props->getBoolValue("default"));
-    info->node = props;
-
-    if (props->hasValue("legend")) {
-        info->legend = props->getStringValue("legend");
-        object->setLegend(info->legend.c_str());
-    }
-
-    if (props->hasValue("label")) {
-        info->label = props->getStringValue("label");
-        object->setLabel(info->label.c_str());
-    }
-
-    if (props->hasValue("border"))
-        object->setBorderThickness( props->getIntValue("border", 2) );
-
-    if (SGPropertyNode *nft = props->getNode("font", false)) {
-       FGFontCache *fc = globals->get_fontcache();
-       puFont *lfnt = fc->get(nft);
-       object->setLabelFont(*lfnt);
-       object->setLegendFont(*lfnt);
-    } else {
-       object->setLabelFont(*_font);
-    }
-
-    if (props->hasChild("visible")) {
-      ConditionalObject* cnd = new ConditionalObject("visible", object);
-      cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("visible")));
-      _conditionalObjects.push_back(cnd);
-    }
-
-    if (props->hasChild("enable")) {
-      ConditionalObject* cnd = new ConditionalObject("enable", object);
-      cnd->setCondition(sgReadCondition(globals->get_props(), props->getChild("enable")));
-      _conditionalObjects.push_back(cnd);
-    }
-
-    string type = props->getName();
-    if (type == "input" && props->getBoolValue("live"))
-        object->setDownCallback(action_callback);
-
-    if (type == "text") {
-        const char *format = props->getStringValue("format", 0);
-        if (format) {
-            info->fmt_type = validate_format(format);
-            if (info->fmt_type != f_INVALID)
-                info->format = format;
-            else
-                SG_LOG(SG_GENERAL, SG_ALERT, "DIALOG: invalid <format> '"
-                        << format << '\'');
-        }
-    }
-
-    if (props->hasValue("property")) {
-        const char *name = props->getStringValue("name");
-        if (name == 0)
-            name = "";
-        const char *propname = props->getStringValue("property");
-        SGPropertyNode_ptr node = fgGetNode(propname, true);
-        if (type == "map") {
-          // mapWidget binds to a sub-tree of properties, and
-          // ignores the puValue mechanism, so special case things here
-          MapWidget* mw = static_cast<MapWidget*>(object);
-          mw->setProperty(node);
-        } else {
-            // normal widget, creating PropertyObject
-            copy_to_pui(node, object);
-            PropertyObject *po = new PropertyObject(name, object, node);
-            _propertyObjects.push_back(po);
-            if (props->getBoolValue("live"))
-                _liveObjects.push_back(po);
-        }
-        
-
-    }
-
-    SGPropertyNode *dest = fgGetNode("/sim/bindings/gui", true);
-    vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
-    if (bindings.size() > 0) {
-        info->key = props->getIntValue("keynum", -1);
-        if (props->hasValue("key"))
-            info->key = getKeyCode(props->getStringValue("key", ""));
-
-        for (unsigned int i = 0; i < bindings.size(); i++) {
-            unsigned int j = 0;
-            SGPropertyNode_ptr binding;
-            while (dest->getChild("binding", j))
-                j++;
-
-            const char *cmd = bindings[i]->getStringValue("command");
-            if (!strcmp(cmd, "nasal"))
-                bindings[i]->setStringValue("module", _module.c_str());
-
-            binding = dest->getChild("binding", j, true);
-            copyProperties(bindings[i], binding);
-            info->bindings.push_back(new SGBinding(binding, globals->get_props()));
-        }
-        object->setCallback(action_callback);
-    }
-}
-
-void
-FGDialog::setupGroup(puGroup *group, SGPropertyNode *props,
-       int width, int height, bool makeFrame)
-{
-    setupObject(group, props);
-
-    if (makeFrame) {
-        puFrame* f = new puFrame(0, 0, width, height);
-        setColor(f, props);
-    }
-
-    int nChildren = props->nChildren();
-    for (int i = 0; i < nChildren; i++)
-        makeObject(props->getChild(i), width, height);
-    group->close();
-}
-
-void
-FGDialog::setColor(puObject *object, SGPropertyNode *props, int which)
-{
-    string type = props->getName();
-    if (type.empty())
-        type = "dialog";
-    if (type == "textbox" && props->getBoolValue("editable"))
-        type += "-editable";
-
-    FGColor c(_gui->getColor("background"));
-    c.merge(_gui->getColor(type));
-    c.merge(props->getNode("color"));
-    if (c.isValid())
-        object->setColourScheme(c.red(), c.green(), c.blue(), c.alpha());
-
-    const struct {
-        int mask;
-        int id;
-        const char *name;
-        const char *cname;
-    } pucol[] = {
-        { BACKGROUND, PUCOL_BACKGROUND, "background", "color-background" },
-        { FOREGROUND, PUCOL_FOREGROUND, "foreground", "color-foreground" },
-        { HIGHLIGHT,  PUCOL_HIGHLIGHT,  "highlight",  "color-highlight" },
-        { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
-        { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
-        { MISC,       PUCOL_MISC,       "misc",       "color-misc" },
-        { EDITFIELD,  PUCOL_EDITFIELD,  "editfield",  "color-editfield" },
-    };
-
-    const int numcol = sizeof(pucol) / sizeof(pucol[0]);
-
-    for (int i = 0; i < numcol; i++) {
-        bool dirty = false;
-        c.clear();
-        c.setAlpha(1.0);
-
-        dirty |= c.merge(_gui->getColor(type + '-' + pucol[i].name));
-        if (which & pucol[i].mask)
-            dirty |= c.merge(props->getNode("color"));
-
-        if ((pucol[i].mask == LABEL) && !c.isValid())
-            dirty |= c.merge(_gui->getColor("label"));
-
-        dirty |= c.merge(props->getNode(pucol[i].cname));
-
-        if (c.isValid() && dirty)
-            object->setColor(pucol[i].id, c.red(), c.green(), c.blue(), c.alpha());
-    }
-}
-
-
-static struct {
-    const char *name;
-    int key;
-} keymap[] = {
-    {"backspace", 8},
-    {"tab", 9},
-    {"return", 13},
-    {"enter", 13},
-    {"esc", 27},
-    {"escape", 27},
-    {"space", ' '},
-    {"&amp;", '&'},
-    {"and", '&'},
-    {"&lt;", '<'},
-    {"&gt;", '>'},
-    {"f1", PU_KEY_F1},
-    {"f2", PU_KEY_F2},
-    {"f3", PU_KEY_F3},
-    {"f4", PU_KEY_F4},
-    {"f5", PU_KEY_F5},
-    {"f6", PU_KEY_F6},
-    {"f7", PU_KEY_F7},
-    {"f8", PU_KEY_F8},
-    {"f9", PU_KEY_F9},
-    {"f10", PU_KEY_F10},
-    {"f11", PU_KEY_F11},
-    {"f12", PU_KEY_F12},
-    {"left", PU_KEY_LEFT},
-    {"up", PU_KEY_UP},
-    {"right", PU_KEY_RIGHT},
-    {"down", PU_KEY_DOWN},
-    {"pageup", PU_KEY_PAGE_UP},
-    {"pagedn", PU_KEY_PAGE_DOWN},
-    {"home", PU_KEY_HOME},
-    {"end", PU_KEY_END},
-    {"insert", PU_KEY_INSERT},
-    {0, -1},
-};
-
-int
-FGDialog::getKeyCode(const char *str)
-{
-    enum {
-        CTRL = 0x1,
-        SHIFT = 0x2,
-        ALT = 0x4,
-    };
-
-    while (*str == ' ')
-        str++;
-
-    char *buf = new char[strlen(str) + 1];
-    strcpy(buf, str);
-    char *s = buf + strlen(buf);
-    while (s > str && s[-1] == ' ')
-        s--;
-    *s = 0;
-    s = buf;
-
-    int mod = 0;
-    while (1) {
-        if (!strncmp(s, "Ctrl-", 5) || !strncmp(s, "CTRL-", 5))
-            s += 5, mod |= CTRL;
-        else if (!strncmp(s, "Shift-", 6) || !strncmp(s, "SHIFT-", 6))
-            s += 6, mod |= SHIFT;
-        else if (!strncmp(s, "Alt-", 4) || !strncmp(s, "ALT-", 4))
-            s += 4, mod |= ALT;
-        else
-            break;
-    }
-
-    int key = -1;
-    if (strlen(s) == 1 && isascii(*s)) {
-        key = *s;
-        if (mod & SHIFT)
-            key = toupper(key);
-        if (mod & CTRL)
-            key = toupper(key) - '@';
-        /* if (mod & ALT)
-            ;   // Alt not propagated to the gui
-        */
-    } else {
-        for (char *t = s; *t; t++)
-            *t = tolower(*t);
-        for (int i = 0; keymap[i].name; i++) {
-            if (!strcmp(s, keymap[i].name)) {
-                key = keymap[i].key;
-                break;
-            }
-        }
-    }
-    delete[] buf;
-    return key;
-}
-
-void\fFGDialog::relayout()
-{
-  _needsRelayout = false;
-  
-  int screenw = globals->get_props()->getIntValue("/sim/startup/xsize");
-  int screenh = globals->get_props()->getIntValue("/sim/startup/ysize");
-
-  bool userx = _props->hasValue("x");
-  bool usery = _props->hasValue("y");
-  bool userw = _props->hasValue("width");
-  bool userh = _props->hasValue("height");
-
-  // Let the layout widget work in the same property subtree.
-  LayoutWidget wid(_props);
-  wid.setDefaultFont(_font, int(_font->getPointSize()));
-
-  int pw = 0, ph = 0;
-  int px, py, savex, savey;
-  if (!userw || !userh) {
-    wid.calcPrefSize(&pw, &ph);
-  }
-  
-  pw = _props->getIntValue("width", pw);
-  ph = _props->getIntValue("height", ph);
-  px = savex = _props->getIntValue("x", (screenw - pw) / 2);
-  py = savey = _props->getIntValue("y", (screenh - ph) / 2);
-
-  // Negative x/y coordinates are interpreted as distance from the top/right
-  // corner rather than bottom/left.
-  if (userx && px < 0)
-    px = screenw - pw + px;
-  if (usery && py < 0)
-    py = screenh - ph + py;
-
-  // Define "x", "y", "width" and/or "height" in the property tree if they
-  // are not specified in the configuration file.
-  wid.layout(px, py, pw, ph);
-
-  applySize(_object);
-  
-  // Remove automatically generated properties, so the layout looks
-  // the same next time around, or restore x and y to preserve negative coords.
-  if (userx)
-      _props->setIntValue("x", savex);
-  else
-      _props->removeChild("x");
-
-  if (usery)
-      _props->setIntValue("y", savey);
-  else
-      _props->removeChild("y");
-
-  if (!userw) _props->removeChild("width");
-  if (!userh) _props->removeChild("height");
-}
-
-void
-FGDialog::applySize(puObject *object)
-{
-  // compound plib widgets use setUserData() for internal purposes, so refuse
-  // to descend into anything that has other bits set than the following
-  const int validUserData = PUCLASS_VALUE|PUCLASS_OBJECT|PUCLASS_GROUP|PUCLASS_INTERFACE
-          |PUCLASS_FRAME|PUCLASS_TEXT|PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT
-          |PUCLASS_ARROW|PUCLASS_DIAL|PUCLASS_POPUP;
-
-  int type = object->getType();
-  if ((type & PUCLASS_GROUP) && !(type & ~validUserData)) {
-    puObject* c = ((puGroup *)object)->getFirstChild();
-    for (; c != NULL; c = c->getNextObject()) {
-      applySize(c);
-    } // of child iteration
-  } // of group object case
-  
-  GUIInfo *info = (GUIInfo *)object->getUserData();
-  if (!info)
-    return;
-
-  SGPropertyNode *n = info->node;
-  if (!n) {
-    SG_LOG(SG_GENERAL, SG_ALERT, "FGDialog::applySize: no props");
-    return;
-  }
-  
-  int x = n->getIntValue("x");
-  int y = n->getIntValue("y");
-  int w = n->getIntValue("width", 4);
-  int h = n->getIntValue("height", 4);
-  object->setPosition(x, y);
-  object->setSize(w, h);
-}
-
-////////////////////////////////////////////////////////////////////////
-// Implementation of FGDialog::PropertyObject.
-////////////////////////////////////////////////////////////////////////
-
-FGDialog::PropertyObject::PropertyObject(const char *n,
-        puObject *o, SGPropertyNode_ptr p) :
-    name(n),
-    object(o),
-    node(p)
-{
-}
-
-
-
-\f
-////////////////////////////////////////////////////////////////////////
-// Implementation of fgValueList and derived pui widgets
-////////////////////////////////////////////////////////////////////////
-
-
-fgValueList::fgValueList(SGPropertyNode *p) :
-    _props(p)
-{
-    make_list();
-}
-
-void
-fgValueList::update()
-{
-    destroy_list();
-    make_list();
-}
-
-fgValueList::~fgValueList()
-{
-    destroy_list();
-}
-
-void
-fgValueList::make_list()
-{
-  SGPropertyNode_ptr values = _props;
-  const char* vname = "value";
-  
-  if (_props->hasChild("properties")) {
-    // dynamic values, read from a property's children
-    const char* path = _props->getStringValue("properties");
-    values = fgGetNode(path, true);
-  }
-  
-  if (_props->hasChild("property-name")) {
-    vname = _props->getStringValue("property-name");
-  }
-  
-  vector<SGPropertyNode_ptr> value_nodes = values->getChildren(vname);
-  _list = new char *[value_nodes.size() + 1];
-  unsigned int i;
-  for (i = 0; i < value_nodes.size(); i++) {
-    _list[i] = strdup((char *)value_nodes[i]->getStringValue());
-  }
-  _list[i] = 0;
-}
-
-void
-fgValueList::destroy_list()
-{
-    for (int i = 0; _list[i] != 0; i++)
-        if (_list[i])
-            free(_list[i]);
-    delete[] _list;
-}
-
-
-
-void
-fgList::update()
-{
-    fgValueList::update();
-    int top = getTopItem();
-    newList(_list);
-    setTopItem(top);
-}
-
-void fgComboBox::update()
-{
-  if (_inHit) {
-    return;
-  }
-  
-  std::string curValue(getStringValue());
-  fgValueList::update();
-  newList(_list);
-  int currentItem = puaComboBox::getCurrentItem();
-
-  
-// look for the previous value, in the new list
-  for (int i = 0; _list[i] != 0; i++) {
-    if (_list[i] == curValue) {
-    // don't set the current item again; this is important to avoid
-    // recursion here if the combo callback ultimately causes another dialog-update
-      if (i != currentItem) {
-        puaComboBox::setCurrentItem(i);
-      }
-      
-      return;
-    }
-  } // of list values iteration
-  
-// cound't find current item, default to first
-  if (currentItem != 0) {
-    puaComboBox::setCurrentItem(0);
-  }
-}
-
-int fgComboBox::checkHit(int b, int up, int x, int y)
-{
-  _inHit = true;
-  int r = puaComboBox::checkHit(b, up, x, y);
-  _inHit = false;
-  return r;
-}
-
-void fgComboBox::setSize(int w, int h)
-{
-  puaComboBox::setSize(w, h);
-  recalc_bbox();
-}
-
-void fgComboBox::recalc_bbox()
-{
-// bug-fix for issue #192
-// http://code.google.com/p/flightgear-bugs/issues/detail?id=192
-// puaComboBox is including the height of its popupMenu in the height
-// computation, which breaks layout computations.
-// this implementation skips popup-menus
-  
-  puBox contents;
-  contents.empty();
-  
-  for (puObject *bo = dlist; bo != NULL; bo = bo -> getNextObject()) {
-    if (bo->getType() & PUCLASS_POPUPMENU) {
-      continue;
-    }
     
-    contents.extend (bo -> getBBox()) ;
-  }
-  
-  abox.max[0] = abox.min[0] + contents.max[0] ;
-  abox.max[1] = abox.min[1] + contents.max[1] ;
-
-  puObject::recalc_bbox () ;
-
 }
 
 // end of dialog.cxx
index 497e07104b8562cbebc07f96a289ab25acb3cfd4..7aa4adc80e6e57ce82afe9846c1ea379fda0ed27 100644 (file)
@@ -7,31 +7,8 @@
 # error This library requires C++
 #endif
 
-#include <plib/puAux.h>
-
-#include <simgear/compiler.h>  // for SG_USING_STD
-#include <simgear/props/props.hxx>
-#include <simgear/misc/sg_path.hxx>
-#include <simgear/props/condition.hxx>
-
-#include <vector>
-using std::vector;
-
-
-// ugly temporary workaround for plib's lack of user defined class ids  FIXME
-#define FGCLASS_LIST          0x00000001
-#define FGCLASS_AIRPORTLIST   0x00000002
-#define FGCLASS_PROPERTYLIST  0x00000004
-#define FGCLASS_WAYPOINTLIST  0x00000008
-
-class GUI_ID { public: GUI_ID(int id) : id(id) {} virtual ~GUI_ID() {} int id; };
-
-
-
-class FGDialog;
-class NewGUI;
-class FGColor;
-
+// forward decls
+class SGPropertyNode;
 
 /**
  * An XML-configured dialog box.
@@ -45,16 +22,6 @@ class FGDialog
 {
 public:
 
-    /**
-     * Construct a new GUI widget configured by a property tree.
-     *
-     * The configuration properties are not part of the main
-     * FlightGear property tree; the GUI manager reads them
-     * from individual configuration files.
-     *
-     * @param props A property tree describing the dialog.
-     */
-    FGDialog (SGPropertyNode * props);
 
 
     /**
@@ -73,7 +40,7 @@ public:
      * @param objectName The name of the GUI object(s) to update.
      *        Use the empty name for all unnamed objects.
      */
-    virtual void updateValues (const char * objectName = 0);
+    virtual void updateValues (const char * objectName = 0) = 0;
 
 
     /**
@@ -86,199 +53,28 @@ public:
      * @param objectName The name of the GUI object(s) to update.
      *        Use the empty name for all unnamed objects.
      */
-    virtual void applyValues (const char * objectName = 0);
+    virtual void applyValues (const char * objectName = 0) = 0;
 
 
     /**
      * Update state.  Called on active dialogs before rendering.
      */
-    virtual void update ();
-
-    /**
-     * Recompute the dialog's layout
-     */
-    void relayout();
-    
-    
-    void setNeedsLayout() {
-      _needsRelayout = true;
-    }
-private:
-
-    enum {
-        BACKGROUND = 0x01,
-        FOREGROUND = 0x02,
-        HIGHLIGHT = 0x04,
-        LABEL = 0x08,
-        LEGEND = 0x10,
-        MISC = 0x20,
-        EDITFIELD = 0x40
-    };
-
-    // Private copy constructor to avoid unpleasant surprises.
-    FGDialog (const FGDialog &);
-
-    // Show the dialog.
-    void display (SGPropertyNode * props);
-
-    // Build the dialog or a subobject of it.
-    puObject * makeObject (SGPropertyNode * props,
-                           int parentWidth, int parentHeight);
-
-    // Common configuration for all GUI objects.
-    void setupObject (puObject * object, SGPropertyNode * props);
-
-    // Common configuration for all GUI group objects.
-    void setupGroup (puGroup * group, SGPropertyNode * props,
-                     int width, int height, bool makeFrame = false);
-
-    // Set object colors: the "which" argument defines which color qualities
-    // (PUCOL_LABEL, etc.) should pick up the <color> property.
-    void setColor(puObject * object, SGPropertyNode * props, int which = 0);
-
-    // return key code number for keystring
-    int getKeyCode(const char *keystring);
+    virtual void update () = 0;
 
+protected:
     /**
-     * Apply layout sizes to a tree of puObjects
+     * Construct a new GUI widget configured by a property tree.
+     *
+     * The configuration properties are not part of the main
+     * FlightGear property tree; the GUI manager reads them
+     * from individual configuration files.
+     *
+     * @param props A property tree describing the dialog.
      */
-    void applySize(puObject *object);
-
-    // The top-level PUI object.
-    puObject * _object;
-
-    // The GUI subsystem.
-    NewGUI * _gui;
-
-    // The dialog font. Defaults to the global gui font, but can get
-    // overridden by a top level font definition.
-    puFont * _font;
-
-    // The source xml tree, so that we can pass data back, such as the
-    // last position.
-    SGPropertyNode_ptr _props;
-
-    bool _needsRelayout;
-
-    // Nasal module.
-    std::string _module;
-    SGPropertyNode_ptr _nasal_close;
-
-    // PUI provides no way for userdata to be deleted automatically
-    // with a GUI object, so we have to keep track of all the special
-    // data we allocated and then free it manually when the dialog
-    // closes.
-    vector<void *> _info;
-    struct PropertyObject {
-        PropertyObject (const char * name,
-                        puObject * object,
-                        SGPropertyNode_ptr node);
-        std::string name;
-        puObject * object;
-        SGPropertyNode_ptr node;
-    };
-    vector<PropertyObject *> _propertyObjects;
-    vector<PropertyObject *> _liveObjects;
-    
-    class ConditionalObject : public SGConditional
-    {
-    public:
-      ConditionalObject(const std::string& aName, puObject* aPu) :
-        _name(aName),
-        _pu(aPu)
-      { ; }
-    
-      void update(FGDialog* aDlg);
-    
-    private:
-      const std::string _name;
-      puObject* _pu;      
-    };
-    
-    typedef SGSharedPtr<ConditionalObject> ConditionalObjectRef;
-    vector<ConditionalObjectRef> _conditionalObjects;
-};
-
-//
-// Custom subclass of puPopup to implement "draggable" windows in the
-// interface.  Note that this is a subclass of puPopup, not
-// puDialogBox.  Sadly, PUI (mis)uses subclassing to implement a
-// boolean property: modality.  That means that we can't implement
-// dragging of both puPopup windows and puDialogBoxes with the same
-// code.  Rather than duplicate code, I've chosen to implement only
-// "non-modal dragability" here.  Modal dialog boxes (like the exit
-// confirmation) are not draggable.
-//
-class fgPopup : public puPopup {
-public:
-    fgPopup(int x, int y, bool r = true, bool d = true) :
-            puPopup(x, y), _draggable(d), _resizable(r), _dragging(false)
-    {}
-    int checkHit(int b, int up, int x, int y);
-    int checkKey(int key, int updown);
-    int getHitObjects(puObject *, int x, int y);
-    puObject *getKeyObject(puObject *, int key);
-    puObject *getActiveInputField(puObject *);
-    void applySize(puObject *);
-private:
-    enum { LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 };
-    bool _draggable;
-    bool _resizable;
-    bool _dragging;
-    int _resizing;
-    int _start_cursor;
-    int _cursor;
-    int _dlgX, _dlgY, _dlgW, _dlgH;
-    int _startX, _startY;
-};
-
-
-class fgValueList {
-public:
-    fgValueList(SGPropertyNode *p);
-    virtual ~fgValueList();
-    virtual void update();
-
-protected:
-    char **_list;
-
-private:
-    void make_list();
-    void destroy_list();
-    SGPropertyNode_ptr _props;
-};
-
-
-class fgList : public fgValueList, public puaList, public GUI_ID {
-public:
-    fgList(int x1, int y1, int x2, int y2, SGPropertyNode *p, int sw) :
-            fgValueList(p), puaList(x1, y1, x2, y2, _list, sw), GUI_ID(FGCLASS_LIST) {}
-    void update();
-};
+    FGDialog (SGPropertyNode * props);
 
-class fgComboBox : public fgValueList, public puaComboBox {
-public:
-    fgComboBox(int x1, int y1, int x2, int y2, SGPropertyNode *p, bool editable) :
-        fgValueList(p), 
-        puaComboBox(x1, y1, x2, y2, _list, editable),
-        _inHit(false)
-      {}
-        
-    void update();
-    
-    virtual void setSize(int w, int h);
-    
-    virtual int checkHit(int b, int up, int x, int y);
-    
-    virtual void recalc_bbox();
 private:
-    bool _inHit;
-};
 
-class fgSelectBox : public fgValueList, public puaSelectBox {
-public:
-    fgSelectBox(int x1, int y1, int x2, int y2, SGPropertyNode *p) :
-        fgValueList(p), puaSelectBox(x1, y1, x2, y2, _list) {}
 };
 
 #endif // __DIALOG_HXX
index 5f05a8f0ec95ff85303d5cbb0bb81ad46e5476de..a374b2912ae3e0e23609137b8b5b7505fb6b45c6 100644 (file)
@@ -27,7 +27,7 @@
 #endif
 
 #include "menubar.hxx"
-#include "dialog.hxx"
+#include "FGPUIDialog.hxx"
 #include "FGFontCache.hxx"
 #include "FGColor.hxx"
 
@@ -80,7 +80,7 @@ void
 NewGUI::reset (bool reload)
 {
     map<string,FGDialog *>::iterator iter;
-    vector<string> dlg;
+    std::vector<string> dlg;
     // close all open dialogs and remember them ...
     for (iter = _active_dialogs.begin(); iter != _active_dialogs.end(); ++iter)
         dlg.push_back(iter->first);
@@ -138,7 +138,7 @@ NewGUI::showDialog (const string &name)
         return false;
     } else {
         if(!_active_dialogs[name])
-            _active_dialogs[name] = new FGDialog(_dialog_props[name]);
+            _active_dialogs[name] = new FGPUIDialog(_dialog_props[name]);
         return true;
     }
 }
index 046c09435b3872c4d7d9871b7d04c48a5fd75032..e11d5bfe896fb73e74f198fe04708e4321f8321a 100644 (file)
@@ -27,7 +27,7 @@
 
 #include <plib/puAux.h>
 #include <simgear/props/props.hxx>
-#include "dialog.hxx"
+#include "FGPUIDialog.hxx"
 
 
 class PropertyList : public puaList, public SGPropertyChangeListener, public GUI_ID {