3 Adapted by Jim Wilson, beginning Sept 2001 (FG v 0.79)
5 **** Insert FlightGear GPL here.
7 Based on puFilePicker from:
10 PLIB - A Suite of Portable Game Libraries
11 Copyright (C) 2001 Steve Baker
13 This library is free software; you can redistribute it and/or
14 modify it under the terms of the GNU Library General Public
15 License as published by the Free Software Foundation; either
16 version 2 of the License, or (at your option) any later version.
18 This library is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 Library General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 For further information visit http://plib.sourceforge.net
38 #include <simgear/compiler.h>
39 #include <simgear/props/props.hxx>
43 #include <Main/fg_os.hxx>
44 #include <Main/globals.hxx>
45 #include "new_gui.hxx"
46 #include "prop_picker.hxx"
50 // A local alternative name, for use when a variable called "string"
51 // is in scope - e.g. in classes derived from puInput.
52 typedef string stdString;
54 #define DOTDOTSLASH "../"
57 static puObject *PP_widget = 0;
59 // widget location and size...
60 #define PROPPICK_X 100
61 #define PROPPICK_Y 200
62 #define PROPPICK_W 500
63 #define PROPPICK_H 300
65 static puObject *PE_widget = 0;
69 // entry point: called from src/GUI/menubar.cxx -- do_properties_dialog() =====
71 void prop_pickerView( puObject * )
76 fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
79 FG_PUSH_PUI_DIALOG( me );
85 // ============================================================================
87 void prop_pickerInit()
89 if ( PP_widget == 0 ) {
90 fgPropPicker *PP = new fgPropPicker ( PROPPICK_X, PROPPICK_Y, PROPPICK_W, PROPPICK_H,
91 1, "/", "FG Properties");
97 void prop_pickerRefresh()
102 fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
103 me -> find_props( true );
108 void prop_editOpen( const char * name, const char * value, char * proppath )
111 PE_widget = new fgPropEdit(name, value, proppath);
113 fgPropEdit *me = (fgPropEdit *)PE_widget -> getUserData();
114 me -> propname -> setLabel (name);
115 me -> propinput -> setValue (value);
116 strcpy(me -> propPath, proppath);
117 me -> propinput -> acceptInput ();
118 FG_PUSH_PUI_DIALOG( me );
122 static string getValueTypeString( const SGPropertyNode_ptr node ) {
128 SGPropertyNode::Type type = node->getType();
129 if ( type == SGPropertyNode::UNSPECIFIED )
130 result = "unspecified";
131 else if ( type == SGPropertyNode::NONE )
133 else if ( type == SGPropertyNode::BOOL )
135 else if ( type == SGPropertyNode::INT )
137 else if ( type == SGPropertyNode::LONG )
139 else if ( type == SGPropertyNode::FLOAT )
141 else if ( type == SGPropertyNode::DOUBLE )
143 else if ( type == SGPropertyNode::STRING )
152 // property picker class ======================================================
154 void fgPropPicker::fgPropPickerHandleSlider ( puObject * slider )
156 puListBox* list_box = (puListBox*) slider -> getUserData ();
159 slider -> getValue ( &val );
161 int scroll_range = list_box->getNumItems () - list_box->getNumVisible();
163 if ( scroll_range > 0 ) {
164 int index = int ( scroll_range * val + 0.5 );
165 list_box -> setTopItem ( index );
170 void fgPropPicker::fgPropPickerHandleArrow ( puObject *arrow )
172 puSlider *slider = (puSlider *) arrow->getUserData ();
173 puListBox* list_box = (puListBox*) slider -> getUserData ();
175 int type = ((puArrowButton *)arrow)->getArrowType();
176 int inc = ( type == PUARROW_DOWN ) ? 1 :
177 ( type == PUARROW_UP ) ? -1 :
178 ( type == PUARROW_FASTDOWN ) ? 10 :
179 ( type == PUARROW_FASTUP ) ? -10 : 0;
182 slider -> getValue ( &val );
184 int scroll_range = list_box->getNumItems () - list_box->getNumVisible();
186 if ( scroll_range > 0 ) {
187 int index = int ( scroll_range * val + 0.5 );
189 // if ( index > scroll_range ) index = scroll_range;
190 // Allow buttons to scroll further than the slider does
191 if ( index > ( list_box->getNumItems () - 1 ) )
192 index = ( list_box->getNumItems () - 1 );
196 slider -> setValue ( 1.0f - (float)index / scroll_range );
197 list_box -> setTopItem ( index );
202 void fgPropPicker::chop_file ( char *fname )
204 /* removes everything back to the last '/' */
206 for ( int i = strlen(fname)-1; fname[i] != SLASH[0] && i >= 0; i-- )
211 void fgPropPicker::go_up_one_directory ( char *fname )
213 /* removes everything back to the last but one '/' */
217 if ( strlen ( fname ) == 0 ) {
218 /* Empty string! The only way to go up is to append a "../" */
219 strcpy ( fname, DOTDOTSLASH );
223 /* If the last path element is a "../" then we'll have to add another "../" */
225 if ( strcmp ( & fname [ strlen(fname)-3 ], DOTDOTSLASH ) == 0 ) {
226 if ( strlen ( fname ) + 4 >= PUSTRING_MAX ) {
227 ulSetError ( UL_WARNING, "PUI: fgPropPicker - path is too long, max is %d.",
232 strcat ( fname, DOTDOTSLASH );
236 /* Otherwise, just delete the last element of the path. */
237 /* Remove the trailing slash - then remove the rest as if it was a file name */
239 fname [ strlen(fname)-1 ] = '\0';
244 void fgPropPicker::handle_select ( puObject* list_box )
246 fgPropPicker* prop_picker = (fgPropPicker*) list_box -> getUserData ();
248 list_box -> getValue ( &selected );
250 if ( selected >= 0 && selected < prop_picker -> num_files ) {
251 char *dst = prop_picker -> startDir;
252 char *src = prop_picker -> files [ selected ];
254 if (prop_picker->dotFiles && (selected < 2)) {
255 if ( strcmp ( src, "." ) == 0 ) {
256 /* Do nothing - but better refresh anyway. */
258 prop_picker -> find_props ();
260 } else if ( strcmp ( src, ".." ) == 0 ) {
261 /* Do back up one level - so refresh. */
263 go_up_one_directory ( dst );
264 prop_picker -> find_props ();
269 // we know we're dealing with a regular entry, so convert
270 // it to an index into children[]
271 if (prop_picker->dotFiles)
273 SGPropertyNode_ptr child = prop_picker->children[selected];
274 assert(child != NULL);
276 // check if it's a directory (had children)
277 if ( child->nChildren() ) {
278 /* If this is a directory - then descend into it and refresh */
280 if ( strlen ( dst ) + strlen ( src ) + 2 >= PUSTRING_MAX ) {
281 ulSetError ( UL_WARNING,
282 "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX );
286 strcat ( dst, src ); /* add path to descend to */
287 prop_picker -> find_props ();
291 /* If this is a regular file - then just append it to the string */
293 if ( strlen ( dst ) + strlen ( src ) + 2 >= PUSTRING_MAX ) {
294 ulSetError ( UL_WARNING,
295 "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX );
299 if (child->getType() == SGPropertyNode::BOOL && (fgGetKeyModifiers() & KEYMOD_CTRL)) {
300 child->setBoolValue(!child->getBoolValue());
301 prop_pickerRefresh();
303 prop_editOpen(child->getName(), child->getStringValue(), dst);
306 // The user clicked on blank screen - maybe we should
307 // refresh just in case some other process created the file.
308 // Should be obsolete once we observe child add/remove on our top node
309 prop_picker -> find_props ();
314 void fgPropPicker::fgPropPickerHandleOk ( puObject* b )
316 fgPropPicker* prop_picker = (fgPropPicker*) b -> getUserData ();
318 /* nothing to do, just hide */
319 FG_POP_PUI_DIALOG( prop_picker );
323 void fgPropPicker::delete_arrays ()
326 for ( int i=0; i<num_files; i++ )
329 for (int C=0; C<num_children; ++C) {
330 if (children[C]->nChildren() == 0)
331 children[C]->removeChangeListener(this);
341 fgPropPicker::~fgPropPicker ()
345 if ( this == puActiveWidget () )
346 puDeactivateWidget ();
352 fgPropPicker::fgPropPicker ( int x, int y, int w, int h, int arrows,
353 const char *dir, const char *title ) :
355 _gui((NewGUI *)globals->get_subsystem("gui"))
357 puFont LegendFont, LabelFont;
358 puGetDefaultFonts ( &LegendFont, &LabelFont );
359 FGColor txtcol(_gui->getColor("label"));
360 txtcol.merge(_gui->getColor("text"));
361 txtcol.merge(_gui->getColor("text-label"));
366 strcpy ( startDir, dir );
367 // printf ( "StartDirLEN=%i", strlen(startDir));
372 arrow_count = arrows;
374 frame = new puFrame ( 0, 0, w, h );
378 proppath = new puText (10, h-30);
379 proppath -> setLabel (startDir);
380 proppath -> setColor(PUCOL_LABEL, txtcol.red(), txtcol.green(),
381 txtcol.blue(), txtcol.alpha());
383 slider = new puSlider (w-30,40+20*arrows,h-100-40*arrows,TRUE,20);
384 slider->setValue(1.0f);
386 list_box = new puListBox ( 10, 40, w-40, h-60 );
387 list_box -> setLabel ( title );
388 list_box -> setLabelPlace ( PUPLACE_ABOVE );
389 list_box -> setStyle ( -PUSTYLE_SMALL_SHADED );
390 list_box -> setUserData ( this );
391 list_box -> setCallback ( handle_select );
392 list_box -> setValue ( 0 );
393 list_box -> setColor(PUCOL_LABEL, txtcol.red(), txtcol.green(),
394 txtcol.blue(), txtcol.alpha());
396 ok_button = new puOneShot ( 10, 10, (w<170)?(w/2-5):80, 30 );
397 ok_button -> setLegend ( "Ok" );
398 ok_button -> setUserData ( this );
399 ok_button -> setCallback ( fgPropPickerHandleOk );
402 down_arrow = new puArrowButton ( w-30, 20+20*arrows, w-10, 40+20*arrows, PUARROW_DOWN );
403 down_arrow->setUserData ( slider );
404 down_arrow->setCallback ( fgPropPickerHandleArrow );
406 up_arrow = new puArrowButton ( w-30, h-60-20*arrows, w-10, h-40-20*arrows, PUARROW_UP );
407 up_arrow->setUserData ( slider );
408 up_arrow->setCallback ( fgPropPickerHandleArrow );
411 // after picker is built, load the list box with data...
414 // printf("after Props files[1]=%s\n",files[1]);
415 // printf("num items %i", list_box -> getNumItems ());
417 slider -> setUserData ( list_box );
418 slider -> setCallback ( fgPropPickerHandleSlider );
420 FG_FINALIZE_PUI_DIALOG( this );
424 // Like strcmp, but for sorting property nodes into a suitable display order.
425 static int nodeNameCompare(const void *ppNode1, const void *ppNode2)
427 const SGPropertyNode_ptr pNode1 = *(const SGPropertyNode_ptr *)ppNode1;
428 const SGPropertyNode_ptr pNode2 = *(const SGPropertyNode_ptr *)ppNode2;
430 // Compare name first, and then index.
431 int diff = strcmp(pNode1->getName(), pNode2->getName());
435 return pNode1->getIndex() - pNode2->getIndex();
439 // Replace the current list of properties with the children of node "startDir".
440 void fgPropPicker::find_props ( bool restore_pos )
448 // printf("dir begin of find_props=%s\n",startDir);
449 // printf("len of dir=%i",strlen(startDir));
450 SGPropertyNode * node = globals->get_props()->getNode(startDir);
452 num_files = node ? (int)node->nChildren() : 0;
454 // instantiate string objects and add [.] and [..] for subdirs
455 if (strcmp(startDir,"/") == 0) {
456 files = new char* [ num_files+1 ];
460 // add two for the .. and .
461 num_files = num_files + 2;
462 // make room for .. and .
463 files = new char* [ num_files+1 ];
465 stdString line = ".";
466 files [ 0 ] = new char[line.size() + 1];
467 strcpy ( files [ 0 ], line.c_str() );
470 files [ 1 ] = new char[line.size() + 1];
471 strcpy ( files [ 1 ], line.c_str() );
479 num_children = node->nChildren();
480 children = new SGPropertyNode_ptr[num_children];
481 for (i = 0; i < num_children; i++)
482 children[i] = node->getChild(i);
484 qsort(children, num_children, sizeof(children[0]), nodeNameCompare);
486 // Make lists of the children's names, values, etc.
487 for (i = 0; i < num_children; i++) {
488 SGPropertyNode * child = children[i];
489 stdString name = child->getDisplayName(true);
491 if ( child->nChildren() > 0 ) {
492 files[ pi ] = new char[ strlen(name.c_str())+2 ];
493 strcpy ( files [ pi ], name.c_str() );
494 strcat ( files [ pi ], "/" );
496 files[pi] = NULL; // ensure it's NULL before setting intial value
497 updateTextForEntry(i);
499 child->addChangeListener(this);
506 files [ num_files ] = NULL;
508 proppath -> setLabel(startDir);
510 int top = list_box->getTopItem();
511 list_box -> newList ( files );
513 list_box->setTopItem(top);
515 // adjust the size of the slider...
516 if (num_files > list_box->getNumVisible()) {
517 slider->setSliderFraction((float)list_box->getNumVisible() / num_files);
519 slider->setValue(1.0f);
523 down_arrow->reveal();
533 void fgPropPicker::updateTextForEntry(int index)
535 assert((index >= 0) && (index < num_children));
536 SGPropertyNode_ptr node = children[index];
538 // take a copy of the value
539 stdString value = node->getStringValue();
541 stdString line = node->getDisplayName() + stdString(" = '") + value + "' " + "(";
542 line += getValueTypeString( node );
544 if (fgGetBool("/sim/gui/dialogs/property-browser/show-flags", false)) {
546 if (!node->getAttribute(SGPropertyNode::READ))
548 if (!node->getAttribute(SGPropertyNode::WRITE))
550 if (node->getAttribute(SGPropertyNode::ARCHIVE))
552 if (node->getAttribute(SGPropertyNode::USERARCHIVE))
562 // truncate entries to plib pui limit
563 if (line.length() >= PUSTRING_MAX)
564 line[PUSTRING_MAX-1] = '\0';
569 // don't leak everywhere if we're updating
570 delete[] files[index];
572 files[index] = new char[ strlen(line.c_str())+2 ];
573 strcpy ( files [ index ], line.c_str() );
577 void fgPropPicker::valueChanged(SGPropertyNode *nd)
579 for (int C=0; C<num_children; ++C)
580 if (children[C] == nd) {
581 updateTextForEntry(C);
589 // property editor class ======================================================
591 void fgPropEdit::fgPropEditHandleCancel ( puObject* b )
593 fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData ();
594 prop_pickerRefresh();
595 FG_POP_PUI_DIALOG( prop_edit );
599 void fgPropEdit::fgPropEditHandleOK ( puObject* b )
601 fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData ();
605 // use label text for property node to be updated
606 tname = prop_edit -> propname -> getLabel();
607 prop_edit -> propinput -> getValue( &tvalue );
609 SGPropertyNode * node = globals->get_props()->getNode(prop_edit -> propPath);
610 node->getNode( prop_edit -> propname -> getLabel(), true)->setStringValue(tvalue);
612 // update the picker display so it shows new value
613 prop_pickerRefresh();
615 FG_POP_PUI_DIALOG( prop_edit );
619 fgPropEdit::fgPropEdit ( const char *name, const char *value, char *proppath ) :
621 _gui((NewGUI *)globals->get_subsystem("gui"))
623 puFont LegendFont, LabelFont;
624 puGetDefaultFonts ( &LegendFont, &LabelFont );
625 FGColor txtcol(_gui->getColor("label"));
626 txtcol.merge(_gui->getColor("text"));
627 txtcol.merge(_gui->getColor("text-label"));
629 // locate in relation to picker widget...
631 int fy = PROPPICK_Y + PROPPICK_H;
632 frame = new puFrame (fx,fy, fx+500, fy+120);
634 strcpy (propPath, proppath);
638 propname = new puText (fx+10, fy+90);
639 propname -> setLabel(name);
640 propname -> setColor(PUCOL_LABEL, txtcol.red(), txtcol.green(),
641 txtcol.blue(), txtcol.alpha());
643 propinput = new puInput (fx+10, fy+50, fx+480, fy+80);
644 propinput -> setValue (value);
645 propinput -> acceptInput();
647 ok_button = new puOneShot (fx+10, fy+10, fx+80, fy+30);
648 ok_button -> setUserData (this);
649 ok_button -> setLegend (gui_msg_OK);
650 ok_button -> setCallback (fgPropEditHandleOK);
651 ok_button -> makeReturnDefault (TRUE);
653 cancel_button = new puOneShot (fx+100, fy+10, fx+180, fy+30);
654 cancel_button -> setUserData (this);
655 cancel_button -> setLegend (gui_msg_CANCEL);
656 cancel_button -> setCallback (fgPropEditHandleCancel);
658 FG_FINALIZE_PUI_DIALOG( this );