]> git.mxchange.org Git - flightgear.git/blob - src/GUI/prop_picker.cxx
- unify several indentation and coding styles (and no, this is not my
[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"
51 // is in scope - e.g. in classes derived from puInput.
52 typedef string stdString;
53
54 #define DOTDOTSLASH "../"
55 #define SLASH       "/"
56
57 static puObject *PP_widget = 0;
58
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
64
65 static puObject *PE_widget = 0;
66
67
68
69 // entry point: called from src/GUI/menubar.cxx -- do_properties_dialog() =====
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
83
84
85 // ============================================================================
86
87 void prop_pickerInit()
88 {
89     if ( PP_widget == 0 ) {
90         fgPropPicker *PP = new fgPropPicker ( PROPPICK_X, PROPPICK_Y, PROPPICK_W, PROPPICK_H,
91                 1, "/", "FG Properties");
92         PP_widget = PP;
93     }
94 }
95
96
97 void prop_pickerRefresh()
98 {
99     if( PP_widget == 0 )
100         prop_pickerInit();
101
102     fgPropPicker *me = (fgPropPicker *)PP_widget -> getUserData();
103     me -> find_props( true );
104     me -> clrValue();
105 }
106
107
108 void prop_editOpen( const char * name, const char * value, char * proppath )
109 {
110     if( PE_widget == 0 )
111         PE_widget = new fgPropEdit(name, value, proppath);
112
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 );
119 }
120
121
122 static string getValueTypeString( const SGPropertyNode_ptr node ) {
123     string result;
124
125     if ( node == NULL )
126         return "unknown";
127
128     SGPropertyNode::Type type = node->getType();
129     if ( type == SGPropertyNode::UNSPECIFIED )
130         result = "unspecified";
131     else if ( type == SGPropertyNode::NONE )
132         result = "none";
133     else if ( type == SGPropertyNode::BOOL )
134         result = "bool";
135     else if ( type == SGPropertyNode::INT )
136         result = "int";
137     else if ( type == SGPropertyNode::LONG )
138         result = "long";
139     else if ( type == SGPropertyNode::FLOAT )
140         result = "float";
141     else if ( type == SGPropertyNode::DOUBLE )
142         result = "double";
143     else if ( type == SGPropertyNode::STRING )
144         result = "string";
145
146     return result;
147 }
148
149
150
151
152 // property picker class ======================================================
153
154 void fgPropPicker::fgPropPickerHandleSlider ( puObject * slider )
155 {
156     puListBox* list_box = (puListBox*) slider -> getUserData ();
157
158     float val;
159     slider -> getValue ( &val );
160     val = 1.0f - val;
161     int scroll_range = list_box->getNumItems () - list_box->getNumVisible();
162
163     if ( scroll_range > 0 ) {
164         int index = int ( scroll_range * val + 0.5 );
165         list_box -> setTopItem ( index );
166     }
167 }
168
169
170 void fgPropPicker::fgPropPickerHandleArrow ( puObject *arrow )
171 {
172     puSlider *slider = (puSlider *) arrow->getUserData ();
173     puListBox* list_box = (puListBox*) slider -> getUserData ();
174
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;
180
181     float val;
182     slider -> getValue ( &val );
183     val = 1.0f - val;
184     int scroll_range = list_box->getNumItems () - list_box->getNumVisible();
185
186     if ( scroll_range > 0 ) {
187         int index = int ( scroll_range * val + 0.5 );
188         index += inc;
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 );
193         if ( index < 0 )
194             index = 0;
195
196         slider -> setValue ( 1.0f - (float)index / scroll_range );
197         list_box -> setTopItem ( index );
198     }
199 }
200
201
202 void fgPropPicker::chop_file ( char *fname )
203 {
204   /* removes everything back to the last '/' */
205
206     for ( int i = strlen(fname)-1; fname[i] != SLASH[0] && i >= 0; i-- )
207         fname[i] = '\0';
208 }
209
210
211 void fgPropPicker::go_up_one_directory ( char *fname )
212 {
213     /* removes everything back to the last but one '/' */
214
215     chop_file ( fname );
216
217     if ( strlen ( fname ) == 0 ) {
218         /* Empty string!  The only way to go up is to append a "../" */
219         strcpy ( fname, DOTDOTSLASH );
220         return;
221     }
222
223     /* If the last path element is a "../" then we'll have to add another "../" */
224
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.",
228                     PUSTRING_MAX );
229             return;
230         }
231
232         strcat ( fname, DOTDOTSLASH );
233         return;
234     }
235
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 */
238
239     fname [ strlen(fname)-1 ] = '\0';
240     chop_file ( fname );
241 }
242
243
244 void fgPropPicker::handle_select ( puObject* list_box )
245 {
246     fgPropPicker* prop_picker = (fgPropPicker*) list_box -> getUserData ();
247     int selected;
248     list_box -> getValue ( &selected );
249
250     if ( selected >= 0 && selected < prop_picker -> num_files ) {
251         char *dst = prop_picker -> startDir;
252         char *src = prop_picker -> files [ selected ];
253
254         if (prop_picker->dotFiles && (selected < 2)) {
255             if ( strcmp ( src, "." ) == 0 ) {
256                 /* Do nothing - but better refresh anyway. */
257
258                 prop_picker -> find_props ();
259                 return;
260             } else if ( strcmp ( src, ".." ) == 0 ) {
261                 /* Do back up one level - so refresh. */
262
263                 go_up_one_directory ( dst );
264                 prop_picker -> find_props ();
265                 return;
266             }
267         }
268
269         // we know we're dealing with a regular entry, so convert
270         // it to an index into children[]
271         if (prop_picker->dotFiles)
272             selected -= 2;
273         SGPropertyNode_ptr child = prop_picker->children[selected];
274         assert(child != NULL);
275
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 */
279
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 );
283                 return;
284             }
285
286             strcat ( dst, src ); /* add path to descend to */
287             prop_picker -> find_props ();
288             return;
289         }
290
291         /* If this is a regular file - then just append it to the string */
292
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 );
296             return;
297         }
298
299         if (child->getType() == SGPropertyNode::BOOL && (fgGetKeyModifiers() & KEYMOD_CTRL)) {
300             child->setBoolValue(!child->getBoolValue());
301             prop_pickerRefresh();
302         } else
303             prop_editOpen(child->getName(), child->getStringValue(), dst);
304
305     } else {
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 ();
310     }
311 }
312
313
314 void fgPropPicker::fgPropPickerHandleOk ( puObject* b )
315 {
316     fgPropPicker* prop_picker = (fgPropPicker*) b -> getUserData ();
317
318     /* nothing to do, just hide */
319     FG_POP_PUI_DIALOG( prop_picker );
320 }
321
322
323 void fgPropPicker::delete_arrays ()
324 {
325     if ( files ) {
326         for ( int i=0; i<num_files; i++ )
327             delete[] files[i];
328
329         for (int C=0; C<num_children; ++C) {
330             if (children[C]->nChildren() == 0)
331                 children[C]->removeChangeListener(this);
332         }
333
334         delete[] files;
335         delete[] children;
336     }
337 }
338
339 /*
340
341 fgPropPicker::~fgPropPicker ()
342 {
343   delete_arrays();
344
345   if ( this == puActiveWidget () )
346     puDeactivateWidget ();
347 }
348
349 */
350
351
352 fgPropPicker::fgPropPicker ( int x, int y, int w, int h, int arrows,
353                              const char *dir, const char *title ) :
354     fgPopup ( x,y ),
355     _gui((NewGUI *)globals->get_subsystem("gui"))
356 {
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"));
362
363     files = NULL;
364     num_files = 0;
365
366     strcpy ( startDir, dir );
367     // printf ( "StartDirLEN=%i", strlen(startDir));
368     if ( arrows > 2 )
369         arrows = 2;
370     if ( arrows < 0 )
371         arrows = 0;
372     arrow_count = arrows;
373
374     frame = new puFrame ( 0, 0, w, h );
375
376     setUserData( this );
377
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());
382
383     slider = new puSlider (w-30,40+20*arrows,h-100-40*arrows,TRUE,20);
384     slider->setValue(1.0f);
385
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());
395
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 );
400
401     if ( arrows > 0 ) {
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 );
405
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 );
409     }
410
411     // after picker is built, load the list box with data...
412     find_props ();
413
414     // printf("after Props files[1]=%s\n",files[1]);
415     // printf("num items %i", list_box -> getNumItems ());
416
417     slider -> setUserData ( list_box );
418     slider -> setCallback ( fgPropPickerHandleSlider );
419
420     FG_FINALIZE_PUI_DIALOG( this );
421 }
422
423
424 // Like strcmp, but for sorting property nodes into a suitable display order.
425 static int nodeNameCompare(const void *ppNode1, const void *ppNode2)
426 {
427     const SGPropertyNode_ptr pNode1 = *(const SGPropertyNode_ptr *)ppNode1;
428     const SGPropertyNode_ptr pNode2 = *(const SGPropertyNode_ptr *)ppNode2;
429
430     // Compare name first, and then index.
431     int diff = strcmp(pNode1->getName(), pNode2->getName());
432     if (diff)
433         return diff;
434
435     return pNode1->getIndex() - pNode2->getIndex();
436 }
437
438
439 // Replace the current list of properties with the children of node "startDir".
440 void fgPropPicker::find_props ( bool restore_pos )
441 {
442     int pi;
443     int i;
444
445     delete_arrays();
446     num_files = 0;
447
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);
451
452     num_files = node ? (int)node->nChildren() : 0;
453
454     // instantiate string objects and add [.] and [..] for subdirs
455     if (strcmp(startDir,"/") == 0) {
456         files = new char* [ num_files+1 ];
457         pi = 0;
458         dotFiles = false;
459     } else {
460         // add two for the .. and .
461         num_files = num_files + 2;
462         // make room for .. and .
463         files = new char* [ num_files+1 ];
464
465         stdString line = ".";
466         files [ 0 ] = new char[line.size() + 1];
467         strcpy ( files [ 0 ], line.c_str() );
468
469         line = "..";
470         files [ 1 ] = new char[line.size() + 1];
471         strcpy ( files [ 1 ], line.c_str() );
472
473         pi = 2;
474         dotFiles = true;
475     }
476
477
478     if (node) {
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);
483
484         qsort(children, num_children, sizeof(children[0]), nodeNameCompare);
485
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);
490
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 ], "/" );
495             } else {
496                 files[pi] = NULL; // ensure it's NULL before setting intial value
497                 updateTextForEntry(i);
498                 // observe it
499                 child->addChangeListener(this);
500             }
501
502             ++pi;
503         }
504     }
505
506     files [ num_files ] = NULL;
507
508     proppath -> setLabel(startDir);
509
510     int top = list_box->getTopItem();
511     list_box -> newList ( files );
512     if (restore_pos)
513         list_box->setTopItem(top);
514
515     // adjust the size of the slider...
516     if (num_files > list_box->getNumVisible()) {
517         slider->setSliderFraction((float)list_box->getNumVisible() / num_files);
518         if (!restore_pos)
519             slider->setValue(1.0f);
520
521         slider->reveal();
522         up_arrow->reveal();
523         down_arrow->reveal();
524
525     } else {
526         slider->hide();
527         up_arrow->hide();
528         down_arrow->hide();
529     }
530 }
531
532
533 void fgPropPicker::updateTextForEntry(int index)
534 {
535     assert((index >= 0) && (index < num_children));
536     SGPropertyNode_ptr node = children[index];
537
538     // take a copy of the value
539     stdString value = node->getStringValue();
540
541     stdString line = node->getDisplayName() + stdString(" = '") + value + "' " + "(";
542     line += getValueTypeString( node );
543
544     if (fgGetBool("/sim/gui/dialogs/property-browser/show-flags", false)) {
545         stdString ext;
546         if (!node->getAttribute(SGPropertyNode::READ))
547             ext += "r";
548         if (!node->getAttribute(SGPropertyNode::WRITE))
549             ext += "w";
550         if (node->getAttribute(SGPropertyNode::ARCHIVE))
551             ext += "A";
552         if (node->getAttribute(SGPropertyNode::USERARCHIVE))
553             ext += "U";
554         if (node->isTied())
555             ext += "T";
556         if (ext.size())
557             line += ", " + ext;
558     }
559
560     line += ")";
561
562     // truncate entries to plib pui limit
563     if (line.length() >= PUSTRING_MAX)
564         line[PUSTRING_MAX-1] = '\0';
565
566     if (dotFiles)
567         index +=2;
568
569     // don't leak everywhere if we're updating
570     delete[] files[index];
571
572     files[index] = new char[ strlen(line.c_str())+2 ];
573     strcpy ( files [ index ], line.c_str() );
574 }
575
576
577 void fgPropPicker::valueChanged(SGPropertyNode *nd)
578 {
579     for (int C=0; C<num_children; ++C)
580         if (children[C] == nd) {
581             updateTextForEntry(C);
582             return;
583         }
584 }
585
586
587
588
589 // property editor class ======================================================
590
591 void fgPropEdit::fgPropEditHandleCancel ( puObject* b )
592 {
593     fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData ();
594     prop_pickerRefresh();
595     FG_POP_PUI_DIALOG( prop_edit );
596 }
597
598
599 void fgPropEdit::fgPropEditHandleOK ( puObject* b )
600 {
601     fgPropEdit* prop_edit = (fgPropEdit*) b -> getUserData ();
602     const char* tname;
603     char* tvalue;
604
605     // use label text for property node to be updated
606     tname = prop_edit -> propname -> getLabel();
607     prop_edit -> propinput -> getValue( &tvalue );
608
609     SGPropertyNode * node = globals->get_props()->getNode(prop_edit -> propPath);
610     node->getNode( prop_edit -> propname -> getLabel(), true)->setStringValue(tvalue);
611
612     // update the picker display so it shows new value
613     prop_pickerRefresh();
614
615     FG_POP_PUI_DIALOG( prop_edit );
616 }
617
618
619 fgPropEdit::fgPropEdit ( const char *name, const char *value, char *proppath ) :
620     fgPopup ( 0, 0 ),
621     _gui((NewGUI *)globals->get_subsystem("gui"))
622 {
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"));
628
629     // locate in relation to picker widget...
630     int fx = PROPPICK_X;
631     int fy = PROPPICK_Y + PROPPICK_H;
632     frame   = new puFrame (fx,fy, fx+500, fy+120);
633
634     strcpy (propPath, proppath);
635
636     setUserData( this );
637
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());
642
643     propinput = new puInput (fx+10, fy+50, fx+480, fy+80);
644     propinput -> setValue (value);
645     propinput -> acceptInput();
646
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);
652
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);
657
658     FG_FINALIZE_PUI_DIALOG( this );
659 }
660
661