24981fd043
from plib's file-picker, where it made some sense to keep the current path as string, and to chop off elements when leaving a dir, and adding them when entering. But it doesn't make the least sense in SGPropertyNode space, where we already have everything to move in a tree. - add R & W flags for TRACE_READ and TRACE_WRITE. Remember: lower case letters: disabled (rw ... reading/writing), upper case letters: enabled (RWAUT) - remove some verbosity & further cleanup ... to make further work easier :-)
592 lines
16 KiB
C++
Executable file
592 lines
16 KiB
C++
Executable file
/*
|
|
|
|
Adapted by Jim Wilson, beginning Sept 2001 (FG v 0.79)
|
|
|
|
**** Insert FlightGear GPL here.
|
|
|
|
Based on puFilePicker from:
|
|
|
|
********
|
|
PLIB - A Suite of Portable Game Libraries
|
|
Copyright (C) 2001 Steve Baker
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
For further information visit http://plib.sourceforge.net
|
|
|
|
$Id$
|
|
********
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <simgear/compiler.h>
|
|
#include <simgear/props/props.hxx>
|
|
|
|
#include STL_STRING
|
|
SG_USING_STD(string);
|
|
|
|
#include <Main/fg_os.hxx>
|
|
#include <Main/globals.hxx>
|
|
#include "new_gui.hxx"
|
|
#include "prop_picker.hxx"
|
|
|
|
|
|
static puObject *PP_widget = 0;
|
|
|
|
#define PROPPICK_X 100
|
|
#define PROPPICK_Y 200
|
|
#define PROPPICK_W 500
|
|
#define PROPPICK_H 300
|
|
|
|
static puObject *PE_widget = 0;
|
|
|
|
|
|
|
|
// entry point: called from src/GUI/menubar.cxx -- do_properties_dialog() =====
|
|
|
|
void prop_pickerView( puObject * )
|
|
{
|
|
if( PP_widget == 0 )
|
|
prop_pickerInit();
|
|
|
|
fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
|
|
// refresh
|
|
me -> find_props();
|
|
FG_PUSH_PUI_DIALOG( me );
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
void prop_pickerInit()
|
|
{
|
|
if (!PP_widget) {
|
|
fgPropPicker *PP = new fgPropPicker ( PROPPICK_X, PROPPICK_Y, PROPPICK_W, PROPPICK_H,
|
|
1, globals->get_props(), "FG Properties");
|
|
PP_widget = PP;
|
|
}
|
|
}
|
|
|
|
|
|
void prop_pickerRefresh()
|
|
{
|
|
if (!PP_widget)
|
|
prop_pickerInit();
|
|
|
|
fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
|
|
me -> find_props( true );
|
|
me -> clrValue();
|
|
}
|
|
|
|
|
|
void prop_editOpen( SGPropertyNode *node )
|
|
{
|
|
assert(node);
|
|
if (!PE_widget)
|
|
PE_widget = new fgPropEdit(node);
|
|
|
|
fgPropEdit *me = (fgPropEdit *)PE_widget -> getUserData();
|
|
me -> namestring = node->getDisplayName();
|
|
me -> propname -> setLabel (me->namestring.c_str());
|
|
me -> propinput -> setValue (node->getStringValue());
|
|
me -> propinput -> acceptInput ();
|
|
me -> setEditNode(node);
|
|
FG_PUSH_PUI_DIALOG( me );
|
|
}
|
|
|
|
|
|
static string getValueTypeString( const SGPropertyNode_ptr node ) {
|
|
string result;
|
|
|
|
if ( node == NULL )
|
|
return "unknown";
|
|
|
|
SGPropertyNode::Type type = node->getType();
|
|
if ( type == SGPropertyNode::UNSPECIFIED )
|
|
result = "unspecified";
|
|
else if ( type == SGPropertyNode::NONE )
|
|
result = "none";
|
|
else if ( type == SGPropertyNode::BOOL )
|
|
result = "bool";
|
|
else if ( type == SGPropertyNode::INT )
|
|
result = "int";
|
|
else if ( type == SGPropertyNode::LONG )
|
|
result = "long";
|
|
else if ( type == SGPropertyNode::FLOAT )
|
|
result = "float";
|
|
else if ( type == SGPropertyNode::DOUBLE )
|
|
result = "double";
|
|
else if ( type == SGPropertyNode::STRING )
|
|
result = "string";
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// Like strcmp, but for sorting property nodes into a suitable display order.
|
|
static int nodeNameCompare(const void *ppNode1, const void *ppNode2)
|
|
{
|
|
const SGPropertyNode_ptr pNode1 = *(const SGPropertyNode_ptr *)ppNode1;
|
|
const SGPropertyNode_ptr pNode2 = *(const SGPropertyNode_ptr *)ppNode2;
|
|
|
|
// Compare name first, and then index.
|
|
int diff = strcmp(pNode1->getName(), pNode2->getName());
|
|
if (diff)
|
|
return diff;
|
|
|
|
return pNode1->getIndex() - pNode2->getIndex();
|
|
}
|
|
|
|
|
|
|
|
|
|
// property picker class ======================================================
|
|
|
|
void fgPropPicker::fgPropPickerHandleSlider ( puObject * slider )
|
|
{
|
|
puListBox* list_box = (puListBox*) slider -> getUserData ();
|
|
|
|
float val;
|
|
slider -> getValue ( &val );
|
|
val = 1.0f - val;
|
|
int scroll_range = list_box->getNumItems () - list_box->getNumVisible();
|
|
|
|
if ( scroll_range > 0 ) {
|
|
int index = int ( scroll_range * val + 0.5 );
|
|
list_box -> setTopItem ( index );
|
|
}
|
|
}
|
|
|
|
|
|
void fgPropPicker::fgPropPickerHandleArrow ( puObject *arrow )
|
|
{
|
|
puSlider *slider = (puSlider *) arrow->getUserData ();
|
|
puListBox* list_box = (puListBox*) slider -> getUserData ();
|
|
|
|
int type = ((puArrowButton *)arrow)->getArrowType();
|
|
int inc = ( type == PUARROW_DOWN ) ? 1 :
|
|
( type == PUARROW_UP ) ? -1 :
|
|
( type == PUARROW_FASTDOWN ) ? 10 :
|
|
( type == PUARROW_FASTUP ) ? -10 : 0;
|
|
|
|
float val;
|
|
slider -> getValue ( &val );
|
|
val = 1.0f - val;
|
|
int scroll_range = list_box->getNumItems () - list_box->getNumVisible();
|
|
|
|
if ( scroll_range > 0 ) {
|
|
int index = int ( scroll_range * val + 0.5 );
|
|
index += inc;
|
|
// if ( index > scroll_range ) index = scroll_range;
|
|
// Allow buttons to scroll further than the slider does
|
|
if ( index > ( list_box->getNumItems () - 1 ) )
|
|
index = ( list_box->getNumItems () - 1 );
|
|
if ( index < 0 )
|
|
index = 0;
|
|
|
|
slider -> setValue ( 1.0f - (float)index / scroll_range );
|
|
list_box -> setTopItem ( index );
|
|
}
|
|
}
|
|
|
|
|
|
void fgPropPicker::handle_select ( puObject* list_box )
|
|
{
|
|
fgPropPicker* prop_picker = (fgPropPicker*) list_box -> getUserData ();
|
|
int selected;
|
|
list_box -> getValue ( &selected );
|
|
|
|
if ( selected >= 0 && selected < prop_picker -> num_files ) {
|
|
const char *src = prop_picker -> files [ selected ];
|
|
|
|
if (prop_picker->dotFiles && (selected < 2)) {
|
|
if ( strcmp ( src, "." ) == 0 ) {
|
|
/* Do nothing - but better refresh anyway. */
|
|
prop_picker -> find_props ();
|
|
return;
|
|
|
|
} else if ( strcmp ( src, ".." ) == 0 ) {
|
|
/* Do back up one level - so refresh. */
|
|
|
|
SGPropertyNode *parent = prop_picker->getCurrent()->getParent();
|
|
if (parent) {
|
|
prop_picker->setCurrent(parent);
|
|
prop_picker -> find_props ();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// we know we're dealing with a regular entry, so convert
|
|
// it to an index into children[]
|
|
if (prop_picker->dotFiles)
|
|
selected -= 2;
|
|
SGPropertyNode_ptr child = prop_picker->children[selected];
|
|
assert(child != NULL);
|
|
|
|
// check if it's a directory (had children)
|
|
if ( child->nChildren() ) {
|
|
prop_picker->setCurrent(child);
|
|
prop_picker -> find_props ();
|
|
return;
|
|
}
|
|
|
|
// it is a regular property
|
|
if (child->getType() == SGPropertyNode::BOOL && (fgGetKeyModifiers() & KEYMOD_CTRL)) {
|
|
child->setBoolValue(!child->getBoolValue());
|
|
prop_pickerRefresh();
|
|
} else
|
|
prop_editOpen(child);
|
|
|
|
} else {
|
|
// The user clicked on blank screen - maybe we should
|
|
// refresh just in case some other process created the file.
|
|
// Should be obsolete once we observe child add/remove on our top node
|
|
prop_picker -> find_props ();
|
|
}
|
|
}
|
|
|
|
|
|
void fgPropPicker::fgPropPickerHandleOk ( puObject* b )
|
|
{
|
|
fgPropPicker* prop_picker = (fgPropPicker*) b -> getUserData ();
|
|
|
|
// nothing to do, just hide
|
|
FG_POP_PUI_DIALOG( prop_picker );
|
|
}
|
|
|
|
|
|
void fgPropPicker::delete_arrays ()
|
|
{
|
|
if ( files ) {
|
|
for ( int i=0; i<num_files; i++ )
|
|
delete[] files[i];
|
|
|
|
for (int C=0; C<num_children; ++C) {
|
|
if (children[C]->nChildren() == 0)
|
|
children[C]->removeChangeListener(this);
|
|
}
|
|
|
|
delete[] files;
|
|
delete[] children;
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
fgPropPicker::~fgPropPicker ()
|
|
{
|
|
delete_arrays();
|
|
|
|
if ( this == puActiveWidget () )
|
|
puDeactivateWidget ();
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
fgPropPicker::fgPropPicker ( int x, int y, int w, int h, int arrows,
|
|
SGPropertyNode *start, const char *title ) :
|
|
fgPopup ( x,y ),
|
|
curr(start),
|
|
flags(fgGetNode("/sim/gui/dialogs/property-browser/show-flags", true)),
|
|
_gui((NewGUI *)globals->get_subsystem("gui"))
|
|
{
|
|
puFont LegendFont, LabelFont;
|
|
puGetDefaultFonts ( &LegendFont, &LabelFont );
|
|
FGColor txtcol(_gui->getColor("label"));
|
|
txtcol.merge(_gui->getColor("text"));
|
|
txtcol.merge(_gui->getColor("text-label"));
|
|
|
|
files = NULL;
|
|
num_files = 0;
|
|
|
|
if ( arrows > 2 )
|
|
arrows = 2;
|
|
if ( arrows < 0 )
|
|
arrows = 0;
|
|
arrow_count = arrows;
|
|
|
|
frame = new puFrame ( 0, 0, w, h );
|
|
|
|
setUserData( this );
|
|
|
|
proppath = new puText (10, h-30);
|
|
proppath -> setLabel (curr->getPath(true));
|
|
proppath -> setColor(PUCOL_LABEL, txtcol.red(), txtcol.green(),
|
|
txtcol.blue(), txtcol.alpha());
|
|
|
|
slider = new puSlider (w-30,40+20*arrows,h-100-40*arrows,TRUE,20);
|
|
slider->setValue(1.0f);
|
|
|
|
list_box = new puListBox ( 10, 40, w-40, h-60 );
|
|
list_box -> setLabel ( title );
|
|
list_box -> setLabelPlace ( PUPLACE_ABOVE );
|
|
list_box -> setStyle ( -PUSTYLE_SMALL_SHADED );
|
|
list_box -> setUserData ( this );
|
|
list_box -> setCallback ( handle_select );
|
|
list_box -> setValue ( 0 );
|
|
list_box -> setColor(PUCOL_LABEL, txtcol.red(), txtcol.green(),
|
|
txtcol.blue(), txtcol.alpha());
|
|
|
|
ok_button = new puOneShot ( 10, 10, (w<170)?(w/2-5):80, 30 );
|
|
ok_button -> setLegend ( "Ok" );
|
|
ok_button -> setUserData ( this );
|
|
ok_button -> setCallback ( fgPropPickerHandleOk );
|
|
|
|
if ( arrows > 0 ) {
|
|
down_arrow = new puArrowButton ( w-30, 20+20*arrows, w-10, 40+20*arrows, PUARROW_DOWN );
|
|
down_arrow->setUserData ( slider );
|
|
down_arrow->setCallback ( fgPropPickerHandleArrow );
|
|
|
|
up_arrow = new puArrowButton ( w-30, h-60-20*arrows, w-10, h-40-20*arrows, PUARROW_UP );
|
|
up_arrow->setUserData ( slider );
|
|
up_arrow->setCallback ( fgPropPickerHandleArrow );
|
|
}
|
|
|
|
// after picker is built, load the list box with data...
|
|
find_props ();
|
|
|
|
// printf("after Props files[1]=%s\n",files[1]);
|
|
// printf("num items %i", list_box -> getNumItems ());
|
|
|
|
slider -> setUserData ( list_box );
|
|
slider -> setCallback ( fgPropPickerHandleSlider );
|
|
|
|
FG_FINALIZE_PUI_DIALOG( this );
|
|
}
|
|
|
|
|
|
// Replace list with children of current
|
|
void fgPropPicker::find_props ( bool restore_pos )
|
|
{
|
|
int pi;
|
|
int i;
|
|
|
|
delete_arrays();
|
|
num_files = (int)curr->nChildren();
|
|
|
|
// instantiate string objects and add [.] and [..] for subdirs
|
|
if (!curr->getParent()) {
|
|
files = new char* [ num_files+1 ];
|
|
pi = 0;
|
|
dotFiles = false;
|
|
|
|
} else {
|
|
// add two for the .. and .
|
|
num_files += 2;
|
|
// make room for .. and .
|
|
files = new char* [ num_files+1 ];
|
|
|
|
stdString line = ".";
|
|
files [ 0 ] = new char[line.size() + 1];
|
|
strcpy ( files [ 0 ], line.c_str() );
|
|
|
|
line = "..";
|
|
files [ 1 ] = new char[line.size() + 1];
|
|
strcpy ( files [ 1 ], line.c_str() );
|
|
|
|
pi = 2;
|
|
dotFiles = true;
|
|
}
|
|
|
|
num_children = curr->nChildren();
|
|
children = new SGPropertyNode_ptr[num_children];
|
|
for (i = 0; i < num_children; i++)
|
|
children[i] = curr->getChild(i);
|
|
|
|
qsort(children, num_children, sizeof(children[0]), nodeNameCompare);
|
|
|
|
// Make lists of the children's names, values, etc.
|
|
for (i = 0; i < num_children; i++) {
|
|
SGPropertyNode * child = children[i];
|
|
|
|
if ( child->nChildren() > 0 ) {
|
|
stdString name = stdString(child->getDisplayName(true)) + '/';
|
|
files[ pi ] = new char[ name.size() + 1 ];
|
|
strcpy ( files [ pi ], name.c_str() );
|
|
|
|
} else {
|
|
files[pi] = NULL; // ensure it's NULL before setting intial value
|
|
updateTextForEntry(i);
|
|
child->addChangeListener(this);
|
|
}
|
|
|
|
++pi;
|
|
}
|
|
|
|
files [ num_files ] = NULL;
|
|
|
|
proppath -> setLabel(curr->getPath(true));
|
|
|
|
int top = list_box->getTopItem();
|
|
list_box -> newList ( files );
|
|
if (restore_pos)
|
|
list_box->setTopItem(top);
|
|
|
|
// adjust the size of the slider...
|
|
if (num_files > list_box->getNumVisible()) {
|
|
slider->setSliderFraction((float)list_box->getNumVisible() / num_files);
|
|
if (!restore_pos)
|
|
slider->setValue(1.0f);
|
|
|
|
slider->reveal();
|
|
up_arrow->reveal();
|
|
down_arrow->reveal();
|
|
|
|
} else {
|
|
slider->hide();
|
|
up_arrow->hide();
|
|
down_arrow->hide();
|
|
}
|
|
}
|
|
|
|
|
|
void fgPropPicker::updateTextForEntry(int index)
|
|
{
|
|
assert((index >= 0) && (index < num_children));
|
|
SGPropertyNode_ptr node = children[index];
|
|
|
|
stdString name = node->getDisplayName(true);
|
|
stdString type = getValueTypeString(node);
|
|
stdString value = node->getStringValue();
|
|
|
|
stdString line = name + " = '" + value + "' (" + type;
|
|
|
|
if (flags->getBoolValue()) {
|
|
stdString ext;
|
|
if (!node->getAttribute(SGPropertyNode::READ))
|
|
ext += 'r';
|
|
if (!node->getAttribute(SGPropertyNode::WRITE))
|
|
ext += 'w';
|
|
if (node->getAttribute(SGPropertyNode::TRACE_READ))
|
|
ext += 'R';
|
|
if (node->getAttribute(SGPropertyNode::TRACE_WRITE))
|
|
ext += 'W';
|
|
if (node->getAttribute(SGPropertyNode::ARCHIVE))
|
|
ext += 'A';
|
|
if (node->getAttribute(SGPropertyNode::USERARCHIVE))
|
|
ext += 'U';
|
|
if (node->isTied())
|
|
ext += 'T';
|
|
if (ext.size())
|
|
line += ", " + ext;
|
|
}
|
|
|
|
line += ')';
|
|
|
|
// truncate entries to plib pui limit
|
|
if (line.length() >= PUSTRING_MAX)
|
|
line[PUSTRING_MAX-1] = '\0';
|
|
|
|
if (dotFiles)
|
|
index += 2;
|
|
|
|
// don't leak everywhere if we're updating
|
|
delete[] files[index];
|
|
|
|
files[index] = new char[ line.size() + 1 ];
|
|
strcpy ( files [ index ], line.c_str() );
|
|
}
|
|
|
|
|
|
void fgPropPicker::valueChanged(SGPropertyNode *nd)
|
|
{
|
|
for (int i = 0; i < num_children; i++)
|
|
if (children[i] == nd) {
|
|
updateTextForEntry(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// property editor class ======================================================
|
|
|
|
void fgPropEdit::fgPropEditHandleCancel ( puObject* b )
|
|
{
|
|
fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData ();
|
|
prop_pickerRefresh();
|
|
FG_POP_PUI_DIALOG( prop_edit );
|
|
}
|
|
|
|
|
|
void fgPropEdit::fgPropEditHandleOK ( puObject* b )
|
|
{
|
|
fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData ();
|
|
char* tvalue;
|
|
|
|
prop_edit -> propinput -> getValue( &tvalue );
|
|
prop_edit->getEditNode()->setStringValue(tvalue);
|
|
|
|
// update the picker display so it shows new value
|
|
prop_pickerRefresh();
|
|
|
|
FG_POP_PUI_DIALOG( prop_edit );
|
|
}
|
|
|
|
|
|
fgPropEdit::fgPropEdit ( SGPropertyNode *n ) :
|
|
fgPopup ( 0, 0 ),
|
|
node(n),
|
|
_gui((NewGUI *)globals->get_subsystem("gui"))
|
|
{
|
|
assert(n);
|
|
|
|
puFont LegendFont, LabelFont;
|
|
puGetDefaultFonts ( &LegendFont, &LabelFont );
|
|
FGColor txtcol(_gui->getColor("label"));
|
|
txtcol.merge(_gui->getColor("text"));
|
|
txtcol.merge(_gui->getColor("text-label"));
|
|
|
|
// locate in relation to picker widget...
|
|
int fx = PROPPICK_X;
|
|
int fy = PROPPICK_Y + PROPPICK_H;
|
|
frame = new puFrame (fx,fy, fx+500, fy+120);
|
|
|
|
setUserData( this );
|
|
|
|
namestring = node->getDisplayName();
|
|
propname = new puText (fx+10, fy+90);
|
|
propname -> setLabel(namestring.c_str());
|
|
propname -> setColor(PUCOL_LABEL, txtcol.red(), txtcol.green(),
|
|
txtcol.blue(), txtcol.alpha());
|
|
|
|
propinput = new puInput (fx+10, fy+50, fx+480, fy+80);
|
|
propinput -> setValue (node->getStringValue());
|
|
propinput -> acceptInput();
|
|
|
|
ok_button = new puOneShot (fx+10, fy+10, fx+80, fy+30);
|
|
ok_button -> setUserData (this);
|
|
ok_button -> setLegend (gui_msg_OK);
|
|
ok_button -> setCallback (fgPropEditHandleOK);
|
|
ok_button -> makeReturnDefault (TRUE);
|
|
|
|
cancel_button = new puOneShot (fx+100, fy+10, fx+180, fy+30);
|
|
cancel_button -> setUserData (this);
|
|
cancel_button -> setLegend (gui_msg_CANCEL);
|
|
cancel_button -> setCallback (fgPropEditHandleCancel);
|
|
|
|
FG_FINALIZE_PUI_DIALOG( this );
|
|
}
|
|
|
|
|