1 //
2 // "$Id: Fl_File_Chooser2.cxx 6092 2008-04-11 12:57:37Z matt $"
3 //
4 // More Fl_File_Chooser routines.
5 //
6 // Copyright 1999-2005 by Michael Sweet.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21 // USA.
22 //
23 // Please report all bugs and problems on the following page:
24 //
25 //     http://www.fltk.org/str.php
26 //
27 // Contents:
28 //
29 //   Fl_File_Chooser::count()             - Return the number of selected files.
30 //   Fl_File_Chooser::directory()         - Set the directory in the file chooser.
31 //   Fl_File_Chooser::filter()            - Set the filter(s) for the chooser.
32 //   Fl_File_Chooser::newdir()            - Make a new directory.
33 //   Fl_File_Chooser::value()             - Return a selected filename.
34 //   Fl_File_Chooser::rescan()            - Rescan the current directory.
35 //   Fl_File_Chooser::favoritesButtonCB() - Handle favorites selections.
36 //   Fl_File_Chooser::fileListCB()        - Handle clicks (and double-clicks)
37 //                                          in the Fl_File_Browser.
38 //   Fl_File_Chooser::fileNameCB()        - Handle text entry in the FileBrowser.
39 //   Fl_File_Chooser::showChoiceCB()      - Handle show selections.
40 //   compare_dirnames()                   - Compare two directory names.
41 //   quote_pathname()                     - Quote a pathname for a menu.
42 //   unquote_pathname()                   - Unquote a pathname from a menu.
43 //
44 
45 //
46 // Include necessary headers.
47 //
48 
49 #include <FL/Fl_File_Chooser.H>
50 #include <FL/filename.H>
51 #include <FL/fl_ask.H>
52 #include <FL/x.H>
53 #include <FL/Fl_Shared_Image.H>
54 
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include "flstring.h"
58 #include <errno.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 
62 #if defined(WIN32) && ! defined (__CYGWIN__)
63 #  include <direct.h>
64 #  include <io.h>
65 // Visual C++ 2005 incorrectly displays a warning about the use of POSIX APIs
66 // on Windows, which is supposed to be POSIX compliant...
67 #  define access _access
68 #  define mkdir _mkdir
69 // Apparently Borland C++ defines DIRECTORY in <direct.h>, which
70 // interfers with the Fl_File_Icon enumeration of the same name.
71 #  ifdef DIRECTORY
72 #    undef DIRECTORY
73 #  endif // DIRECTORY
74 #else
75 #  include <unistd.h>
76 #  include <pwd.h>
77 #endif /* WIN32 */
78 
79 
80 //
81 // File chooser label strings and sort function...
82 //
83 
84 Fl_Preferences	Fl_File_Chooser::prefs_(Fl_Preferences::USER, "fltk.org", "filechooser");
85 
86 const char	*Fl_File_Chooser::add_favorites_label = "Add to Favorites";
87 const char	*Fl_File_Chooser::all_files_label = "All Files (*)";
88 const char	*Fl_File_Chooser::custom_filter_label = "Custom Filter";
89 const char	*Fl_File_Chooser::existing_file_label = "Please choose an existing file!";
90 const char	*Fl_File_Chooser::favorites_label = "Favorites";
91 const char	*Fl_File_Chooser::filename_label = "Filename:";
92 #ifdef WIN32
93 const char	*Fl_File_Chooser::filesystems_label = "My Computer";
94 #else
95 const char	*Fl_File_Chooser::filesystems_label = "File Systems";
96 #endif // WIN32
97 const char	*Fl_File_Chooser::manage_favorites_label = "Manage Favorites";
98 const char	*Fl_File_Chooser::new_directory_label = "New Directory?";
99 const char	*Fl_File_Chooser::new_directory_tooltip = "Create a new directory.";
100 const char	*Fl_File_Chooser::preview_label = "Preview";
101 const char	*Fl_File_Chooser::save_label = "Save";
102 const char	*Fl_File_Chooser::show_label = "Show:";
103 Fl_File_Sort_F	*Fl_File_Chooser::sort = fl_numericsort;
104 
105 
106 //
107 // Local functions...
108 //
109 
110 static int	compare_dirnames(const char *a, const char *b);
111 static void	quote_pathname(char *, const char *, int);
112 static void	unquote_pathname(char *, const char *, int);
113 
114 
115 //
116 // 'Fl_File_Chooser::count()' - Return the number of selected files.
117 //
118 
119 int				// O - Number of selected files
count()120 Fl_File_Chooser::count() {
121   int		i;		// Looping var
122   int		fcount;		// Number of selected files
123   const char	*filename;	// Filename in input field or list
124 
125 
126   filename = fileName->value();
127 
128   if (!(type_ & MULTI)) {
129     // Check to see if the file name input field is blank...
130     if (!filename || !filename[0]) return 0;
131     else return 1;
132   }
133 
134   for (i = 1, fcount = 0; i <= fileList->size(); i ++)
135     if (fileList->selected(i)) {
136       // See if this file is a directory...
137       // matt: why would we do that? It is perfectly legal to select multiple
138       // directories in a DIR chooser. They are visually selected and value(i)
139       // returns all of them as expected
140       //filename = (char *)fileList->text(i);
141 
142       //if (filename[strlen(filename) - 1] != '/')
143 	fcount ++;
144     }
145 
146   if (fcount) return fcount;
147   else if (!filename || !filename[0]) return 0;
148   else return 1;
149 }
150 
151 
152 //
153 // 'Fl_File_Chooser::directory()' - Set the directory in the file chooser.
154 //
155 
156 void
directory(const char * d)157 Fl_File_Chooser::directory(const char *d)// I - Directory to change to
158 {
159   char	*dirptr;			// Pointer into directory
160 
161 
162 //  printf("Fl_File_Chooser::directory(\"%s\")\n", d == NULL ? "(null)" : d);
163 
164   // NULL == current directory
165   if (d == NULL)
166     d = ".";
167 
168 #ifdef WIN32
169   // See if the filename contains backslashes...
170   char	*slash;				// Pointer to slashes
171   char	fixpath[1024];			// Path with slashes converted
172   if (strchr(d, '\\')) {
173     // Convert backslashes to slashes...
174     strlcpy(fixpath, d, sizeof(fixpath));
175 
176     for (slash = strchr(fixpath, '\\'); slash; slash = strchr(slash + 1, '\\'))
177       *slash = '/';
178 
179     d = fixpath;
180   }
181 #endif // WIN32
182 
183   if (d[0] != '\0')
184   {
185     // Make the directory absolute...
186 #if (defined(WIN32) && ! defined(__CYGWIN__))|| defined(__EMX__)
187     if (d[0] != '/' && d[0] != '\\' && d[1] != ':')
188 #else
189     if (d[0] != '/' && d[0] != '\\')
190 #endif /* WIN32 || __EMX__ */
191       fl_filename_absolute(directory_, d);
192     else
193       strlcpy(directory_, d, sizeof(directory_));
194 
195     // Strip any trailing slash...
196     dirptr = directory_ + strlen(directory_) - 1;
197     if ((*dirptr == '/' || *dirptr == '\\') && dirptr > directory_)
198       *dirptr = '\0';
199 
200     // See if we have a trailing .. or . in the filename...
201     dirptr = directory_ + strlen(directory_) - 3;
202     if (dirptr >= directory_ && strcmp(dirptr, "/..") == 0) {
203       // Yes, we have "..", so strip the trailing path...
204       *dirptr = '\0';
205       while (dirptr > directory_) {
206         if (*dirptr == '/') break;
207 	dirptr --;
208       }
209 
210       if (dirptr >= directory_ && *dirptr == '/')
211         *dirptr = '\0';
212     } else if ((dirptr + 1) >= directory_ && strcmp(dirptr + 1, "/.") == 0) {
213       // Strip trailing "."...
214       dirptr[1] = '\0';
215     }
216   }
217   else
218     directory_[0] = '\0';
219 
220   if (shown()) {
221     // Rescan the directory...
222     rescan();
223   }
224 }
225 
226 
227 //
228 // 'Fl_File_Chooser::favoritesButtonCB()' - Handle favorites selections.
229 //
230 
231 void
favoritesButtonCB()232 Fl_File_Chooser::favoritesButtonCB()
233 {
234   int		v;			// Current selection
235   char		pathname[1024],		// Pathname
236 		menuname[2048];		// Menu name
237 
238 
239   v = favoritesButton->value();
240 
241   if (!v) {
242     // Add current directory to favorites...
243     if (getenv("HOME")) v = favoritesButton->size() - 5;
244     else v = favoritesButton->size() - 4;
245 
246     sprintf(menuname, "favorite%02d", v);
247 
248     prefs_.set(menuname, directory_);
249     prefs_.flush();
250 
251     quote_pathname(menuname, directory_, sizeof(menuname));
252     favoritesButton->add(menuname);
253 
254     if (favoritesButton->size() > 104) {
255       ((Fl_Menu_Item *)favoritesButton->menu())[0].deactivate();
256     }
257   } else if (v == 1) {
258     // Manage favorites...
259     favoritesCB(0);
260   } else if (v == 2) {
261     // Filesystems/My Computer
262     directory("");
263   } else {
264     unquote_pathname(pathname, favoritesButton->text(v), sizeof(pathname));
265     directory(pathname);
266   }
267 }
268 
269 
270 //
271 // 'Fl_File_Chooser::favoritesCB()' - Handle favorites dialog.
272 //
273 
274 void
favoritesCB(Fl_Widget * w)275 Fl_File_Chooser::favoritesCB(Fl_Widget *w)
276 					// I - Widget
277 {
278   int		i;			// Looping var
279   char		name[32],		// Preference name
280 		pathname[1024];		// Directory in list
281 
282 
283   if (!w) {
284     // Load the favorites list...
285     favList->clear();
286     favList->deselect();
287 
288     for (i = 0; i < 100; i ++) {
289       // Get favorite directory 0 to 99...
290       sprintf(name, "favorite%02d", i);
291 
292       prefs_.get(name, pathname, "", sizeof(pathname));
293 
294       // Stop on the first empty favorite...
295       if (!pathname[0]) break;
296 
297       // Add the favorite to the list...
298       favList->add(pathname,
299                    Fl_File_Icon::find(pathname, Fl_File_Icon::DIRECTORY));
300     }
301 
302     favUpButton->deactivate();
303     favDeleteButton->deactivate();
304     favDownButton->deactivate();
305     favOkButton->deactivate();
306 
307     favWindow->hotspot(favList);
308     favWindow->show();
309   } else if (w == favList) {
310     i = favList->value();
311     if (i) {
312       if (i > 1) favUpButton->activate();
313       else favUpButton->deactivate();
314 
315       favDeleteButton->activate();
316 
317       if (i < favList->size()) favDownButton->activate();
318       else favDownButton->deactivate();
319     } else {
320       favUpButton->deactivate();
321       favDeleteButton->deactivate();
322       favDownButton->deactivate();
323     }
324   } else if (w == favUpButton) {
325     i = favList->value();
326 
327     favList->insert(i - 1, favList->text(i), favList->data(i));
328     favList->remove(i + 1);
329     favList->select(i - 1);
330 
331     if (i == 2) favUpButton->deactivate();
332 
333     favDownButton->activate();
334 
335     favOkButton->activate();
336   } else if (w == favDeleteButton) {
337     i = favList->value();
338 
339     favList->remove(i);
340 
341     if (i > favList->size()) i --;
342     favList->select(i);
343 
344     if (i < favList->size()) favDownButton->activate();
345     else favDownButton->deactivate();
346 
347     if (i > 1) favUpButton->activate();
348     else favUpButton->deactivate();
349 
350     if (!i) favDeleteButton->deactivate();
351 
352     favOkButton->activate();
353   } else if (w == favDownButton) {
354     i = favList->value();
355 
356     favList->insert(i + 2, favList->text(i), favList->data(i));
357     favList->remove(i);
358     favList->select(i + 1);
359 
360     if ((i + 1) == favList->size()) favDownButton->deactivate();
361 
362     favUpButton->activate();
363 
364     favOkButton->activate();
365   } else if (w == favOkButton) {
366     // Copy the new list over...
367     for (i = 0; i < favList->size(); i ++) {
368       // Set favorite directory 0 to 99...
369       sprintf(name, "favorite%02d", i);
370 
371       prefs_.set(name, favList->text(i + 1));
372     }
373 
374     // Clear old entries as necessary...
375     for (; i < 100; i ++) {
376       // Clear favorite directory 0 to 99...
377       sprintf(name, "favorite%02d", i);
378 
379       prefs_.get(name, pathname, "", sizeof(pathname));
380 
381       if (pathname[0]) prefs_.set(name, "");
382       else break;
383     }
384 
385     update_favorites();
386     prefs_.flush();
387 
388     favWindow->hide();
389   }
390 }
391 
392 
393 //
394 // 'Fl_File_Chooser::fileListCB()' - Handle clicks (and double-clicks) in the
395 //                                   Fl_File_Browser.
396 //
397 
398 void
fileListCB()399 Fl_File_Chooser::fileListCB()
400 {
401   char	*filename,			// New filename
402 	pathname[1024];			// Full pathname to file
403 
404 
405   filename = (char *)fileList->text(fileList->value());
406   if (!filename)
407     return;
408 
409   if (!directory_[0]) {
410     strlcpy(pathname, filename, sizeof(pathname));
411   } else if (strcmp(directory_, "/") == 0) {
412     snprintf(pathname, sizeof(pathname), "/%s", filename);
413   } else {
414     snprintf(pathname, sizeof(pathname), "%s/%s", directory_, filename);
415   }
416 
417   if (Fl::event_clicks()) {
418 #if (defined(WIN32) && ! defined(__CYGWIN__)) || defined(__EMX__)
419     if ((strlen(pathname) == 2 && pathname[1] == ':') ||
420         _fl_filename_isdir_quick(pathname))
421 #else
422     if (_fl_filename_isdir_quick(pathname))
423 #endif /* WIN32 || __EMX__ */
424     {
425       // Change directories...
426       directory(pathname);
427 
428       // Reset the click count so that a click in the same spot won't
429       // be treated as a triple-click.  We use a value of -1 because
430       // the next click will increment click count to 0, which is what
431       // we really want...
432       Fl::event_clicks(-1);
433     }
434     else
435     {
436       // Hide the window - picked the file...
437       window->hide();
438     }
439   }
440   else
441   {
442     // Check if the user clicks on a directory when picking files;
443     // if so, make sure only that item is selected...
444     filename = pathname + strlen(pathname) - 1;
445 
446     if ((type_ & MULTI) && !(type_ & DIRECTORY)) {
447       if (*filename == '/') {
448 	// Clicked on a directory, deselect everything else...
449 	int i = fileList->value();
450 	fileList->deselect();
451 	fileList->select(i);
452       } else {
453         // Clicked on a file - see if there are other directories selected...
454         int i;
455 	const char *temp;
456 	for (i = 1; i <= fileList->size(); i ++) {
457 	  if (i != fileList->value() && fileList->selected(i)) {
458 	    temp = fileList->text(i);
459 	    temp += strlen(temp) - 1;
460 	    if (*temp == '/') break;	// Yes, selected directory
461 	  }
462 	}
463 
464         if (i <= fileList->size()) {
465 	  i = fileList->value();
466 	  fileList->deselect();
467 	  fileList->select(i);
468 	}
469       }
470     }
471     // Strip any trailing slash from the directory name...
472     if (*filename == '/') *filename = '\0';
473 
474 //    puts("Setting fileName from fileListCB...");
475     fileName->value(pathname);
476 
477     // Update the preview box...
478     Fl::remove_timeout((Fl_Timeout_Handler)previewCB, this);
479     Fl::add_timeout(1.0, (Fl_Timeout_Handler)previewCB, this);
480 
481     // Do any callback that is registered...
482     if (callback_) (*callback_)(this, data_);
483 
484     // Activate the OK button as needed...
485     if (!_fl_filename_isdir_quick(pathname) || (type_ & DIRECTORY))
486       okButton->activate();
487     else
488       okButton->deactivate();
489   }
490 }
491 
492 
493 //
494 // 'Fl_File_Chooser::fileNameCB()' - Handle text entry in the FileBrowser.
495 //
496 
497 void
fileNameCB()498 Fl_File_Chooser::fileNameCB()
499 {
500   char		*filename,	// New filename
501 		*slash,		// Pointer to trailing slash
502 		pathname[1024],	// Full pathname to file
503 		matchname[256];	// Matching filename
504   int		i,		// Looping var
505 		min_match,	// Minimum number of matching chars
506 		max_match,	// Maximum number of matching chars
507 		num_files,	// Number of files in directory
508 		first_line;	// First matching line
509   const char	*file;		// File from directory
510 
511 //  puts("fileNameCB()");
512 //  printf("Event: %s\n", fl_eventnames[Fl::event()]);
513 
514   // Get the filename from the text field...
515   filename = (char *)fileName->value();
516 
517   if (!filename || !filename[0]) {
518     okButton->deactivate();
519     return;
520   }
521 
522   // Expand ~ and $ variables as needed...
523   if (strchr(filename, '~') || strchr(filename, '$')) {
524     fl_filename_expand(pathname, sizeof(pathname), filename);
525     filename = pathname;
526     value(pathname);
527   }
528 
529   // Make sure we have an absolute path...
530 #if (defined(WIN32) && !defined(__CYGWIN__)) || defined(__EMX__)
531   if (directory_[0] != '\0' && filename[0] != '/' &&
532       filename[0] != '\\' &&
533       !(isalpha(filename[0] & 255) && (!filename[1] || filename[1] == ':'))) {
534 #else
535   if (directory_[0] != '\0' && filename[0] != '/') {
536 #endif /* WIN32 || __EMX__ */
537     fl_filename_absolute(pathname, sizeof(pathname), filename);
538     value(pathname);
539     fileName->mark(fileName->position()); // no selection after expansion
540   } else if (filename != pathname) {
541     // Finally, make sure that we have a writable copy...
542     strlcpy(pathname, filename, sizeof(pathname));
543   }
544 
545   filename = pathname;
546 
547   // Now process things according to the key pressed...
548   if (Fl::event_key() == FL_Enter || Fl::event_key() == FL_KP_Enter) {
549     // Enter pressed - select or change directory...
550 #if (defined(WIN32) && ! defined(__CYGWIN__)) || defined(__EMX__)
551     if ((isalpha(pathname[0] & 255) && pathname[1] == ':' && !pathname[2]) ||
552         _fl_filename_isdir_quick(pathname) &&
553 	compare_dirnames(pathname, directory_)) {
554 #else
555     if (_fl_filename_isdir_quick(pathname) &&
556 	compare_dirnames(pathname, directory_)) {
557 #endif /* WIN32 || __EMX__ */
558       directory(pathname);
559     } else if ((type_ & CREATE) || access(pathname, 0) == 0) {
560       if (!_fl_filename_isdir_quick(pathname) || (type_ & DIRECTORY)) {
561 	// Update the preview box...
562 	update_preview();
563 
564 	// Do any callback that is registered...
565 	if (callback_) (*callback_)(this, data_);
566 
567 	// Hide the window to signal things are done...
568 	window->hide();
569       }
570     } else {
571       // File doesn't exist, so beep at and alert the user...
572       fl_alert(existing_file_label);
573     }
574   }
575   else if (Fl::event_key() != FL_Delete &&
576            Fl::event_key() != FL_BackSpace) {
577     // Check to see if the user has entered a directory...
578     if ((slash = strrchr(pathname, '/')) == NULL)
579       slash = strrchr(pathname, '\\');
580 
581     if (!slash) return;
582 
583     // Yes, change directories if necessary...
584     *slash++ = '\0';
585     filename = slash;
586 
587 #if defined(WIN32) || defined(__EMX__)
588     if (strcasecmp(pathname, directory_) &&
589         (pathname[0] || strcasecmp("/", directory_))) {
590 #else
591     if (strcmp(pathname, directory_) &&
592         (pathname[0] || strcasecmp("/", directory_))) {
593 #endif // WIN32 || __EMX__
594       int p = fileName->position();
595       int m = fileName->mark();
596 
597       directory(pathname);
598 
599       if (filename[0]) {
600 	char tempname[1024];
601 
602 	snprintf(tempname, sizeof(tempname), "%s/%s", directory_, filename);
603 	fileName->value(tempname);
604 	strlcpy(pathname, tempname, sizeof(pathname));
605       }
606 
607       fileName->position(p, m);
608     }
609 
610     // Other key pressed - do filename completion as possible...
611     num_files  = fileList->size();
612     min_match  = strlen(filename);
613     max_match  = min_match + 1;
614     first_line = 0;
615 
616     for (i = 1; i <= num_files && max_match > min_match; i ++) {
617       file = fileList->text(i);
618 
619 #if (defined(WIN32) && ! defined(__CYGWIN__)) || defined(__EMX__)
620       if (strncasecmp(filename, file, min_match) == 0) {
621 #else
622       if (strncmp(filename, file, min_match) == 0) {
623 #endif // WIN32 || __EMX__
624         // OK, this one matches; check against the previous match
625 	if (!first_line) {
626 	  // First match; copy stuff over...
627 	  strlcpy(matchname, file, sizeof(matchname));
628 	  max_match = strlen(matchname);
629 
630           // Strip trailing /, if any...
631 	  if (matchname[max_match - 1] == '/') {
632 	    max_match --;
633 	    matchname[max_match] = '\0';
634 	  }
635 
636 	  // And then make sure that the item is visible
637           fileList->topline(i);
638 	  first_line = i;
639 	} else {
640 	  // Succeeding match; compare to find maximum string match...
641 	  while (max_match > min_match)
642 #if (defined(WIN32) && ! defined(__CYGWIN__)) || defined(__EMX__)
643 	    if (strncasecmp(file, matchname, max_match) == 0)
644 #else
645 	    if (strncmp(file, matchname, max_match) == 0)
646 #endif // WIN32 || __EMX__
647 	      break;
648 	    else
649 	      max_match --;
650 
651           // Truncate the string as needed...
652           matchname[max_match] = '\0';
653 	}
654       }
655     }
656 
657     // If we have any matches, add them to the input field...
658     if (first_line > 0 && min_match == max_match &&
659         max_match == (int)strlen(fileList->text(first_line))) {
660       // This is the only possible match...
661       fileList->deselect(0);
662       fileList->select(first_line);
663       fileList->redraw();
664     } else if (max_match > min_match && first_line) {
665       // Add the matching portion...
666       fileName->replace(filename - pathname, filename - pathname + min_match,
667                         matchname);
668 
669       // Highlight it with the cursor at the end of the selection so
670       // s/he can press the right arrow to accept the selection
671       // (Tab and End also do this for both cases.)
672       fileName->position(filename - pathname + max_match,
673 	                 filename - pathname + min_match);
674     } else if (max_match == 0) {
675       fileList->deselect(0);
676       fileList->redraw();
677     }
678 
679     // See if we need to enable the OK button...
680     if (((type_ & CREATE) || !access(fileName->value(), 0)) &&
681         (!fl_filename_isdir(fileName->value()) || (type_ & DIRECTORY))) {
682       okButton->activate();
683     } else {
684       okButton->deactivate();
685     }
686   } else {
687     // FL_Delete or FL_BackSpace
688     fileList->deselect(0);
689     fileList->redraw();
690     if (((type_ & CREATE) || !access(fileName->value(), 0)) &&
691         (!fl_filename_isdir(fileName->value()) || (type_ & DIRECTORY))) {
692       okButton->activate();
693     } else {
694       okButton->deactivate();
695     }
696   }
697 }
698 
699 
700 //
701 // 'Fl_File_Chooser::filter()' - Set the filter(s) for the chooser.
702 //
703 
704 void
705 Fl_File_Chooser::filter(const char *p)		// I - Pattern(s)
706 {
707   char		*copyp,				// Copy of pattern
708 		*start,				// Start of pattern
709 		*end;				// End of pattern
710   int		allfiles;			// Do we have a "*" pattern?
711   char		temp[1024];			// Temporary pattern string
712 
713 
714   // Make sure we have a pattern...
715   if (!p || !*p) p = "*";
716 
717   // Copy the pattern string...
718   copyp = strdup(p);
719 
720   // Separate the pattern string as necessary...
721   showChoice->clear();
722 
723   for (start = copyp, allfiles = 0; start && *start; start = end) {
724     end = strchr(start, '\t');
725     if (end) *end++ = '\0';
726 
727     if (strcmp(start, "*") == 0) {
728       showChoice->add(all_files_label);
729       allfiles = 1;
730     } else {
731       quote_pathname(temp, start, sizeof(temp));
732       showChoice->add(temp);
733       if (strstr(start, "(*)") != NULL) allfiles = 1;
734     }
735   }
736 
737   free(copyp);
738 
739   if (!allfiles) showChoice->add(all_files_label);
740 
741   showChoice->add(custom_filter_label);
742 
743   showChoice->value(0);
744   showChoiceCB();
745 }
746 
747 
748 //
749 // 'Fl_File_Chooser::newdir()' - Make a new directory.
750 //
751 
752 void
753 Fl_File_Chooser::newdir()
754 {
755   const char	*dir;		// New directory name
756   char		pathname[1024];	// Full path of directory
757 
758 
759   // Get a directory name from the user
760   if ((dir = fl_input(new_directory_label, NULL)) == NULL)
761     return;
762 
763   // Make it relative to the current directory as needed...
764 #if (defined(WIN32) && ! defined (__CYGWIN__)) || defined(__EMX__)
765   if (dir[0] != '/' && dir[0] != '\\' && dir[1] != ':')
766 #else
767   if (dir[0] != '/' && dir[0] != '\\')
768 #endif /* WIN32 || __EMX__ */
769     snprintf(pathname, sizeof(pathname), "%s/%s", directory_, dir);
770   else
771     strlcpy(pathname, dir, sizeof(pathname));
772 
773   // Create the directory; ignore EEXIST errors...
774 #if defined(WIN32) && ! defined (__CYGWIN__)
775   if (mkdir(pathname))
776 #else
777   if (mkdir(pathname, 0777))
778 #endif /* WIN32 */
779     if (errno != EEXIST)
780     {
781       fl_alert("%s", strerror(errno));
782       return;
783     }
784 
785   // Show the new directory...
786   directory(pathname);
787 }
788 
789 
790 //
791 // 'Fl_File_Chooser::preview()' - Enable or disable the preview tile.
792 //
793 
794 void
795 Fl_File_Chooser::preview(int e)// I - 1 = enable preview, 0 = disable preview
796 {
797   previewButton->value(e);
798   prefs_.set("preview", e);
799   prefs_.flush();
800 
801   Fl_Group *p = previewBox->parent();
802   if (e) {
803     int w = p->w() * 2 / 3;
804     fileList->resize(fileList->x(), fileList->y(),
805                      w, fileList->h());
806     previewBox->resize(fileList->x()+w, previewBox->y(),
807                        p->w()-w, previewBox->h());
808     previewBox->show();
809     update_preview();
810   } else {
811     fileList->resize(fileList->x(), fileList->y(),
812                      p->w(), fileList->h());
813     previewBox->resize(p->x()+p->w(), previewBox->y(),
814                        0, previewBox->h());
815     previewBox->hide();
816   }
817   p->init_sizes();
818 
819   fileList->parent()->redraw();
820 }
821 
822 
823 //
824 // 'Fl_File_Chooser::previewCB()' - Timeout handler for the preview box.
825 //
826 
827 void
828 Fl_File_Chooser::previewCB(Fl_File_Chooser *fc) {	// I - File chooser
829   fc->update_preview();
830 }
831 
832 
833 //
834 // 'Fl_File_Chooser::rescan()' - Rescan the current directory.
835 //
836 
837 void
838 Fl_File_Chooser::rescan()
839 {
840   char	pathname[1024];		// New pathname for filename field
841 
842 
843   // Clear the current filename
844   strlcpy(pathname, directory_, sizeof(pathname));
845   if (pathname[0] && pathname[strlen(pathname) - 1] != '/') {
846     strlcat(pathname, "/", sizeof(pathname));
847   }
848 //  puts("Setting fileName in rescan()");
849   fileName->value(pathname);
850 
851   if (type_ & DIRECTORY)
852     okButton->activate();
853   else
854     okButton->deactivate();
855 
856   // Build the file list...
857   fileList->load(directory_, sort);
858 
859   // Update the preview box...
860   update_preview();
861 }
862 
863 //
864 // 'Fl_File_Chooser::rescan_keep_filename()' - Rescan the current directory
865 // without clearing the filename, then select the file if it is in the list
866 //
867 
868 void
869 Fl_File_Chooser::rescan_keep_filename()
870 {
871   // if no filename was set, this is likely a diretory browser
872   const char *fn = fileName->value();
873   if (!fn || !*fn || fn[strlen(fn) - 1]=='/') {
874     rescan();
875     return;
876   }
877 
878   int   i;
879   char	pathname[1024];		// New pathname for filename field
880   strlcpy(pathname, fn, sizeof(pathname));
881 
882   // Build the file list...
883   fileList->load(directory_, sort);
884 
885   // Update the preview box...
886   update_preview();
887 
888   // and select the chosen file
889   char found = 0;
890   char *slash = strrchr(pathname, '/');
891   if (slash)
892     slash++;
893   else
894     slash = pathname;
895   for (i = 1; i <= fileList->size(); i ++)
896 #if defined(WIN32) || defined(__EMX__)
897     if (strcasecmp(fileList->text(i), slash) == 0) {
898 #else
899     if (strcmp(fileList->text(i), slash) == 0) {
900 #endif // WIN32 || __EMX__
901       fileList->topline(i);
902       fileList->select(i);
903       found = 1;
904       break;
905     }
906 
907   // update OK button activity
908   if (found || type_ & CREATE)
909     okButton->activate();
910   else
911     okButton->deactivate();
912 }
913 
914 
915 //
916 // 'Fl_File_Chooser::showChoiceCB()' - Handle show selections.
917 //
918 
919 void
920 Fl_File_Chooser::showChoiceCB()
921 {
922   const char	*item,			// Selected item
923 		*patstart;		// Start of pattern
924   char		*patend;		// End of pattern
925   char		temp[1024];		// Temporary string for pattern
926 
927 
928   item = showChoice->text(showChoice->value());
929 
930   if (strcmp(item, custom_filter_label) == 0) {
931     if ((item = fl_input(custom_filter_label, pattern_)) != NULL) {
932       strlcpy(pattern_, item, sizeof(pattern_));
933 
934       quote_pathname(temp, item, sizeof(temp));
935       showChoice->add(temp);
936       showChoice->value(showChoice->size() - 2);
937     }
938   } else if ((patstart = strchr(item, '(')) == NULL) {
939     strlcpy(pattern_, item, sizeof(pattern_));
940   } else {
941     strlcpy(pattern_, patstart + 1, sizeof(pattern_));
942     if ((patend = strrchr(pattern_, ')')) != NULL) *patend = '\0';
943   }
944 
945   fileList->filter(pattern_);
946 
947   if (shown()) {
948     // Rescan the directory...
949     rescan_keep_filename();
950   }
951 }
952 
953 
954 //
955 // 'Fl_File_Chooser::update_favorites()' - Update the favorites menu.
956 //
957 
958 void
959 Fl_File_Chooser::update_favorites()
960 {
961   int		i;			// Looping var
962   char		pathname[1024],		// Pathname
963 		menuname[2048];		// Menu name
964   const char	*home;			// Home directory
965 
966 
967   favoritesButton->clear();
968   favoritesButton->add("bla");
969   favoritesButton->clear();
970   favoritesButton->add(add_favorites_label, FL_ALT + 'a', 0);
971   favoritesButton->add(manage_favorites_label, FL_ALT + 'm', 0, 0, FL_MENU_DIVIDER);
972   favoritesButton->add(filesystems_label, FL_ALT + 'f', 0);
973 
974   if ((home = getenv("HOME")) != NULL) {
975     quote_pathname(menuname, home, sizeof(menuname));
976     favoritesButton->add(menuname, FL_ALT + 'h', 0);
977   }
978 
979   for (i = 0; i < 100; i ++) {
980     sprintf(menuname, "favorite%02d", i);
981     prefs_.get(menuname, pathname, "", sizeof(pathname));
982     if (!pathname[0]) break;
983 
984     quote_pathname(menuname, pathname, sizeof(menuname));
985 
986     if (i < 10) favoritesButton->add(menuname, FL_ALT + '0' + i, 0);
987     else favoritesButton->add(menuname);
988   }
989 
990   if (i == 100) ((Fl_Menu_Item *)favoritesButton->menu())[0].deactivate();
991 }
992 
993 
994 //
995 // 'Fl_File_Chooser::update_preview()' - Update the preview box...
996 //
997 
998 void
999 Fl_File_Chooser::update_preview()
1000 {
1001   const char		*filename;	// Current filename
1002   Fl_Shared_Image	*image,		// New image
1003 			*oldimage;	// Old image
1004   int			pbw, pbh;	// Width and height of preview box
1005   int			w, h;		// Width and height of preview image
1006 
1007 
1008   if (!previewButton->value()) return;
1009 
1010   if ((filename = value()) == NULL || fl_filename_isdir(filename)) image = NULL;
1011   else {
1012     window->cursor(FL_CURSOR_WAIT);
1013     Fl::check();
1014 
1015     image = Fl_Shared_Image::get(filename);
1016 
1017     if (image) {
1018       window->cursor(FL_CURSOR_DEFAULT);
1019       Fl::check();
1020     }
1021   }
1022 
1023   oldimage = (Fl_Shared_Image *)previewBox->image();
1024 
1025   if (oldimage) oldimage->release();
1026 
1027   previewBox->image(0);
1028 
1029   if (!image) {
1030     FILE	*fp;
1031     int		bytes;
1032     char	*ptr;
1033 
1034     if (filename) fp = fopen(filename, "rb");
1035     else fp = NULL;
1036 
1037     if (fp != NULL) {
1038       // Try reading the first 1k of data for a label...
1039       bytes = fread(preview_text_, 1, sizeof(preview_text_) - 1, fp);
1040       preview_text_[bytes] = '\0';
1041       fclose(fp);
1042     } else {
1043       // Assume we can't read any data...
1044       preview_text_[0] = '\0';
1045     }
1046 
1047     window->cursor(FL_CURSOR_DEFAULT);
1048     Fl::check();
1049 
1050     // Scan the buffer for printable chars...
1051     for (ptr = preview_text_;
1052          *ptr && (isprint(*ptr & 255) || isspace(*ptr & 255));
1053 	 ptr ++);
1054 
1055     if (*ptr || ptr == preview_text_) {
1056       // Non-printable file, just show a big ?...
1057       previewBox->label(filename ? "?" : 0);
1058       previewBox->align(FL_ALIGN_CLIP);
1059       previewBox->labelsize(100);
1060       previewBox->labelfont(FL_HELVETICA);
1061     } else {
1062       // Show the first 1k of text...
1063       int size = previewBox->h() / 20;
1064       if (size < 6) size = 6;
1065       else if (size > 14) size = 14;
1066 
1067       previewBox->label(preview_text_);
1068       previewBox->align((Fl_Align)(FL_ALIGN_CLIP | FL_ALIGN_INSIDE |
1069                                    FL_ALIGN_LEFT | FL_ALIGN_TOP));
1070       previewBox->labelsize((uchar)size);
1071       previewBox->labelfont(FL_COURIER);
1072     }
1073   } else {
1074     pbw = previewBox->w() - 20;
1075     pbh = previewBox->h() - 20;
1076 
1077     if (image->w() > pbw || image->h() > pbh) {
1078       w   = pbw;
1079       h   = w * image->h() / image->w();
1080 
1081       if (h > pbh) {
1082 	h = pbh;
1083 	w = h * image->w() / image->h();
1084       }
1085 
1086       oldimage = (Fl_Shared_Image *)image->copy(w, h);
1087       previewBox->image((Fl_Image *)oldimage);
1088 
1089       image->release();
1090     } else {
1091       previewBox->image((Fl_Image *)image);
1092     }
1093 
1094     previewBox->align(FL_ALIGN_CLIP);
1095     previewBox->label(0);
1096   }
1097 
1098   previewBox->redraw();
1099 }
1100 
1101 
1102 //
1103 // 'Fl_File_Chooser::value()' - Return a selected filename.
1104 //
1105 
1106 const char *			// O - Filename or NULL
1107 Fl_File_Chooser::value(int f)	// I - File number
1108 {
1109   int		i;		// Looping var
1110   int		fcount;		// Number of selected files
1111   const char	*name;		// Current filename
1112   static char	pathname[1024];	// Filename + directory
1113 
1114 
1115   name = fileName->value();
1116 
1117   if (!(type_ & MULTI)) {
1118     // Return the filename in the filename field...
1119     if (!name || !name[0]) return NULL;
1120     else return name;
1121   }
1122 
1123   // Return a filename from the list...
1124   for (i = 1, fcount = 0; i <= fileList->size(); i ++)
1125     if (fileList->selected(i)) {
1126       // See if this file is a selected file/directory...
1127       name = fileList->text(i);
1128 
1129       fcount ++;
1130 
1131       if (fcount == f) {
1132 	if (directory_[0]) {
1133 	  snprintf(pathname, sizeof(pathname), "%s/%s", directory_, name);
1134 	} else {
1135 	  strlcpy(pathname, name, sizeof(pathname));
1136 	}
1137 
1138 	return pathname;
1139       }
1140     }
1141 
1142   // If nothing is selected, use the filename field...
1143   if (!name || !name[0]) return NULL;
1144   else return name;
1145 }
1146 
1147 
1148 //
1149 // 'Fl_File_Chooser::value()' - Set the current filename.
1150 //
1151 
1152 void
1153 Fl_File_Chooser::value(const char *filename)
1154 					// I - Filename + directory
1155 {
1156   int	i,				// Looping var
1157   	fcount;				// Number of items in list
1158   char	*slash;				// Directory separator
1159   char	pathname[1024];			// Local copy of filename
1160 
1161 
1162 //  printf("Fl_File_Chooser::value(\"%s\")\n", filename == NULL ? "(null)" : filename);
1163 
1164   // See if the filename is the "My System" directory...
1165   if (filename == NULL || !filename[0]) {
1166     // Yes, just change the current directory...
1167     directory(filename);
1168     fileName->value("");
1169     okButton->deactivate();
1170     return;
1171   }
1172 
1173 #ifdef WIN32
1174   // See if the filename contains backslashes...
1175   char	fixpath[1024];			// Path with slashes converted
1176   if (strchr(filename, '\\')) {
1177     // Convert backslashes to slashes...
1178     strlcpy(fixpath, filename, sizeof(fixpath));
1179 
1180     for (slash = strchr(fixpath, '\\'); slash; slash = strchr(slash + 1, '\\'))
1181       *slash = '/';
1182 
1183     filename = fixpath;
1184   }
1185 #endif // WIN32
1186 
1187   // See if there is a directory in there...
1188   fl_filename_absolute(pathname, sizeof(pathname), filename);
1189 
1190   if ((slash = strrchr(pathname, '/')) != NULL) {
1191     // Yes, change the display to the directory...
1192     if (!fl_filename_isdir(pathname)) *slash++ = '\0';
1193 
1194     directory(pathname);
1195     if (*slash == '/') slash = pathname;
1196   } else {
1197     directory(".");
1198     slash = pathname;
1199   }
1200 
1201   // Set the input field to the absolute path...
1202   if (slash > pathname) slash[-1] = '/';
1203 
1204   fileName->value(pathname);
1205   fileName->position(0, strlen(pathname));
1206   okButton->activate();
1207 
1208   // Then find the file in the file list and select it...
1209   fcount = fileList->size();
1210 
1211   fileList->deselect(0);
1212   fileList->redraw();
1213 
1214   for (i = 1; i <= fcount; i ++)
1215 #if defined(WIN32) || defined(__EMX__)
1216     if (strcasecmp(fileList->text(i), slash) == 0) {
1217 #else
1218     if (strcmp(fileList->text(i), slash) == 0) {
1219 #endif // WIN32 || __EMX__
1220 //      printf("Selecting line %d...\n", i);
1221       fileList->topline(i);
1222       fileList->select(i);
1223       break;
1224     }
1225 }
1226 
1227 
1228 //
1229 // 'compare_dirnames()' - Compare two directory names.
1230 //
1231 
1232 static int
1233 compare_dirnames(const char *a, const char *b) {
1234   int alen, blen;
1235 
1236   // Get length of each string...
1237   alen = strlen(a) - 1;
1238   blen = strlen(b) - 1;
1239 
1240   if (alen < 0 || blen < 0) return alen - blen;
1241 
1242   // Check for trailing slashes...
1243   if (a[alen] != '/') alen ++;
1244   if (b[blen] != '/') blen ++;
1245 
1246   // If the lengths aren't the same, then return the difference...
1247   if (alen != blen) return alen - blen;
1248 
1249   // Do a comparison of the first N chars (alen == blen at this point)...
1250 #ifdef WIN32
1251   return strncasecmp(a, b, alen);
1252 #else
1253   return strncmp(a, b, alen);
1254 #endif // WIN32
1255 }
1256 
1257 
1258 //
1259 // 'quote_pathname()' - Quote a pathname for a menu.
1260 //
1261 
1262 static void
1263 quote_pathname(char       *dst,		// O - Destination string
1264                const char *src,		// I - Source string
1265 	       int        dstsize)	// I - Size of destination string
1266 {
1267   dstsize --;
1268 
1269   while (*src && dstsize > 1) {
1270     if (*src == '\\') {
1271       // Convert backslash to forward slash...
1272       *dst++ = '\\';
1273       *dst++ = '/';
1274       src ++;
1275     } else {
1276       if (*src == '/') *dst++ = '\\';
1277 
1278       *dst++ = *src++;
1279     }
1280   }
1281 
1282   *dst = '\0';
1283 }
1284 
1285 
1286 //
1287 // 'unquote_pathname()' - Unquote a pathname from a menu.
1288 //
1289 
1290 static void
1291 unquote_pathname(char       *dst,	// O - Destination string
1292                  const char *src,	// I - Source string
1293 	         int        dstsize)	// I - Size of destination string
1294 {
1295   dstsize --;
1296 
1297   while (*src && dstsize > 1) {
1298     if (*src == '\\') src ++;
1299     *dst++ = *src++;
1300   }
1301 
1302   *dst = '\0';
1303 }
1304 
1305 
1306 //
1307 // End of "$Id: Fl_File_Chooser2.cxx 6092 2008-04-11 12:57:37Z matt $".
1308 //
1309