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