5a38a9d071
- PropertyObject: remove additional "values" member again - don't create entry lists for <list>, <select>, and <combo> at dialog creation and delete them on dialog close, but let a separate class fgValueList handle this. The three widgets are now subclassed and the derived widget lets fgValueList manage the lists - make <select> consistent with <list>. This breaks backwards compatibility, but not a single dialog in fgfs uses it and did so since ... forever. (Shoot me!) Rationale: now that dialogs are a bit more dynamic than they used to be (thanks to embedded Nasal), we have to delete and recreate entry lists during dialog use. Deleting only at dialog closing doesn't cut it anymore. Especially list widgets that are updated several times per second would use up a lot of memory otherwise. The route-manager doesn't update that often, but it did already leak. One TODO less. :-)
983 lines
27 KiB
C++
983 lines
27 KiB
C++
// dialog.cxx: implementation of an XML-configurable dialog box.
|
||
|
||
#ifdef HAVE_CONFIG_H
|
||
# include "config.h"
|
||
#endif
|
||
|
||
#include <stdlib.h> // atof()
|
||
|
||
#include <Input/input.hxx>
|
||
#include <Scripting/NasalSys.hxx>
|
||
|
||
#include "dialog.hxx"
|
||
#include "new_gui.hxx"
|
||
|
||
#include "AirportList.hxx"
|
||
#include "layout.hxx"
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Implementation of GUIInfo.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
/**
|
||
* User data for a GUI object.
|
||
*/
|
||
struct GUIInfo
|
||
{
|
||
GUIInfo (FGDialog * d);
|
||
virtual ~GUIInfo ();
|
||
|
||
FGDialog * dialog;
|
||
vector <FGBinding *> bindings;
|
||
int key;
|
||
};
|
||
|
||
GUIInfo::GUIInfo (FGDialog * d)
|
||
: dialog(d),
|
||
key(-1)
|
||
{
|
||
}
|
||
|
||
GUIInfo::~GUIInfo ()
|
||
{
|
||
for (unsigned int i = 0; i < bindings.size(); i++) {
|
||
delete bindings[i];
|
||
bindings[i] = 0;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 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 && ((puInput *)object)->isAcceptingInput())
|
||
return object;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Mouse handler.
|
||
*/
|
||
int fgPopup::checkHit(int button, int updown, int x, int y)
|
||
{
|
||
int 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 hit = getHitObjects(this, x, y);
|
||
if(hit & (PUCLASS_BUTTON|PUCLASS_ONESHOT|PUCLASS_INPUT))
|
||
return result;
|
||
|
||
int px, py;
|
||
getPosition(&px, &py);
|
||
_dragging = true;
|
||
_dX = px - x;
|
||
_dY = py - y;
|
||
} else if(updown == PU_DRAG && _dragging) {
|
||
setPosition(x + _dX, y + _dY);
|
||
} else {
|
||
_dragging = false;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
int fgPopup::getHitObjects(puObject *object, int x, int y)
|
||
{
|
||
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;
|
||
}
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// 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);
|
||
}
|
||
|
||
|
||
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());
|
||
}
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Static helper functions.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
/**
|
||
* Copy a property value to a PUI object.
|
||
*/
|
||
static void
|
||
copy_to_pui (SGPropertyNode * node, puObject * object)
|
||
{
|
||
// Treat puText objects specially, so their "values" can be set
|
||
// from properties.
|
||
if(object->getType() & PUCLASS_TEXT) {
|
||
object->setLabel(node->getStringValue());
|
||
return;
|
||
}
|
||
|
||
switch (node->getType()) {
|
||
case SGPropertyNode::BOOL:
|
||
case SGPropertyNode::INT:
|
||
case SGPropertyNode::LONG:
|
||
object->setValue(node->getIntValue());
|
||
break;
|
||
case SGPropertyNode::FLOAT:
|
||
case SGPropertyNode::DOUBLE:
|
||
object->setValue(node->getFloatValue());
|
||
break;
|
||
default:
|
||
object->setValue(node->getStringValue());
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
static void
|
||
copy_from_pui (puObject * object, SGPropertyNode * node)
|
||
{
|
||
// puText objects are immutable, so should not be copied out
|
||
if(object->getType() & PUCLASS_TEXT)
|
||
return;
|
||
|
||
switch (node->getType()) {
|
||
case SGPropertyNode::BOOL:
|
||
case SGPropertyNode::INT:
|
||
case SGPropertyNode::LONG:
|
||
node->setIntValue(object->getIntegerValue());
|
||
break;
|
||
case SGPropertyNode::FLOAT:
|
||
case SGPropertyNode::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());
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Implementation of FGDialog.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
FGDialog::FGDialog (SGPropertyNode * props)
|
||
: _object(0),
|
||
_gui((NewGUI *)globals->get_subsystem("gui")),
|
||
_props(props)
|
||
{
|
||
_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");
|
||
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 (_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 *obj = _propertyObjects[i]->object;
|
||
if (obj->getType() & PUCLASS_LIST) {
|
||
fgList *pl = static_cast<fgList *>(obj);
|
||
pl->update();
|
||
} else
|
||
copy_to_pui(_propertyObjects[i]->node, obj);
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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 = _gui->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)
|
||
{
|
||
if (props->getBoolValue("hide"))
|
||
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 == "")
|
||
type = "dialog";
|
||
|
||
if (type == "dialog") {
|
||
puPopup * obj;
|
||
bool draggable = props->getBoolValue("draggable", true);
|
||
if (props->getBoolValue("modal", false))
|
||
obj = new puDialogBox(x, y);
|
||
else
|
||
obj = new fgPopup(x, y, 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);
|
||
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 == "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);
|
||
|
||
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)
|
||
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 == "combo") {
|
||
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));
|
||
obj->setMinValue(props->getFloatValue("min", 0.0));
|
||
obj->setMaxValue(props->getFloatValue("max", 1.0));
|
||
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);
|
||
puaLargeInput * obj = new puaLargeInput(x, y,
|
||
x+width, x+height, 2, slider_width, wrap);
|
||
|
||
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);
|
||
return obj;
|
||
|
||
} else if (type == "select") {
|
||
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 {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
void
|
||
FGDialog::setupObject (puObject * object, SGPropertyNode * props)
|
||
{
|
||
string type = props->getName();
|
||
object->setLabelPlace(PUPLACE_CENTERED_RIGHT);
|
||
|
||
if (props->hasValue("legend"))
|
||
object->setLegend(props->getStringValue("legend"));
|
||
|
||
if (props->hasValue("label"))
|
||
object->setLabel(props->getStringValue("label"));
|
||
|
||
if (props->hasValue("border"))
|
||
object->setBorderThickness( props->getIntValue("border", 2) );
|
||
|
||
if ( SGPropertyNode *nft = props->getNode("font", false) ) {
|
||
FGFontCache *fc = _gui->get_fontcache();
|
||
puFont *lfnt = fc->get(nft);
|
||
object->setLabelFont(*lfnt);
|
||
} else {
|
||
object->setLabelFont(*_font);
|
||
}
|
||
|
||
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);
|
||
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) {
|
||
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;
|
||
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 FGBinding(binding));
|
||
}
|
||
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)
|
||
{
|
||
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 == "")
|
||
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());
|
||
|
||
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" },
|
||
#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);
|
||
|
||
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", ' '},
|
||
{"&", '&'},
|
||
{"and", '&'},
|
||
{"<", '<'},
|
||
{">", '>'},
|
||
{"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) - 64;
|
||
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;
|
||
}
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Implementation of FGDialog::PropertyObject.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
FGDialog::PropertyObject::PropertyObject (const char * n,
|
||
puObject * o,
|
||
SGPropertyNode_ptr p)
|
||
: name(n),
|
||
object(o),
|
||
node(p)
|
||
{
|
||
}
|
||
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Implementation of fgList 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()
|
||
{
|
||
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;
|
||
}
|
||
|
||
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();
|
||
newList(_list);
|
||
}
|
||
|
||
// end of dialog.cxx
|