1 // fgadmin_funcs.cxx -- FG Admin UI functions.
3 // Written by Curtis Olson, started February 2004.
5 // Copyright (c) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
32 #define unlink _unlink
38 #include <FL/Fl_File_Chooser.H>
40 #include <simgear/misc/sg_path.hxx>
50 extern string def_install_source;
51 extern string def_scenery_dest;
53 static const float min_progress = 0.0;
54 static const float max_progress = 5000.0;
56 // FIXME: Ugly hack to detect the situation below
58 //#if (FL_MAJOR_VERSION > 1)||((FL_MAJOR_VERSION == 1)&&(FL_MINOR_VERSION >= 3))
59 // Fltk 1.3 or newer, need to use "fl_filename_free_list"
60 #define FL_FREE_DIR_ENTRY(e) // do nothing, since "fl_filename_free_list" frees entire list
61 #define FL_FREE_DIR_LIST(list,count) fl_filename_free_list(&list, count)
62 #define FL_STAT(file,info) fl_stat( file.str().c_str(), info )
64 // Fltk < 1.3, "fl_filename_free_list", "fl_stat" not available
65 #define FL_FREE_DIR_ENTRY(e) free(e)
66 #define FL_FREE_DIR_LIST(list,count) free(list)
67 #define FL_STAT(file,info) stat( file.str().c_str(), info );
70 /** Strip a single trailing '/' or '\\' */
71 static char* stripSlash(char* str)
75 ((str[l-1]=='/')||(str[l-1]=='\\')))
83 FGAdminUI::~FGAdminUI() {
88 // initialize additional class elements
89 void FGAdminUI::init() {
90 prefs = new Fl_Preferences( Fl_Preferences::USER,
93 char buf[FL_PATH_MAX];
94 prefs->get( "install-source", buf, def_install_source.c_str(), FL_PATH_MAX );
95 source_text->value( buf );
98 prefs->get( "scenery-dest", buf, def_scenery_dest.c_str(), FL_PATH_MAX );
99 dest_text->value( buf );
104 progress->minimum( min_progress );
105 progress->maximum( max_progress );
107 main_window->size_range( 465, 435 );
111 void FGAdminUI::show() {
116 // refresh the check box lists
117 void FGAdminUI::refresh_lists() {
118 update_install_box();
123 // scan the source directory and update the install_box contents
125 void FGAdminUI::quit() {
130 // select the install source
131 void FGAdminUI::select_install_source() {
132 char* p = fl_dir_chooser( "Select Scenery Source Directory",
137 source_text->value( p );
138 prefs->set( "install-source", p );
144 // select the install source
145 void FGAdminUI::select_install_dest() {
146 char* p = fl_dir_chooser( "Select Scenery Install Directory",
151 dest_text->value( p );
152 prefs->set( "scenery-dest", p );
158 // Like strcmp, but for strings
159 static int stringCompare(const void *string1, const void *string2)
161 const string *s1 = (const string *)string1;
162 const string *s2 = (const string *)string2;
164 // Compare name first, and then index.
165 return strcmp(s1->c_str(), s2->c_str());
169 void FGAdminUI::update_install_box() {
170 set<string> file_list;
172 install_box->clear();
174 if ( source.length() ) {
175 struct dirent **list;
176 int nb = fl_filename_list( source.c_str(), &list );
177 for ( int i = 0; i < nb; ++i ) {
178 // find base name of archive file
179 char base[FL_PATH_MAX];
180 dirent *ent = list[i];
181 stripSlash(ent->d_name);
182 strncpy( base, ent->d_name, FL_PATH_MAX );
183 const char *p = fl_filename_ext( base );
185 string::size_type expected_length = 0;
186 if ( strcmp( p, ".gz" ) == 0 ) {
189 p = fl_filename_ext( base );
190 if ( strcmp( p, ".tar" ) == 0 ) {
193 expected_length = 14;
195 } else if ( strcmp( p, ".tgz" ) == 0 ) {
198 expected_length = 11;
199 } else if ( strcmp( p, ".zip" ) == 0 ) {
202 expected_length = 11;
205 if ( strlen(ent->d_name) != expected_length ) {
206 // simple heuristic to ignore non-scenery files
207 } else if ( ent->d_name[0] != 'e' && ent->d_name[0] != 'w' ) {
208 // further sanity checks on name
209 } else if ( ent->d_name[4] != 'n' && ent->d_name[4] != 's' ) {
210 // further sanity checks on name
212 // add to list if not installed
213 SGPath install( dest );
214 install.append( base );
215 if ( ! fl_filename_isdir( install.c_str() ) ) {
216 // cout << install.str() << " install candidate." << endl;
217 file_list.insert( ent->d_name );
219 // cout << install.str() << " exists." << endl;
222 FL_FREE_DIR_ENTRY(ent);
224 FL_FREE_DIR_LIST(list, nb);
226 for ( set<string>::iterator it = file_list.begin(); it != file_list.end(); ++it ) {
227 install_box->add( it->c_str() );
230 install_box->redraw();
235 // scan the source directory and update the install_box contents
236 void FGAdminUI::update_remove_box() {
240 if ( dest.length() ) {
242 path[0] = dest + "/Terrain";
243 path[1] = dest + "/Objects";
244 if ( !fl_filename_isdir( path[0].c_str() ) ) {
247 } else if ( !fl_filename_isdir( path[1].c_str() ) ) {
251 set<string> dir_list;
252 for ( int i = 0; i < 2; i++ ) {
253 if ( !path[i].empty() ) {
255 int nb = fl_filename_list( path[i].c_str(), &list );
256 for ( int i = 0; i < nb; ++i ) {
257 dirent *ent = list[i];
258 stripSlash(ent->d_name);
259 if ( strlen(ent->d_name) == 7 &&
260 ( ent->d_name[0] == 'e' || ent->d_name[0] == 'w' ) &&
261 ( ent->d_name[4] == 'n' || ent->d_name[4] == 's' ) ) {
262 dir_list.insert( ent->d_name );
264 FL_FREE_DIR_ENTRY(ent);
266 FL_FREE_DIR_LIST(list, nb);
270 for ( set<string>::iterator it = dir_list.begin(); it != dir_list.end(); ++it ) {
271 remove_box->add( it->c_str() );
274 remove_box->redraw();
279 // install selected files
280 void FGAdminUI::install_selected() {
283 install_b->deactivate();
284 remove_b->deactivate();
285 quit_b->deactivate();
287 // traverse install box and install each item
288 for ( int i = 0; i <= install_box->nitems(); ++i ) {
289 if ( install_box->checked( i ) ) {
290 f = install_box->text( i );
291 SGPath file( source );
294 FL_STAT( file, &info );
295 float old_max = progress->maximum();
296 progress->maximum( info.st_size );
297 progress_label = "Installing ";
299 progress->label( progress_label.c_str() );
300 progress->value( min_progress );
301 main_window->cursor( FL_CURSOR_WAIT );
302 tarextract( (char *)file.c_str(), (char *)dest.c_str(), true, &FGAdminUI::step, this );
303 progress->value( min_progress );
304 main_window->cursor( FL_CURSOR_DEFAULT );
305 progress->label( "" );
306 progress->maximum( old_max );
310 install_b->activate();
311 remove_b->activate();
317 static unsigned long count_dir( const char *dir_name, bool top = true ) {
318 unsigned long cnt = 0L;
320 int nb = fl_filename_list( dir_name, &list );
322 for ( int i = 0; i < nb; ++i ) {
323 dirent *ent = list[i];
324 stripSlash(ent->d_name);
325 if ( strcmp( ent->d_name, "." ) == 0 ) {
327 } else if ( strcmp( ent->d_name, ".." ) == 0 ) {
329 } else if ( fl_filename_isdir( ent->d_name ) ) {
330 SGPath child( dir_name );
331 child.append( ent->d_name );
332 cnt += count_dir( child.c_str(), false );
336 FL_FREE_DIR_ENTRY(ent);
338 FL_FREE_DIR_LIST(list, nb);
340 string base = dir_name;
341 size_t pos = base.rfind('/');
342 string file = base.substr( pos );
344 string path = base + "/Terrain" + file;
345 cnt = count_dir( path.c_str(), false );
346 path = base + "/Objects" + file;
347 cnt += count_dir( path.c_str(), false );
352 static void remove_dir( const char *dir_name, void (*step)(void*,int), void *data, bool top = true ) {
354 int nb = fl_filename_list( dir_name, &list );
356 for ( int i = 0; i < nb; ++i ) {
357 dirent *ent = list[i];
358 SGPath child( dir_name );
359 child.append( ent->d_name );
360 stripSlash(ent->d_name);
361 if ( strcmp( ent->d_name, "." ) == 0 ) {
363 } else if ( strcmp( ent->d_name, ".." ) == 0 ) {
365 } else if ( child.isDir() ) {
366 remove_dir( child.c_str(), step, data, false );
368 unlink( child.c_str() );
369 if (step) step( data, 1 );
371 FL_FREE_DIR_ENTRY(ent);
373 FL_FREE_DIR_LIST(list, nb);
376 string base = dir_name;
377 size_t pos = base.rfind('/');
378 string file = base.substr( pos );
380 string path = base + "/Terrain" + file;
381 remove_dir( path.c_str(), step, data, false );
382 path = base + "/Objects" + file;
383 remove_dir( path.c_str(), step, data, false );
388 // remove selected files
389 void FGAdminUI::remove_selected() {
392 install_b->deactivate();
393 remove_b->deactivate();
394 quit_b->deactivate();
395 // traverse remove box and recursively remove each item
396 for ( int i = 0; i <= remove_box->nitems(); ++i ) {
397 if ( remove_box->checked( i ) ) {
398 f = remove_box->text( i );
401 float old_max = progress->maximum();
402 progress_label = "Removing ";
404 progress->label( progress_label.c_str() );
405 progress->value( min_progress );
406 main_window->cursor( FL_CURSOR_WAIT );
407 progress->maximum( count_dir( dir.c_str() ) );
408 remove_dir( dir.c_str(), &FGAdminUI::step, this );
409 progress->value( min_progress );
410 main_window->cursor( FL_CURSOR_DEFAULT );
411 progress->label( "" );
412 progress->maximum( old_max );
416 install_b->activate();
417 remove_b->activate();
423 void FGAdminUI::step(void *data)
425 Fl_Progress *p = ((FGAdminUI*)data)->progress;
427 // we don't actually know the total work in advanced due to the
428 // nature of tar archives, and it would be inefficient to prescan
429 // the files, so just cycle the progress bar until we are done.
430 float tmp = p->value() + 1;
431 if ( tmp > max_progress ) {
439 void FGAdminUI::step(void *data, int n)
441 Fl_Progress *p = ((FGAdminUI*)data)->progress;
443 float tmp = p->value() + n;