/* 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 #include
#include
#include "new_gui.hxx" #include "prop_picker.hxx" SG_USING_STD(string); // A local alternative name, for use when a variable called "string" // is in scope - e.g. in classes derived from puInput. typedef string stdString; #define DOTDOTSLASH "../" #define SLASH "/" static puObject *PP_widget = 0; // widget location and size... #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 == 0 ) { fgPropPicker *PP = new fgPropPicker ( PROPPICK_X, PROPPICK_Y, PROPPICK_W, PROPPICK_H, 1, "/", "FG Properties"); PP_widget = PP; } } void prop_pickerRefresh() { if( PP_widget == 0 ) prop_pickerInit(); fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData(); me -> find_props( true ); me -> clrValue(); } void prop_editOpen( const char * name, const char * value, char * proppath ) { if( PE_widget == 0 ) PE_widget = new fgPropEdit(name, value, proppath); fgPropEdit *me = (fgPropEdit *)PE_widget -> getUserData(); me -> propname -> setLabel (name); me -> propinput -> setValue (value); strcpy(me -> propPath, proppath); me -> propinput -> acceptInput (); 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; } // 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::chop_file ( char *fname ) { /* removes everything back to the last '/' */ for ( int i = strlen(fname)-1; fname[i] != SLASH[0] && i >= 0; i-- ) fname[i] = '\0'; } void fgPropPicker::go_up_one_directory ( char *fname ) { /* removes everything back to the last but one '/' */ chop_file ( fname ); if ( strlen ( fname ) == 0 ) { /* Empty string! The only way to go up is to append a "../" */ strcpy ( fname, DOTDOTSLASH ); return; } /* If the last path element is a "../" then we'll have to add another "../" */ if ( strcmp ( & fname [ strlen(fname)-3 ], DOTDOTSLASH ) == 0 ) { if ( strlen ( fname ) + 4 >= PUSTRING_MAX ) { ulSetError ( UL_WARNING, "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX ); return; } strcat ( fname, DOTDOTSLASH ); return; } /* Otherwise, just delete the last element of the path. */ /* Remove the trailing slash - then remove the rest as if it was a file name */ fname [ strlen(fname)-1 ] = '\0'; chop_file ( fname ); } 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 ) { char *dst = prop_picker -> startDir; 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. */ go_up_one_directory ( dst ); 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() ) { /* If this is a directory - then descend into it and refresh */ if ( strlen ( dst ) + strlen ( src ) + 2 >= PUSTRING_MAX ) { ulSetError ( UL_WARNING, "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX ); return; } strcat ( dst, src ); /* add path to descend to */ prop_picker -> find_props (); return; } /* If this is a regular file - then just append it to the string */ if ( strlen ( dst ) + strlen ( src ) + 2 >= PUSTRING_MAX ) { ulSetError ( UL_WARNING, "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX ); return; } if (child->getType() == SGPropertyNode::BOOL && (fgGetKeyModifiers() & KEYMOD_CTRL)) { child->setBoolValue(!child->getBoolValue()); prop_pickerRefresh(); } else prop_editOpen(child->getName(), child->getStringValue(), dst); } 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, const char *dir, const char *title ) : fgPopup ( x,y ), _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; strcpy ( startDir, dir ); // printf ( "StartDirLEN=%i", strlen(startDir)); 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 (startDir); 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 ); } // 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(); } // Replace the current list of properties with the children of node "startDir". void fgPropPicker::find_props ( bool restore_pos ) { int pi; int i; delete_arrays(); num_files = 0; // printf("dir begin of find_props=%s\n",startDir); // printf("len of dir=%i",strlen(startDir)); SGPropertyNode * node = globals->get_props()->getNode(startDir); num_files = node ? (int)node->nChildren() : 0; // instantiate string objects and add [.] and [..] for subdirs if (strcmp(startDir,"/") == 0) { files = new char* [ num_files+1 ]; pi = 0; dotFiles = false; } else { // add two for the .. and . num_files = 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; } if (node) { num_children = node->nChildren(); children = new SGPropertyNode_ptr[num_children]; for (i = 0; i < num_children; i++) children[i] = node->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]; stdString name = child->getDisplayName(true); if ( child->nChildren() > 0 ) { files[ pi ] = new char[ strlen(name.c_str())+2 ]; strcpy ( files [ pi ], name.c_str() ); strcat ( files [ pi ], "/" ); } else { files[pi] = NULL; // ensure it's NULL before setting intial value updateTextForEntry(i); // observe it child->addChangeListener(this); } ++pi; } } files [ num_files ] = NULL; proppath -> setLabel(startDir); 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]; // take a copy of the value stdString value = node->getStringValue(); stdString line = node->getDisplayName() + stdString(" = '") + value + "' " + "("; line += getValueTypeString( node ); if (fgGetBool("/sim/gui/dialogs/property-browser/show-flags", false)) { stdString ext; if (!node->getAttribute(SGPropertyNode::READ)) ext += "r"; if (!node->getAttribute(SGPropertyNode::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[ strlen(line.c_str())+2 ]; strcpy ( files [ index ], line.c_str() ); } void fgPropPicker::valueChanged(SGPropertyNode *nd) { for (int C=0; C getUserData (); prop_pickerRefresh(); FG_POP_PUI_DIALOG( prop_edit ); } void fgPropEdit::fgPropEditHandleOK ( puObject* b ) { fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData (); const char* tname; char* tvalue; // use label text for property node to be updated tname = prop_edit -> propname -> getLabel(); prop_edit -> propinput -> getValue( &tvalue ); SGPropertyNode * node = globals->get_props()->getNode(prop_edit -> propPath); node->getNode( prop_edit -> propname -> getLabel(), true)->setStringValue(tvalue); // update the picker display so it shows new value prop_pickerRefresh(); FG_POP_PUI_DIALOG( prop_edit ); } fgPropEdit::fgPropEdit ( const char *name, const char *value, char *proppath ) : fgPopup ( 0, 0 ), _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")); // 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); strcpy (propPath, proppath); setUserData( this ); propname = new puText (fx+10, fy+90); propname -> setLabel(name); 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 (value); 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 ); }