/* 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 #endif #include #include #include STL_STRING SG_USING_STD(string); #include
#include
#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; inChildren() == 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 ); }