]> git.mxchange.org Git - flightgear.git/blobdiff - src/GUI/dialog.cxx
Merge branch 'syd/ias-limit'
[flightgear.git] / src / GUI / dialog.cxx
index 6bb15501ff289467e23f4ee5c38a61cebf303e46..fbf758b2979e9ed9f6f94775b7e9b6074094fa24 100644 (file)
@@ -4,19 +4,77 @@
 #  include "config.h"
 #endif
 
-#include <stdlib.h>            // atof()
-
-#include <Input/input.hxx>
+#include <simgear/structure/SGBinding.hxx>
 #include <Scripting/NasalSys.hxx>
+#include <Main/fg_os.hxx>
 
 #include "dialog.hxx"
 #include "new_gui.hxx"
-
 #include "AirportList.hxx"
 #include "property_list.hxx"
 #include "layout.hxx"
+#include "WaypointList.hxx"
+#include "MapWidget.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;
+}
 
 
 ////////////////////////////////////////////////////////////////////////
  */
 struct GUIInfo
 {
-    GUIInfo (FGDialog * d);
-    virtual ~GUIInfo ();
+    GUIInfo(FGDialog *d);
+    virtual ~GUIInfo();
+    void apply_format(SGPropertyNode *);
 
-    FGDialog * dialog;
-    vector <FGBinding *> bindings;
+    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)
+GUIInfo::GUIInfo (FGDialog *d) :
+    dialog(d),
+    key(-1),
+    fmt_type(f_INVALID)
 {
 }
 
@@ -50,6 +113,24 @@ GUIInfo::~GUIInfo ()
     }
 }
 
+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
 /**
@@ -81,7 +162,7 @@ int fgPopup::checkKey(int key, int updown)
 puObject *fgPopup::getKeyObject(puObject *object, int key)
 {
     puObject *ret;
-    if(object->getType() & PUCLASS_GROUP)
+    if (object->getType() & PUCLASS_GROUP)
         for (puObject *obj = ((puGroup *)object)->getFirstChild();
                 obj; obj = obj->getNextObject())
             if ((ret = getKeyObject(obj, key)))
@@ -97,13 +178,14 @@ puObject *fgPopup::getKeyObject(puObject *object, int key)
 puObject *fgPopup::getActiveInputField(puObject *object)
 {
     puObject *ret;
-    if(object->getType() & PUCLASS_GROUP)
+    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 && ((puInput *)object)->isAcceptingInput())
+    if (object->getType() & (PUCLASS_INPUT|PUCLASS_LARGEINPUT)
+            && ((puInput *)object)->isAcceptingInput())
         return object;
 
     return 0;
@@ -114,9 +196,11 @@ puObject *fgPopup::getActiveInputField(puObject *object)
  */
 int fgPopup::checkHit(int button, int updown, int x, int y)
 {
-    int result = puPopup::checkHit(button, updown, x, y);
+    int result = 0;
+    if (updown != PU_DRAG && !_dragging)
+        result = puPopup::checkHit(button, updown, x, y);
 
-    if ( !_draggable)
+    if (!_draggable)
        return result;
 
     // This is annoying.  We would really want a true result from the
@@ -125,22 +209,106 @@ int fgPopup::checkHit(int button, int updown, int x, int y)
     // intersection test (again) to make sure we don't start a drag
     // when inside controls.
 
-    if(updown == PU_DOWN && !_dragging) {
-        if(!result)
+    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_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT))
+        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;
 
-        int px, py;
-        getPosition(&px, &py);
+        getPosition(&_dlgX, &_dlgY);
+        getSize(&_dlgW, &_dlgH);
+        _start_cursor = fgGetMouseCursor();
         _dragging = true;
-        _dX = px - x;
-        _dY = py - y;
-    } else if(updown == PU_DRAG && _dragging) {
-        setPosition(x + _dX, y + _dY);
-    } else {
+        _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;
@@ -148,8 +316,11 @@ int fgPopup::checkHit(int button, int updown, int x, int y)
 
 int fgPopup::getHitObjects(puObject *object, int x, int y)
 {
+    if (!object->isVisible())
+        return 0;
+
     int type = 0;
-    if(object->getType() & PUCLASS_GROUP)
+    if (object->getType() & PUCLASS_GROUP)
         for (puObject *obj = ((puGroup *)object)->getFirstChild();
                 obj; obj = obj->getNextObject())
             type |= getHitObjects(obj, x, y);
@@ -157,14 +328,77 @@ int fgPopup::getHitObjects(puObject *object, int x, int 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)
+    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);
+}
 
-\f
 ////////////////////////////////////////////////////////////////////////
+
+void FGDialog::ConditionalObject::update(FGDialog* aDlg)
+{
+  if (_name == "visible") {
+    bool wasVis = _pu->isVisible();
+    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();
+    bool newEnable = test();
+    
+    if (wasEnabled == newEnable) {
+      return;
+    }
+    
+    if (newEnable) {
+      _pu->activate();
+    } else {
+      _pu->greyOut();
+    }
+  }
+  
+  aDlg->setNeedsLayout();
+}
+\f////////////////////////////////////////////////////////////////////////
 // Callbacks.
 ////////////////////////////////////////////////////////////////////////
 
@@ -172,10 +406,10 @@ int fgPopup::getHitObjects(puObject *object, int x, int y)
  * Action callback.
  */
 static void
-action_callback (puObject * object)
+action_callback (puObject *object)
 {
-    GUIInfo * info = (GUIInfo *)object->getUserData();
-    NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
+    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++) {
@@ -187,71 +421,6 @@ action_callback (puObject * object)
 }
 
 
-static void
-format_callback(puObject *obj, int dx, int dy, void *n)
-{
-    SGPropertyNode *node = (SGPropertyNode *)n;
-    const char *format = node->getStringValue("format"), *f = format;
-    bool number, l = false;
-    // make sure the format matches '[ -+#]?\d*(\.\d*)?l?[fs]'
-    for (; *f; f++) {
-        if (*f == '%') {
-            if (f[1] == '%')
-                f++;
-            else
-                break;
-        }
-    }
-    if (*f++ != '%')
-        return;
-    if (*f == ' ' || *f == '+' || *f == '-' || *f == '#')
-        f++;
-    while (*f && isdigit(*f))
-        f++;
-    if (*f == '.') {
-        f++;
-        while (*f && isdigit(*f))
-            f++;
-    }
-    if (*f == 'l')
-        l = true, f++;
-
-    if (*f == 'f')
-        number = true;
-    else if (*f == 's') {
-        if (l)
-            return;
-        number = false;
-    } else
-        return;
-
-    for (++f; *f; f++) {
-        if (*f == '%') {
-            if (f[1] == '%')
-                f++;
-            else
-                return;
-        }
-    }
-
-    char buf[256];
-    const char *src = obj->getLabel();
-
-    if (number) {
-        float value = atof(src);
-        snprintf(buf, 256, format, value);
-    } else {
-        snprintf(buf, 256, format, src);
-    }
-
-    buf[255] = '\0';
-
-    SGPropertyNode *result = node->getNode("formatted", true);
-    result->setStringValue(buf);
-    obj->setLabel(result->getStringValue());
-}
-
-
 \f
 ////////////////////////////////////////////////////////////////////////
 // Static helper functions.
@@ -261,61 +430,67 @@ format_callback(puObject *obj, int dx, int dy, void *n)
  * Copy a property value to a PUI object.
  */
 static void
-copy_to_pui (SGPropertyNode * node, puObject * object)
+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) {
-        object->setLabel(node->getStringValue());
+    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 SGPropertyNode::BOOL:
-    case SGPropertyNode::INT:
-    case SGPropertyNode::LONG:
+    case props::BOOL:
+    case props::INT:
+    case props::LONG:
         object->setValue(node->getIntValue());
         break;
-    case SGPropertyNode::FLOAT:
-    case SGPropertyNode::DOUBLE:
+    case props::FLOAT:
+    case props::DOUBLE:
         object->setValue(node->getFloatValue());
         break;
     default:
-        object->setValue(node->getStringValue());
+        info->text = node->getStringValue();
+        object->setValue(info->text.c_str());
         break;
     }
 }
 
 
 static void
-copy_from_pui (puObject * object, SGPropertyNode * node)
+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)
+    if (object->getType() & PUCLASS_TEXT)
         return;
 
     switch (node->getType()) {
-    case SGPropertyNode::BOOL:
-    case SGPropertyNode::INT:
-    case SGPropertyNode::LONG:
+    case props::BOOL:
+    case props::INT:
+    case props::LONG:
         node->setIntValue(object->getIntegerValue());
         break;
-    case SGPropertyNode::FLOAT:
-    case SGPropertyNode::DOUBLE:
+    case props::FLOAT:
+    case props::DOUBLE:
         node->setFloatValue(object->getFloatValue());
         break;
     default:
-        // Special case to handle lists, as getStringValue cannot be overridden
-        if(object->getType() & PUCLASS_LIST)
-        {
-            const char *s = ((puList *) object)->getListStringValue();
-            if (s)
-                node->setStringValue(s);
-        }
-        else
-        {
-            node->setStringValue(object->getStringValue());
-        }
+        const char *s = object->getStringValue();
+        if (s)
+            node->setStringValue(s);
         break;
     }
 }
@@ -326,12 +501,14 @@ copy_from_pui (puObject * object, SGPropertyNode * node)
 // Implementation of FGDialog.
 ////////////////////////////////////////////////////////////////////////
 
-FGDialog::FGDialog (SGPropertyNode * props)
-    : _object(0),
-      _gui((NewGUI *)globals->get_subsystem("gui")),
-      _props(props)
+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");
@@ -339,7 +516,8 @@ FGDialog::FGDialog (SGPropertyNode * props)
         if (open) {
             const char *s = open->getStringValue();
             FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
-            nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
+            if (nas)
+                nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), props);
         }
     }
     display(props);
@@ -353,11 +531,13 @@ FGDialog::~FGDialog ()
     _props->setIntValue("lasty", y);
 
     FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
-    if (_nasal_close) {
-        const char *s = _nasal_close->getStringValue();
-        nas->createModule(_module.c_str(), _module.c_str(), s, strlen(s), _props);
+    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());
     }
-    nas->deleteModule(_module.c_str());
 
     puDeleteObject(_object);
 
@@ -377,27 +557,38 @@ FGDialog::~FGDialog ()
 }
 
 void
-FGDialog::updateValues (const char * objectName)
+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 *obj = _propertyObjects[i]->object;
-        if ((obj->getType() & PUCLASS_LIST) && (dynamic_cast<GUI_ID *>(obj)->id & FGCLASS_LIST)) {
-            fgList *pl = static_cast<fgList *>(obj);
-            pl->update();
-        } else
-            copy_to_pui(_propertyObjects[i]->node, obj);
+  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)
+FGDialog::applyValues (const char *objectName)
 {
     if (objectName && !objectName[0])
         objectName = 0;
@@ -422,10 +613,18 @@ FGDialog::update ()
 
         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)
+FGDialog::display (SGPropertyNode *props)
 {
     if (_object != 0) {
         SG_LOG(SG_GENERAL, SG_ALERT, "This widget is already active");
@@ -452,9 +651,9 @@ FGDialog::display (SGPropertyNode * props)
     }
     wid.setDefaultFont(_font, int(_font->getPointSize()));
 
-    int pw=0, ph=0;
+    int pw = 0, ph = 0;
     int px, py, savex, savey;
-    if(!userw || !userh)
+    if (!userw || !userh)
         wid.calcPrefSize(&pw, &ph);
     pw = props->getIntValue("width", pw);
     ph = props->getIntValue("height", ph);
@@ -478,18 +677,18 @@ FGDialog::display (SGPropertyNode * props)
 
     // Remove automatically generated properties, so the layout looks
     // the same next time around, or restore x and y to preserve negative coords.
-    if(userx)
+    if (userx)
         props->setIntValue("x", savex);
     else
         props->removeChild("x");
 
-    if(usery)
+    if (usery)
         props->setIntValue("y", savey);
     else
         props->removeChild("y");
 
-    if(!userw) props->removeChild("width");
-    if(!userh) props->removeChild("height");
+    if (!userw) props->removeChild("width");
+    if (!userh) props->removeChild("height");
 
     if (_object != 0) {
         _object->reveal();
@@ -501,9 +700,9 @@ FGDialog::display (SGPropertyNode * props)
 }
 
 puObject *
-FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
+FGDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeight)
 {
-    if (props->getBoolValue("hide"))
+    if (!props->getBoolValue("enabled", true))
         return 0;
 
     bool presetSize = props->hasValue("width") && props->hasValue("height");
@@ -513,41 +712,43 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
     int y = props->getIntValue("y", (parentHeight - height) / 2);
     string type = props->getName();
 
-    if (type == "")
+    if (type.empty())
         type = "dialog";
 
     if (type == "dialog") {
-        puPopup * obj;
+        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, draggable);
+            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);
+        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);
+        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);
+        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);
+        fgList *obj = new fgList(x, y, x + width, y + height, props, slider_width);
         if (presetSize)
             obj->setSize(width, height);
         setupObject(obj, props);
@@ -555,7 +756,7 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
         return obj;
 
     } else if (type == "airport-list") {
-        AirportList * obj = new AirportList(x, y, x + width, y + height);
+        AirportList *obj = new AirportList(x, y, x + width, y + height);
         if (presetSize)
             obj->setSize(width, height);
         setupObject(obj, props);
@@ -563,7 +764,7 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
         return obj;
 
     } else if (type == "property-list") {
-        PropertyList * obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
+        PropertyList *obj = new PropertyList(x, y, x + width, y + height, globals->get_props());
         if (presetSize)
             obj->setSize(width, height);
         setupObject(obj, props);
@@ -571,22 +772,15 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
         return obj;
 
     } else if (type == "input") {
-        puInput * obj = new puInput(x, y, x + width, y + height);
+        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);
+        puText *obj = new puText(x, y);
         setupObject(obj, props);
 
-        if (props->getNode("format")) {
-            SGPropertyNode *live = props->getNode("live");
-            if (live && live->getBoolValue())
-                obj->setRenderCallback(format_callback, props);
-            else
-                format_callback(obj, x, y, props);
-        }
         // Layed-out objects need their size set, and non-layout ones
         // get a different placement.
         if (presetSize)
@@ -597,22 +791,22 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
         return obj;
 
     } else if (type == "checkbox") {
-        puButton * obj;
+        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;
+        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]");
+        puButton *obj;
+        const char *legend = props->getStringValue("legend", "[none]");
         if (props->getBoolValue("one-shot", true))
             obj = new puOneShot(x, y, legend);
         else
@@ -622,23 +816,27 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
         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));
+        fgComboBox *obj = new fgComboBox(x, y, x + width, y + height, props,
+                props->getBoolValue("editable", false));
         setupObject(obj, props);
-#ifdef PUCOL_EDITFIELD  // plib > 0.8.4
         setColor(obj, props, EDITFIELD);
-#else
-        setColor(obj, props, FOREGROUND|LABEL);
-#endif
         return obj;
 
     } else if (type == "slider") {
         bool vertical = props->getBoolValue("vertical", false);
-        puSlider * obj = new puSlider(x, y, (vertical ? height : width));
+        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);
@@ -646,7 +844,7 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
         return obj;
 
     } else if (type == "dial") {
-        puDial * obj = new puDial(x, y, width);
+        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));
@@ -657,29 +855,36 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
     } else if (type == "textbox") {
         int slider_width = props->getIntValue("slider", 20);
         int wrap = props->getBoolValue("wrap", true);
-        puaLargeInput * obj = new puaLargeInput(x, y,
-                x+width, x+height, 2, slider_width, wrap);
+#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 (props->hasValue("editable")) {
-            if (props->getBoolValue("editable")==false)
-                obj->disableInput();
-            else
-                obj->enableInput();
-        }
         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);
+        fgSelectBox *obj = new fgSelectBox(x, y, x + width, y + height, props);
         setupObject(obj, props);
-#ifdef PUCOL_EDITFIELD  // plib > 0.8.4
         setColor(obj, props, EDITFIELD);
-#else
-        setColor(obj, props, FOREGROUND|LABEL);
-#endif
+        return obj;
+    } else if (type == "waypointlist") {
+        ScrolledWaypointList* obj = new ScrolledWaypointList(x, y, width, height);
+        setupObject(obj, props);
         return obj;
     } else {
         return 0;
@@ -687,53 +892,98 @@ FGDialog::makeObject (SGPropertyNode * props, int parentWidth, int parentHeight)
 }
 
 void
-FGDialog::setupObject (puObject * object, SGPropertyNode * props)
+FGDialog::setupObject (puObject *object, SGPropertyNode *props)
 {
-    string type = props->getName();
+    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"))
-        object->setLegend(props->getStringValue("legend"));
+    if (props->hasValue("legend")) {
+        info->legend = props->getStringValue("legend");
+        object->setLegend(info->legend.c_str());
+    }
 
-    if (props->hasValue("label"))
-        object->setLabel(props->getStringValue("label"));
+    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) ) {
+    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");
+        const char *name = props->getStringValue("name");
         if (name == 0)
             name = "";
-        const char * propname = props->getStringValue("property");
+        const char *propname = props->getStringValue("property");
         SGPropertyNode_ptr node = fgGetNode(propname, true);
-        copy_to_pui(node, object);
+        if (type == "map") {
+          // mapWidget binds to a sub-tree of properties, and
+          // ignroes 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);
+        }
+        
 
-        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);
+    SGPropertyNode *dest = fgGetNode("/sim/bindings/gui", true);
     vector<SGPropertyNode_ptr> bindings = props->getChildren("binding");
     if (bindings.size() > 0) {
-        GUIInfo * info = new GUIInfo(this);
         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 *binding;
+            SGPropertyNode_ptr binding;
             while (dest->getChild("binding", j))
                 j++;
 
@@ -743,23 +993,15 @@ FGDialog::setupObject (puObject * object, SGPropertyNode * props)
 
             binding = dest->getChild("binding", j, true);
             copyProperties(bindings[i], binding);
-            info->bindings.push_back(new FGBinding(binding));
+            info->bindings.push_back(new SGBinding(binding, globals->get_props()));
         }
         object->setCallback(action_callback);
-
-        if (type == "input" && props->getBoolValue("live"))
-            object->setDownCallback(action_callback);
-
-        object->setUserData(info);
-        _info.push_back(info);
     }
-
-    object->makeReturnDefault(props->getBoolValue("default"));
 }
 
 void
-FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
-                    int width, int height, bool makeFrame)
+FGDialog::setupGroup(puGroup *group, SGPropertyNode *props,
+       int width, int height, bool makeFrame)
 {
     setupObject(group, props);
 
@@ -775,19 +1017,19 @@ FGDialog::setupGroup (puGroup * group, SGPropertyNode * props,
 }
 
 void
-FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
+FGDialog::setColor(puObject *object, SGPropertyNode *props, int which)
 {
     string type = props->getName();
-    if (type == "")
+    if (type.empty())
         type = "dialog";
     if (type == "textbox" && props->getBoolValue("editable"))
         type += "-editable";
 
-    FGColor *c = new FGColor(_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());
+    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;
@@ -801,29 +1043,27 @@ FGDialog::setColor(puObject * object, SGPropertyNode * props, int which)
         { LABEL,      PUCOL_LABEL,      "label",      "color-label" },
         { LEGEND,     PUCOL_LEGEND,     "legend",     "color-legend" },
         { MISC,       PUCOL_MISC,       "misc",       "color-misc" },
-#ifdef PUCOL_EDITFIELD  // plib > 0.8.4
         { EDITFIELD,  PUCOL_EDITFIELD,  "editfield",  "color-editfield" },
-#endif
     };
 
     const int numcol = sizeof(pucol) / sizeof(pucol[0]);
 
     for (int i = 0; i < numcol; i++) {
         bool dirty = false;
-        c->clear();
-        c->setAlpha(1.0);
+        c.clear();
+        c.setAlpha(1.0);
 
-        dirty |= c->merge(_gui->getColor(type + '-' + pucol[i].name));
+        dirty |= c.merge(_gui->getColor(type + '-' + pucol[i].name));
         if (which & pucol[i].mask)
-            dirty |= c->merge(props->getNode("color"));
+            dirty |= c.merge(props->getNode("color"));
 
-        if ((pucol[i].mask == LABEL) && !c->isValid())
-            dirty |= c->merge(_gui->getColor("label"));
+        if ((pucol[i].mask == LABEL) && !c.isValid())
+            dirty |= c.merge(_gui->getColor("label"));
 
-        dirty |= c->merge(props->getNode(pucol[i].cname));
+        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());
+        if (c.isValid() && dirty)
+            object->setColor(pucol[i].id, c.red(), c.green(), c.blue(), c.alpha());
     }
 }
 
@@ -905,9 +1145,10 @@ FGDialog::getKeyCode(const char *str)
         if (mod & SHIFT)
             key = toupper(key);
         if (mod & CTRL)
-            key = toupper(key) - 64;
-        if (mod & ALT)
+            key = toupper(key) - '@';
+        /* if (mod & ALT)
             ;   // Alt not propagated to the gui
+        */
     } else {
         for (char *t = s; *t; t++)
             *t = tolower(*t);
@@ -922,18 +1163,106 @@ FGDialog::getKeyCode(const char *str)
     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);
+}
 
-\f
 ////////////////////////////////////////////////////////////////////////
 // Implementation of FGDialog::PropertyObject.
 ////////////////////////////////////////////////////////////////////////
 
-FGDialog::PropertyObject::PropertyObject (const char * n,
-                                           puObject * o,
-                                           SGPropertyNode_ptr p)
-    : name(n),
-      object(o),
-      node(p)
+FGDialog::PropertyObject::PropertyObject(const char *n,
+        puObject *o, SGPropertyNode_ptr p) :
+    name(n),
+    object(o),
+    node(p)
 {
 }
 
@@ -966,12 +1295,26 @@ fgValueList::~fgValueList()
 void
 fgValueList::make_list()
 {
-    vector<SGPropertyNode_ptr> value_nodes = _props->getChildren("value");
-    _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;
+  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
@@ -989,7 +1332,15 @@ void
 fgList::update()
 {
     fgValueList::update();
+    int top = getTopItem();
     newList(_list);
+    setTopItem(top);
+}
+
+void fgComboBox::update()
+{
+  fgValueList::update();
+  newList(_list);
 }
 
 // end of dialog.cxx