2c8aad12ba
Change fgcommand to take an optional property tree root element. This fixes the animation bindings to use the defined property tree root - to support multiplayer (or other) model that can bind to the correct part of the property tree. Requires a corresponding fix in sg to allow the command methods to take an optional root parameter. What this means is that when inside someone else's multiplayer model (e.g. backseat, or co-pilot), the multipalyer (AI) model will correctly modify properties inside the correct part of the property tree inside (/ai), rather than modifying the properties inside the same part of the tree as the non-ai model. This means that a properly setup model will operate within it's own space in the property tree; and permit more generic multiplayer code to be written. This is probably responsible for some of the pollution of the root property tree with MP aircraft properties.
435 lines
12 KiB
C++
435 lines
12 KiB
C++
#ifdef HAVE_CONFIG_H
|
||
# include <config.h>
|
||
#endif
|
||
|
||
#include <string.h>
|
||
#include <iostream>
|
||
#include <plib/pu.h>
|
||
#include <simgear/debug/logstream.hxx>
|
||
#include <simgear/structure/SGBinding.hxx>
|
||
#include <simgear/props/props_io.hxx>
|
||
|
||
#include <Main/globals.hxx>
|
||
#include <Main/locale.hxx>
|
||
#include <Main/fg_props.hxx>
|
||
|
||
#include "new_gui.hxx"
|
||
#include "FGPUIMenuBar.hxx"
|
||
|
||
using std::vector;
|
||
using std::string;
|
||
using std::map;
|
||
////////////////////////////////////////////////////////////////////////
|
||
// FIXME!!
|
||
//
|
||
// Deprecated wrappers for old menu commands.
|
||
//
|
||
// DO NOT ADD TO THESE. THEY WILL BE DELETED SOON!
|
||
//
|
||
// These are defined in gui_funcs.cxx. They should be replaced with
|
||
// user-configured dialogs and new commands where necessary.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
#if defined(TR_HIRES_SNAP)
|
||
extern void dumpHiResSnapShot ();
|
||
static bool
|
||
do_hires_snapshot_dialog (const SGPropertyNode * arg, SGPropertyNode * root)
|
||
{
|
||
dumpHiResSnapShot();
|
||
return true;
|
||
}
|
||
#endif // TR_HIRES_SNAP
|
||
|
||
static struct {
|
||
const char * name;
|
||
SGCommandMgr::command_t command;
|
||
} deprecated_dialogs [] = {
|
||
#if defined(TR_HIRES_SNAP)
|
||
{ "old-hires-snapshot-dialog", do_hires_snapshot_dialog },
|
||
#endif
|
||
{ 0, 0 }
|
||
};
|
||
|
||
static void
|
||
add_deprecated_dialogs ()
|
||
{
|
||
SG_LOG(SG_GENERAL, SG_INFO, "Initializing old dialog commands:");
|
||
for (int i = 0; deprecated_dialogs[i].name != 0; i++) {
|
||
SG_LOG(SG_GENERAL, SG_INFO, " " << deprecated_dialogs[i].name);
|
||
globals->get_commands()->addCommand(deprecated_dialogs[i].name,
|
||
deprecated_dialogs[i].command);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Static functions.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
static void
|
||
menu_callback (puObject * object)
|
||
{
|
||
NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
|
||
FGPUIMenuBar* mb = static_cast<FGPUIMenuBar*>(gui->getMenuBar());
|
||
mb->fireItem(object);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////
|
||
// Implementation of FGPUIMenuBar.
|
||
////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
FGPUIMenuBar::FGPUIMenuBar ()
|
||
: _visible(false),
|
||
_menuBar(0)
|
||
{
|
||
}
|
||
|
||
FGPUIMenuBar::~FGPUIMenuBar ()
|
||
{
|
||
destroy_menubar();
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::init ()
|
||
{
|
||
bool visible = _visible;
|
||
destroy_menubar();
|
||
|
||
make_menubar();
|
||
// FIXME: temporary commands to get at
|
||
// old, hard-coded dialogs.
|
||
add_deprecated_dialogs();
|
||
|
||
// Keep menu visible during gui-redraw
|
||
if( visible )
|
||
show();
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::show ()
|
||
{
|
||
_visible = true;
|
||
recomputeVisibility();
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::hide ()
|
||
{
|
||
_visible = false;
|
||
recomputeVisibility();
|
||
}
|
||
|
||
bool
|
||
FGPUIMenuBar::isVisible () const
|
||
{
|
||
return _visible;
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::setHideIfOverlapsWindow(bool hide)
|
||
{
|
||
_hideOverlapping = hide;
|
||
recomputeVisibility();
|
||
}
|
||
|
||
bool
|
||
FGPUIMenuBar::getHideIfOverlapsWindow() const
|
||
{
|
||
return _hideOverlapping;
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::recomputeVisibility()
|
||
{
|
||
if (_menuBar) {
|
||
const bool actualVis = _visible && (!_hideOverlapping);
|
||
if (actualVis) {
|
||
_menuBar->reveal();
|
||
} else {
|
||
_menuBar->hide();
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::fireItem (puObject * item)
|
||
{
|
||
const char * name = item->getLegend();
|
||
vector<SGBinding *> &bindings = _bindings[name];
|
||
int nBindings = bindings.size();
|
||
|
||
for (int i = 0; i < nBindings; i++)
|
||
bindings[i]->fire();
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::make_menu (SGPropertyNode * node)
|
||
{
|
||
string s = getLocalizedLabel(node);
|
||
|
||
// hack: map at least some UTF-8 characters to Latin1, since FG fonts are
|
||
// Latin1 (or plain ASCII, which is a subset). This hack can be removed once
|
||
// the PLIB/OSG port is complete (OSG has full UTF-8 support! :) ).
|
||
FGLocale::utf8toLatin1(s);
|
||
|
||
const char* name = strdup(s.c_str());
|
||
|
||
vector<SGPropertyNode_ptr> item_nodes = node->getChildren("item");
|
||
|
||
int array_size = item_nodes.size();
|
||
|
||
char ** items = make_char_array(array_size);
|
||
puCallback * callbacks = make_callback_array(array_size);
|
||
|
||
for (unsigned int i = 0, j = item_nodes.size() - 1;
|
||
i < item_nodes.size();
|
||
i++, j--) {
|
||
|
||
// Set up the PUI entries for this item
|
||
string label = getLocalizedLabel(item_nodes[i]);
|
||
FGLocale::utf8toLatin1(label);
|
||
|
||
// append the keyboard hint to the menu entry
|
||
const char* key = item_nodes[i]->getStringValue("key", 0);
|
||
if (key)
|
||
{
|
||
label.append(" <");
|
||
label.append(key);
|
||
label.append(">");
|
||
}
|
||
items[j] = strdup(label.c_str());
|
||
callbacks[j] = menu_callback;
|
||
|
||
// Load all the bindings for this item
|
||
vector<SGPropertyNode_ptr> bindings = item_nodes[i]->getChildren("binding");
|
||
SGPropertyNode * dest = fgGetNode("/sim/bindings/menu", true);
|
||
|
||
for (unsigned int k = 0; k < bindings.size(); k++) {
|
||
unsigned int m = 0;
|
||
SGPropertyNode_ptr binding;
|
||
while (dest->getChild("binding", m))
|
||
m++;
|
||
|
||
binding = dest->getChild("binding", m, true);
|
||
copyProperties(bindings[k], binding);
|
||
_bindings[items[j]].push_back(new SGBinding(binding, globals->get_props()));
|
||
}
|
||
}
|
||
|
||
_menuBar->add_submenu(name, items, callbacks);
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::make_menubar ()
|
||
{
|
||
SGPropertyNode *targetpath;
|
||
|
||
targetpath = fgGetNode("/sim/menubar/default",true);
|
||
// fgLoadProps("gui/menubar.xml", targetpath);
|
||
|
||
/* NOTE: there is no check to see whether there's any usable data at all
|
||
*
|
||
* This would also have the advantage of being able to create some kind of
|
||
* 'fallback' menu - just in case that either menubar.xml is empty OR that
|
||
* its XML data is not valid, that way we would avoid displaying an
|
||
* unusable menubar without any functionality - if we decided to add another
|
||
* char * element to the commands structure in
|
||
* $FG_SRC/src/Main/fgcommands.cxx
|
||
* we could additionally save each function's (short) description and use
|
||
* this as label for the fallback PUI menubar item labels - as a workaround
|
||
* one might simply use the internal fgcommands and put them into the
|
||
* fallback menu, so that the user is at least able to re-init the menu
|
||
* loading - just in case there was some malformed XML in it
|
||
* (it happend to me ...)
|
||
*/
|
||
|
||
make_menubar(targetpath);
|
||
}
|
||
|
||
/* WARNING: We aren't yet doing any validation of what's found - but since
|
||
* this isn't done with menubar.xml either, it should not really matter
|
||
* right now. Although one should later on consider to validate the
|
||
* contents, whether they are representing a 'legal' menubar structure.
|
||
*/
|
||
void
|
||
FGPUIMenuBar::make_menubar(SGPropertyNode * props)
|
||
{
|
||
// Just in case.
|
||
destroy_menubar();
|
||
_menuBar = new puMenuBar;
|
||
|
||
vector<SGPropertyNode_ptr> menu_nodes = props->getChildren("menu");
|
||
for (unsigned int i = 0; i < menu_nodes.size(); i++)
|
||
make_menu(menu_nodes[i]);
|
||
|
||
_menuBar->close();
|
||
make_object_map(props);
|
||
|
||
if (_visible)
|
||
_menuBar->reveal();
|
||
else
|
||
_menuBar->hide();
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::destroy_menubar ()
|
||
{
|
||
if ( _menuBar == 0 )
|
||
return;
|
||
|
||
hide();
|
||
puDeleteObject(_menuBar);
|
||
|
||
unsigned int i;
|
||
|
||
// Delete all the character arrays
|
||
// we were forced to keep around for
|
||
// plib.
|
||
SG_LOG(SG_GENERAL, SG_BULK, "Deleting char arrays");
|
||
for (i = 0; i < _char_arrays.size(); i++) {
|
||
for (int j = 0; _char_arrays[i][j] != 0; j++) {
|
||
free(_char_arrays[i][j]); // added with strdup
|
||
_char_arrays[i][j] = 0;
|
||
}
|
||
delete[] _char_arrays[i];
|
||
_char_arrays[i] = 0;
|
||
}
|
||
|
||
// Delete all the callback arrays
|
||
// we were forced to keep around for
|
||
// plib.
|
||
SG_LOG(SG_GENERAL, SG_BULK, "Deleting callback arrays");
|
||
for (i = 0; i < _callback_arrays.size(); i++)
|
||
delete[] _callback_arrays[i];
|
||
|
||
// Delete all those bindings
|
||
SG_LOG(SG_GENERAL, SG_BULK, "Deleting bindings");
|
||
map<string,vector<SGBinding *> >::iterator it;
|
||
for (it = _bindings.begin(); it != _bindings.end(); it++) {
|
||
SG_LOG(SG_GENERAL, SG_BULK, "Deleting bindings for " << it->first);
|
||
for ( i = 0; i < it->second.size(); i++ )
|
||
delete it->second[i];
|
||
}
|
||
|
||
_menuBar = NULL;
|
||
_bindings.clear();
|
||
_char_arrays.clear();
|
||
_callback_arrays.clear();
|
||
SG_LOG(SG_GENERAL, SG_BULK, "Done.");
|
||
}
|
||
|
||
void
|
||
FGPUIMenuBar::make_object_map(SGPropertyNode * node)
|
||
{
|
||
unsigned int menu_index = 0;
|
||
vector<SGPropertyNode_ptr> menus = node->getChildren("menu");
|
||
for (puObject *obj = ((puGroup *)_menuBar)->getFirstChild();
|
||
obj; obj = obj->getNextObject()) {
|
||
|
||
// skip puPopupMenus. They are also children of _menuBar,
|
||
// but we access them via getUserData() (see below)
|
||
if (!(obj->getType() & PUCLASS_ONESHOT))
|
||
continue;
|
||
|
||
if (menu_index >= menus.size()) {
|
||
SG_LOG(SG_GENERAL, SG_WARN, "'menu' object without node: "
|
||
<< node->getPath() << "/menu[" << menu_index << ']');
|
||
return;
|
||
}
|
||
|
||
SGPropertyNode *menu = menus.at(menu_index);
|
||
_objects[menu->getPath()] = obj;
|
||
add_enabled_listener(menu);
|
||
|
||
puGroup *popup = (puGroup *)obj->getUserData();
|
||
if (!popup)
|
||
continue;
|
||
|
||
// the entries are for some reason reversed (last first), and we
|
||
// don't know yet how many there will be; so we collect first
|
||
vector<puObject *> e;
|
||
for (puObject *me = popup->getFirstChild(); me; me = me->getNextObject())
|
||
e.push_back(me);
|
||
|
||
vector<SGPropertyNode_ptr> items = menu->getChildren("item");
|
||
for (unsigned int i = 0; i < e.size(); i++) {
|
||
if (i >= items.size()) {
|
||
SG_LOG(SG_GENERAL, SG_WARN, "'item' object without node: "
|
||
<< menu->getPath() << "/item[" << i << ']');
|
||
break;
|
||
}
|
||
SGPropertyNode *item = items.at(e.size() - i - 1);
|
||
_objects[item->getPath()] = e[i];
|
||
add_enabled_listener(item);
|
||
}
|
||
menu_index++;
|
||
}
|
||
}
|
||
|
||
namespace {
|
||
|
||
struct EnabledListener : SGPropertyChangeListener {
|
||
void valueChanged(SGPropertyNode *node) {
|
||
NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
|
||
if (!gui)
|
||
return;
|
||
FGPUIMenuBar* menubar = static_cast<FGPUIMenuBar*>(gui->getMenuBar());
|
||
if (menubar)
|
||
menubar->enable_item(node->getParent(), node->getBoolValue());
|
||
}
|
||
};
|
||
|
||
} // of anonymous namespace
|
||
|
||
void
|
||
FGPUIMenuBar::add_enabled_listener(SGPropertyNode * node)
|
||
{
|
||
if (!node->hasValue("enabled"))
|
||
node->setBoolValue("enabled", true);
|
||
|
||
enable_item(node, node->getBoolValue("enabled"));
|
||
node->getNode("enabled")->addChangeListener(new EnabledListener());
|
||
}
|
||
|
||
bool
|
||
FGPUIMenuBar::enable_item(const SGPropertyNode * node, bool state)
|
||
{
|
||
string path = node->getPath();
|
||
if (_objects.find(path) == _objects.end()) {
|
||
SG_LOG(SG_GENERAL, SG_ALERT, "Trying to enable/disable "
|
||
"non-existent menu item for node `" << path << '\'');
|
||
return false;
|
||
}
|
||
puObject *object = _objects[path];
|
||
if (state)
|
||
object->activate();
|
||
else
|
||
object->greyOut();
|
||
|
||
return true;
|
||
}
|
||
|
||
char **
|
||
FGPUIMenuBar::make_char_array (int size)
|
||
{
|
||
char ** list = new char*[size+1];
|
||
for (int i = 0; i <= size; i++)
|
||
list[i] = 0;
|
||
_char_arrays.push_back(list);
|
||
return list;
|
||
}
|
||
|
||
puCallback *
|
||
FGPUIMenuBar::make_callback_array (int size)
|
||
{
|
||
puCallback * list = new puCallback[size+1];
|
||
for (int i = 0; i <= size; i++)
|
||
list[i] = 0;
|
||
_callback_arrays.push_back(list);
|
||
return list;
|
||
}
|
||
|
||
// end of menubar.cxx
|