]> git.mxchange.org Git - flightgear.git/blob - src/GUI/prop_picker.cxx
Make sure that all elapsed time gets passed to update when a subsystem
[flightgear.git] / src / GUI / prop_picker.cxx
1 /*
2
3      Adapted by Jim Wilson, beginning Sept 2001 (FG v 0.79)
4
5 ****     Insert FlightGear GPL here.
6
7      Based on puFilePicker from:
8
9      ********
10      PLIB - A Suite of Portable Game Libraries
11      Copyright (C) 2001  Steve Baker
12  
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.
17  
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.
22  
23      You should have received a copy of the GNU Library General Public
24      License along with this library; if not, write to the Free
25      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
26  
27      For further information visit http://plib.sourceforge.net
28
29      $Id$
30      ********
31 */
32
33
34 #ifdef HAVE_CONFIG_H
35 #  include <config.h>
36 #endif
37
38 #include "prop_picker.hxx"
39 #include <Main/globals.hxx>
40
41 #include <simgear/compiler.h>
42 #include <simgear/misc/props.hxx>
43
44 #include STL_STRING
45 SG_USING_STD(string);
46
47 // A local alternative name, for use when a variable called "string" is in scope - e.g. in classes derived from puInput.
48 typedef string stdString;
49
50 #define DOTDOTSLASH "../"
51 #define SLASH       "/"
52
53 static puObject *PP_widget = 0;
54
55 // widget location and size...
56 #define PROPPICK_X 100
57 #define PROPPICK_Y 200
58 #define PROPPICK_W 500
59 #define PROPPICK_H 300
60
61 static puObject *PE_widget = 0;
62
63 void prop_pickerInit()
64 {
65         if( PP_widget == 0 ) {
66             fgPropPicker *PP = new fgPropPicker ( PROPPICK_X, PROPPICK_Y, PROPPICK_W, PROPPICK_H, 1, "/", "FG Properties");
67             PP_widget = PP;
68         }
69 }
70
71 void prop_pickerView( puObject * )
72 {
73         if( PP_widget == 0 ) {
74                 prop_pickerInit();
75         }
76         fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
77         // refresh
78         me -> find_props();
79         FG_PUSH_PUI_DIALOG( me );
80 }
81
82 void prop_pickerRefresh()
83 {
84         if( PP_widget == 0 ) {
85                 prop_pickerInit();
86         }
87         fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
88         me -> find_props();
89 }
90
91 void prop_editInit( const char * name, const char * value, char * proppath )
92 {
93         if( PE_widget == 0 ) {
94             fgPropEdit *PP = new fgPropEdit ( name, value, proppath );
95             PE_widget = PP;
96         }
97 }
98
99 void prop_editOpen( const char * name, const char * value, char * proppath )
100 {
101         if( PE_widget == 0 ) {
102                 prop_editInit( name, value, proppath );
103         }
104         fgPropEdit *me = (fgPropEdit *)PE_widget -> getUserData();
105         me -> propname ->    setLabel          (name);
106         me -> propinput ->   setValue          (value);
107         strcpy(me -> propPath, proppath);
108         me -> propinput -> acceptInput ();
109         FG_PUSH_PUI_DIALOG( me );
110 }
111
112 // return a human readable form of the value "type"
113 static string getValueTypeString( const SGPropertyNode_ptr node ) {
114     string result;
115
116     if ( node == NULL ) {
117         return "unknown";
118     }
119
120     SGPropertyNode::Type type = node->getType();
121     if ( type == SGPropertyNode::UNSPECIFIED ) {
122         result = "unspecified";
123     } else if ( type == SGPropertyNode::NONE ) {
124         result = "none";
125     } else if ( type == SGPropertyNode::BOOL ) {
126         result = "bool";
127     } else if ( type == SGPropertyNode::INT ) {
128         result = "int";
129     } else if ( type == SGPropertyNode::LONG ) {
130         result = "long";
131     } else if ( type == SGPropertyNode::FLOAT ) {
132         result = "float";
133     } else if ( type == SGPropertyNode::DOUBLE ) {
134         result = "double";
135     } else if ( type == SGPropertyNode::STRING ) {
136         result = "string";
137     }
138
139     return result;
140 }
141
142 void fgPropPicker::fgPropPickerHandleSlider ( puObject * slider )
143 {
144   puListBox* list_box = (puListBox*) slider -> getUserData () ;
145
146   float val ;
147   slider -> getValue ( &val ) ;
148   val = 1.0f - val ;
149   int scroll_range = list_box->getNumItems () - list_box->getNumVisible() ;
150   if ( scroll_range > 0 )
151   {
152     int index = int ( scroll_range * val + 0.5 ) ;
153     list_box -> setTopItem ( index ) ;
154   }
155 }
156
157 void fgPropPicker::fgPropPickerHandleArrow ( puObject *arrow )
158 {
159   puSlider *slider = (puSlider *) arrow->getUserData () ;
160   puListBox* list_box = (puListBox*) slider -> getUserData () ;
161
162   int type = ((puArrowButton *)arrow)->getArrowType() ;
163   int inc = ( type == PUARROW_DOWN     ) ?   1 :
164             ( type == PUARROW_UP       ) ?  -1 :
165             ( type == PUARROW_FASTDOWN ) ?  10 :
166             ( type == PUARROW_FASTUP   ) ? -10 : 0 ;
167
168   float val ;
169   slider -> getValue ( &val ) ;
170   val = 1.0f - val ;
171   int scroll_range = list_box->getNumItems () - list_box->getNumVisible() ;
172   if ( scroll_range > 0 )
173   {
174     int index = int ( scroll_range * val + 0.5 ) ;
175     index += inc ;
176     // if ( index > scroll_range ) index = scroll_range ;
177     // Allow buttons to scroll further than the slider does
178     if ( index > ( list_box->getNumItems () - 1 ) )
179       index = ( list_box->getNumItems () - 1 ) ;
180     if ( index < 0 ) index = 0 ;
181
182     slider -> setValue ( 1.0f - (float)index / scroll_range ) ;
183     list_box -> setTopItem ( index ) ;
184   }
185 }
186
187
188 void fgPropPicker::chop_file ( char *fname )
189 {
190   /* removes everything back to the last '/' */
191
192   for ( int i = strlen(fname)-1 ; fname[i] != SLASH[0] && i >= 0 ; i-- )
193     fname[i] = '\0' ;
194 }
195
196
197 void fgPropPicker::go_up_one_directory ( char *fname )
198 {
199   /* removes everything back to the last but one '/' */
200
201   chop_file ( fname ) ;
202
203   if ( strlen ( fname ) == 0 )
204   {
205     /* Empty string!  The only way to go up is to append a "../" */
206
207     strcpy ( fname, DOTDOTSLASH ) ;
208     return ;
209   }
210
211   /* If the last path element is a "../" then we'll have to add another "../" */
212
213   if ( strcmp ( & fname [ strlen(fname)-3 ], DOTDOTSLASH ) == 0 )
214   {
215    if ( strlen ( fname ) + 4 >= PUSTRING_MAX )
216     {
217       ulSetError ( UL_WARNING, "PUI: fgPropPicker - path is too long, max is %d.",
218                           PUSTRING_MAX ) ;
219       return ;
220     }
221
222     strcat ( fname, DOTDOTSLASH ) ;
223     return ;
224   }
225
226   /* Otherwise, just delete the last element of the path. */
227
228   /* Remove the trailing slash - then remove the rest as if it was a file name */
229
230   fname [ strlen(fname)-1 ] = '\0' ;
231   chop_file ( fname ) ;
232 }
233
234
235 void fgPropPicker::handle_select ( puObject* list_box )
236 {
237   fgPropPicker* prop_picker = (fgPropPicker*) list_box -> getUserData () ;
238   int selected ;
239   list_box -> getValue ( &selected ) ;
240
241   if ( selected >= 0 && selected < prop_picker -> num_files )
242   {
243     char *dst = prop_picker -> startDir ;
244     char *src = prop_picker -> files [ selected ] ;
245
246     if (prop_picker->dotFiles && (selected < 2)) {
247         if ( strcmp ( src, "." ) == 0 ) {
248             /* Do nothing - but better refresh anyway. */
249         
250             prop_picker -> find_props () ;
251             return ;
252         } else if ( strcmp ( src, ".." ) == 0 ) {
253             /* Do back up one level - so refresh. */
254         
255             go_up_one_directory ( dst ) ;
256             prop_picker -> find_props () ;
257             return ;
258         }
259     }
260
261     //  we know we're dealing with a regular entry, so convert
262     // it to an index into children[]
263     if (prop_picker->dotFiles) selected -= 2; 
264     SGPropertyNode_ptr child = prop_picker->children[selected];
265     assert(child != NULL);
266                 
267     // check if it's a directory (had children)
268     if ( child->nChildren() ) {
269       /* If this is a directory - then descend into it and refresh */
270
271       if ( strlen ( dst ) + strlen ( src ) + 2 >= PUSTRING_MAX )
272       {
273         ulSetError ( UL_WARNING,
274           "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX ) ;
275         return ;
276       }
277
278       strcat ( dst, src ) ; /* add path to descend to */
279       prop_picker -> find_props () ;
280       return ;
281     }
282
283     /* If this is a regular file - then just append it to the string */
284
285     if ( strlen ( dst ) + strlen ( src ) + 2 >= PUSTRING_MAX )
286     {
287       ulSetError ( UL_WARNING, 
288         "PUI: fgPropPicker - path is too long, max is %d.", PUSTRING_MAX ) ;
289       return ;
290     }
291         
292     prop_editOpen(child->getName(), child->getStringValue(), dst);
293   }
294   else
295   {
296     /*
297       The user clicked on blank screen - maybe we should
298       refresh just in case some other process created the
299       file.
300     */
301       // should be obsolete once we observe child add/remove on our top node
302       prop_picker -> find_props () ;
303   }
304 }
305
306
307 void fgPropPicker::fgPropPickerHandleOk ( puObject* b )
308 {
309   fgPropPicker* prop_picker = (fgPropPicker*) b -> getUserData () ;
310
311   /* nothing to do, just hide */
312   FG_POP_PUI_DIALOG( prop_picker );
313 }
314
315
316 void fgPropPicker::delete_arrays ()
317 {
318   if ( files )
319   {
320     for ( int i=0; i<num_files; i++ ) {
321                 delete[] files[i];
322     }
323
324     for (int C=0; C<num_children; ++C) {
325         if (children[C]->nChildren() == 0)
326             children[C]->removeChangeListener(this);
327     }
328         
329     delete[] files;
330     delete[] children;
331   }
332 }
333
334 /*
335
336 fgPropPicker::~fgPropPicker ()
337 {
338   delete_arrays();
339
340   if ( this == puActiveWidget () )
341     puDeactivateWidget () ;
342 }
343
344 */
345
346 fgPropPicker::fgPropPicker ( int x, int y, int w, int h, int arrows,
347                                       const char *dir, const char *title ) : puDialogBox ( x,y )
348 {
349   puFont LegendFont, LabelFont;
350   puGetDefaultFonts ( &LegendFont, &LabelFont );
351
352   files = NULL ;
353   num_files = 0 ;
354
355   strcpy ( startDir, dir ) ;
356   // printf ( "StartDirLEN=%i", strlen(startDir));
357   if ( arrows > 2 ) arrows = 2 ;
358   if ( arrows < 0 ) arrows = 0 ;
359   arrow_count = arrows ;
360
361   frame = new puFrame ( 0, 0, w, h );
362
363   setUserData( this );
364
365   proppath = new puText            (10, h-30);
366   proppath ->    setLabel          (startDir);
367
368   slider = new puSlider (w-30,40+20*arrows,h-100-40*arrows,TRUE,20);
369   slider->setValue(1.0f);
370   
371   list_box = new puListBox ( 10, 40, w-40, h-60 ) ;
372   list_box -> setLabel ( title );
373   list_box -> setLabelPlace ( PUPLACE_ABOVE ) ;
374   list_box -> setStyle ( -PUSTYLE_SMALL_SHADED ) ;
375   list_box -> setUserData ( this ) ;
376   list_box -> setCallback ( handle_select ) ;
377   list_box -> setValue ( 0 ) ;
378
379   ok_button = new puOneShot ( 10, 10, (w<170)?(w/2-5):80, 30 ) ;
380   ok_button -> setLegend ( "Ok" ) ;
381   ok_button -> setUserData ( this ) ;
382   ok_button -> setCallback ( fgPropPickerHandleOk ) ;
383
384   if ( arrows > 0 )
385   {
386     down_arrow = new puArrowButton ( w-30, 20+20*arrows, w-10, 40+20*arrows, PUARROW_DOWN ) ;
387     down_arrow->setUserData ( slider ) ;
388     down_arrow->setCallback ( fgPropPickerHandleArrow ) ;
389
390     up_arrow = new puArrowButton ( w-30, h-60-20*arrows, w-10, h-40-20*arrows, PUARROW_UP ) ;
391     up_arrow->setUserData ( slider ) ;
392     up_arrow->setCallback ( fgPropPickerHandleArrow ) ;
393   }
394
395   // after picker is built, load the list box with data...
396   find_props () ;
397
398 //  printf("after Props files[1]=%s\n",files[1]);
399 //  printf("num items %i", list_box -> getNumItems ());
400
401   slider -> setUserData ( list_box ) ;
402   slider -> setCallback ( fgPropPickerHandleSlider ) ;
403
404   FG_FINALIZE_PUI_DIALOG( this );
405 }
406
407
408 // Like strcmp, but for sorting property nodes into a suitable display order.
409 static int nodeNameCompare(const void *ppNode1, const void *ppNode2)
410 {
411   const SGPropertyNode_ptr pNode1 = *(const SGPropertyNode_ptr *)ppNode1;
412   const SGPropertyNode_ptr pNode2 = *(const SGPropertyNode_ptr *)ppNode2;
413
414   // Compare name first, and then index.
415   int diff = strcmp(pNode1->getName(), pNode2->getName());
416   if (diff) return diff;
417   return pNode1->getIndex() - pNode2->getIndex();
418 }
419
420
421 // Replace the current list of properties with the children of node "startDir".
422 void fgPropPicker::find_props ()
423 {
424   int pi;
425   int i;
426
427   delete_arrays();
428   num_files = 0 ;
429
430 //  printf("dir begin of find_props=%s\n",startDir);
431 //  printf("len of dir=%i",strlen(startDir));
432   SGPropertyNode * node = globals->get_props()->getNode(startDir);
433
434   num_files = (node) ? (int)node->nChildren() : 0;
435                 
436   // instantiate string objects and add [.] and [..] for subdirs
437   if (strcmp(startDir,"/") == 0) {
438     files = new char* [ num_files+1 ] ;
439     pi = 0;
440     dotFiles = false;
441   } else {
442     // add two for the .. and .
443     num_files = num_files + 2;
444     // make room for .. and .
445     files = new char* [ num_files+1 ] ;
446
447     stdString line = ".";
448     files [ 0 ] = new char[line.size() + 1];
449     strcpy ( files [ 0 ], line.c_str() );
450
451     line = "..";
452     files [ 1 ] = new char[line.size() + 1];
453     strcpy ( files [ 1 ], line.c_str() );
454
455     pi = 2;
456     dotFiles = true;
457   };
458
459
460   if (node) {
461     // Get the list of children
462     num_children = node->nChildren();
463     children = new SGPropertyNode_ptr[num_children];
464     for (i = 0; i < num_children; i++) {
465       children[i] = node->getChild(i);
466     }
467         
468     // Sort the children into display order
469     qsort(children, num_children, sizeof(children[0]), nodeNameCompare);
470
471     // Make lists of the children's names, values, etc.
472     for (i = 0; i < num_children; i++) {
473         SGPropertyNode * child = children[i];
474         stdString name = child->getDisplayName(true);
475
476         if ( child->nChildren() > 0 ) {
477             files[ pi ] = new char[ strlen(name.c_str())+2 ] ;
478             strcpy ( files [ pi ], name.c_str() ) ;
479             strcat ( files [ pi ], "/" ) ;
480         } else {
481             files[pi] = NULL; // ensure it's NULL before setting intial value
482             updateTextForEntry(i);
483             // observe it
484             child->addChangeListener(this);
485         }
486                 
487         ++pi;
488     }
489   }
490
491   files [ num_files ] = NULL ;
492
493   proppath ->    setLabel          (startDir);
494
495   list_box -> newList ( files ) ;
496
497   // adjust the size of the slider...
498   if (num_files > list_box->getNumVisible()) {
499     slider->setSliderFraction((float)list_box->getNumVisible() / num_files);
500     slider->setValue(1.0f);
501     slider->reveal();
502     up_arrow->reveal();
503     down_arrow->reveal();
504   } else {
505     slider->hide();
506     up_arrow->hide();
507     down_arrow->hide();      
508   }
509 }
510
511 void fgPropPicker::updateTextForEntry(int index)
512 {
513     assert((index >= 0) && (index < num_children));
514     SGPropertyNode_ptr node = children[index];
515         
516     // take a copy of the value 
517     stdString value = node->getStringValue();
518
519     stdString line = node->getDisplayName() + stdString(" = '")
520         + value + "' " + "(";
521     line += getValueTypeString( node );
522     line += ")";
523         
524     // truncate entries to plib pui limit
525     if (line.length() >= PUSTRING_MAX)
526         line[PUSTRING_MAX-1] = '\0';
527
528     if (dotFiles) index +=2;
529                 
530     // don't leak everywhere if we're updating
531     if (files[index]) delete[] files[index];
532                 
533     files[index] = new char[ strlen(line.c_str())+2 ] ;
534     strcpy ( files [ index ], line.c_str() ) ;
535 }
536
537 void fgPropPicker::valueChanged(SGPropertyNode *nd)
538 {
539     for (int C=0; C<num_children; ++C)
540         if (children[C] == nd) {
541             updateTextForEntry(C);
542             return;
543         }
544 }
545
546 void fgPropEdit::fgPropEditHandleCancel ( puObject* b )
547 {
548   fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData () ;
549   FG_POP_PUI_DIALOG( prop_edit );
550 }
551
552 void fgPropEdit::fgPropEditHandleOK ( puObject* b )
553 {
554   fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData () ;
555   const char* tname;
556   char* tvalue;
557
558   // use label text for property node to be updated
559   tname = prop_edit -> propname -> getLabel();
560   prop_edit -> propinput -> getValue( &tvalue );
561
562   SGPropertyNode * node = globals->get_props()->getNode(prop_edit -> propPath);
563   node->getNode( prop_edit -> propname -> getLabel(), true)->setStringValue(tvalue);
564
565   // update the picker display so it shows new value
566   prop_pickerRefresh();
567
568   FG_POP_PUI_DIALOG( prop_edit );
569 }
570
571 fgPropEdit::fgPropEdit ( const char *name, const char *value, char *proppath ) : puDialogBox ( 0, 0 )
572
573 {
574     puFont LegendFont, LabelFont;
575     puGetDefaultFonts ( &LegendFont, &LabelFont );
576
577     // locate in relation to picker widget...
578     int fx = PROPPICK_X;
579     int fy = PROPPICK_Y + PROPPICK_H;
580     frame   = new puFrame           (fx,fy, fx+500, fy+120);
581
582     strcpy (propPath, proppath);
583
584     setUserData( this );
585
586     propname = new puText            (fx+10, fy+90);
587     propname ->    setLabel          (name);
588         
589     propinput   = new puInput           (fx+10, fy+50, fx+480, fy+80);
590     propinput   ->    setValue          (value);
591     propinput   ->    acceptInput();
592         
593
594     ok_button     =  new puOneShot   (fx+10, fy+10, fx+80, fy+30);
595     ok_button     ->     setUserData (this);
596     ok_button     ->     setLegend   (gui_msg_OK);
597     ok_button     ->     setCallback (fgPropEditHandleOK);
598     ok_button     ->     makeReturnDefault(TRUE);
599         
600     cancel_button =  new puOneShot   (fx+100, fy+10, fx+180, fy+30);
601     cancel_button     ->     setUserData (this);
602     cancel_button ->     setLegend   (gui_msg_CANCEL);
603     cancel_button ->     setCallback (fgPropEditHandleCancel);
604         
605     FG_FINALIZE_PUI_DIALOG( this );
606 }